Skip to content

Download URL audio before speech extraction and plugins - #1667

Merged
raivisdejus merged 8 commits into
chidiwilliams:mainfrom
doqhu:fix/url-download-phase
Oct 24, 2026
Merged

raivisdejus merged 8 commits into
chidiwilliams:mainfrom
doqhu:fix/url-download-phase

Conversation

@doqhu

@doqhu doqhu commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Problem

URL imports had no local file until FileTranscriber.run() downloaded the audio. Speech extraction and the before_transcription / check_skip plugins run before that, so they got file_path=None. URL imports with Extract speech enabled could not work, and plugins never saw the audio.

Fix

The download is now its own phase. For URL tasks the queue worker runs FileTranscriber.download() on a worker thread first. When the audio is on disk, it continues with speech extraction, plugins and transcription, as for local files.

Moving the download earlier exposed a few issues, fixed in separate commits:

  • Skip Already Transcribed skipped unrelated URL imports. Plugins now see the download under its fixed temp name audio.wav. The result file check then matched any audio*.txt/srt/vtt and marked new imports as skipped. URL imports are never skipped now, which was the behaviour before.
  • Races between cancel and the download.
    • A late completed signal from a canceled download could start transcription for whichever task was current by then. Download signals now carry their own task and activity token and are ignored once that task is no longer current.
    • A cancel between the download and the transcriber thread had nothing to stop, so the task was transcribed anyway. The worker now checks for a cancel before and after speech extraction and after plugins.
  • Canceling a download froze the UI. FileTranscriber.stop() did nothing, so cancel_task blocked the GUI thread for up to 5 s and then terminate()d the thread.
    • stop() now aborts yt-dlp from its progress hook (DownloadCancelled) and kills the ffmpeg conversion, which now runs with Popen.
    • yt-dlp can't be interrupted while it resolves the URL, so cancel_task releases the queue right away instead of waiting for a download thread.
  • Cleanups:
    • task_started is sent once per URL task. A second send reset time_started.
    • Downloading happens only in download().
    • original_file_path is reset after a download, so it can't point at a previous run's file.
    • The download phase has its own _setup_download_thread.

The commits can be reviewed one by one. Every commit passes the affected tests.

Testing

  • Full suite on Linux (Fedora 44, whisper.cpp built with Vulkan): 1382 passed, 15 skipped, 3 failed (measured with Fix CUDA prompt firing on a deleted main window #1668 applied as well, which adds 2 of those tests). The 3 failures are in tests/widgets/speaker_count_test.py (MSDDDiarizer.diarize() got an unexpected keyword argument 'num_speakers'). They fail the same way on main, and this PR doesn't touch speaker code. On machines with an NVIDIA GPU, test_should_run_file_transcription_task can also fail intermittently; that is fixed separately in Fix CUDA prompt firing on a deleted main window #1668.
  • test_transcribe_from_url calls run() directly, so it now calls download() first, as the queue worker does.
  • New tests:
    • the download runs before speech extraction, plugins and transcription, and each step gets the downloaded (or extracted) file;
    • late or stale download signals are ignored;
    • a cancel during extraction or plugins stops the task;
    • stop() aborts yt-dlp and kills ffmpeg;
    • the skip plugin ignores URL imports.
  • ruff check reports nothing new. Only the lines this PR adds are ruff-formatted; the rest of each file is unchanged.

🤖 Generated with Claude Code

https://claude.ai/code/session_012ozoVtp2JvfpDkZ1SpBgvW

doqhu and others added 7 commits October 20, 2026 16:18
URL tasks now reach plugins after their download, under the fixed temp
name audio.wav. The result file check then looked for audio*.txt/srt/vtt
next to it and in the output folder, so any unrelated match marked a new
URL import as skipped and gave it that file's segments.

The temp name says nothing about the video, so URL imports are never
skipped, as before the download moved ahead of plugins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ozoVtp2JvfpDkZ1SpBgvW
The download's completed handler checked self.current.task, so a late
completion from a canceled download could start transcription for
whichever task was current by then. It now carries the task and activity
token it was started for, like the error handler, and ignores signals
for a task that is no longer current. This also stops a second
_on_task_finished() for a task that cancel_task already finished.

Between the download and the transcriber thread there is no transcriber
for cancel_task to stop. A cancel there was lost: the task was set back
to in progress and transcribed in full. The worker now checks for a
cancel before and after speech extraction and after plugins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ozoVtp2JvfpDkZ1SpBgvW
FileTranscriber.stop() did nothing, so canceling a download blocked the
GUI thread for up to 5 s in QThread.wait() and then terminate()d a
thread running Python code.

stop() now sets a flag that the yt-dlp progress hook checks, raising
DownloadCancelled (yt-dlp re-raises it out of extract_info), and kills
the ffmpeg conversion, which now runs with Popen. Subclasses chain to it.

yt-dlp can't be interrupted while it resolves the URL, so cancel_task
no longer waits for a download thread. It releases the queue at once,
and the download's progress and error signals are dropped once its task
is no longer current, so they can't mark the next task as failed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ozoVtp2JvfpDkZ1SpBgvW
- Send task_started once per URL task. The second send, from the
  transcription phase, reset time_started (dropping the download from
  the recorded duration) and could overwrite a status written between
  the two phases.
- Download only in download(). run() skipped the download whenever a
  URL task already had a file_path, so a stale path from an earlier run
  would have been transcribed instead of a fresh download.
- Reset original_file_path after a download. A value kept from an
  earlier run's speech extraction pointed plugins at the previous
  download.
- Give the download phase its own _setup_download_thread instead of a
  download_only flag in _setup_transcriber_thread.
- Check in the test that the download runs before speech extraction,
  plugins and transcription, and which file each step gets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ozoVtp2JvfpDkZ1SpBgvW
Apply ruff format only to lines this branch added. The rest of these
files is left as is, since main is not ruff-formatted either.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ozoVtp2JvfpDkZ1SpBgvW
run() no longer downloads URL tasks; the queue worker calls download()
first. The test called run() directly, so it now does the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ozoVtp2JvfpDkZ1SpBgvW
- Store the downloaded audio, not the deleted speech file, for URL imports
  with speech extraction.
- Remove the extracted speech file when a task is canceled before its
  transcriber starts.
- Don't report a canceled speech extraction as a failure, and remove the
  partial file the killed process leaves behind.
- Drop the pass-through stop() override in OpenAIWhisperAPIFileTranscriber.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ozoVtp2JvfpDkZ1SpBgvW
@codecov

codecov Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.52066% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.14%. Comparing base (6186d09) to head (8c84bb3).

Files with missing lines Patch % Lines
buzz/file_transcriber_queue_worker.py 96.05% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1667      +/-   ##
==========================================
+ Coverage   83.94%   84.14%   +0.20%     
==========================================
  Files         115      115              
  Lines       13835    13928      +93     
==========================================
+ Hits        11614    11720     +106     
+ Misses       2221     2208      -13     
Flag Coverage Δ
Linux 83.42% <97.52%> (+0.24%) ⬆️
macOS 83.63% <97.52%> (+0.18%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@raivisdejus
raivisdejus merged commit 23d848b into chidiwilliams:main Oct 24, 2026
15 of 16 checks passed
@doqhu
doqhu deleted the fix/url-download-phase branch November 3, 2026 14:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants