diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aae6ff2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,160 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +# *.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +# *.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1625c17 --- /dev/null +++ b/LICENSE @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..de3f07d --- /dev/null +++ b/README.md @@ -0,0 +1,146 @@ +
+ + + + +[![Contributors][contributors-shield]][contributors-url] +[![Forks][forks-shield]][forks-url] +[![Stargazers][stars-shield]][stars-url] +[![Issues][issues-shield]][issues-url] + + +
+
+ + Logo + + +

Audiotext

+ +

+ A program that transcribes audio from a file or microphone to text in any supported language by Google API. +
+ Report Bug · Request Feature +

+
+ + +
+ Table of Contents +
    +
  1. + About The Project + +
  2. +
  3. + Getting Started +
  4. +
  5. Contributing
  6. +
  7. Contact
  8. +
  9. Acknowledgments
  10. +
+
+ + + +## About The Project + +[![Main screenshot light][main-system]](https://github.com/HenestrosaConH/audiotext) + +The project structure is as follows: + +- `docs`: Contains files related to the documentation of the project. +- `res`: Contains all the static resources used by the app, which are the app icon (located in the `img` folder) and the i18n files (located in the `locales` folder). +- `src`: Contains the source code files of the app. + +Besides those directories, there are also these two files (apart from the .gitignore, README.md and LICENSE): + +- `audiotext.spec`: Used to generate a .exe file with [PyInstaller](https://pyinstaller.org/en/stable/). Notice that, inside the file, there are some annotations, which are `ROOT DIRECTORY PATH`, `PATH TO CUSTOM TKINTER` and `PATH TO RES FOLDER`. You will have to replace them by the indicated path of your computer. For example, my `PATH TO CUSTOMTKINTER` is `c:\\users\\JC\\appdata\\local\\programs\\python\\python310\\lib\\site-packages\\customtkinter`. +- `requirements.txt`: Lists the names and versions of each package used to build this project. + +

(back to top)

+ + + +### Built With + +- [pydub](https://github.com/jiaaro/pydub) +- [CustomTkinter](https://github.com/TomSchimansky/CustomTkinter) +- [SpeechRecognition](https://pypi.org/project/SpeechRecognition/) +- [PyAudio](https://pypi.org/project/PyAudio/) + +

(back to top)

+ + + +## Getting Started + +**Important**: You need to install [FFmpeg](https://ffmpeg.org) to execute the program. Otherwise, it won't be able to process the audio files. You can download FFmpeg from the [official site](https://ffmpeg.org/download.html) + +If you want to execute the program: +- Go to [releases](https://github.com/HenestrosaConH/audiotext/releases) and download the latest one. Once you download it and uncompress it, open the `audiotext` folder and open the `audiotext.exe` file. + +If you want to open the code: +- Clone the project with the `git clone https://github.com/HenestrosaConH/audiotext.git` command and then open it with your favourite IDE (mine is [PyCharm](https://www.jetbrains.com/pycharm/)). +- Please bear in mind that you cannot generate a single .exe file for this project with PyInstaller due to the dependency with the CustonTkinter package (reason [here](https://github.com/TomSchimansky/CustomTkinter/wiki/Packaging)). + +

(back to top)

+ + + +## Contributing + +Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. + +If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag `enhancement`. +Don't forget to give the project a star! Thanks again! + +1. Fork the Project +2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) +3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) +4. Push to the Branch (`git push origin feature/AmazingFeature`) +5. Open a Pull Request + +

(back to top)

+ + + +## Contact + +Email - [LinkedIn][linkedin-url] + +

(back to top)

+ + + +## Acknowledgments + +I've made use of the following repositories to make this README: + +- [Best-README-Template](https://github.com/othneildrew/Best-README-Template/) +- [Img Shields](https://shields.io) + +

(back to top)

