Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Change also in pyproject.toml and buzz/__version__.py
version := 1.4.5
version := 1.4.6

mac_app_path := ./dist/Buzz.app
mac_zip_path := ./dist/Buzz-${version}-mac.zip
Expand Down
2 changes: 1 addition & 1 deletion buzz/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
VERSION = "1.4.5"
VERSION = "1.4.6"
4 changes: 4 additions & 0 deletions buzz/widgets/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ def show_main_window(self):

def add_task(self, task: FileTranscriptionTask, quit_on_complete: bool = False):
self.window.quit_on_complete = quit_on_complete
if quit_on_complete:
# Track every queued task so the app only quits once the whole
# batch is done.
self.window.pending_quit_task_uids.add(task.uid)
self.window.add_task(task)

def close_database(self):
Expand Down
25 changes: 15 additions & 10 deletions buzz/widgets/main_window.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import os
import logging
from typing import Tuple, List, Optional
from typing import Tuple, List, Optional, Set
from uuid import UUID

from PyQt6 import QtGui
Expand Down Expand Up @@ -90,6 +90,7 @@ def __init__(self, transcription_service: TranscriptionService):
self.shortcuts = Shortcuts(settings=self.settings)

self.quit_on_complete = False
self.pending_quit_task_uids: Set[UUID] = set()
self.transcription_service = transcription_service

