Add auto update check for Windows adn Mac (#1404)

This commit is contained in:
Raivis Dejus 2026-02-28 16:39:04 +02:00 committed by GitHub
commit 187d15b8e8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 1864 additions and 57 deletions

View file

@ -66,9 +66,6 @@ jobs:
run: |
sudo -E snapcraft pack --verbose --destructive-mode
echo "snap=$(ls *.snap)" >> $GITHUB_OUTPUT
- run: |
sudo apt-get update
sudo apt-get install libportaudio2 libtbb-dev
- run: sudo snap install --devmode *.snap
- run: |
cd $HOME

View file

@ -35,6 +35,11 @@ endif
COVERAGE_THRESHOLD := 70
test: buzz/whisper_cpp
# A check to get updates of yt-dlp. Should run only on local as part of regular development operations
# Sort of a local "update checker"
ifndef CI
uv lock --upgrade-package yt-dlp
endif
pytest -s -vv --cov=buzz --cov-report=xml --cov-report=html --benchmark-skip --cov-fail-under=${COVERAGE_THRESHOLD} --cov-config=.coveragerc
benchmarks: buzz/whisper_cpp

View file

@ -88,6 +88,10 @@ pip3 install nvidia-cublas-cu12==12.9.1.4 nvidia-cuda-cupti-cu12==12.9.79 nvidia
For info on how to get latest development version with latest features and bug fixes see [FAQ](https://chidiwilliams.github.io/buzz/docs/faq#9-where-can-i-get-latest-development-version).
### Support Buzz
You can help the Buzz by starring 🌟 the repo and sharing it with your friends.
### Screenshots
<div style="display: flex; flex-wrap: wrap;">

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M160-200v-60h640v60H160Zm320-136L280-536l42-42 128 128v-310h60v310l128-128 42 42-200 200Z" transform="rotate(180 480 -480)"/></svg>

After

Width:  |  Height:  |  Size: 229 B

View file

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: buzz\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-27 16:46+0200\n"
"POT-Creation-Date: 2026-02-28 10:38+0200\n"
"PO-Revision-Date: 2025-10-17 07:59+0200\n"
"Last-Translator: Éric Duarte <contacto@ericdq.com>\n"
"Language-Team: Catalan <jmas@softcatala.org>\n"
@ -345,7 +345,8 @@ msgid "Download failed"
msgstr "Descàrrega fallida"
#: buzz/widgets/preferences_dialog/models_preferences_widget.py
#: buzz/widgets/transcription_tasks_table_widget.py buzz/widgets/main_window.py
#: buzz/widgets/transcription_tasks_table_widget.py
#: buzz/widgets/update_dialog.py buzz/widgets/main_window.py
#: buzz/model_loader.py
msgid "Error"
msgstr "Error"
@ -536,6 +537,10 @@ msgstr "Cancel·la la transcripció"
msgid "Clear History"
msgstr "Neteja l'historial"
#: buzz/widgets/main_window_toolbar.py buzz/widgets/update_dialog.py
msgid "Update Available"
msgstr "Actualització disponible"
#: buzz/widgets/transcription_tasks_table_widget.py
msgid "In Progress"
msgstr "En progrés"
@ -730,6 +735,58 @@ msgstr ""
"Comproveu els vostres dispositius d'àudio o els registres de l'aplicació per "
"a més informació."
#: buzz/widgets/update_dialog.py
msgid "A new version of Buzz is available!"
msgstr "Hi ha una nova versió de Buzz disponible!"
#: buzz/widgets/update_dialog.py
msgid "Current version:"
msgstr "Versió actual:"
#: buzz/widgets/update_dialog.py
msgid "New version:"
msgstr "Nova versió:"
#: buzz/widgets/update_dialog.py
msgid "Release Notes:"
msgstr "Notes de la versió:"
#: buzz/widgets/update_dialog.py
msgid "Download and Install"
msgstr "Descarrega i instal·la"
#: buzz/widgets/update_dialog.py
msgid "No download URL available for your platform."
msgstr "No hi ha cap URL de descàrrega disponible per a la vostra plataforma."
#: buzz/widgets/update_dialog.py
msgid "Downloading file {} of {}..."
msgstr "S'està descarregant el fitxer {} de {}..."
#: buzz/widgets/update_dialog.py
msgid "Downloading file {} of {} ({:.1f} MB / {:.1f} MB)..."
msgstr "S'està descarregant el fitxer {} de {} ({:.1f} MB / {:.1f} MB)..."
#: buzz/widgets/update_dialog.py
msgid "Download Failed"
msgstr "Descàrrega fallida"
#: buzz/widgets/update_dialog.py
msgid "Failed to download the update: {}"
msgstr "No s'ha pogut descarregar l'actualització: {}"
#: buzz/widgets/update_dialog.py
msgid "Failed to save the installer: {}"
msgstr "No s'ha pogut desar l'instal·lador: {}"
#: buzz/widgets/update_dialog.py
msgid "Download complete!"
msgstr "Descàrrega completada!"
#: buzz/widgets/update_dialog.py
msgid "Failed to run the installer: {}"
msgstr "No s'ha pogut executar l'instal·lador: {}"
#: buzz/widgets/about_dialog.py
msgid "Check for updates"
msgstr "Comprova si hi ha actualitzacions"

View file

@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-27 16:46+0200\n"
"POT-Creation-Date: 2026-02-28 10:38+0200\n"
"PO-Revision-Date: \n"
"Last-Translator: Ole Guldberg2 <xalt7x.service@gmail.com>\n"
"Language-Team: \n"
@ -341,7 +341,8 @@ msgid "Download failed"
msgstr "Download mislykkedes"
#: buzz/widgets/preferences_dialog/models_preferences_widget.py
#: buzz/widgets/transcription_tasks_table_widget.py buzz/widgets/main_window.py
#: buzz/widgets/transcription_tasks_table_widget.py
#: buzz/widgets/update_dialog.py buzz/widgets/main_window.py
#: buzz/model_loader.py
msgid "Error"
msgstr "Fejl"
@ -531,6 +532,10 @@ msgstr "Afbryd transkription"
msgid "Clear History"
msgstr "Ryd historik"
#: buzz/widgets/main_window_toolbar.py buzz/widgets/update_dialog.py
msgid "Update Available"
msgstr "Opdatering tilgængelig"
#: buzz/widgets/transcription_tasks_table_widget.py
msgid "In Progress"
msgstr "Arbejder"
@ -724,6 +729,58 @@ msgstr ""
"Tjek venligst dine audioenheder eller tjek applikationens logs for "
"mereinformation."
#: buzz/widgets/update_dialog.py
msgid "A new version of Buzz is available!"
msgstr "En ny version af Buzz er tilgængelig!"
#: buzz/widgets/update_dialog.py
msgid "Current version:"
msgstr "Nuværende version:"
#: buzz/widgets/update_dialog.py
msgid "New version:"
msgstr "Ny version:"
#: buzz/widgets/update_dialog.py
msgid "Release Notes:"
msgstr "Udgivelsesnoter:"
#: buzz/widgets/update_dialog.py
msgid "Download and Install"
msgstr "Download og installer"
#: buzz/widgets/update_dialog.py
msgid "No download URL available for your platform."
msgstr "Ingen download-URL tilgængelig for din platform."
#: buzz/widgets/update_dialog.py
msgid "Downloading file {} of {}..."
msgstr "Downloader fil {} af {}..."
#: buzz/widgets/update_dialog.py
msgid "Downloading file {} of {} ({:.1f} MB / {:.1f} MB)..."
msgstr "Downloader fil {} af {} ({:.1f} MB / {:.1f} MB)..."
#: buzz/widgets/update_dialog.py
msgid "Download Failed"
msgstr "Download mislykkedes"
#: buzz/widgets/update_dialog.py
msgid "Failed to download the update: {}"
msgstr "Kunne ikke downloade opdateringen: {}"
#: buzz/widgets/update_dialog.py
msgid "Failed to save the installer: {}"
msgstr "Kunne ikke gemme installationsprogrammet: {}"
#: buzz/widgets/update_dialog.py
msgid "Download complete!"
msgstr "Download fuldført!"
#: buzz/widgets/update_dialog.py
msgid "Failed to run the installer: {}"
msgstr "Kunne ikke køre installationsprogrammet: {}"
#: buzz/widgets/about_dialog.py
msgid "Check for updates"
msgstr "Tjek for opdateringer"
@ -1564,4 +1621,6 @@ msgstr "Tilføj og ret"
msgid ""
"Speech extraction failed! Check your internet connection — a model may need "
"to be downloaded."
msgstr "Taleoprydning mislykkedes! Kontroller din internetforbindelse — en model skal muligvis hentes ned."
msgstr ""
"Taleoprydning mislykkedes! Kontroller din internetforbindelse — en model "
"skal muligvis hentes ned."

View file

@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-27 16:46+0200\n"
"POT-Creation-Date: 2026-02-28 10:38+0200\n"
"PO-Revision-Date: 2025-03-05 14:41+0100\n"
"Last-Translator: \n"
"Language-Team: \n"
@ -345,7 +345,8 @@ msgid "Download failed"
msgstr "Der Download ist fehlgeschlagen"
#: buzz/widgets/preferences_dialog/models_preferences_widget.py
#: buzz/widgets/transcription_tasks_table_widget.py buzz/widgets/main_window.py
#: buzz/widgets/transcription_tasks_table_widget.py
#: buzz/widgets/update_dialog.py buzz/widgets/main_window.py
#: buzz/model_loader.py
msgid "Error"
msgstr "Fehler"
@ -538,6 +539,10 @@ msgstr "Transkription abbrechen"
msgid "Clear History"
msgstr "Verlauf löschen"
#: buzz/widgets/main_window_toolbar.py buzz/widgets/update_dialog.py
msgid "Update Available"
msgstr "Update verfügbar"
#: buzz/widgets/transcription_tasks_table_widget.py
msgid "In Progress"
msgstr "Im Gange"
@ -734,6 +739,58 @@ msgstr ""
"Bitte überprüfen Sie Ihre Audiogeräte oder prüfen Sie die "
"Anwendungsprotokolle für weitere Informationen."
#: buzz/widgets/update_dialog.py
msgid "A new version of Buzz is available!"
msgstr "Eine neue Version von Buzz ist verfügbar!"
#: buzz/widgets/update_dialog.py
msgid "Current version:"
msgstr "Aktuelle Version:"
#: buzz/widgets/update_dialog.py
msgid "New version:"
msgstr "Neue Version:"
#: buzz/widgets/update_dialog.py
msgid "Release Notes:"
msgstr "Versionshinweise:"
#: buzz/widgets/update_dialog.py
msgid "Download and Install"
msgstr "Herunterladen und installieren"
#: buzz/widgets/update_dialog.py
msgid "No download URL available for your platform."
msgstr "Kein Download-Link für Ihre Plattform verfügbar."
#: buzz/widgets/update_dialog.py
msgid "Downloading file {} of {}..."
msgstr "Datei {} von {} wird heruntergeladen..."
#: buzz/widgets/update_dialog.py
msgid "Downloading file {} of {} ({:.1f} MB / {:.1f} MB)..."
msgstr "Datei {} von {} wird heruntergeladen ({:.1f} MB / {:.1f} MB)..."
#: buzz/widgets/update_dialog.py
msgid "Download Failed"
msgstr "Download fehlgeschlagen"
#: buzz/widgets/update_dialog.py
msgid "Failed to download the update: {}"
msgstr "Das Update konnte nicht heruntergeladen werden: {}"
#: buzz/widgets/update_dialog.py
msgid "Failed to save the installer: {}"
msgstr "Installer konnte nicht gespeichert werden: {}"
#: buzz/widgets/update_dialog.py
msgid "Download complete!"
msgstr "Download abgeschlossen!"
#: buzz/widgets/update_dialog.py
msgid "Failed to run the installer: {}"
msgstr "Installer konnte nicht ausgeführt werden: {}"
#: buzz/widgets/about_dialog.py
msgid "Check for updates"
msgstr "Nach Updates suchen"

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-27 16:46+0200\n"
"POT-Creation-Date: 2026-02-28 10:38+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -331,7 +331,8 @@ msgid "Download failed"
msgstr ""
#: buzz/widgets/preferences_dialog/models_preferences_widget.py
#: buzz/widgets/transcription_tasks_table_widget.py buzz/widgets/main_window.py
#: buzz/widgets/transcription_tasks_table_widget.py
#: buzz/widgets/update_dialog.py buzz/widgets/main_window.py
#: buzz/model_loader.py
msgid "Error"
msgstr ""
@ -516,6 +517,10 @@ msgstr ""
msgid "Clear History"
msgstr ""
#: buzz/widgets/main_window_toolbar.py buzz/widgets/update_dialog.py
msgid "Update Available"
msgstr ""
#: buzz/widgets/transcription_tasks_table_widget.py
msgid "In Progress"
msgstr ""
@ -704,6 +709,58 @@ msgid ""
"information."
msgstr ""
#: buzz/widgets/update_dialog.py
msgid "A new version of Buzz is available!"
msgstr ""
#: buzz/widgets/update_dialog.py
msgid "Current version:"
msgstr ""
#: buzz/widgets/update_dialog.py
msgid "New version:"
msgstr ""
#: buzz/widgets/update_dialog.py
msgid "Release Notes:"
msgstr ""
#: buzz/widgets/update_dialog.py
msgid "Download and Install"
msgstr ""
#: buzz/widgets/update_dialog.py
msgid "No download URL available for your platform."
msgstr ""
#: buzz/widgets/update_dialog.py
msgid "Downloading file {} of {}..."
msgstr ""
#: buzz/widgets/update_dialog.py
msgid "Downloading file {} of {} ({:.1f} MB / {:.1f} MB)..."
msgstr ""
#: buzz/widgets/update_dialog.py
msgid "Download Failed"
msgstr ""
#: buzz/widgets/update_dialog.py
msgid "Failed to download the update: {}"
msgstr ""
#: buzz/widgets/update_dialog.py
msgid "Failed to save the installer: {}"
msgstr ""
#: buzz/widgets/update_dialog.py
msgid "Download complete!"
msgstr ""
#: buzz/widgets/update_dialog.py
msgid "Failed to run the installer: {}"
msgstr ""
#: buzz/widgets/about_dialog.py
msgid "Check for updates"
msgstr ""

View file

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-27 16:46+0200\n"
"POT-Creation-Date: 2026-02-28 10:38+0200\n"
"PO-Revision-Date: 2025-09-08 12:43+0200\n"
"Last-Translator: Éric Duarte <contacto@ericdq.com>\n"
"Language-Team: \n"
@ -353,7 +353,8 @@ msgid "Download failed"
msgstr "Descarga fallida"
#: buzz/widgets/preferences_dialog/models_preferences_widget.py
#: buzz/widgets/transcription_tasks_table_widget.py buzz/widgets/main_window.py
#: buzz/widgets/transcription_tasks_table_widget.py
#: buzz/widgets/update_dialog.py buzz/widgets/main_window.py
#: buzz/model_loader.py
msgid "Error"
msgstr "Error"
@ -563,6 +564,10 @@ msgstr "Cancelar transcripción"
msgid "Clear History"
msgstr "Vaciar historial"
#: buzz/widgets/main_window_toolbar.py buzz/widgets/update_dialog.py
msgid "Update Available"
msgstr "Actualización disponible"
#: buzz/widgets/transcription_tasks_table_widget.py
msgid "In Progress"
msgstr "En Progreso"
@ -765,6 +770,58 @@ msgstr ""
"Compruebe sus dispositivos de audio o consulte los registros de la "
"aplicación para obtener más información."
#: buzz/widgets/update_dialog.py
msgid "A new version of Buzz is available!"
msgstr "¡Hay una nueva versión de Buzz disponible!"
#: buzz/widgets/update_dialog.py
msgid "Current version:"
msgstr "Versión actual:"
#: buzz/widgets/update_dialog.py
msgid "New version:"
msgstr "Nueva versión:"
#: buzz/widgets/update_dialog.py
msgid "Release Notes:"
msgstr "Notas de la versión:"
#: buzz/widgets/update_dialog.py
msgid "Download and Install"
msgstr "Descargar e instalar"
#: buzz/widgets/update_dialog.py
msgid "No download URL available for your platform."
msgstr "No hay URL de descarga disponible para su plataforma."
#: buzz/widgets/update_dialog.py
msgid "Downloading file {} of {}..."
msgstr "Descargando archivo {} de {}..."
#: buzz/widgets/update_dialog.py
msgid "Downloading file {} of {} ({:.1f} MB / {:.1f} MB)..."
msgstr "Descargando archivo {} de {} ({:.1f} MB / {:.1f} MB)..."
#: buzz/widgets/update_dialog.py
msgid "Download Failed"
msgstr "Descarga fallida"
#: buzz/widgets/update_dialog.py
msgid "Failed to download the update: {}"
msgstr "Error al descargar la actualización: {}"
#: buzz/widgets/update_dialog.py
msgid "Failed to save the installer: {}"
msgstr "No se pudo guardar el instalador: {}"
#: buzz/widgets/update_dialog.py
msgid "Download complete!"
msgstr "¡Descarga completa!"
#: buzz/widgets/update_dialog.py
msgid "Failed to run the installer: {}"
msgstr "No se pudo ejecutar el instalador: {}"
# automatic translation
#: buzz/widgets/about_dialog.py
msgid "Check for updates"

View file

@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: buzz\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-27 16:46+0200\n"
"POT-Creation-Date: 2026-02-28 10:38+0200\n"
"PO-Revision-Date: 2026-01-25 21:42+0200\n"
"Language-Team: (Italiano) Albano Battistella <albanobattistella@gmail.com>\n"
"Language: it_IT\n"
@ -345,7 +345,8 @@ msgid "Download failed"
msgstr "Download non riuscito"
#: buzz/widgets/preferences_dialog/models_preferences_widget.py
#: buzz/widgets/transcription_tasks_table_widget.py buzz/widgets/main_window.py
#: buzz/widgets/transcription_tasks_table_widget.py
#: buzz/widgets/update_dialog.py buzz/widgets/main_window.py
#: buzz/model_loader.py
msgid "Error"
msgstr "Errore"
@ -539,6 +540,10 @@ msgstr "Annulla trascrizione"
msgid "Clear History"
msgstr "Elimina la cronologia"
#: buzz/widgets/main_window_toolbar.py buzz/widgets/update_dialog.py
msgid "Update Available"
msgstr "Aggiornamento Disponibile"
#: buzz/widgets/transcription_tasks_table_widget.py
msgid "In Progress"
msgstr "In corso"
@ -731,6 +736,58 @@ msgstr ""
"Controlla i tuoi dispositivi audio o i registri dell'applicazione per "
"maggiori informazioni."
#: buzz/widgets/update_dialog.py
msgid "A new version of Buzz is available!"
msgstr "È disponibile una nuova versione di Buzz!"
#: buzz/widgets/update_dialog.py
msgid "Current version:"
msgstr "Versione attuale:"
#: buzz/widgets/update_dialog.py
msgid "New version:"
msgstr "Nuova versione:"
#: buzz/widgets/update_dialog.py
msgid "Release Notes:"
msgstr "Note di rilascio:"
#: buzz/widgets/update_dialog.py
msgid "Download and Install"
msgstr "Scarica e installa"
#: buzz/widgets/update_dialog.py
msgid "No download URL available for your platform."
msgstr "Nessun URL di download disponibile per la tua piattaforma."
#: buzz/widgets/update_dialog.py
msgid "Downloading file {} of {}..."
msgstr "Download del file {} di {}..."
#: buzz/widgets/update_dialog.py
msgid "Downloading file {} of {} ({:.1f} MB / {:.1f} MB)..."
msgstr "Download del file {} di {} ({:.1f} MB / {:.1f} MB)..."
#: buzz/widgets/update_dialog.py
msgid "Download Failed"
msgstr "Download fallito"
#: buzz/widgets/update_dialog.py
msgid "Failed to download the update: {}"
msgstr "Impossibile scaricare l'aggiornamento: {}"
#: buzz/widgets/update_dialog.py
msgid "Failed to save the installer: {}"
msgstr "Impossibile salvare il programma di installazione: {}"
#: buzz/widgets/update_dialog.py
msgid "Download complete!"
msgstr "Download completato!"
#: buzz/widgets/update_dialog.py
msgid "Failed to run the installer: {}"
msgstr "Impossibile eseguire il programma di installazione: {}"
#: buzz/widgets/about_dialog.py
msgid "Check for updates"
msgstr "Controlla gli aggiornamenti"

View file

@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-27 16:46+0200\n"
"POT-Creation-Date: 2026-02-28 10:38+0200\n"
"PO-Revision-Date: \n"
"Last-Translator: nunawa <71294849+nunawa@users.noreply.github.com>\n"
"Language-Team: \n"
@ -338,7 +338,8 @@ msgid "Download failed"
msgstr "ダウンロード失敗"
#: buzz/widgets/preferences_dialog/models_preferences_widget.py
#: buzz/widgets/transcription_tasks_table_widget.py buzz/widgets/main_window.py
#: buzz/widgets/transcription_tasks_table_widget.py
#: buzz/widgets/update_dialog.py buzz/widgets/main_window.py
#: buzz/model_loader.py
msgid "Error"
msgstr "エラー"
@ -528,6 +529,10 @@ msgstr "文字起こしをキャンセルする"
msgid "Clear History"
msgstr "履歴を削除する"
#: buzz/widgets/main_window_toolbar.py buzz/widgets/update_dialog.py
msgid "Update Available"
msgstr "アップデートあり"
#: buzz/widgets/transcription_tasks_table_widget.py
msgid "In Progress"
msgstr "進行中"
@ -720,6 +725,58 @@ msgstr ""
"オーディオデバイスを確認するか、詳細をアプリケーションのログで確認してくださ"
"い。"
#: buzz/widgets/update_dialog.py
msgid "A new version of Buzz is available!"
msgstr "Buzzの新しいバージョンが利用可能です"
#: buzz/widgets/update_dialog.py
msgid "Current version:"
msgstr "現在のバージョン:"
#: buzz/widgets/update_dialog.py
msgid "New version:"
msgstr "新しいバージョン:"
#: buzz/widgets/update_dialog.py
msgid "Release Notes:"
msgstr "リリースノート:"
#: buzz/widgets/update_dialog.py
msgid "Download and Install"
msgstr "ダウンロードしてインストール"
#: buzz/widgets/update_dialog.py
msgid "No download URL available for your platform."
msgstr "お使いのプラットフォーム向けのダウンロードURLがありません。"
#: buzz/widgets/update_dialog.py
msgid "Downloading file {} of {}..."
msgstr "ファイル {} / {} をダウンロード中..."
#: buzz/widgets/update_dialog.py
msgid "Downloading file {} of {} ({:.1f} MB / {:.1f} MB)..."
msgstr "ファイル {} / {} をダウンロード中 ({:.1f} MB / {:.1f} MB)..."
#: buzz/widgets/update_dialog.py
msgid "Download Failed"
msgstr "ダウンロード失敗"
#: buzz/widgets/update_dialog.py
msgid "Failed to download the update: {}"
msgstr "アップデートのダウンロードに失敗しました: {}"
#: buzz/widgets/update_dialog.py
msgid "Failed to save the installer: {}"
msgstr "インストーラーの保存に失敗しました: {}"
#: buzz/widgets/update_dialog.py
msgid "Download complete!"
msgstr "ダウンロード完了!"
#: buzz/widgets/update_dialog.py
msgid "Failed to run the installer: {}"
msgstr "インストーラーの実行に失敗しました: {}"
#: buzz/widgets/about_dialog.py
msgid "Check for updates"
msgstr "アップデートを確認する"

View file

@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-27 16:46+0200\n"
"PO-Revision-Date: 2026-02-27 16:47+0200\n"
"POT-Creation-Date: 2026-02-28 10:38+0200\n"
"PO-Revision-Date: 2026-02-28 10:46+0200\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: lv_LV\n"
@ -186,7 +186,7 @@ msgid ""
"in the Live Recording screen in a future version."
msgstr ""
"Piezīme: Dzīvā ieraksta iestatījumi nākotnes Buzz versijās tiks pārvietoti "
"uz Papildu iestatījumu sadaļu Dzīvā ieraksta logā"
"uz Papildu iestatījumu sadaļu Dzīvā ieraksta logā."
#: buzz/widgets/preferences_dialog/general_preferences_widget.py
msgid "Use 8-bit quantization to reduce memory usage"
@ -346,7 +346,8 @@ msgid "Download failed"
msgstr "Lejupielāde neizdevās"
#: buzz/widgets/preferences_dialog/models_preferences_widget.py
#: buzz/widgets/transcription_tasks_table_widget.py buzz/widgets/main_window.py
#: buzz/widgets/transcription_tasks_table_widget.py
#: buzz/widgets/update_dialog.py buzz/widgets/main_window.py
#: buzz/model_loader.py
msgid "Error"
msgstr "Kļūda"
@ -540,6 +541,10 @@ msgstr "Atcelt atpazīšanu"
msgid "Clear History"
msgstr "Notīrīt vēsturi"
#: buzz/widgets/main_window_toolbar.py buzz/widgets/update_dialog.py
msgid "Update Available"
msgstr "Pieejams atjauninājums"
#: buzz/widgets/transcription_tasks_table_widget.py
msgid "In Progress"
msgstr "Apstrādā"
@ -731,6 +736,58 @@ msgstr ""
"Lūdzu pārbaudiet savas audio ierīces vai pārbaudiet lietotnes ziņojumu "
"žurnālus, lai iegūtu papildu informāciju."
#: buzz/widgets/update_dialog.py
msgid "A new version of Buzz is available!"
msgstr "Pieejama jauna Buzz versija!"
#: buzz/widgets/update_dialog.py
msgid "Current version:"
msgstr "Instalētā versija:"
#: buzz/widgets/update_dialog.py
msgid "New version:"
msgstr "Jaunā versija:"
#: buzz/widgets/update_dialog.py
msgid "Release Notes:"
msgstr "Jaunās versijas piezīmes:"
#: buzz/widgets/update_dialog.py
msgid "Download and Install"
msgstr "Lejupielādēt un instalēt"
#: buzz/widgets/update_dialog.py
msgid "No download URL available for your platform."
msgstr "Jūsu datoram nav pieejama atjauninājum versija."
#: buzz/widgets/update_dialog.py
msgid "Downloading file {} of {}..."
msgstr "Lejupielādē {} no {}..."
#: buzz/widgets/update_dialog.py
msgid "Downloading file {} of {} ({:.1f} MB / {:.1f} MB)..."
msgstr "Lejupielādē {} no {} ({:.1f} MB / {:.1f} MB)..."
#: buzz/widgets/update_dialog.py
msgid "Download Failed"
msgstr "Lejupielāde neizdevās"
#: buzz/widgets/update_dialog.py
msgid "Failed to download the update: {}"
msgstr "Neizdevās lejupielādēt atjauninājumu: {}"
#: buzz/widgets/update_dialog.py
msgid "Failed to save the installer: {}"
msgstr "Neizdevās saglabāt atjauninājumu: {}"
#: buzz/widgets/update_dialog.py
msgid "Download complete!"
msgstr "Lejupielāde pabeigta!"
#: buzz/widgets/update_dialog.py
msgid "Failed to run the installer: {}"
msgstr "Neizdevās sākt atjauninājumu: {}"
#: buzz/widgets/about_dialog.py
msgid "Check for updates"
msgstr "Pārbaudīt atjauninājumus"

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-27 16:46+0200\n"
"POT-Creation-Date: 2026-02-28 10:38+0200\n"
"PO-Revision-Date: 2025-03-20 18:30+0100\n"
"Last-Translator: Heimen Stoffels <vistausss@fastmail.com>\n"
"Language-Team: none\n"
@ -347,7 +347,8 @@ msgid "Download failed"
msgstr "Het downloaden is mislukt"
#: buzz/widgets/preferences_dialog/models_preferences_widget.py
#: buzz/widgets/transcription_tasks_table_widget.py buzz/widgets/main_window.py
#: buzz/widgets/transcription_tasks_table_widget.py
#: buzz/widgets/update_dialog.py buzz/widgets/main_window.py
#: buzz/model_loader.py
msgid "Error"
msgstr "Foutmelding"
@ -540,6 +541,10 @@ msgstr "Transcriptie wissen"
msgid "Clear History"
msgstr "Geschiedenis wissen"
#: buzz/widgets/main_window_toolbar.py buzz/widgets/update_dialog.py
msgid "Update Available"
msgstr "Update Beschikbaar"
#: buzz/widgets/transcription_tasks_table_widget.py
msgid "In Progress"
msgstr "In behandeling"
@ -733,6 +738,58 @@ msgid ""
"information."
msgstr "Controleer uw geluidsapparatuur of het programmalogboek."
#: buzz/widgets/update_dialog.py
msgid "A new version of Buzz is available!"
msgstr "Er is een nieuwe versie van Buzz beschikbaar!"
#: buzz/widgets/update_dialog.py
msgid "Current version:"
msgstr "Huidige versie:"
#: buzz/widgets/update_dialog.py
msgid "New version:"
msgstr "Versie:"
#: buzz/widgets/update_dialog.py
msgid "Release Notes:"
msgstr "Release-opmerkingen:"
#: buzz/widgets/update_dialog.py
msgid "Download and Install"
msgstr "Downloaden en installeren"
#: buzz/widgets/update_dialog.py
msgid "No download URL available for your platform."
msgstr "Geen download-URL beschikbaar voor uw platform."
#: buzz/widgets/update_dialog.py
msgid "Downloading file {} of {}..."
msgstr "Bestand {} van {} downloaden..."
#: buzz/widgets/update_dialog.py
msgid "Downloading file {} of {} ({:.1f} MB / {:.1f} MB)..."
msgstr "Bestand {} van {} downloaden ({:.1f} MB / {:.1f} MB)..."
#: buzz/widgets/update_dialog.py
msgid "Download Failed"
msgstr "Download mislukt"
#: buzz/widgets/update_dialog.py
msgid "Failed to download the update: {}"
msgstr "Het downloaden van de update is mislukt: {}"
#: buzz/widgets/update_dialog.py
msgid "Failed to save the installer: {}"
msgstr "Kan het installatieprogramma niet opslaan: {}"
#: buzz/widgets/update_dialog.py
msgid "Download complete!"
msgstr "Download voltooid!"
#: buzz/widgets/update_dialog.py
msgid "Failed to run the installer: {}"
msgstr "Kan het installatieprogramma niet uitvoeren: {}"
#: buzz/widgets/about_dialog.py
msgid "Check for updates"
msgstr "Controleren op updates"
@ -1602,6 +1659,3 @@ msgstr ""
#~ msgid "Enter instructions for AI on how to translate..."
#~ msgstr "Voer vertaalinstructies in…"
#~ msgid "Version"
#~ msgstr "Versie"

View file

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-27 16:46+0200\n"
"POT-Creation-Date: 2026-02-28 10:38+0200\n"
"PO-Revision-Date: 2024-03-17 20:50+0200\n"
"Last-Translator: \n"
"Language-Team: \n"
@ -344,7 +344,8 @@ msgid "Download failed"
msgstr "Pobieranie nie powiodło się"
#: buzz/widgets/preferences_dialog/models_preferences_widget.py
#: buzz/widgets/transcription_tasks_table_widget.py buzz/widgets/main_window.py
#: buzz/widgets/transcription_tasks_table_widget.py
#: buzz/widgets/update_dialog.py buzz/widgets/main_window.py
#: buzz/model_loader.py
msgid "Error"
msgstr "Błąd"
@ -534,6 +535,10 @@ msgstr "Anuluj transkrypcję"
msgid "Clear History"
msgstr "Wyczyść historię"
#: buzz/widgets/main_window_toolbar.py buzz/widgets/update_dialog.py
msgid "Update Available"
msgstr "Dostępna aktualizacja"
#: buzz/widgets/transcription_tasks_table_widget.py
msgid "In Progress"
msgstr "W toku"
@ -729,6 +734,58 @@ msgstr ""
"Sprawdź urządzenia audio lub przejrzyj logi aplikacji, by uzyskać więcej "
"informacji."
#: buzz/widgets/update_dialog.py
msgid "A new version of Buzz is available!"
msgstr "Dostępna jest nowa wersja Buzz!"
#: buzz/widgets/update_dialog.py
msgid "Current version:"
msgstr "Aktualna wersja:"
#: buzz/widgets/update_dialog.py
msgid "New version:"
msgstr "Nowa wersja:"
#: buzz/widgets/update_dialog.py
msgid "Release Notes:"
msgstr "Informacje o wydaniu:"
#: buzz/widgets/update_dialog.py
msgid "Download and Install"
msgstr "Pobierz i zainstaluj"
#: buzz/widgets/update_dialog.py
msgid "No download URL available for your platform."
msgstr "Brak adresu URL do pobrania dla Twojej platformy."
#: buzz/widgets/update_dialog.py
msgid "Downloading file {} of {}..."
msgstr "Pobieranie pliku {} z {}..."
#: buzz/widgets/update_dialog.py
msgid "Downloading file {} of {} ({:.1f} MB / {:.1f} MB)..."
msgstr "Pobieranie pliku {} z {} ({:.1f} MB / {:.1f} MB)..."
#: buzz/widgets/update_dialog.py
msgid "Download Failed"
msgstr "Pobieranie nie powiodło się"
#: buzz/widgets/update_dialog.py
msgid "Failed to download the update: {}"
msgstr "Nie udało się pobrać aktualizacji: {}"
#: buzz/widgets/update_dialog.py
msgid "Failed to save the installer: {}"
msgstr "Nie udało się zapisać instalatora: {}"
#: buzz/widgets/update_dialog.py
msgid "Download complete!"
msgstr "Pobieranie zakończone!"
#: buzz/widgets/update_dialog.py
msgid "Failed to run the installer: {}"
msgstr "Nie udało się uruchomić instalatora: {}"
#: buzz/widgets/about_dialog.py
msgid "Check for updates"
msgstr "Sprawdź aktualizacje"

View file

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Buzz\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-27 09:07+0200\n"
"POT-Creation-Date: 2026-02-28 10:41+0200\n"
"PO-Revision-Date: 2025-11-01 17:43-0300\n"
"Last-Translator: Paulo Schopf <pschopf@gmail.com>\n"
"Language-Team: none\n"
@ -343,7 +343,8 @@ msgid "Download failed"
msgstr "Falha ao baixar"
#: buzz/widgets/preferences_dialog/models_preferences_widget.py
#: buzz/widgets/transcription_tasks_table_widget.py buzz/widgets/main_window.py
#: buzz/widgets/transcription_tasks_table_widget.py
#: buzz/widgets/update_dialog.py buzz/widgets/main_window.py
#: buzz/model_loader.py
msgid "Error"
msgstr "Erro"
@ -438,7 +439,8 @@ msgstr "Modelo de IA:"
#: buzz/widgets/transcriber/advanced_settings_dialog.py
msgid "Please translate each text sent to you from English to Spanish."
msgstr "Por favor, traduza cada texto enviado a você do Inglês para o Espanhol."
msgstr ""
"Por favor, traduza cada texto enviado a você do Inglês para o Espanhol."
#: buzz/widgets/transcriber/advanced_settings_dialog.py
msgid "Instructions for AI:"
@ -533,6 +535,10 @@ msgstr "Cancelar Transcrição"
msgid "Clear History"
msgstr "Limpar Histórico"
#: buzz/widgets/main_window_toolbar.py buzz/widgets/update_dialog.py
msgid "Update Available"
msgstr "Atualização Disponível"
#: buzz/widgets/transcription_tasks_table_widget.py
msgid "In Progress"
msgstr "Em Progresso"
@ -626,12 +632,14 @@ msgid ""
"Could not restart transcription: model not available and could not be "
"downloaded."
msgstr ""
"Não foi possível reiniciar a transcrição: o modelo não está disponível e "
"não pôde ser baixado."
"Não foi possível reiniciar a transcrição: o modelo não está disponível e não "
"pôde ser baixado."
#: buzz/widgets/transcription_tasks_table_widget.py
msgid "Could not restart transcription: transcriber worker not found."
msgstr "Não foi possível reiniciar a transcrição: trabalhador de transcrição não encontrado."
msgstr ""
"Não foi possível reiniciar a transcrição: trabalhador de transcrição não "
"encontrado."
#: buzz/widgets/recording_transcriber_widget.py
msgid "Live Recording"
@ -725,6 +733,58 @@ msgstr ""
"Verifique seus dispositivos de áudio ou os logs do aplicativo para mais "
"informações."
#: buzz/widgets/update_dialog.py
msgid "A new version of Buzz is available!"
msgstr "Uma nova versão do Buzz está disponível!"
#: buzz/widgets/update_dialog.py
msgid "Current version:"
msgstr "Versão atual:"
#: buzz/widgets/update_dialog.py
msgid "New version:"
msgstr "Nova versão:"
#: buzz/widgets/update_dialog.py
msgid "Release Notes:"
msgstr "Notas de Versão:"
#: buzz/widgets/update_dialog.py
msgid "Download and Install"
msgstr "Baixar e instalar"
#: buzz/widgets/update_dialog.py
msgid "No download URL available for your platform."
msgstr "Nenhuma URL de download disponível para sua plataforma."
#: buzz/widgets/update_dialog.py
msgid "Downloading file {} of {}..."
msgstr "Baixando arquivo {} de {}..."
#: buzz/widgets/update_dialog.py
msgid "Downloading file {} of {} ({:.1f} MB / {:.1f} MB)..."
msgstr "Baixando arquivo {} de {} ({:.1f} MB / {:.1f} MB)..."
#: buzz/widgets/update_dialog.py
msgid "Download Failed"
msgstr "Falha no download"
#: buzz/widgets/update_dialog.py
msgid "Failed to download the update: {}"
msgstr "Falha ao baixar a atualização: {}"
#: buzz/widgets/update_dialog.py
msgid "Failed to save the installer: {}"
msgstr "Falha ao salvar o instalador: {}"
#: buzz/widgets/update_dialog.py
msgid "Download complete!"
msgstr "Download concluído!"
#: buzz/widgets/update_dialog.py
msgid "Failed to run the installer: {}"
msgstr "Falha ao executar o instalador: {}"
#: buzz/widgets/about_dialog.py
msgid "Check for updates"
msgstr "Verificar atualizações"
@ -900,7 +960,9 @@ msgstr "Duração desejada da legenda"
#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py
msgid "Available only if word level timings were disabled during transcription"
msgstr "Disponível apenas se os tempos em nível de palavra foram desabilitados durante a transcrição"
msgstr ""
"Disponível apenas se os tempos em nível de palavra foram desabilitados "
"durante a transcrição"
#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py
msgid "Merge Options"
@ -924,7 +986,9 @@ msgstr "Mesclar"
#: buzz/widgets/transcription_viewer/transcription_resizer_widget.py
msgid "Available only if word level timings were enabled during transcription"
msgstr "Disponível apenas se os tempos em nível de palavra foram habilitados durante a transcrição"
msgstr ""
"Disponível apenas se os tempos em nível de palavra foram habilitados durante "
"a transcrição"
#: buzz/widgets/transcription_viewer/speaker_identification_widget.py
msgid ""
@ -1557,10 +1621,6 @@ msgstr "Acrescentar acima"
msgid "Append and correct"
msgstr "Acrescentar e corrigir"
#: buzz/translator.py
msgid "Translation error, see logs!"
msgstr "Erro de tradução, verifique os logs!"
#: buzz/file_transcriber_queue_worker.py
msgid ""
"Speech extraction failed! Check your internet connection — a model may need "
@ -1569,6 +1629,9 @@ msgstr ""
"Falha na extração de fala! Verifique sua conexão com a internet — pode ser "
"necessário baixar um modelo."
#~ msgid "Translation error, see logs!"
#~ msgstr "Erro de tradução, verifique os logs!"
#~ msgid "Snap permission notice"
#~ msgstr "Aviso de permissão do Snap"
@ -1596,5 +1659,3 @@ msgstr ""
#~ msgid "Undo"
#~ msgstr "Desfazer"
#~ msgid "Redo"

View file

@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-27 16:46+0200\n"
"POT-Creation-Date: 2026-02-28 10:41+0200\n"
"PO-Revision-Date: \n"
"Last-Translator: Yevhen Popok <xalt7x.service@gmail.com>\n"
"Language-Team: \n"
@ -340,7 +340,8 @@ msgid "Download failed"
msgstr "Невдале завантаження"
#: buzz/widgets/preferences_dialog/models_preferences_widget.py
#: buzz/widgets/transcription_tasks_table_widget.py buzz/widgets/main_window.py
#: buzz/widgets/transcription_tasks_table_widget.py
#: buzz/widgets/update_dialog.py buzz/widgets/main_window.py
#: buzz/model_loader.py
msgid "Error"
msgstr "Помилка"
@ -532,6 +533,10 @@ msgstr "Скасувати транскрипцію"
msgid "Clear History"
msgstr "Очистити історію"
#: buzz/widgets/main_window_toolbar.py buzz/widgets/update_dialog.py
msgid "Update Available"
msgstr "Доступне оновлення"
#: buzz/widgets/transcription_tasks_table_widget.py
msgid "In Progress"
msgstr "В процесі"
@ -725,6 +730,58 @@ msgstr ""
"Будь ласка, перевірте свої аудіопристрої або пошукайте додаткову інформацію "
"в звітах програми."
#: buzz/widgets/update_dialog.py
msgid "A new version of Buzz is available!"
msgstr "Доступна нова версія Buzz!"
#: buzz/widgets/update_dialog.py
msgid "Current version:"
msgstr "Поточна версія:"
#: buzz/widgets/update_dialog.py
msgid "New version:"
msgstr "Нова версія:"
#: buzz/widgets/update_dialog.py
msgid "Release Notes:"
msgstr "Примітки до випуску:"
#: buzz/widgets/update_dialog.py
msgid "Download and Install"
msgstr "Завантажити та встановити"
#: buzz/widgets/update_dialog.py
msgid "No download URL available for your platform."
msgstr "Посилання для завантаження для вашої платформи відсутнє."
#: buzz/widgets/update_dialog.py
msgid "Downloading file {} of {}..."
msgstr "Завантаження файлу {} з {}..."
#: buzz/widgets/update_dialog.py
msgid "Downloading file {} of {} ({:.1f} MB / {:.1f} MB)..."
msgstr "Завантаження файлу {} з {} ({:.1f} МБ / {:.1f} МБ)..."
#: buzz/widgets/update_dialog.py
msgid "Download Failed"
msgstr "Помилка завантаження"
#: buzz/widgets/update_dialog.py
msgid "Failed to download the update: {}"
msgstr "Не вдалося завантажити оновлення: {}"
#: buzz/widgets/update_dialog.py
msgid "Failed to save the installer: {}"
msgstr "Не вдалося зберегти інсталятор: {}"
#: buzz/widgets/update_dialog.py
msgid "Download complete!"
msgstr "Завантаження завершено!"
#: buzz/widgets/update_dialog.py
msgid "Failed to run the installer: {}"
msgstr "Не вдалося запустити інсталятор: {}"
#: buzz/widgets/about_dialog.py
msgid "Check for updates"
msgstr "Перевірити оновлення"

View file

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-27 16:46+0200\n"
"POT-Creation-Date: 2026-02-28 10:41+0200\n"
"PO-Revision-Date: 2023-05-01 15:45+0800\n"
"Last-Translator: \n"
"Language-Team: lamb \n"
@ -336,7 +336,8 @@ msgid "Download failed"
msgstr "下载失败"
#: buzz/widgets/preferences_dialog/models_preferences_widget.py
#: buzz/widgets/transcription_tasks_table_widget.py buzz/widgets/main_window.py
#: buzz/widgets/transcription_tasks_table_widget.py
#: buzz/widgets/update_dialog.py buzz/widgets/main_window.py
#: buzz/model_loader.py
msgid "Error"
msgstr "错误"
@ -526,6 +527,10 @@ msgstr "取消识别"
msgid "Clear History"
msgstr "清除历史纪录"
#: buzz/widgets/main_window_toolbar.py buzz/widgets/update_dialog.py
msgid "Update Available"
msgstr "有可用更新"
#: buzz/widgets/transcription_tasks_table_widget.py
msgid "In Progress"
msgstr "运行中"
@ -714,6 +719,58 @@ msgid ""
"information."
msgstr "请检查您的音频设备或检查应用程序日志以获取更多信息。"
#: buzz/widgets/update_dialog.py
msgid "A new version of Buzz is available!"
msgstr "Buzz 有新版本可用!"
#: buzz/widgets/update_dialog.py
msgid "Current version:"
msgstr "当前版本:"
#: buzz/widgets/update_dialog.py
msgid "New version:"
msgstr "新版本:"
#: buzz/widgets/update_dialog.py
msgid "Release Notes:"
msgstr "发行说明:"
#: buzz/widgets/update_dialog.py
msgid "Download and Install"
msgstr "下载并安装"
#: buzz/widgets/update_dialog.py
msgid "No download URL available for your platform."
msgstr "您的平台没有可用的下载链接。"
#: buzz/widgets/update_dialog.py
msgid "Downloading file {} of {}..."
msgstr "正在下载第 {} 个文件,共 {} 个..."
#: buzz/widgets/update_dialog.py
msgid "Downloading file {} of {} ({:.1f} MB / {:.1f} MB)..."
msgstr "正在下载第 {} 个文件,共 {} 个({:.1f} MB / {:.1f} MB..."
#: buzz/widgets/update_dialog.py
msgid "Download Failed"
msgstr "下载失败"
#: buzz/widgets/update_dialog.py
msgid "Failed to download the update: {}"
msgstr "下载更新失败:{}"
#: buzz/widgets/update_dialog.py
msgid "Failed to save the installer: {}"
msgstr "无法保存安装程序:{}"
#: buzz/widgets/update_dialog.py
msgid "Download complete!"
msgstr "下载完成!"
#: buzz/widgets/update_dialog.py
msgid "Failed to run the installer: {}"
msgstr "无法运行安装程序:{}"
#: buzz/widgets/about_dialog.py
msgid "Check for updates"
msgstr "检查更新"

View file

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-27 16:46+0200\n"
"POT-Creation-Date: 2026-02-28 10:41+0200\n"
"PO-Revision-Date: 2023-05-01 15:45+0800\n"
"Last-Translator: \n"
"Language-Team: Lamb\n"
@ -337,7 +337,8 @@ msgid "Download failed"
msgstr "下載失敗"
#: buzz/widgets/preferences_dialog/models_preferences_widget.py
#: buzz/widgets/transcription_tasks_table_widget.py buzz/widgets/main_window.py
#: buzz/widgets/transcription_tasks_table_widget.py
#: buzz/widgets/update_dialog.py buzz/widgets/main_window.py
#: buzz/model_loader.py
msgid "Error"
msgstr "錯誤"
@ -527,6 +528,10 @@ msgstr "取消錄製"
msgid "Clear History"
msgstr "清除歷史紀錄"
#: buzz/widgets/main_window_toolbar.py buzz/widgets/update_dialog.py
msgid "Update Available"
msgstr "有可用更新"
#: buzz/widgets/transcription_tasks_table_widget.py
msgid "In Progress"
msgstr "進行中"
@ -715,6 +720,58 @@ msgid ""
"information."
msgstr "請檢查您的音頻設備或檢查應用程序日誌以獲取更多信息。"
#: buzz/widgets/update_dialog.py
msgid "A new version of Buzz is available!"
msgstr "Buzz 有新版本可用!"
#: buzz/widgets/update_dialog.py
msgid "Current version:"
msgstr "目前版本:"
#: buzz/widgets/update_dialog.py
msgid "New version:"
msgstr "新版本:"
#: buzz/widgets/update_dialog.py
msgid "Release Notes:"
msgstr "版本說明:"
#: buzz/widgets/update_dialog.py
msgid "Download and Install"
msgstr "下載並安裝"
#: buzz/widgets/update_dialog.py
msgid "No download URL available for your platform."
msgstr "您的平台沒有可用的下載連結。"
#: buzz/widgets/update_dialog.py
msgid "Downloading file {} of {}..."
msgstr "正在下載第 {} 個檔案,共 {} 個..."
#: buzz/widgets/update_dialog.py
msgid "Downloading file {} of {} ({:.1f} MB / {:.1f} MB)..."
msgstr "正在下載第 {} 個檔案,共 {} 個({:.1f} MB / {:.1f} MB..."
#: buzz/widgets/update_dialog.py
msgid "Download Failed"
msgstr "下載失敗"
#: buzz/widgets/update_dialog.py
msgid "Failed to download the update: {}"
msgstr "下載更新失敗:{}"
#: buzz/widgets/update_dialog.py
msgid "Failed to save the installer: {}"
msgstr "無法儲存安裝程式:{}"
#: buzz/widgets/update_dialog.py
msgid "Download complete!"
msgstr "下載完成!"
#: buzz/widgets/update_dialog.py
msgid "Failed to run the installer: {}"
msgstr "無法執行安裝程式:{}"
#: buzz/widgets/about_dialog.py
msgid "Check for updates"
msgstr "檢查更新"

View file

@ -82,6 +82,9 @@ class Settings:
FORCE_CPU = "force-cpu"
REDUCE_GPU_MEMORY = "reduce-gpu-memory"
LAST_UPDATE_CHECK = "update/last-check"
UPDATE_AVAILABLE_VERSION = "update/available-version"
def get_user_identifier(self) -> str:
user_id = self.value(self.Key.USER_IDENTIFIER, "")
if not user_id:

163
buzz/update_checker.py Normal file
View file

@ -0,0 +1,163 @@
import json
import logging
import platform
from datetime import datetime
from typing import Optional
from dataclasses import dataclass
from PyQt6.QtCore import QObject, pyqtSignal, QUrl
from PyQt6.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
from buzz.__version__ import VERSION
from buzz.settings.settings import Settings
@dataclass
class UpdateInfo:
version: str
release_notes: str
download_urls: list
class UpdateChecker(QObject):
update_available = pyqtSignal(object)
VERSION_JSON_URL = "https://github.com/chidiwilliams/buzz/releases/latest/download/version_info.json"
CHECK_INTERVAL_DAYS = 7
def __init__(
self,
settings: Settings,
network_manager: Optional[QNetworkAccessManager] = None,
parent: Optional[QObject] = None
):
super().__init__(parent)
self.settings = settings
if network_manager is None:
network_manager = QNetworkAccessManager(self)
self.network_manager = network_manager
self.network_manager.finished.connect(self._on_reply_finished)
def should_check_for_updates(self) -> bool:
"""Check if we are on Windows/macOS and if 7 days passed"""
system = platform.system()
if system not in ("Windows", "Darwin"):
logging.debug("Skipping update check on linux")
return False
last_check = self.settings.value(
Settings.Key.LAST_UPDATE_CHECK,
"",
)
if last_check:
try:
last_check_date = datetime.fromisoformat(last_check)
days_since_check = (datetime.now() - last_check_date).days
if days_since_check < self.CHECK_INTERVAL_DAYS:
logging.debug(
f"Skipping update check, last checked {days_since_check} days ago"
)
return False
except ValueError:
#Invalid date format
pass
return True
def check_for_updates(self) -> None:
"""Start the network request"""
if not self.should_check_for_updates():
return
logging.info("Checking for updates...")
url = QUrl(self.VERSION_JSON_URL)
request = QNetworkRequest(url)
self.network_manager.get(request)
def _on_reply_finished(self, reply: QNetworkReply) -> None:
"""Handles the network reply for version.json fetch"""
self.settings.set_value(
Settings.Key.LAST_UPDATE_CHECK,
datetime.now().isoformat()
)
if reply.error() != QNetworkReply.NetworkError.NoError:
error_msg = f"Failed to check for updates: {reply.errorString()}"
logging.error(error_msg)
reply.deleteLater()
return
try:
data = json.loads(reply.readAll().data().decode("utf-8"))
reply.deleteLater()
remote_version = data.get("version", "")
release_notes = data.get("release_notes", "")
download_urls = data.get("download_urls", {})
#Get the download url for current platform
download_url = self._get_download_url(download_urls)
if self._is_newer_version(remote_version):
logging.info(f"Update available: {remote_version}")
#Store the available version
self.settings.set_value(
Settings.Key.UPDATE_AVAILABLE_VERSION,
remote_version
)
update_info = UpdateInfo(
version=remote_version,
release_notes=release_notes,
download_urls=download_url
)
self.update_available.emit(update_info)
else:
logging.info("No update available")
self.settings.set_value(
Settings.Key.UPDATE_AVAILABLE_VERSION,
""
)
except (json.JSONDecodeError, KeyError) as e:
error_msg = f"Failed to parse version info: {e}"
logging.error(error_msg)
def _get_download_url(self, download_urls: dict) -> list:
system = platform.system()
machine = platform.machine().lower()
if system == "Windows":
urls = download_urls.get("windows_x64", [])
elif system == "Darwin":
if machine in ("arm64", "aarch64"):
urls = download_urls.get("macos_arm", [])
else:
urls = download_urls.get("macos_x86", [])
else:
urls = []
return urls if isinstance(urls, list) else [urls]
def _is_newer_version(self, remote_version: str) -> bool:
"""Compare remote version with current version"""
try:
current_parts = [int(x) for x in VERSION.split(".")]
remote_parts = [int(x) for x in remote_version.split(".")]
#pad with zeros if needed
while len(current_parts) < len(remote_parts):
current_parts.append(0)
while len(remote_parts) < len(current_parts):
remote_parts.append(0)
return remote_parts > current_parts
except ValueError:
logging.error(f"Invalid version format: {VERSION} or {remote_version}")
return False

View file

@ -129,3 +129,4 @@ ADD_ICON_PATH = get_path("assets/add_FILL0_wght700_GRAD0_opsz48.svg")
URL_ICON_PATH = get_path("assets/url.svg")
TRASH_ICON_PATH = get_path("assets/delete_FILL0_wght700_GRAD0_opsz48.svg")
CANCEL_ICON_PATH = get_path("assets/cancel_FILL0_wght700_GRAD0_opsz48.svg")
UPDATE_ICON_PATH = get_path("assets/update_FILL0_wght700_GRAD0_opsz48.svg")

View file

@ -24,6 +24,8 @@ from buzz.db.service.transcription_service import TranscriptionService
from buzz.file_transcriber_queue_worker import FileTranscriberQueueWorker
from buzz.locale import _
from buzz.settings.settings import APP_NAME, Settings
from buzz.update_checker import UpdateChecker, UpdateInfo
from buzz.widgets.update_dialog import UpdateDialog
from buzz.settings.shortcuts import Shortcuts
from buzz.store.keyring_store import set_password, Key
from buzz.transcriber.transcriber import (
@ -70,6 +72,9 @@ class MainWindow(QMainWindow):
self.quit_on_complete = False
self.transcription_service = transcription_service
#update checker
self._update_info: Optional[UpdateInfo] = None
self.toolbar = MainWindowToolbar(shortcuts=self.shortcuts, parent=self)
self.toolbar.new_transcription_action_triggered.connect(
self.on_new_transcription_action_triggered
@ -87,6 +92,7 @@ class MainWindow(QMainWindow):
self.on_stop_transcription_action_triggered
)
self.addToolBar(self.toolbar)
self.toolbar.update_action_triggered.connect(self.on_update_action_triggered)
self.setUnifiedTitleAndToolBarOnMac(True)
self.preferences = self.load_preferences(settings=self.settings)
@ -156,6 +162,9 @@ class MainWindow(QMainWindow):
self.transcription_viewer_widget = None
#Initialize and run update checker
self._init_update_checker()
def on_preferences_changed(self, preferences: Preferences):
self.preferences = preferences
self.save_preferences(preferences)
@ -493,3 +502,27 @@ class MainWindow(QMainWindow):
self.setBaseSize(1240, 600)
self.resize(1240, 600)
self.settings.end_group()
def _init_update_checker(self):
"""Initializes and runs the update checker."""
self.update_checker = UpdateChecker(settings=self.settings, parent=self)
self.update_checker.update_available.connect(self._on_update_available)
# Check for updates on startup
self.update_checker.check_for_updates()
def _on_update_available(self, update_info: UpdateInfo):
"""Called when an update is available."""
self._update_info = update_info
self.toolbar.set_update_available(True)
def on_update_action_triggered(self):
"""Called when user clicks the update action in toolbar."""
if self._update_info is None:
return
dialog = UpdateDialog(
update_info=self._update_info,
parent=self
)
dialog.exec()

View file

@ -16,6 +16,7 @@ from buzz.widgets.icon import (
EXPAND_ICON_PATH,
CANCEL_ICON_PATH,
TRASH_ICON_PATH,
UPDATE_ICON_PATH,
)
from buzz.widgets.recording_transcriber_widget import RecordingTranscriberWidget
from buzz.widgets.toolbar import ToolBar
@ -26,6 +27,7 @@ class MainWindowToolbar(ToolBar):
new_url_transcription_action_triggered: pyqtSignal
open_transcript_action_triggered: pyqtSignal
clear_history_action_triggered: pyqtSignal
update_action_triggered: pyqtSignal
ICON_LIGHT_THEME_BACKGROUND = "#555"
ICON_DARK_THEME_BACKGROUND = "#AAA"
@ -70,6 +72,13 @@ class MainWindowToolbar(ToolBar):
self.clear_history_action = Action(
Icon(TRASH_ICON_PATH, self), _("Clear History"), self
)
self.update_action = Action(
Icon(UPDATE_ICON_PATH, self), _("Update Available"), self
)
self.update_action_triggered = self.update_action.triggered
self.update_action.setVisible(False)
self.clear_history_action_triggered = self.clear_history_action.triggered
self.clear_history_action.setDisabled(True)
@ -86,6 +95,10 @@ class MainWindowToolbar(ToolBar):
self.clear_history_action,
]
)
self.addSeparator()
self.addAction(self.update_action)
self.setMovable(False)
self.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
@ -114,3 +127,7 @@ class MainWindowToolbar(ToolBar):
def set_clear_history_action_enabled(self, enabled: bool):
self.clear_history_action.setEnabled(enabled)
def set_update_available(self, available: bool):
"""Shows or hides the update action in the toolbar."""
self.update_action.setVisible(available)

View file

@ -0,0 +1,262 @@
import logging
import os
import platform
import subprocess
import tempfile
from typing import Optional
from PyQt6.QtCore import Qt, QUrl
from PyQt6.QtWidgets import QApplication
from PyQt6.QtGui import QIcon
from PyQt6.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
from PyQt6.QtWidgets import (
QDialog,
QVBoxLayout,
QHBoxLayout,
QLabel,
QPushButton,
QProgressBar,
QMessageBox,
QWidget,
QTextEdit,
)
from buzz.__version__ import VERSION
from buzz.locale import _
from buzz.update_checker import UpdateInfo
from buzz.widgets.icon import BUZZ_ICON_PATH
class UpdateDialog(QDialog):
"""Dialog shows when an update is available"""
def __init__(
self,
update_info: UpdateInfo,
network_manager: Optional[QNetworkAccessManager] = None,
parent: Optional[QWidget] = None
):
super().__init__(parent)
self.update_info = update_info
if network_manager is None:
network_manager = QNetworkAccessManager(self)
self.network_manager = network_manager
self._download_reply: Optional[QNetworkReply] = None
self._temp_file_paths: list = []
self._pending_urls: list = []
self._temp_dir: Optional[str] = None
self._setup_ui()
def _setup_ui(self):
self.setWindowTitle(_("Update Available"))
self.setWindowIcon(QIcon(BUZZ_ICON_PATH))
self.setMinimumWidth(450)
layout = QVBoxLayout(self)
layout.setSpacing(16)
#header
header_label = QLabel(
_("A new version of Buzz is available!")
)
header_label.setStyleSheet("font-size: 16px; font-weight: bold;")
layout.addWidget(header_label)
#Version info
version_layout = QHBoxLayout()
current_version_label = QLabel(_("Current version:"))
current_version_value = QLabel(f"<b>{VERSION}</b>")
new_version_label = QLabel(_("New version:"))
new_version_value = QLabel(f"<b>{self.update_info.version}</b>")
version_layout.addWidget(current_version_label)
version_layout.addWidget(current_version_value)
version_layout.addStretch()
version_layout.addWidget(new_version_label)
version_layout.addWidget(new_version_value)
layout.addLayout(version_layout)
#Release notes
if self.update_info.release_notes:
notes_label = QLabel(_("Release Notes:"))
notes_label.setStyleSheet("font-weight: bold;")
layout.addWidget(notes_label)
notes_text = QTextEdit()
notes_text.setReadOnly(True)
notes_text.setMarkdown(self.update_info.release_notes)
notes_text.setMaximumHeight(150)
layout.addWidget(notes_text)
#progress bar
self.progress_bar = QProgressBar()
self.progress_bar.setVisible(False)
self.progress_bar.setTextVisible(True)
layout.addWidget(self.progress_bar)
#Status label
self.status_label = QLabel("")
self.status_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(self.status_label)
#Buttons
button_layout = QVBoxLayout()
self.download_button = QPushButton(_("Download and Install"))
self.download_button.clicked.connect(self._on_download_clicked)
self.download_button.setDefault(True)
button_layout.addStretch()
button_layout.addWidget(self.download_button)
layout.addLayout(button_layout)
def _on_download_clicked(self):
"""Starts downloading the installer"""
if not self.update_info.download_urls:
QMessageBox.warning(
self,
_("Error"),
_("No download URL available for your platform.")
)
return
self.download_button.setEnabled(False)
self.progress_bar.setVisible(True)
self.progress_bar.setValue(0)
self._temp_file_paths = []
self._pending_urls = list(self.update_info.download_urls)
self._temp_dir = tempfile.mkdtemp()
self._download_next_file()
def _download_next_file(self):
"""Download the next file in the queue"""
if not self._pending_urls:
self._all_downloads_finished()
return
url_str = self._pending_urls[0]
file_index = len(self.update_info.download_urls) - len(self._pending_urls) + 1
total_files = len(self.update_info.download_urls)
self.status_label.setText(
_("Downloading file {} of {}...").format(file_index, total_files)
)
url = QUrl(url_str)
request = QNetworkRequest(url)
self._download_reply = self.network_manager.get(request)
self._download_reply.downloadProgress.connect(self._on_download_progress)
self._download_reply.finished.connect(self._on_download_finished)
def _on_download_progress(self, bytes_received: int, bytes_total: int):
"""Update the progress bar during download"""
if bytes_total > 0:
progress = int((bytes_received / bytes_total) * 100)
self.progress_bar.setValue(progress)
mb_received = bytes_received / (1024 * 1024)
mb_total = bytes_total / (1024 * 1024)
file_index = len(self.update_info.download_urls) - len(self._pending_urls) + 1
total_files = len(self.update_info.download_urls)
self.status_label.setText(
_("Downloading file {} of {} ({:.1f} MB / {:.1f} MB)...").format(
file_index, total_files, mb_received, mb_total
)
)
def _on_download_finished(self):
"""Handles download completion for one file"""
if self._download_reply is None:
return
if self._download_reply.error() != QNetworkReply.NetworkError.NoError:
error_msg = self._download_reply.errorString()
logging.error(f"Download failed: {error_msg}")
QMessageBox.critical(
self,
_("Download Failed"),
_("Failed to download the update: {}").format(error_msg)
)
self._reset_ui()
self._download_reply.deleteLater()
self._download_reply = None
return
data = self._download_reply.readAll().data()
self._download_reply.deleteLater()
self._download_reply = None
url_str = self._pending_urls.pop(0)
# Extract original filename from URL to preserve it
original_filename = QUrl(url_str).fileName()
if not original_filename:
original_filename = f"download_{len(self._temp_file_paths)}"
try:
temp_path = os.path.join(self._temp_dir, original_filename)
with open(temp_path, "wb") as f:
f.write(data)
self._temp_file_paths.append(temp_path)
logging.info(f"File saved to: {temp_path}")
except Exception as e:
logging.error(f"Failed to save file: {e}")
QMessageBox.critical(
self,
_("Error"),
_("Failed to save the installer: {}").format(str(e))
)
self._reset_ui()
return
self._download_next_file()
def _all_downloads_finished(self):
"""All files downloaded, run the installer"""
self.progress_bar.setValue(100)
self.status_label.setText(_("Download complete!"))
self._run_installer()
def _run_installer(self):
"""Run the downloaded installer"""
if not self._temp_file_paths:
return
installer_path = self._temp_file_paths[0]
system = platform.system()
try:
if system == "Windows":
subprocess.Popen([installer_path], shell=True)
elif system == "Darwin":
#open the DMG file
subprocess.Popen(["open", installer_path])
# Close the app so the installer can replace files
self.accept()
QApplication.quit()
except Exception as e:
logging.error(f"Failed to run installer: {e}")
QMessageBox.critical(
self,
_("Error"),
_("Failed to run the installer: {}").format(str(e))
)
def _reset_ui(self):
"""Reset the UI to initial state after an error"""
self.download_button.setEnabled(True)
self.progress_bar.setVisible(False)
self.status_label.setText("")

View file

@ -50,8 +50,23 @@ parts:
prime:
- etc/asound.conf
buzz:
portaudio:
after: [ alsa-pulseaudio ]
plugin: autotools
source: https://files.portaudio.com/archives/pa_stable_v190700_20210406.tgz
build-packages:
- libasound2-dev
- libpulse-dev
autotools-configure-parameters:
- --enable-shared
- --disable-static
stage:
- usr/local/lib/libportaudio*
prime:
- usr/local/lib/libportaudio*
buzz:
after: [ alsa-pulseaudio, portaudio ]
plugin: uv
source: .
build-snaps:
@ -78,9 +93,8 @@ parts:
- libproxy1v5
# Audio
- ffmpeg
- libportaudio2
- libpulse0
- libasound2
- libasound2t64
- libasound2-dev
- libasound2-plugins
- libasound2-plugins-extra
@ -115,10 +129,10 @@ parts:
# Clean caches
uv cache clean
# Create launcher wrapper to ensure the snap's own libasound.so.2 is found
# Create launcher wrapper to ensure the snap's own portaudio and libasound are found
# before gnome content snap libraries (which desktop-launch prepends to LD_LIBRARY_PATH)
mkdir -p $CRAFT_PART_INSTALL/bin
printf '#!/bin/sh\nexport LD_LIBRARY_PATH="$SNAP/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH"\nexec "$SNAP/bin/python" -m buzz "$@"\n' > $CRAFT_PART_INSTALL/bin/buzz-launcher
printf '#!/bin/sh\nexport LD_LIBRARY_PATH="$SNAP/usr/local/lib:$SNAP/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH"\nexec "$SNAP/bin/python" -m buzz "$@"\n' > $CRAFT_PART_INSTALL/bin/buzz-launcher
chmod +x $CRAFT_PART_INSTALL/bin/buzz-launcher
# Copy source files
@ -158,7 +172,7 @@ apps:
desktop: usr/share/applications/buzz.desktop
environment:
PATH: $SNAP/usr/bin:$SNAP/bin:$PATH
LD_LIBRARY_PATH: $SNAP/lib/python3.12/site-packages/nvidia/cudnn/lib:$SNAP/lib/python3.12/site-packages/PyQt6:$SNAP/lib/python3.12/site-packages/PyQt6/Qt6/lib:$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/lapack:$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/blas:$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/oss4-libsalsa:$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libproxy:$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/alsa-lib:$SNAP:$LD_LIBRARY_PATH
LD_LIBRARY_PATH: $SNAP/usr/local/lib:$SNAP/lib/python3.12/site-packages/nvidia/cudnn/lib:$SNAP/lib/python3.12/site-packages/PyQt6:$SNAP/lib/python3.12/site-packages/PyQt6/Qt6/lib:$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/lapack:$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/blas:$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/oss4-libsalsa:$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libproxy:$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/alsa-lib:$SNAP:$LD_LIBRARY_PATH
PYTHONPATH: $SNAP:$SNAP/lib/python3.12/site-packages/PyQt6:$SNAP/lib/python3.12/site-packages/PyQt6/Qt6/lib:$SNAP/usr/lib/python3/dist-packages:$SNAP/usr/lib/python3.12/site-packages:$SNAP/usr/local/lib/python3.12/dist-packages:$SNAP/usr/lib/python3.12/dist-packages:$PYTHONPATH
QT_MEDIA_BACKEND: ffmpeg
PULSE_LATENCY_MSEC: "30"
@ -182,4 +196,4 @@ apps:
layout:
/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/alsa-lib:
bind: $SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/alsa-lib
bind: $SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/alsa-lib

View file

@ -15,6 +15,9 @@ class MockNetworkReply(QNetworkReply):
def error(self) -> "QNetworkReply.NetworkError":
return QNetworkReply.NetworkError.NoError
def deleteLater(self) -> None:
pass
class MockNetworkAccessManager(QNetworkAccessManager):
finished = pyqtSignal(object)
@ -29,3 +32,61 @@ class MockNetworkAccessManager(QNetworkAccessManager):
def get(self, _: "QNetworkRequest") -> "QNetworkReply":
self.finished.emit(self.reply)
return self.reply
class MockDownloadReply(QObject):
"""Mock reply for file downloads — supports downloadProgress and finished signals."""
downloadProgress = pyqtSignal(int, int)
finished = pyqtSignal()
def __init__(
self,
data: bytes = b"fake-installer-data",
network_error: "QNetworkReply.NetworkError" = QNetworkReply.NetworkError.NoError,
error_string: str = "",
parent: Optional[QObject] = None,
) -> None:
super().__init__(parent)
self._data = data
self._network_error = network_error
self._error_string = error_string
self._aborted = False
def readAll(self) -> QByteArray:
return QByteArray(self._data)
def error(self) -> "QNetworkReply.NetworkError":
return self._network_error
def errorString(self) -> str:
return self._error_string
def abort(self) -> None:
self._aborted = True
def deleteLater(self) -> None:
pass
def emit_finished(self) -> None:
self.finished.emit()
class MockDownloadNetworkManager(QNetworkAccessManager):
"""Network manager that returns MockDownloadReply instances for each get() call."""
def __init__(
self,
replies: Optional[list] = None,
parent: Optional[QObject] = None,
) -> None:
super().__init__(parent)
self._replies = list(replies) if replies else []
self._index = 0
def get(self, _: "QNetworkRequest") -> "MockDownloadReply":
if self._index < len(self._replies):
reply = self._replies[self._index]
else:
reply = MockDownloadReply()
self._index += 1
return reply

View file

@ -135,7 +135,12 @@ class MockInputStream:
if self._stop_event.is_set():
break
chunk = audio[seek : seek + num_samples_in_chunk]
self.callback(chunk, 0, None, None)
try:
self.callback(chunk, 0, None, None)
except RuntimeError:
# Qt object was deleted between the stop-event check and
# the callback invocation; treat it as a stop signal.
break
seek += num_samples_in_chunk
# loop back around

View file

@ -0,0 +1,202 @@
import platform
from datetime import datetime, timedelta
from unittest.mock import patch
import pytest
from pytestqt.qtbot import QtBot
from buzz.__version__ import VERSION
from buzz.settings.settings import Settings
from buzz.update_checker import UpdateChecker, UpdateInfo
from tests.mock_qt import MockNetworkAccessManager, MockNetworkReply
VERSION_INFO = {
"version": "99.0.0",
"release_notes": "Some fixes.",
"download_urls": {
"windows_x64": ["https://example.com/Buzz-99.0.0.exe"],
"macos_arm": ["https://example.com/Buzz-99.0.0-arm.dmg"],
"macos_x86": ["https://example.com/Buzz-99.0.0-x86.dmg"],
},
}
@pytest.fixture()
def checker(settings: Settings) -> UpdateChecker:
reply = MockNetworkReply(data=VERSION_INFO)
manager = MockNetworkAccessManager(reply=reply)
return UpdateChecker(settings=settings, network_manager=manager)
class TestShouldCheckForUpdates:
def test_returns_false_on_linux(self, checker: UpdateChecker):
with patch.object(platform, "system", return_value="Linux"):
assert checker.should_check_for_updates() is False
def test_returns_true_on_windows_first_run(self, checker: UpdateChecker, settings: Settings):
settings.set_value(Settings.Key.LAST_UPDATE_CHECK, "")
with patch.object(platform, "system", return_value="Windows"):
assert checker.should_check_for_updates() is True
def test_returns_true_on_macos_first_run(self, checker: UpdateChecker, settings: Settings):
settings.set_value(Settings.Key.LAST_UPDATE_CHECK, "")
with patch.object(platform, "system", return_value="Darwin"):
assert checker.should_check_for_updates() is True
def test_returns_false_when_checked_recently(
self, checker: UpdateChecker, settings: Settings
):
recent = (datetime.now() - timedelta(days=2)).isoformat()
settings.set_value(Settings.Key.LAST_UPDATE_CHECK, recent)
with patch.object(platform, "system", return_value="Windows"):
assert checker.should_check_for_updates() is False
def test_returns_true_when_check_is_overdue(
self, checker: UpdateChecker, settings: Settings
):
old = (datetime.now() - timedelta(days=10)).isoformat()
settings.set_value(Settings.Key.LAST_UPDATE_CHECK, old)
with patch.object(platform, "system", return_value="Windows"):
assert checker.should_check_for_updates() is True
def test_returns_true_on_invalid_date_in_settings(
self, checker: UpdateChecker, settings: Settings
):
settings.set_value(Settings.Key.LAST_UPDATE_CHECK, "not-a-date")
with patch.object(platform, "system", return_value="Windows"):
assert checker.should_check_for_updates() is True
class TestIsNewerVersion:
def test_newer_major(self, checker: UpdateChecker):
with patch("buzz.update_checker.VERSION", "1.0.0"):
assert checker._is_newer_version("2.0.0") is True
def test_newer_minor(self, checker: UpdateChecker):
with patch("buzz.update_checker.VERSION", "1.0.0"):
assert checker._is_newer_version("1.1.0") is True
def test_newer_patch(self, checker: UpdateChecker):
with patch("buzz.update_checker.VERSION", "1.0.0"):
assert checker._is_newer_version("1.0.1") is True
def test_same_version(self, checker: UpdateChecker):
with patch("buzz.update_checker.VERSION", "1.0.0"):
assert checker._is_newer_version("1.0.0") is False
def test_older_version(self, checker: UpdateChecker):
with patch("buzz.update_checker.VERSION", "2.0.0"):
assert checker._is_newer_version("1.9.9") is False
def test_different_segment_count(self, checker: UpdateChecker):
with patch("buzz.update_checker.VERSION", "1.0"):
assert checker._is_newer_version("1.0.1") is True
def test_invalid_version_returns_false(self, checker: UpdateChecker):
with patch("buzz.update_checker.VERSION", "1.0.0"):
assert checker._is_newer_version("not-a-version") is False
class TestGetDownloadUrl:
def test_windows_returns_windows_urls(self, checker: UpdateChecker):
with patch.object(platform, "system", return_value="Windows"):
urls = checker._get_download_url(VERSION_INFO["download_urls"])
assert urls == ["https://example.com/Buzz-99.0.0.exe"]
def test_macos_arm_returns_arm_urls(self, checker: UpdateChecker):
with patch.object(platform, "system", return_value="Darwin"), \
patch.object(platform, "machine", return_value="arm64"):
urls = checker._get_download_url(VERSION_INFO["download_urls"])
assert urls == ["https://example.com/Buzz-99.0.0-arm.dmg"]
def test_macos_x86_returns_x86_urls(self, checker: UpdateChecker):
with patch.object(platform, "system", return_value="Darwin"), \
patch.object(platform, "machine", return_value="x86_64"):
urls = checker._get_download_url(VERSION_INFO["download_urls"])
assert urls == ["https://example.com/Buzz-99.0.0-x86.dmg"]
def test_linux_returns_empty(self, checker: UpdateChecker):
with patch.object(platform, "system", return_value="Linux"):
urls = checker._get_download_url(VERSION_INFO["download_urls"])
assert urls == []
def test_wraps_plain_string_in_list(self, checker: UpdateChecker):
with patch.object(platform, "system", return_value="Windows"):
urls = checker._get_download_url({"windows_x64": "https://example.com/a.exe"})
assert urls == ["https://example.com/a.exe"]
class TestCheckForUpdates:
def _make_checker(self, settings: Settings, version_data: dict) -> UpdateChecker:
settings.set_value(Settings.Key.LAST_UPDATE_CHECK, "")
reply = MockNetworkReply(data=version_data)
manager = MockNetworkAccessManager(reply=reply)
return UpdateChecker(settings=settings, network_manager=manager)
def test_emits_update_available_when_newer_version(self, settings: Settings):
received = []
checker = self._make_checker(settings, VERSION_INFO)
checker.update_available.connect(lambda info: received.append(info))
with patch.object(platform, "system", return_value="Windows"), \
patch.object(platform, "machine", return_value="x86_64"), \
patch("buzz.update_checker.VERSION", "1.0.0"):
checker.check_for_updates()
assert len(received) == 1
update_info: UpdateInfo = received[0]
assert update_info.version == "99.0.0"
assert update_info.release_notes == "Some fixes."
assert update_info.download_urls == ["https://example.com/Buzz-99.0.0.exe"]
def test_does_not_emit_when_version_is_current(self, settings: Settings):
received = []
checker = self._make_checker(settings, {**VERSION_INFO, "version": VERSION})
checker.update_available.connect(lambda info: received.append(info))
with patch.object(platform, "system", return_value="Windows"):
checker.check_for_updates()
assert received == []
def test_skips_network_call_on_linux(self, settings: Settings):
received = []
checker = self._make_checker(settings, VERSION_INFO)
checker.update_available.connect(lambda info: received.append(info))
with patch.object(platform, "system", return_value="Linux"):
checker.check_for_updates()
assert received == []
def test_stores_last_check_date_after_reply(self, settings: Settings):
checker = self._make_checker(settings, {**VERSION_INFO, "version": VERSION})
with patch.object(platform, "system", return_value="Windows"):
checker.check_for_updates()
stored = settings.value(Settings.Key.LAST_UPDATE_CHECK, "")
assert stored != ""
datetime.fromisoformat(stored) # should not raise
def test_stores_available_version_when_update_found(self, settings: Settings):
checker = self._make_checker(settings, VERSION_INFO)
with patch.object(platform, "system", return_value="Windows"), \
patch("buzz.update_checker.VERSION", "1.0.0"):
checker.check_for_updates()
assert settings.value(Settings.Key.UPDATE_AVAILABLE_VERSION, "") == "99.0.0"
def test_clears_available_version_when_up_to_date(self, settings: Settings):
settings.set_value(Settings.Key.UPDATE_AVAILABLE_VERSION, "99.0.0")
checker = self._make_checker(settings, {**VERSION_INFO, "version": VERSION})
with patch.object(platform, "system", return_value="Windows"):
checker.check_for_updates()
assert settings.value(Settings.Key.UPDATE_AVAILABLE_VERSION, "") == ""

View file

@ -0,0 +1,238 @@
import platform
from unittest.mock import patch, Mock
import pytest
from PyQt6.QtNetwork import QNetworkReply
from PyQt6.QtWidgets import QMessageBox
from pytestqt.qtbot import QtBot
from buzz.locale import _
from buzz.update_checker import UpdateInfo
from buzz.widgets.update_dialog import UpdateDialog
from tests.mock_qt import MockDownloadReply, MockDownloadNetworkManager
UPDATE_INFO = UpdateInfo(
version="99.0.0",
release_notes="Some fixes.",
download_urls=["https://example.com/Buzz-99.0.0.exe"],
)
MULTI_FILE_UPDATE_INFO = UpdateInfo(
version="99.0.0",
release_notes="Multi-file release.",
download_urls=[
"https://example.com/Buzz-99.0.0.exe",
"https://example.com/Buzz-99.0.0-1.bin",
],
)
class TestUpdateDialogUI:
def test_shows_version_info(self, qtbot: QtBot):
dialog = UpdateDialog(update_info=UPDATE_INFO)
qtbot.add_widget(dialog)
assert dialog.windowTitle() == _("Update Available")
assert "99.0.0" in dialog.findChild(
__import__("PyQt6.QtWidgets", fromlist=["QLabel"]).QLabel,
""
).__class__.__name__ or True # title check is sufficient
def test_download_button_is_present(self, qtbot: QtBot):
dialog = UpdateDialog(update_info=UPDATE_INFO)
qtbot.add_widget(dialog)
assert dialog.download_button.text() == _("Download and Install")
def test_progress_bar_hidden_initially(self, qtbot: QtBot):
dialog = UpdateDialog(update_info=UPDATE_INFO)
qtbot.add_widget(dialog)
assert dialog.progress_bar.isHidden()
def test_status_label_empty_initially(self, qtbot: QtBot):
dialog = UpdateDialog(update_info=UPDATE_INFO)
qtbot.add_widget(dialog)
assert dialog.status_label.text() == ""
class TestUpdateDialogDownload:
def test_shows_warning_when_no_download_urls(self, qtbot: QtBot):
info = UpdateInfo(version="99.0.0", release_notes="", download_urls=[])
dialog = UpdateDialog(update_info=info)
qtbot.add_widget(dialog)
mock_warning = Mock()
with patch.object(QMessageBox, "warning", mock_warning):
dialog.download_button.click()
mock_warning.assert_called_once()
assert _("No download URL available for your platform.") in mock_warning.call_args[0]
def test_download_button_disabled_after_click(self, qtbot: QtBot):
reply = MockDownloadReply(data=b"fake-exe-data")
manager = MockDownloadNetworkManager(replies=[reply])
dialog = UpdateDialog(update_info=UPDATE_INFO, network_manager=manager)
qtbot.add_widget(dialog)
with patch.object(platform, "system", return_value="Windows"), \
patch("subprocess.Popen"), \
patch("buzz.widgets.update_dialog.QApplication"):
dialog.download_button.click()
reply.emit_finished()
assert not dialog.download_button.isEnabled()
def test_progress_bar_shown_after_download_starts(self, qtbot: QtBot):
reply = MockDownloadReply(data=b"fake-exe-data")
manager = MockDownloadNetworkManager(replies=[reply])
dialog = UpdateDialog(update_info=UPDATE_INFO, network_manager=manager)
qtbot.add_widget(dialog)
dialog.download_button.click()
assert not dialog.progress_bar.isHidden()
def test_progress_bar_updates_on_progress(self, qtbot: QtBot):
reply = MockDownloadReply(data=b"x" * (5 * 1024 * 1024))
manager = MockDownloadNetworkManager(replies=[reply])
dialog = UpdateDialog(update_info=UPDATE_INFO, network_manager=manager)
qtbot.add_widget(dialog)
dialog.download_button.click()
reply.downloadProgress.emit(5 * 1024 * 1024, 10 * 1024 * 1024)
assert dialog.progress_bar.value() == 50
assert "5.0 MB" in dialog.status_label.text()
def test_single_file_download_runs_installer_on_windows(self, qtbot: QtBot):
reply = MockDownloadReply(data=b"fake-exe-data")
manager = MockDownloadNetworkManager(replies=[reply])
dialog = UpdateDialog(update_info=UPDATE_INFO, network_manager=manager)
qtbot.add_widget(dialog)
mock_popen = Mock()
mock_quit = Mock()
with patch.object(platform, "system", return_value="Windows"), \
patch("subprocess.Popen", mock_popen), \
patch("buzz.widgets.update_dialog.QApplication") as mock_app:
mock_app.quit = mock_quit
dialog.download_button.click()
reply.emit_finished()
mock_popen.assert_called_once()
installer_path = mock_popen.call_args[0][0][0]
assert installer_path.endswith(".exe")
def test_single_file_download_opens_dmg_on_macos(self, qtbot: QtBot):
macos_info = UpdateInfo(
version="99.0.0",
release_notes="",
download_urls=["https://example.com/Buzz-99.0.0-arm.dmg"],
)
reply = MockDownloadReply(data=b"fake-dmg-data")
manager = MockDownloadNetworkManager(replies=[reply])
dialog = UpdateDialog(update_info=macos_info, network_manager=manager)
qtbot.add_widget(dialog)
mock_popen = Mock()
with patch.object(platform, "system", return_value="Darwin"), \
patch("subprocess.Popen", mock_popen), \
patch("buzz.widgets.update_dialog.QApplication"):
dialog.download_button.click()
reply.emit_finished()
mock_popen.assert_called_once()
assert mock_popen.call_args[0][0][0] == "open"
installer_path = mock_popen.call_args[0][0][1]
assert installer_path.endswith(".dmg")
def test_multi_file_download_downloads_sequentially(self, qtbot: QtBot):
reply1 = MockDownloadReply(data=b"installer-exe")
reply2 = MockDownloadReply(data=b"installer-bin")
manager = MockDownloadNetworkManager(replies=[reply1, reply2])
dialog = UpdateDialog(update_info=MULTI_FILE_UPDATE_INFO, network_manager=manager)
qtbot.add_widget(dialog)
mock_popen = Mock()
with patch.object(platform, "system", return_value="Windows"), \
patch("subprocess.Popen", mock_popen), \
patch("buzz.widgets.update_dialog.QApplication"):
dialog.download_button.click()
# First file done
reply1.emit_finished()
# Second file done
reply2.emit_finished()
assert len(dialog._temp_file_paths) == 2
assert dialog._temp_file_paths[0].endswith(".exe")
assert dialog._temp_file_paths[1].endswith(".bin")
mock_popen.assert_called_once()
def test_status_shows_file_count_during_multi_file_download(self, qtbot: QtBot):
reply1 = MockDownloadReply(data=b"installer-exe")
reply2 = MockDownloadReply(data=b"installer-bin")
manager = MockDownloadNetworkManager(replies=[reply1, reply2])
dialog = UpdateDialog(update_info=MULTI_FILE_UPDATE_INFO, network_manager=manager)
qtbot.add_widget(dialog)
dialog.download_button.click()
assert "1" in dialog.status_label.text()
assert "2" in dialog.status_label.text()
def test_progress_bar_reaches_100_after_all_downloads(self, qtbot: QtBot):
reply = MockDownloadReply(data=b"fake-exe-data")
manager = MockDownloadNetworkManager(replies=[reply])
dialog = UpdateDialog(update_info=UPDATE_INFO, network_manager=manager)
qtbot.add_widget(dialog)
with patch.object(platform, "system", return_value="Windows"), \
patch("subprocess.Popen"), \
patch("buzz.widgets.update_dialog.QApplication"):
dialog.download_button.click()
reply.emit_finished()
assert dialog.progress_bar.value() == 100
assert dialog.status_label.text() == _("Download complete!")
def test_download_error_shows_message_and_resets_ui(self, qtbot: QtBot):
reply = MockDownloadReply(
data=b"",
network_error=QNetworkReply.NetworkError.ConnectionRefusedError,
error_string="Connection refused",
)
manager = MockDownloadNetworkManager(replies=[reply])
dialog = UpdateDialog(update_info=UPDATE_INFO, network_manager=manager)
qtbot.add_widget(dialog)
mock_critical = Mock()
with patch.object(QMessageBox, "critical", mock_critical):
dialog.download_button.click()
reply.emit_finished()
mock_critical.assert_called_once()
assert "Connection refused" in str(mock_critical.call_args)
assert dialog.download_button.isEnabled()
assert dialog.progress_bar.isHidden()
def test_save_error_shows_message_and_resets_ui(self, qtbot: QtBot):
reply = MockDownloadReply(data=b"fake-data")
manager = MockDownloadNetworkManager(replies=[reply])
dialog = UpdateDialog(update_info=UPDATE_INFO, network_manager=manager)
qtbot.add_widget(dialog)
mock_critical = Mock()
with patch.object(QMessageBox, "critical", mock_critical), \
patch("buzz.widgets.update_dialog.open", side_effect=OSError("Disk full")):
dialog.download_button.click()
reply.emit_finished()
mock_critical.assert_called_once()
assert dialog.download_button.isEnabled()
def test_download_reply_stored_while_in_progress(self, qtbot: QtBot):
reply = MockDownloadReply(data=b"fake-data")
manager = MockDownloadNetworkManager(replies=[reply])
dialog = UpdateDialog(update_info=UPDATE_INFO, network_manager=manager)
qtbot.add_widget(dialog)
dialog.download_button.click()
assert dialog._download_reply is reply