+ + + + +[contributors-shield]: https://img.shields.io/github/contributors/HenestrosaConH/audiotext.svg?style=for-the-badge +[contributors-url]: https://github.com/HenestrosaConH/audiotext/graphs/contributors +[forks-shield]: https://img.shields.io/github/forks/HenestrosaConH/audiotext.svg?style=for-the-badge +[forks-url]: https://github.com/HenestrosaConH/audiotext/network/members +[stars-shield]: https://img.shields.io/github/stars/HenestrosaConH/audiotext.svg?style=for-the-badge +[stars-url]: https://github.com/HenestrosaConH/audiotext/stargazers +[issues-shield]: https://img.shields.io/github/issues/HenestrosaConH/audiotext.svg?style=for-the-badge +[issues-url]: https://github.com/HenestrosaConH/audiotext/issues +[linkedin-url]: https://linkedin.com/in/henestrosaconh +[main-system]: docs/main-system.png +[icon]: docs/icon.png diff --git a/audiotext.spec b/audiotext.spec new file mode 100644 index 0000000..ccbb792 --- /dev/null +++ b/audiotext.spec @@ -0,0 +1,54 @@ +# -*- mode: python ; coding: utf-8 -*- + + +block_cipher = None + + +a = Analysis( + ['D:\\Mio\\Proyectos\\Python\\audiotext\\src\\main.py'], + pathex=[], + binaries=[], + datas=[ + ('PATH TO CUSTOMTKINTER', 'customtkinter/'), + ('PATH TO RES FOLDER', 'res/') + ], + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False, +) +pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) + +exe = EXE( + pyz, + a.scripts, + [], + exclude_binaries=True, + name='audiotext', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + icon=['ROOT DIRECTORY PATH\\audiotext\\res\\img\\icon.ico'], +) +coll = COLLECT( + exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=True, + upx_exclude=[], + name='audiotext', +) diff --git a/docs/icon.png b/docs/icon.png new file mode 100644 index 0000000..d13057d Binary files /dev/null and b/docs/icon.png differ diff --git a/docs/main-light.png b/docs/main-light.png new file mode 100644 index 0000000..8c7b38d Binary files /dev/null and b/docs/main-light.png differ diff --git a/docs/main-system.png b/docs/main-system.png new file mode 100644 index 0000000..8aac30e Binary files /dev/null and b/docs/main-system.png differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d2f7893 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +customtkinter==5.0.5 +pydub==0.25.1 +SpeechRecognition==3.9.0 +pyaudio==0.2.13 \ No newline at end of file diff --git a/res/img/icon.ico b/res/img/icon.ico new file mode 100644 index 0000000..63d6494 Binary files /dev/null and b/res/img/icon.ico differ diff --git a/res/locales/en/LC_MESSAGES/all.mo b/res/locales/en/LC_MESSAGES/all.mo new file mode 100644 index 0000000..8b8ae03 Binary files /dev/null and b/res/locales/en/LC_MESSAGES/all.mo differ diff --git a/res/locales/en/LC_MESSAGES/all.po b/res/locales/en/LC_MESSAGES/all.po new file mode 100644 index 0000000..6aec3f5 --- /dev/null +++ b/res/locales/en/LC_MESSAGES/all.po @@ -0,0 +1,104 @@ +# Copyright (C) 2023 HenestrosaConH +# José Carlos López Henestrosa , 2023. +# +msgid "" +msgstr "" +"Project-Id-Version: 1.00\n" +"POT-Creation-Date: 2023-01-26 11:32+0100\n" +"PO-Revision-Date: 2023-01-26 11:32+0100\n" +"Last-Translator: José Carlos López Henestrosa \n" +"Language-Team: José Carlos López Henestrosa \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: pygettext.py 1.5\n" + + +#: audiotext\src\main.py:53 +#: audiotext\src\main.py:118 +msgid "Audio language" +msgstr "Audio language" + +#: audiotext\src\main.py:65 +#: audiotext\src\main.py:119 +msgid "Select audio file" +msgstr "Select audio file" + +#: audiotext\src\main.py:72 +msgid "Transcribe from microphone" +msgstr "Transcribe from microphone" + +#: audiotext\src\main.py:81 +#: audiotext\src\main.py:120 +msgid "Generate text" +msgstr "Generate text" + +#: audiotext\src\main.py:89 +#: audiotext\src\main.py:121 +msgid "App language" +msgstr "App language" + +#: audiotext\src\main.py:103 +#: audiotext\src\main.py:122 +msgid "Appearance mode" +msgstr "Appearance mode" + +#: audiotext\src\main.py:109 +#: audiotext\src\main.py:123 +#: audiotext\src\main.py:124 +#: audiotext\src\main.py:155 +msgid "System" +msgstr "System" + +#: audiotext\src\main.py:109 +#: audiotext\src\main.py:123 +#: audiotext\src\main.py:153 +msgid "Dark" +msgstr "Dark" + +#: audiotext\src\main.py:109 +#: audiotext\src\main.py:123 +#: audiotext\src\main.py:154 +msgid "Light" +msgstr "Light" + +#: audiotext\src\main.py:125 +#: audiotext\src\main.py:142 +msgid "Save transcription" +msgstr "Save transcription" + +#: audiotext\src\main.py:175 +msgid "Error: No audio file selected, please select one before generating text." +msgstr "Error: No audio file selected, please select one before generating text." + +#: audiotext\src\main.py:219 +msgid "Error: The selected audio language is not valid." +msgstr "Error: The selected audio language is not valid." + +#: audiotext\src\logic.py:20 +msgid "Select a file" +msgstr "Select a file" + +#: audiotext\src\logic.py:21 +msgid "Audio files" +msgstr "Audio files" + +#: audiotext\src\logic.py:102 +msgid "Say something" +msgstr "Say something" + +#: audiotext\src\logic.py:109 +msgid "Error: Microphone not available." +msgstr "Error: Microphone not available." + +#: audiotext\src\logic.py:126 +msgid "Save as" +msgstr "Save as" + +#: audiotext\src\logic.py:128 +msgid "All Files" +msgstr "All Files" + +#: audiotext\src\logic.py:128 +msgid "Text file" +msgstr "Text file" diff --git a/res/locales/en/LC_MESSAGES/logic.po b/res/locales/en/LC_MESSAGES/logic.po new file mode 100644 index 0000000..0c568a6 --- /dev/null +++ b/res/locales/en/LC_MESSAGES/logic.po @@ -0,0 +1,44 @@ +# Copyright (C) 2023 HenestrosaConH +# José Carlos López Henestrosa , 2023. +# +msgid "" +msgstr "" +"Project-Id-Version: 1.00\n" +"POT-Creation-Date: 2023-01-26 11:32+0100\n" +"PO-Revision-Date: 2023-01-26 11:32+0100\n" +"Last-Translator: José Carlos López Henestrosa \n" +"Language-Team: José Carlos López Henestrosa \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: pygettext.py 1.5\n" + + +#: audiotext\src\logic.py:20 +msgid "Select a file" +msgstr "Select a file" + +#: audiotext\src\logic.py:21 +msgid "Audio files" +msgstr "Audio files" + +#: audiotext\src\logic.py:102 +msgid "Say something" +msgstr "Say something" + +#: audiotext\src\logic.py:109 +msgid "Error: Microphone not available." +msgstr "Error: Microphone not available." + +#: audiotext\src\logic.py:126 +msgid "Save as" +msgstr "Save as" + +#: audiotext\src\logic.py:128 +msgid "All Files" +msgstr "All Files" + +#: audiotext\src\logic.py:128 +msgid "Text file" +msgstr "Text file" + diff --git a/res/locales/en/LC_MESSAGES/main.po b/res/locales/en/LC_MESSAGES/main.po new file mode 100644 index 0000000..3bbf900 --- /dev/null +++ b/res/locales/en/LC_MESSAGES/main.po @@ -0,0 +1,77 @@ +# Copyright (C) 2023 HenestrosaConH +# José Carlos López Henestrosa , 2023. +# +msgid "" +msgstr "" +"Project-Id-Version: 1.00\n" +"POT-Creation-Date: 2023-01-26 11:32+0100\n" +"PO-Revision-Date: 2023-01-26 11:32+0100\n" +"Last-Translator: José Carlos López Henestrosa \n" +"Language-Team: José Carlos López Henestrosa \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: pygettext.py 1.5\n" + + +#: audiotext\src\main.py:53 +#: audiotext\src\main.py:118 +msgid "Audio language" +msgstr "Audio language" + +#: audiotext\src\main.py:65 +#: audiotext\src\main.py:119 +msgid "Select audio file" +msgstr "Select audio file" + +#: audiotext\src\main.py:72 +msgid "Transcribe from microphone" +msgstr "Transcribe from microphone" + +#: audiotext\src\main.py:81 +#: audiotext\src\main.py:120 +msgid "Generate text" +msgstr "Generate text" + +#: audiotext\src\main.py:89 +#: audiotext\src\main.py:121 +msgid "App language" +msgstr "App language" + +#: audiotext\src\main.py:103 +#: audiotext\src\main.py:122 +msgid "Appearance mode" +msgstr "Appearance mode" + +#: audiotext\src\main.py:109 +#: audiotext\src\main.py:123 +#: audiotext\src\main.py:124 +#: audiotext\src\main.py:155 +msgid "System" +msgstr "System" + +#: audiotext\src\main.py:109 +#: audiotext\src\main.py:123 +#: audiotext\src\main.py:153 +msgid "Dark" +msgstr "Dark" + +#: audiotext\src\main.py:109 +#: audiotext\src\main.py:123 +#: audiotext\src\main.py:154 +msgid "Light" +msgstr "Light" + +#: audiotext\src\main.py:125 +#: audiotext\src\main.py:142 +msgid "Save transcription" +msgstr "Save transcription" + +#: audiotext\src\main.py:175 +msgid "Error: No audio file selected, please select one before generating text." +msgstr "Error: No audio file selected, please select one before generating text." + +#: audiotext\src\main.py:219 +msgid "Error: The selected audio language is not valid." +msgstr "Error: The selected audio language is not valid." + diff --git a/res/locales/es/LC_MESSAGES/all.mo b/res/locales/es/LC_MESSAGES/all.mo new file mode 100644 index 0000000..ac9549a Binary files /dev/null and b/res/locales/es/LC_MESSAGES/all.mo differ diff --git a/res/locales/es/LC_MESSAGES/all.po b/res/locales/es/LC_MESSAGES/all.po new file mode 100644 index 0000000..e647051 --- /dev/null +++ b/res/locales/es/LC_MESSAGES/all.po @@ -0,0 +1,108 @@ +# Copyright (C) 2023 HenestrosaConH +# José Carlos López Henestrosa , 2023. +# +msgid "" +msgstr "" +"Project-Id-Version: 1.00\n" +"POT-Creation-Date: 2023-01-26 11:32+0100\n" +"PO-Revision-Date: 2023-01-26 11:32+0100\n" +"Last-Translator: José Carlos López Henestrosa \n" +"Language-Team: José Carlos López Henestrosa \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: pygettext.py 1.5\n" + +#: audiotext\src\main.py:53 +#: audiotext\src\main.py:118 +msgid "Audio language" +msgstr "Idioma del audio" + +#: audiotext\src\main.py:65 +#: audiotext\src\main.py:119 +msgid "Select audio file" +msgstr "Seleccionar archivo de audio" + +#: audiotext\src\main.py:72 +msgid "Transcribe from microphone" +msgstr "Transcribir del micrófono" + +#: audiotext\src\main.py:81 +#: audiotext\src\main.py:120 +msgid "Generate text" +msgstr "Generar texto" + +#: audiotext\src\main.py:89 +#: audiotext\src\main.py:121 +msgid "App language" +msgstr "Idioma de la app" + +#: audiotext\src\main.py:103 +#: audiotext\src\main.py:122 +msgid "Appearance mode" +msgstr "Tema actual" + +#: audiotext\src\main.py:86 +#: audiotext\src\main.py:109 +#: audiotext\src\main.py:123 +#: audiotext\src\main.py:124 +#: audiotext\src\main.py:155 +msgid "System" +msgstr "Sistema" + +#: audiotext\src\main.py:109 +#: audiotext\src\main.py:123 +#: audiotext\src\main.py:153 +msgid "Dark" +msgstr "Oscuro" + +#: audiotext\src\main.py:109 +#: audiotext\src\main.py:123 +#: audiotext\src\main.py:154 +msgid "Light" +msgstr "Claro" + +#: audiotext\src\main.py:125 +#: audiotext\src\main.py:142 +msgid "Save transcription" +msgstr "Guardar transcripción" + +#: audiotext\src\main.py:175 +msgid "Error: No audio file selected, please select one before generating text." +msgstr "Error: No se ha seleccionado un archivo de audio, por favor seleccione uno antes de generar el texto." + +#: audiotext\src\main.py:219 +msgid "Error: The selected audio language is not valid." +msgstr "Error: El idioma del audio seleccionado no es válido." + +#: audiotext\src\logic.py:20 +msgid "Select a file" +msgstr "Selecciona un archivo" + +#: audiotext\src\logic.py:21 +msgid "Audio files" +msgstr "Archivos de audio" + +#: audiotext\src\logic.py:102 +msgid "Say something" +msgstr "Di algo" + +#: audiotext\src\logic.py:111 +msgid "Error: Microphone not available." +msgstr "Error: Micrófono no disponible." + +#: audiotext\src\logic.py:113 +msgid "Error: Please, try again." +msgstr "Error: Por favor, inténtalo de nuevo." + +#: audiotext\src\logic.py:126 +msgid "Save as" +msgstr "Guardar como" + +#: audiotext\src\logic.py:128 +msgid "All Files" +msgstr "Todos los archivos" + +#: audiotext\src\logic.py:130 +msgid "Text file" +msgstr "Archivo de texto" diff --git a/res/locales/es/LC_MESSAGES/logic.po b/res/locales/es/LC_MESSAGES/logic.po new file mode 100644 index 0000000..acc77f3 --- /dev/null +++ b/res/locales/es/LC_MESSAGES/logic.po @@ -0,0 +1,48 @@ +# Copyright (C) 2023 HenestrosaConH +# José Carlos López Henestrosa , 2023. +# +msgid "" +msgstr "" +"Project-Id-Version: 1.00\n" +"POT-Creation-Date: 2023-01-26 11:32+0100\n" +"PO-Revision-Date: 2023-01-26 11:32+0100\n" +"Last-Translator: José Carlos López Henestrosa \n" +"Language-Team: José Carlos López Henestrosa \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: pygettext.py 1.5\n" + + +#: audiotext\src\logic.py:20 +msgid "Select a file" +msgstr "Selecciona un archivo" + +#: audiotext\src\logic.py:21 +msgid "Audio files" +msgstr "Archivos de audio" + +#: audiotext\src\logic.py:102 +msgid "Say something" +msgstr "Di algo" + +#: audiotext\src\logic.py:111 +msgid "Error: Microphone not available." +msgstr "Error: Micrófono no disponible." + +#: audiotext\src\logic.py:113 +msgid "Error: Please, try again." +msgstr "Error: Por favor, inténtalo de nuevo." + +#: audiotext\src\logic.py:126 +msgid "Save as" +msgstr "Guardar como" + +#: audiotext\src\logic.py:128 +msgid "All Files" +msgstr "Todos los archivos" + +#: audiotext\src\logic.py:130 +msgid "Text file" +msgstr "Archivo de texto" + diff --git a/res/locales/es/LC_MESSAGES/main.po b/res/locales/es/LC_MESSAGES/main.po new file mode 100644 index 0000000..5299a06 --- /dev/null +++ b/res/locales/es/LC_MESSAGES/main.po @@ -0,0 +1,78 @@ +# Copyright (C) 2023 HenestrosaConH +# José Carlos López Henestrosa , 2023. +# +msgid "" +msgstr "" +"Project-Id-Version: 1.00\n" +"POT-Creation-Date: 2023-01-26 11:32+0100\n" +"PO-Revision-Date: 2023-01-26 11:32+0100\n" +"Last-Translator: José Carlos López Henestrosa \n" +"Language-Team: José Carlos López Henestrosa \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: pygettext.py 1.5\n" + + +#: audiotext\src\main.py:53 +#: audiotext\src\main.py:118 +msgid "Audio language" +msgstr "Idioma del audio" + +#: audiotext\src\main.py:65 +#: audiotext\src\main.py:119 +msgid "Select audio file" +msgstr "Seleccionar archivo de audio" + +#: audiotext\src\main.py:72 +msgid "Transcribe from microphone" +msgstr "Transcribir del micrófono" + +#: audiotext\src\main.py:81 +#: audiotext\src\main.py:120 +msgid "Generate text" +msgstr "Generar texto" + +#: audiotext\src\main.py:89 +#: audiotext\src\main.py:121 +msgid "App language" +msgstr "Idioma de la app" + +#: audiotext\src\main.py:103 +#: audiotext\src\main.py:122 +msgid "Appearance mode" +msgstr "Tema actual" + +#: audiotext\src\main.py:86 +#: audiotext\src\main.py:109 +#: audiotext\src\main.py:123 +#: audiotext\src\main.py:124 +#: audiotext\src\main.py:155 +msgid "System" +msgstr "Sistema" + +#: audiotext\src\main.py:109 +#: audiotext\src\main.py:123 +#: audiotext\src\main.py:153 +msgid "Dark" +msgstr "Oscuro" + +#: audiotext\src\main.py:109 +#: audiotext\src\main.py:123 +#: audiotext\src\main.py:154 +msgid "Light" +msgstr "Claro" + +#: audiotext\src\main.py:125 +#: audiotext\src\main.py:142 +msgid "Save transcription" +msgstr "Guardar transcripción" + +#: audiotext\src\main.py:175 +msgid "Error: No audio file selected, please select one before generating text." +msgstr "Error: No se ha seleccionado un archivo de audio, por favor seleccione uno antes de generar el texto." + +#: audiotext\src\main.py:219 +msgid "Error: The selected audio language is not valid." +msgstr "Error: El idioma del audio seleccionado no es válido." + diff --git a/src/constants.py b/src/constants.py new file mode 100644 index 0000000..3dc4166 --- /dev/null +++ b/src/constants.py @@ -0,0 +1,140 @@ +LANGUAGES = { + "af": "Afrikaans", + "am": "አማርኛ", + "ar": "العربية", + "ar_AE": "(العربية (الإمارات", + "ar_BH": "(العربية (البحرين", + "ar_DZ": "(العربية (الجزائر", + "ar_EG": "(العربية (مصر", + "ar_IL": "(العربية (إسرائيل", + "ar_JO": "(العربية (الأردن", + "ar_KW": "(العربية (الكويت", + "ar_LB": "(العربية (لبنان", + "ar_MA": "(العربية (المغرب", + "ar_OM": "(العربية (عُمان", + "ar_PS": "(العربية (فلسطين", + "ar_QA": "(العربية (قطر", + "ar_SA": "(العربية (السعودية", + "ar_TN": "(العربية (تونس", + "az": "Azərbaycan", + "be": "беларуская", + "bg": "Български", + "bn": "বাংলা", + "bn_BD": "বাংলা (বাংলাদেশ)", + "bn_IN": "বাংলা (ভারত)", + "ca": "Català", + "cs": "Čeština", + "da": "Dansk", + "de": "Deutsch", + "de_CH": "Schweizer Hochdeutsch", + "el": "Ελληνικά", + "en": "English (United States)", + "en_AU": "English (Australia)", + "en_CA": "English (Canada)", + "en_GB": "English (United Kingdom)", + "en_GH": "English (Ghana)", + "en_IE": "English (Ireland)", + "en_IN": "English (India)", + "en_KE": "English (Kenya)", + "en_NZ": "English (New Zealand)", + "en_NG": "English (Nigeria)", + "en_PH": "English (Philippines)", + "en_TZ": "English (Tanzania)", + "en_ZA": "English (South Africa)", + "es": "Español", + "es_419": "Español (Latinoamérica)", + "es_AR": "Español (Argentina)", + "es_BO": "Español (Bolivia)", + "es_CL": "Español (Chile)", + "es_CO": "Español (Colombia)", + "es_CR": "Español (Costa Rica)", + "es_DO": "Español (República Dominicana)", + "es_EC": "Español (Ecuador)", + "es_ES": "Español (España)", + "es_GT": "Español (Guatemala)", + "es_HN": "Español (Honduras)", + "es_MX": "Español (México)", + "es_NI": "Español (Nicaragua)", + "es_PA": "Español (Panamá)", + "es_PE": "Español (Perú)", + "es_PR": "Español (Puerto Rico)", + "es_PY": "Español (Paraguay)", + "es_SV": "Español (El Salvador)", + "es_US": "Español (Estados Unidos)", + "es_UY": "Español (Uruguay)", + "es_VE": "Español (Venezuela)", + "et": "Eesti", + "eu": "Euskara", + "fa": "فارسی", + "fi": "Suomi", + "fil": "Filipino", + "fr": "Français", + "fr_CA": "Français (Canada)", + "gl": "Galego", + "gu": "ગુજરાતી", + "hi": "हिन्दी", + "hr": "Hrvatski", + "hu": "Magyar", + "hy": "հայերեն", + "id": "Bahasa Indonesia", + "is": "Íslenska", + "it": "Italiano", + "it_CH": "Italiano (Svizzera)", + "it_IT": "Italiano (Italia)", + "iw": "עברית", + "ja": "日本語", + "jv": "Basa Jawa", + "ka": "ქართულად", + "kk": "Қазақ", + "km": "ខ្មែរ", + "kn": "ಕನ್ನಡ", + "ko": "한국어", + "lo": "ລາວ", + "lt": "Lietuvių", + "lv": "Latviešu", + "ml": "മലയാളം", + "mn": "Монгол", + "mr": "मराठी", + "ms": "Bahasa Melayu", + "mt": "Malti", + "ne": "नेपाली", + "nl": "Nederlands", + "nn": "Norsk (Nynorsk)", + "no": "Norsk (Bokmål)", + "pa": "ਪੰਜਾਬੀ", + "pl": "Polski", + "pt_BR": "Português (Brasil)", + "pt_PT": "Português (Portugal)", + "ro": "Română", + "ru": "Русский", + "si": "සිංහල", + "sk": "Slovenčina", + "sl": "Slovenščina", + "sr": "Српски", + "su": "Basa Sunda", + "sv": "Svenska", + "sw": "Kiswahili", + "sw_KE": "Kiswahili (Kenya)", + "sw_TZ": "Kiswahili (Tanzania)", + "ta": "தமிழ்", + "ta_IN": "தமிழ் (இந்தியா)", + "ta_LK": "தமிழ் (இலங்கை)", + "ta_MY": "தமிழ் (மலேஷியா)", + "ta_SG": "தமிழ் (சிங்கப்பூர்)", + "te": "తెలుగు", + "th": "ไทย", + "tr": "Türkçe", + "uk": "Українська", + "ur": "اردو", + "ur_IN": "(اردو (بھارت", + "ur_PK": "(اردو (پاکستان", + "vi": "Tiếng Việt", + "zh_CN": "中文(中国)", + "zh_HK": "中文(香港)", + "zh_TW": "中文(台灣)", + "zu": "Isizulu", +} + +AUDIO_FILE_EXTENSIONS = [".mp3", ".wav", ".ogg", ".opus", ".mpeg"] +FILE = "file" +MIC = "mic" diff --git a/src/logic.py b/src/logic.py new file mode 100644 index 0000000..041e48c --- /dev/null +++ b/src/logic.py @@ -0,0 +1,131 @@ +import constants +import os +import shutil +import speech_recognition as sr +import utils +from pathlib import Path +from pydub import AudioSegment +from pydub.silence import split_on_silence +from tkinter import filedialog + + +def open_file() -> str: + """ + Prompts a file explorer to determine the audio file path to transcribe. + + :return str: Filepath. + """ + filepath = filedialog.askopenfilename( + initialdir="/", + title=utils._("Select a file"), + filetypes=[(utils._("Audio files"), constants.AUDIO_FILE_EXTENSIONS)] + ) + return filepath + + +async def generate_file_transcription(filepath: str, language_code: str) -> str: + """ + Splits a large audio file into chunks + and applies speech recognition on each one. + + :param str filepath: Path of the file to transcribe. + :param str language_code: Code of the language spoken in the audio. + :return str: Audio transcription. + """ + # Create a speech recognition object + r = sr.Recognizer() + + # Open the audio file using pydub + content_type = Path(filepath).suffix + + if "wav" in content_type: + sound = AudioSegment.from_wav(filepath) + elif "ogg" in content_type or "opus" in content_type: + sound = AudioSegment.from_ogg(filepath) + elif "mp3" in content_type or "mpeg" in content_type: + sound = AudioSegment.from_mp3(filepath) + + # Split audio sound where silence is 700 miliseconds or more and get chunks + chunks = split_on_silence( + sound, + # Experiment with this value for your target audio file + min_silence_len=500, + # Adjust this per requirement + silence_thresh=sound.dBFS - 14, + # Keep the silence for 1 second, adjustable as well + keep_silence=500, + ) + + # Create a directory to store the audio chunks + folder_name = utils.ROOT_PATH / "audio-chunks" + folder_name.mkdir(exist_ok=True) + + whole_text = "" + # Process each chunk + for i, audio_chunk in enumerate(chunks, start=1): + # Export audio chunk and save it in the `folder_name` directory. + chunk_filename = os.path.join(folder_name, f"chunk{i}.wav") + audio_chunk.export(chunk_filename, format="wav") + + # Recognize the chunk + with sr.AudioFile(chunk_filename) as source: + audio_listened = r.record(source) + + # Try converting it to text + try: + text = r.recognize_google(audio_listened, language=language_code) + except Exception: + shutil.rmtree(folder_name) + return utils._("Error: Please, try again.") + + text = f"{text.capitalize()}. " + whole_text += text + + # Delete temporal directory and files + shutil.rmtree(folder_name) + + # Return the text for all chunks detected + return whole_text + + +async def generate_mic_transcription(language_code: str) -> str: + """ + Generates the transcription from a microphone as + the source of the audio. + + :param str language_code: Code of the language spoken. + :return str: Transcription. + """ + with sr.Microphone() as mic: + try: + r = sr.Recognizer() + print(f"{utils._('Say something')}…") + r.energy_threshold = 300 + r.adjust_for_ambient_noise(mic) + audio = r.listen(mic, timeout=3, phrase_time_limit=3) + text = r.recognize_google(audio, language=language_code) + return text + except OSError: + return utils._("Error: Microphone not available.") + + +def save_transcription(filepath, transcription): + """ + Prompts a file explorer to determine the file to save the + generated transcription. + + :param str | None filepath: Filepath of the audio file, in case that there is one. + :param str transcription: Text to save in the file. + """ + file = filedialog.asksaveasfile( + mode='w', + initialdir=Path(filepath).parent, + initialfile=f'{Path(filepath).stem}.txt', + title=utils._("Save as"), + defaultextension='.txt', + filetypes=[(utils._("Text file"), "*.txt"), (utils._("All Files"), "*.*")], + ) + + if file: + file.write(transcription) + file.close() diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..409c07c --- /dev/null +++ b/src/main.py @@ -0,0 +1,236 @@ +import asyncio +import constants +import customtkinter +import locale +import logic +import threading +import utils +from pathlib import Path + + +class App(customtkinter.CTk): + def __init__(self): + super().__init__() + self.filepath = "/" + self.transcription = None + + self.configure_window() + self.create_sidebar() + self.create_main_content() + + def configure_window(self): + # Modes: "System" (standard), "Dark", "Light" + customtkinter.set_appearance_mode("System") + # Themes: "blue" (standard), "green", "dark-blue" + customtkinter.set_default_color_theme("blue") + + self.title("Audiotext") + self.wm_iconbitmap(utils.ROOT_PATH / "res/img/icon.ico") + self.geometry(f"{1000}x{700}") + self.minsize(750, 550) + + self.grid_columnconfigure(1, weight=1) + self.grid_rowconfigure(1, weight=1) + + # VIEWS INITIALIZATION + + def create_sidebar(self): + # Sidebar frame + self.frm_sidebar = customtkinter.CTkFrame(self, width=140, corner_radius=0) + self.frm_sidebar.grid(row=0, column=0, rowspan=4, sticky="nsew") + self.frm_sidebar.grid_rowconfigure(6, weight=1) + + # Logo label + self.lbl_logo = customtkinter.CTkLabel( + self.frm_sidebar, + text="Audiotext", + font=customtkinter.CTkFont(size=20, weight="bold"), + ) + self.lbl_logo.grid(row=0, column=0, padx=20, pady=(20, 10)) + + # Audio language + self.lbl_audio_language = customtkinter.CTkLabel( + self.frm_sidebar, text=f'{utils._("Audio language")}:', anchor="w" + ) + self.lbl_audio_language.grid(row=1, column=0, padx=20, pady=(20, 0)) + + self.cbx_audio_language = customtkinter.CTkComboBox( + self.frm_sidebar, values=list(constants.LANGUAGES.values()) + ) + self.cbx_audio_language.grid(row=2, column=0, padx=20, pady=10) + self.cbx_audio_language.set(constants.LANGUAGES[locale.getdefaultlocale()[0]]) + + # Select file button + self.btn_select_file = customtkinter.CTkButton( + self.frm_sidebar, text=utils._("Select audio file"), command=self.open_file + ) + self.btn_select_file.grid(row=3, column=0, padx=20, pady=(30, 20)) + + # Transcribe from microphone button + self.btn_transcribe_from_mic = customtkinter.CTkButton( + self.frm_sidebar, + text=utils._("Transcribe from microphone"), + command=lambda: self.get_transcription(constants.MIC) + ) + self.btn_transcribe_from_mic.grid(row=4, column=0, padx=20, pady=(20, 30)) + + # Generate text button + self.btn_generate_text = customtkinter.CTkButton( + self.frm_sidebar, + fg_color="green", + text=utils._("Generate text"), + command=lambda: self.get_transcription(constants.FILE) + ) + self.btn_generate_text.grid(row=5, column=0, padx=20, pady=10) + self.btn_generate_text.grid_remove() # hidden at start + + # App language + self.lbl_app_language = customtkinter.CTkLabel( + self.frm_sidebar, text=f'{utils._("App language")}:', anchor="w" + ) + self.lbl_app_language.grid(row=7, column=0, padx=20, pady=(20, 0)) + + self.omn_app_language = customtkinter.CTkOptionMenu( + self.frm_sidebar, + values=[constants.LANGUAGES["es"], constants.LANGUAGES["en"]], + command=self.change_app_language, + ) + self.omn_app_language.grid(row=8, column=0, padx=20, pady=10) + self.omn_app_language.set(constants.LANGUAGES[locale.getdefaultlocale()[0]]) + + # Appearance mode + self.lbl_appearance_mode = customtkinter.CTkLabel( + self.frm_sidebar, text=f'{utils._("Appearance mode")}:', anchor="w" + ) + self.lbl_appearance_mode.grid(row=9, column=0, padx=20, pady=(10, 0)) + + self.omn_appearance_mode = customtkinter.CTkOptionMenu( + self.frm_sidebar, + values=[utils._("System"), utils._("Light"), utils._("Dark")], + command=self.change_appearance_mode_event, + ) + self.omn_appearance_mode.grid(row=10, column=0, padx=20, pady=10) + + def change_app_language(self, language_name): + language_code = [i for i in constants.LANGUAGES if constants.LANGUAGES[i] == language_name][0] + utils.load_translation(language_code) + + self.lbl_audio_language.configure(text=f'{utils._("Audio language")}:') + self.btn_select_file.configure(text=utils._("Select audio file")) + self.btn_transcribe_from_mic.configure(text=utils._("Transcribe from microphone")) + self.btn_generate_text.configure(text=utils._("Generate text")) + self.lbl_app_language.configure(text=f'{utils._("App language")}:') + self.lbl_appearance_mode.configure(text=f'{utils._("Appearance mode")}:') + self.omn_appearance_mode.configure(values=[utils._("System"), utils._("Light"), utils._("Dark")]) + self.omn_appearance_mode.set(utils._("System")) + self.btn_save.configure(text=utils._("Save transcription")) + customtkinter.set_appearance_mode(utils._(self.omn_appearance_mode.get())) + + def create_main_content(self): + # Selected file entry + self.ent_selected_file = customtkinter.CTkEntry(self, state="disabled") + self.ent_selected_file.grid(row=0, column=1, padx=20, pady=(20, 0), sticky="new") + self.ent_selected_file.grid_remove() # hidden at start + + # Text audio textbox + self.tbx_transcription = customtkinter.CTkTextbox(self, wrap="word") + self.tbx_transcription.grid(row=1, column=1, padx=20, pady=20, sticky="nsew") + + # Save text button + self.btn_save = customtkinter.CTkButton( + self, + fg_color="green", + text=utils._("Save transcription"), + command=self.save_transcription + ) + self.btn_save.grid(row=2, column=1, padx=20, pady=(0, 20), sticky="sew") + self.btn_save.grid_remove() # hidden at start + + # EVENTS + + @staticmethod + def change_appearance_mode_event(new_appearance_mode: str): + appearance_mode_map = { + utils._("Dark"): "Dark", + utils._("Light"): "Light", + utils._("System"): "System" + } + appearance_mode = appearance_mode_map.get(new_appearance_mode, "System") + customtkinter.set_appearance_mode(appearance_mode) + + def open_file(self): + # Open the file dialog + filepath = logic.open_file() + + if filepath: + self.filepath = filepath + self.ent_selected_file.grid() + self.ent_selected_file.configure(textvariable=customtkinter.StringVar(self, filepath)) + self.btn_generate_text.grid() + + def get_transcription(self, source): + selected_path = Path(self.filepath) + is_file_valid = selected_path.is_file() and selected_path.suffix in constants.AUDIO_FILE_EXTENSIONS + + if source == constants.FILE and not is_file_valid: + self.display_text(utils._("Error: No audio file selected, please select one before generating text.")) + else: + threading.Thread( + target=lambda loop: loop.run_until_complete(self.async_get_transcription(source)), + args=(asyncio.new_event_loop(),) + ).start() + + async def async_get_transcription(self, source): + try: + # Get the selected language code + language_code = [ + key + for key, value in constants.LANGUAGES.items() + if value.lower() == self.cbx_audio_language.get().strip().lower() + ][0] + + # Show progress bar + self.progress_bar = customtkinter.CTkProgressBar(self) + self.progress_bar.grid(row=1, column=1, padx=40, pady=0, sticky="ew") + self.progress_bar.configure(mode="indeterminnate") + self.progress_bar.start() + + # Disable action buttons to avoid multiple requests at the same time + self.btn_generate_text.configure(state="disabled") + self.btn_transcribe_from_mic.configure(state="disabled") + + # Get transcription + if source == constants.FILE: + self.transcription = await logic.generate_file_transcription(self.filepath, language_code) + elif source == constants.MIC: + self.transcription = await logic.generate_mic_transcription(language_code) + + # Display transcription + self.display_text(self.transcription) + + # Re-enable action buttons + self.btn_generate_text.configure(state="enabled") + self.btn_transcribe_from_mic.configure(state="enabled") + + # Hide generate button and remove progress bar + self.progress_bar.grid_forget() + self.btn_generate_text.grid_remove() + + # Show save button if transcription is not empty + if self.transcription: + self.btn_save.grid() + except IndexError: + self.display_text(utils._("Error: The selected audio language is not valid.")) + + def display_text(self, message): + self.tbx_transcription.delete("1.0", "end") + self.tbx_transcription.insert("0.0", message) + + def save_transcription(self): + logic.save_transcription(self.ent_selected_file.get(), self.tbx_transcription.get("1.0", "end-1c")) + + +if __name__ == "__main__": + app = App() + app.eval('tk::PlaceWindow . center') + app.mainloop() diff --git a/src/utils.py b/src/utils.py new file mode 100644 index 0000000..f248722 --- /dev/null +++ b/src/utils.py @@ -0,0 +1,35 @@ +import gettext +import locale +import sys +from pathlib import Path + + +def get_path(relative_path: str = ""): + """Get absolute path, works for dev and for PyInstaller""" + try: + # PyInstaller creates a temp folder and stores path in _MEIPASS + base_path = Path(sys._MEIPASS) + except (Exception,): + base_path = Path(__file__).parent.parent.resolve() + + return (base_path / relative_path).resolve() + + +ROOT_PATH = get_path("") + +_ = None + + +def load_translation(language_code: str): + translation = gettext.translation( + "all", + localedir=ROOT_PATH / "res/locales", + languages=[language_code], + fallback=True + ) + translation.install() + global _ + _ = translation.gettext + + +load_translation(locale.getdefaultlocale()[0])