self.plugin_manager = PluginManager(self.transcription_service, self.settings)
Expand Down Expand Up @@ -484,9 +485,7 @@ def on_task_completed(self, task: FileTranscriptionTask, segments: List[Segment]
if task.status == FileTranscriptionTask.Status.SKIPPED:
self.transcription_service.update_transcription_as_skipped(task.uid, segments)
self.table_widget.refresh_row(task.uid)
if self.quit_on_complete:
self.close()
QApplication.quit()
self.quit_if_all_tasks_done(task)
return

# Update file path in database only for URL imports where file is downloaded
Expand Down Expand Up @@ -523,18 +522,24 @@ def on_task_completed(self, task: FileTranscriptionTask, segments: List[Segment]
self.transcription_service.update_transcription_as_completed(task.uid, segments)
self.table_widget.refresh_row(task.uid)

if self.quit_on_complete:
self.close()
QApplication.quit()
self.quit_if_all_tasks_done(task)

def quit_if_all_tasks_done(self, task: FileTranscriptionTask):
if not self.quit_on_complete:
return

self.pending_quit_task_uids.discard(task.uid)
if len(self.pending_quit_task_uids) > 0:
return

self.close()
QApplication.quit()

def on_task_error(self, task: FileTranscriptionTask, error: str):
self.transcription_service.update_transcription_as_failed(task.uid, error)
self.table_widget.refresh_row(task.uid)

if self.quit_on_complete:
self.close()
QApplication.quit()
self.quit_if_all_tasks_done(task)

def on_shortcuts_changed(self):
self.menu_bar.reset_shortcuts()
Expand Down
32 changes: 24 additions & 8 deletions docs/docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,29 +60,45 @@ Options:
(Yiddish), yo (Yoruba), zh (Chinese). Leave
empty to detect language.
-p, --prompt <prompt> Initial prompt.
-w, --word-timestamps Generate word-level timestamps. (available since 1.2.0)
-e, --extract-speech Extract speech from audio before transcribing. (available since 1.3.0)
-w, --word-timestamps Generate word-level timestamps.
-e, --extract-speech Extract speech from audio before transcribing.
--openai-token <token> OpenAI access token. Use only when
--model-type is openaiapi. Defaults to your
previously saved access token, if one exists.
-d, --output-directory <directory>
Output directory. Defaults to the directory of
the input file.
--srt Output result in an SRT file.
--vtt Output result in a VTT file.
--txt Output result in a TXT file.
--hide-gui Hide the main application window. (available since 1.2.0)
--hide-gui Hide the main application window.
-h, --help Displays help on commandline options.
--help-all Displays help including Qt specific options.
-v, --version Displays version information.

Arguments:
files or urls Input file paths or urls. Url import availalbe since 1.2.0.
files or urls Input file paths or urls.
```

**Examples**:

```shell
# Translate two MP3 files from French to English using OpenAI Whisper API
buzz add --task translate --language fr --model-type openaiapi /Users/user/Downloads/1b3b03e4-8db5-ea2c-ace5-b71ff32e3304.mp3 /Users/user/Downloads/koaf9083k1lkpsfdi0.mp3
# Transcribe a single file and save the transcript as a TXT file next to it
buzz add --txt interview.mp3

# Transcribe an MP4 using Whisper.cpp "small" model and immediately export to SRT and VTT files
buzz add --task transcribe --model-type whispercpp --model-size small --prompt "My initial prompt" --srt --vtt /Users/user/Downloads/buzz/1b3b03e4-8db5-ea2c-ace5-b71ff32e3304.mp4
# Transcribe several files at once, they will be processed one after another.
# Support for multiple input files available since version 1.4.6
buzz add --txt lecture-1.mp3 lecture-2.mp3 lecture-3.mp3

# Transcribe a whole folder of recordings into a separate output folder
buzz add --srt --output-directory ./subtitles recordings/*.mp4

# Translate French audio to English with the OpenAI Whisper API
buzz add --task translate --language fr --model-type openaiapi meeting-fr.mp3

# Transcribe a video with the Whisper.cpp "small" model, exporting SRT and VTT subtitles
buzz add --model-type whispercpp --model-size small --srt --vtt presentation.mp4

# Transcribe a YouTube video without opening the app window
buzz add --txt --hide-gui https://www.youtube.com/watch?v=dQw4w9WgXcQ
```
2 changes: 1 addition & 1 deletion docs/docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ In addition to choosing an appropriate model size you also can choose whisper ty
- **Whisper.cpp** is optimized C++ implementation, it quite fast and efficient and will use any brand of GPU. Whisper.cpp is capable of running real time transcription even on a modern laptop with integrated GPU. It can also run on CPU only. Use this option if you do not have Nvidia GPU. This is best option to us on Macs.
- **HuggingFace** option is a `Transformers` implementation and is good in that it supports wide range of custom models that may be optimized for a particular language.
- Since version `1.4.0` This option also supports [MMS](https://ai.meta.com/blog/multilingual-model-speech-recognition/) family of models from Meta AI that support over 1000 of worlds languages as well as [PEFT](https://github.com/huggingface/peft) adjustments to Whisper models.
- Since version `1.5.0` [Parakeet](https://huggingface.co/models?sort=trending&search=parakeet), [Qwen3-ASR](https://huggingface.co/models?sort=trending&search=qwen+asr+hf) and [VibeVoice ASR](https://huggingface.co/models?sort=trending&search=vibevoice+hf+) models are also supported.
- Since version `1.4.5` [Parakeet](https://huggingface.co/models?sort=trending&search=parakeet), [Qwen3-ASR](https://huggingface.co/models?sort=trending&search=qwen+asr+hf) and [VibeVoice ASR](https://huggingface.co/models?sort=trending&search=vibevoice+hf+) models are also supported.

Models with `.En` in their names are for English and will not work for other languages.

Expand Down
10 changes: 5 additions & 5 deletions docs/docs/preferences.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,22 +109,22 @@ Defaults to [user_cache_dir](https://pypi.org/project/platformdirs/).

**BUZZ_DOWNLOAD_COOKIEFILE** - Location of a [cookiefile](https://github.com/yt-dlp/yt-dlp/wiki/FAQ#how-do-i-pass-cookies-to-yt-dlp) to use for downloading private videos or as workaround for anti-bot protection.

**BUZZ_FORCE_CPU** - Will force Buzz to use CPU and not GPU, useful for setups with older GPU if that is slower than GPU or GPU has issues. Example usage `BUZZ_FORCE_CPU=true`. Available since `1.2.1`
**BUZZ_FORCE_CPU** - Will force Buzz to use CPU and not GPU, useful for setups with older GPU if that is slower than GPU or GPU has issues. Example usage `BUZZ_FORCE_CPU=true`.

**BUZZ_REDUCE_GPU_MEMORY** - Will use 8bit quantization for Huggingface, Faster Whisper and Whisper.cpp transcriptions to reduce required GPU memory. Example usage `BUZZ_REDUCE_GPU_MEMORY=true`. Available since `1.4.0`

**BUZZ_MERGE_REGROUP_RULE** - Custom regroup merge rule to use when combining transcripts with word-level timings. More information on available options [in stable-ts repo](https://github.com/jianfch/stable-ts?tab=readme-ov-file#regrouping-methods). Available since `1.3.0`
**BUZZ_MERGE_REGROUP_RULE** - Custom regroup merge rule to use when combining transcripts with word-level timings. More information on available options [in stable-ts repo](https://github.com/jianfch/stable-ts?tab=readme-ov-file#regrouping-methods).

**BUZZ_DISABLE_TELEMETRY** - Buzz collects basic OS name and architecture usage statistics to better focus development efforts. This variable lets disable collection of these statistics. Example usage `BUZZ_DISABLE_TELEMETRY=true`. Available since `1.3.0`
**BUZZ_DISABLE_TELEMETRY** - Buzz collects basic OS name and architecture usage statistics to better focus development efforts. This variable lets disable collection of these statistics. Example usage `BUZZ_DISABLE_TELEMETRY=true`.

**BUZZ_DISABLE_UPDATE_CHECK** - Disable checks for updates. Example usage `BUZZ_DISABLE_UPDATE_CHECK=true`. Available since `1.4.5`

**BUZZ_UPLOAD_URL** - Live recording transcripts and translations can be uploaded to a server for display on the web. Set this variable to the desired upload url. You can use [buzz-transcription-server](https://github.com/raivisdejus/buzz-transcription-server) as a server. Buzz will upload the following `json` via `POST` requests - `{"kind": "transcript", "text": "Sample transcript"}` or `{"kind": "translation", "text": "Sample translation"}`. Example usage `BUZZ_UPLOAD_URL=http://localhost:5000/upload`. Available since `1.3.0`
**BUZZ_UPLOAD_URL** - Live recording transcripts and translations can be uploaded to a server for display on the web. Set this variable to the desired upload url. You can use [buzz-transcription-server](https://github.com/raivisdejus/buzz-transcription-server) as a server. Buzz will upload the following `json` via `POST` requests - `{"kind": "transcript", "text": "Sample transcript"}` or `{"kind": "translation", "text": "Sample translation"}`. Example usage `BUZZ_UPLOAD_URL=http://localhost:5000/upload`.

**HF_ENDPOINT** - To speed up downloads in China set `HF_ENDPOINT=https://hf-mirror.com`

Example of data collected by telemetry:
```
Buzz: 1.3.0, locale: ('lv_LV', 'UTF-8'), system: Linux, release: 6.14.0-27-generic, machine: x86_64, version: #27~24.04.1-Ubuntu SMP PREEMPT_DYNAMIC Tue Jul 22 17:38:49 UTC 2,
```
**BUZZ_PARAGRAPH_SPLIT_TIME** - Time in milliseconds of silence to split paragraphs in transcript and add two newlines when exporting the transcripts as text. Default is `2000` or 2 seconds. Available since `1.3.0`
**BUZZ_PARAGRAPH_SPLIT_TIME** - Time in milliseconds of silence to split paragraphs in transcript and add two newlines when exporting the transcripts as text. Default is `2000` or 2 seconds.
6 changes: 3 additions & 3 deletions docs/i18n/zh/docusaurus-plugin-content-docs/current/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,20 @@ Options:
(Yiddish), yo (Yoruba), zh (Chinese). Leave
empty to detect language.
-p, --prompt <prompt> Initial prompt.
-w, --word-timestamps Generate word-level timestamps. (available since 1.2.0)
-w, --word-timestamps Generate word-level timestamps.
--openai-token <token> OpenAI access token. Use only when
--model-type is openaiapi. Defaults to your
previously saved access token, if one exists.
--srt Output result in an SRT file.
--vtt Output result in a VTT file.
--txt Output result in a TXT file.
--hide-gui Hide the main application window. (available since 1.2.0)
--hide-gui Hide the main application window.
-h, --help Displays help on commandline options.
--help-all Displays help including Qt specific options.
-v, --version Displays version information.

Arguments:
files or urls Input file paths or urls. Url import availalbe since 1.2.0.
files or urls Input file paths or urls.
```

**示例**:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[project]
name = "buzz-captions"
# Change also in Makefile and buzz/__version__.py
version = "1.4.5"
version = "1.4.6"
description = ""
authors = [{ name = "Chidi Williams", email = "williamschidi1@gmail.com" }]
requires-python = ">=3.13,<3.14"
Expand Down
20 changes: 14 additions & 6 deletions tests/cli_test.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import os
import sys
from tempfile import mkdtemp

import pytest
from pytestqt.qtbot import QtBot

from buzz.cli import parse_command_line
from tests.audio import test_audio_path
from tests.audio import test_audio_path, test_multibyte_utf8_audio_path


class TestCLI:
# A batch of more than one file also covers the single file case. The app
# closes its database when the CLI run finishes, so only one CLI test can
# run per pytest process.
@pytest.mark.parametrize(
"qapp_args",
[
Expand All @@ -25,17 +27,23 @@ class TestCLI:
mkdtemp(),
"--txt",
test_audio_path,
test_multibyte_utf8_audio_path,
],
)
],
indirect=True,
)
def test_cli(self, qapp, qapp_args, qtbot: QtBot):
def test_cli_multiple_files(self, qapp, qapp_args, qtbot: QtBot):
"""Every file in a batch must be transcribed, not just the first one.
"""
output_directory = qapp_args[7]

parse_command_line(qapp)

def output_exists_at_output_directory():
assert any(file.endswith(".txt") for file in os.listdir(output_directory))
def all_outputs_exist():
outputs = [
file for file in os.listdir(output_directory) if file.endswith(".txt")
]
assert len(outputs) == 2

qtbot.wait_until(output_exists_at_output_directory, timeout=5 * 60 * 1000)
qtbot.wait_until(all_outputs_exist, timeout=5 * 60 * 1000)