2 Commits

Author SHA1 Message Date
Gitea Actions
68ae48a771 chore: bump version to 0.2.5 [skip ci] 2026-03-22 03:23:01 +00:00
Claude
4fed9bccb8 Fix sidecar crash: torch circular import under PyInstaller
All checks were successful
Release / Bump version and tag (push) Successful in 4s
Release / Build (Linux) (push) Successful in 7m27s
Release / Build (macOS) (push) Successful in 7m47s
Release / Build (Windows) (push) Successful in 19m25s
- Exclude ctranslate2.converters from PyInstaller bundle — these modules
  import torch at module level causing circular import crashes, and are
  only needed for model conversion (never used at runtime)
- Defer all heavy ML imports to first handler call instead of startup,
  so the sidecar can send its ready message without loading torch/whisper

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 20:22:56 -07:00
6 changed files with 45 additions and 20 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "voice-to-notes", "name": "voice-to-notes",
"version": "0.2.4", "version": "0.2.5",
"description": "Desktop app for transcribing audio/video with speaker identification", "description": "Desktop app for transcribing audio/video with speaker identification",
"type": "module", "type": "module",
"scripts": { "scripts": {

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "voice-to-notes" name = "voice-to-notes"
version = "0.2.4" version = "0.2.5"
description = "Python sidecar for Voice to Notes — transcription, diarization, and AI services" description = "Python sidecar for Voice to Notes — transcription, diarization, and AI services"
requires-python = ">=3.11" requires-python = ">=3.11"
license = "MIT" license = "MIT"

View File

@@ -33,7 +33,13 @@ a = Analysis(
hookspath=[], hookspath=[],
hooksconfig={}, hooksconfig={},
runtime_hooks=[], runtime_hooks=[],
excludes=["tkinter", "test", "unittest", "pip", "setuptools"], excludes=[
"tkinter", "test", "unittest", "pip", "setuptools",
# ctranslate2.converters imports torch at module level and causes
# circular import crashes under PyInstaller. These modules are only
# needed for model format conversion, never for inference.
"ctranslate2.converters",
],
win_no_prefer_redirects=False, win_no_prefer_redirects=False,
win_private_assemblies=False, win_private_assemblies=False,
cipher=block_cipher, cipher=block_cipher,

View File

@@ -41,11 +41,15 @@ def ping_handler(msg: IPCMessage) -> IPCMessage:
def make_transcribe_handler() -> HandlerFunc: def make_transcribe_handler() -> HandlerFunc:
"""Create a transcription handler with a persistent TranscribeService.""" """Create a transcription handler with a persistent TranscribeService."""
from voice_to_notes.services.transcribe import TranscribeService, result_to_payload service = None
service = TranscribeService()
def handler(msg: IPCMessage) -> IPCMessage: def handler(msg: IPCMessage) -> IPCMessage:
nonlocal service
if service is None:
from voice_to_notes.services.transcribe import TranscribeService
service = TranscribeService()
from voice_to_notes.services.transcribe import result_to_payload
payload = msg.payload payload = msg.payload
result = service.transcribe( result = service.transcribe(
request_id=msg.id, request_id=msg.id,
@@ -66,11 +70,15 @@ def make_transcribe_handler() -> HandlerFunc:
def make_diarize_handler() -> HandlerFunc: def make_diarize_handler() -> HandlerFunc:
"""Create a diarization handler with a persistent DiarizeService.""" """Create a diarization handler with a persistent DiarizeService."""
from voice_to_notes.services.diarize import DiarizeService, diarization_to_payload service = None
service = DiarizeService()
def handler(msg: IPCMessage) -> IPCMessage: def handler(msg: IPCMessage) -> IPCMessage:
nonlocal service
if service is None:
from voice_to_notes.services.diarize import DiarizeService
service = DiarizeService()
from voice_to_notes.services.diarize import diarization_to_payload
payload = msg.payload payload = msg.payload
result = service.diarize( result = service.diarize(
request_id=msg.id, request_id=msg.id,
@@ -163,11 +171,15 @@ def make_diarize_download_handler() -> HandlerFunc:
def make_pipeline_handler() -> HandlerFunc: def make_pipeline_handler() -> HandlerFunc:
"""Create a full pipeline handler (transcribe + diarize + merge).""" """Create a full pipeline handler (transcribe + diarize + merge)."""
from voice_to_notes.services.pipeline import PipelineService, pipeline_result_to_payload service = None
service = PipelineService()
def handler(msg: IPCMessage) -> IPCMessage: def handler(msg: IPCMessage) -> IPCMessage:
nonlocal service
if service is None:
from voice_to_notes.services.pipeline import PipelineService
service = PipelineService()
from voice_to_notes.services.pipeline import pipeline_result_to_payload
payload = msg.payload payload = msg.payload
result = service.run( result = service.run(
request_id=msg.id, request_id=msg.id,
@@ -193,11 +205,15 @@ def make_pipeline_handler() -> HandlerFunc:
def make_export_handler() -> HandlerFunc: def make_export_handler() -> HandlerFunc:
"""Create an export handler.""" """Create an export handler."""
from voice_to_notes.services.export import ExportService, make_export_request service = None
service = ExportService()
def handler(msg: IPCMessage) -> IPCMessage: def handler(msg: IPCMessage) -> IPCMessage:
nonlocal service
if service is None:
from voice_to_notes.services.export import ExportService
service = ExportService()
from voice_to_notes.services.export import make_export_request
request = make_export_request(msg.payload) request = make_export_request(msg.payload)
output_path = service.export(request) output_path = service.export(request)
return IPCMessage( return IPCMessage(
@@ -211,11 +227,14 @@ def make_export_handler() -> HandlerFunc:
def make_ai_chat_handler() -> HandlerFunc: def make_ai_chat_handler() -> HandlerFunc:
"""Create an AI chat handler with persistent AIProviderService.""" """Create an AI chat handler with persistent AIProviderService."""
from voice_to_notes.services.ai_provider import create_default_service service = None
service = create_default_service()
def handler(msg: IPCMessage) -> IPCMessage: def handler(msg: IPCMessage) -> IPCMessage:
nonlocal service
if service is None:
from voice_to_notes.services.ai_provider import create_default_service
service = create_default_service()
payload = msg.payload payload = msg.payload
action = payload.get("action", "chat") action = payload.get("action", "chat")

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "voice-to-notes" name = "voice-to-notes"
version = "0.2.4" version = "0.2.5"
description = "Voice to Notes — desktop transcription with speaker identification" description = "Voice to Notes — desktop transcription with speaker identification"
authors = ["Voice to Notes Contributors"] authors = ["Voice to Notes Contributors"]
license = "MIT" license = "MIT"

View File

@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "Voice to Notes", "productName": "Voice to Notes",
"version": "0.2.4", "version": "0.2.5",
"identifier": "com.voicetonotes.app", "identifier": "com.voicetonotes.app",
"build": { "build": {
"beforeDevCommand": "npm run dev", "beforeDevCommand": "npm run dev",