mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-16 21:30:39 -07:00
Enhance provider packaging and CUDA download functionality
- Added support for packaging provider archives in the release workflow, creating platform-specific zip and tar.gz files for distribution. - Updated the `.gitignore` to exclude `.spec` files. - Introduced a new `CudaDownloadSection` component to manage CUDA downloads, including progress tracking and error handling. - Refactored provider download logic to handle archive extraction and cleanup after download. - Improved subprocess output handling in the provider manager for better logging and error reporting.
This commit is contained in:
@@ -91,6 +91,30 @@ jobs:
|
||||
cd providers/${{ matrix.provider }}
|
||||
python build.py
|
||||
|
||||
- name: Package provider for distribution
|
||||
shell: bash
|
||||
run: |
|
||||
cd providers/${{ matrix.provider }}/dist
|
||||
|
||||
# Add platform suffix for archive name
|
||||
if [ "${{ matrix.platform }}" == "windows-latest" ]; then
|
||||
ARCHIVE_NAME="tts-provider-${{ matrix.provider }}-windows.zip"
|
||||
# On Windows, zip the directory
|
||||
powershell Compress-Archive -Path "tts-provider-${{ matrix.provider }}/*" -DestinationPath "$ARCHIVE_NAME"
|
||||
elif [ "${{ matrix.platform }}" == "macos-latest" ]; then
|
||||
ARCHIVE_NAME="tts-provider-${{ matrix.provider }}-macos-arm64.tar.gz"
|
||||
tar -czf "$ARCHIVE_NAME" tts-provider-${{ matrix.provider }}/
|
||||
elif [ "${{ matrix.platform }}" == "macos-15-intel" ]; then
|
||||
ARCHIVE_NAME="tts-provider-${{ matrix.provider }}-macos-x64.tar.gz"
|
||||
tar -czf "$ARCHIVE_NAME" tts-provider-${{ matrix.provider }}/
|
||||
else
|
||||
ARCHIVE_NAME="tts-provider-${{ matrix.provider }}-linux.tar.gz"
|
||||
tar -czf "$ARCHIVE_NAME" tts-provider-${{ matrix.provider }}/
|
||||
fi
|
||||
|
||||
echo "Created archive: $ARCHIVE_NAME"
|
||||
ls -lh "$ARCHIVE_NAME"
|
||||
|
||||
- name: Upload provider to R2
|
||||
shell: bash
|
||||
env:
|
||||
@@ -106,31 +130,23 @@ jobs:
|
||||
aws configure set aws_secret_access_key $R2_SECRET_ACCESS_KEY
|
||||
aws configure set region auto
|
||||
|
||||
# Determine binary name based on platform
|
||||
# Determine archive name based on platform
|
||||
if [ "${{ matrix.platform }}" == "windows-latest" ]; then
|
||||
BINARY_NAME="tts-provider-${{ matrix.provider }}.exe"
|
||||
BINARY_PATH="providers/${{ matrix.provider }}/dist/tts-provider-${{ matrix.provider }}.exe"
|
||||
else
|
||||
BINARY_NAME="tts-provider-${{ matrix.provider }}"
|
||||
BINARY_PATH="providers/${{ matrix.provider }}/dist/tts-provider-${{ matrix.provider }}"
|
||||
fi
|
||||
|
||||
# Add platform suffix for clarity
|
||||
if [ "${{ matrix.platform }}" == "windows-latest" ]; then
|
||||
UPLOAD_NAME="tts-provider-${{ matrix.provider }}-windows.exe"
|
||||
ARCHIVE_NAME="tts-provider-${{ matrix.provider }}-windows.zip"
|
||||
elif [ "${{ matrix.platform }}" == "macos-latest" ]; then
|
||||
UPLOAD_NAME="tts-provider-${{ matrix.provider }}-macos-arm64"
|
||||
ARCHIVE_NAME="tts-provider-${{ matrix.provider }}-macos-arm64.tar.gz"
|
||||
elif [ "${{ matrix.platform }}" == "macos-15-intel" ]; then
|
||||
UPLOAD_NAME="tts-provider-${{ matrix.provider }}-macos-x64"
|
||||
ARCHIVE_NAME="tts-provider-${{ matrix.provider }}-macos-x64.tar.gz"
|
||||
else
|
||||
UPLOAD_NAME="tts-provider-${{ matrix.provider }}-linux"
|
||||
ARCHIVE_NAME="tts-provider-${{ matrix.provider }}-linux.tar.gz"
|
||||
fi
|
||||
|
||||
# Upload to R2 (bucket: voicebox)
|
||||
aws s3 cp "$BINARY_PATH" "s3://voicebox/providers/v${{ env.PROVIDER_VERSION }}/$UPLOAD_NAME" \
|
||||
aws s3 cp "providers/${{ matrix.provider }}/dist/$ARCHIVE_NAME" \
|
||||
"s3://voicebox/providers/v${{ env.PROVIDER_VERSION }}/$ARCHIVE_NAME" \
|
||||
--endpoint-url "$R2_ENDPOINT"
|
||||
|
||||
echo "Uploaded $UPLOAD_NAME to R2"
|
||||
echo "Uploaded $ARCHIVE_NAME to R2"
|
||||
|
||||
# ============================================
|
||||
# Build Main App (without bundled TTS on Win/Linux)
|
||||
|
||||
@@ -15,6 +15,7 @@ dist/
|
||||
build/
|
||||
*.egg-info/
|
||||
*.egg
|
||||
*.spec
|
||||
target/
|
||||
*.app
|
||||
*.dmg
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Loader2, RefreshCw, CheckCircle, AlertCircle } from 'lucide-react';
|
||||
import { relaunch } from '@tauri-apps/plugin-process';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { ModelProgress } from './ModelProgress';
|
||||
import { apiClient } from '@/lib/api/client';
|
||||
import { useServerStore } from '@/stores/serverStore';
|
||||
import { toast } from '@/components/ui/use-toast';
|
||||
import { usePlatform } from '@/platform/PlatformContext';
|
||||
import { useServerHealth } from '@/lib/hooks/useServer';
|
||||
|
||||
export function CudaDownloadSection() {
|
||||
const platform = usePlatform();
|
||||
const serverUrl = useServerStore((state) => state.serverUrl);
|
||||
const { data: health } = useServerHealth();
|
||||
const [downloadingCuda, setDownloadingCuda] = useState(false);
|
||||
const [downloadError, setDownloadError] = useState<string | null>(null);
|
||||
|
||||
// Platform checks
|
||||
const isWindows = navigator.userAgent.includes('Windows');
|
||||
const gpuAvailable = health?.gpu_available ?? false;
|
||||
|
||||
// Only show this section on Windows + Tauri + GPU available
|
||||
if (!platform.metadata.isTauri || !isWindows || !gpuAvailable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Query CUDA status
|
||||
const { data: cudaStatus, refetch } = useQuery({
|
||||
queryKey: ['cudaStatus'],
|
||||
queryFn: () => apiClient.getCudaStatus(),
|
||||
enabled: isWindows && platform.metadata.isTauri && gpuAvailable,
|
||||
retry: false,
|
||||
});
|
||||
|
||||
// Handle download trigger
|
||||
const handleDownload = async () => {
|
||||
try {
|
||||
setDownloadError(null);
|
||||
await apiClient.triggerCudaDownload();
|
||||
setDownloadingCuda(true);
|
||||
} catch (error) {
|
||||
setDownloadError(error instanceof Error ? error.message : 'Failed to start download');
|
||||
}
|
||||
};
|
||||
|
||||
// Handle retry
|
||||
const handleRetry = () => {
|
||||
setDownloadError(null);
|
||||
handleDownload();
|
||||
};
|
||||
|
||||
// Monitor download progress and auto-restart on completion
|
||||
useEffect(() => {
|
||||
if (!downloadingCuda || !serverUrl) return;
|
||||
|
||||
const eventSource = new EventSource(`${serverUrl}/models/progress/cuda-binary`);
|
||||
|
||||
eventSource.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
if (data.status === 'complete' && data.progress >= 100) {
|
||||
eventSource.close();
|
||||
setDownloadingCuda(false);
|
||||
|
||||
// Show restart toast
|
||||
toast({
|
||||
title: 'CUDA Downloaded',
|
||||
description: 'Restarting app to enable GPU acceleration...',
|
||||
});
|
||||
|
||||
// Restart after 2 seconds
|
||||
setTimeout(async () => {
|
||||
await relaunch();
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
if (data.status === 'error') {
|
||||
eventSource.close();
|
||||
setDownloadingCuda(false);
|
||||
setDownloadError(data.error || 'Download failed');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error parsing CUDA download progress:', error);
|
||||
}
|
||||
};
|
||||
|
||||
eventSource.onerror = () => {
|
||||
eventSource.close();
|
||||
setDownloadingCuda(false);
|
||||
setDownloadError('Connection to server lost');
|
||||
};
|
||||
|
||||
return () => eventSource.close();
|
||||
}, [downloadingCuda, serverUrl]);
|
||||
|
||||
if (!cudaStatus) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { cuda_available, cuda_active, cuda_binary_size_mb } = cudaStatus;
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>GPU Acceleration</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{/* Status badges */}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Badge variant={cuda_active ? 'default' : 'secondary'}>
|
||||
Mode: {cuda_active ? 'CUDA' : 'CPU'}
|
||||
</Badge>
|
||||
<Badge variant={gpuAvailable ? 'default' : 'secondary'}>
|
||||
GPU: {gpuAvailable ? 'Available' : 'Not Available'}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Already downloaded status */}
|
||||
{cuda_available && !downloadingCuda && (
|
||||
<Alert>
|
||||
<CheckCircle className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
CUDA support is active. GPU acceleration enabled.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{/* Download section - only show if not already downloaded */}
|
||||
{!cuda_available && !downloadError && !downloadingCuda && (
|
||||
<div className="space-y-3">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Download CUDA support for 4-5x faster inference with your NVIDIA GPU
|
||||
</p>
|
||||
<Button onClick={handleDownload} className="w-full">
|
||||
Download CUDA Support ({cuda_binary_size_mb.toFixed(1)}GB)
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Progress display */}
|
||||
{downloadingCuda && (
|
||||
<div className="space-y-2">
|
||||
<ModelProgress
|
||||
modelName="cuda-binary"
|
||||
displayName="CUDA Server Binary"
|
||||
isDownloading={true}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Error with retry button */}
|
||||
{downloadError && (
|
||||
<div className="space-y-2">
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>{downloadError}</AlertDescription>
|
||||
</Alert>
|
||||
<Button onClick={handleRetry} variant="outline" className="w-full">
|
||||
<RefreshCw className="mr-2 h-4 w-4" />
|
||||
Retry Download
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -100,13 +100,43 @@ class ProviderManager:
|
||||
stdout_content = ""
|
||||
stderr_content = ""
|
||||
|
||||
# Try to read available output (works on Windows and Unix)
|
||||
try:
|
||||
if stdout_log.exists():
|
||||
stdout_content = stdout_log.read_text()
|
||||
if stderr_log.exists():
|
||||
stderr_content = stderr_log.read_text()
|
||||
except Exception as read_err:
|
||||
logger.error(f"Failed to read provider logs: {read_err}")
|
||||
# Use non-blocking read with timeout
|
||||
import threading
|
||||
import queue
|
||||
|
||||
def enqueue_output(stream, queue):
|
||||
try:
|
||||
for line in iter(stream.readline, ''):
|
||||
queue.put(line)
|
||||
except:
|
||||
pass
|
||||
|
||||
stdout_queue = queue.Queue()
|
||||
stderr_queue = queue.Queue()
|
||||
|
||||
if process.stdout:
|
||||
t = threading.Thread(target=enqueue_output, args=(process.stdout, stdout_queue))
|
||||
t.daemon = True
|
||||
t.start()
|
||||
|
||||
if process.stderr:
|
||||
t2 = threading.Thread(target=enqueue_output, args=(process.stderr, stderr_queue))
|
||||
t2.daemon = True
|
||||
t2.start()
|
||||
|
||||
# Give threads a moment to read
|
||||
import time
|
||||
time.sleep(0.5)
|
||||
|
||||
# Collect output
|
||||
while not stdout_queue.empty():
|
||||
stdout_lines.append(stdout_queue.get_nowait())
|
||||
while not stderr_queue.empty():
|
||||
stderr_lines.append(stderr_queue.get_nowait())
|
||||
except Exception as ex:
|
||||
logger.warning(f"Could not capture subprocess output: {ex}")
|
||||
|
||||
logger.error(f"Provider failed to start within 30 seconds")
|
||||
logger.error(f"Check logs at: {logs_dir}")
|
||||
|
||||
@@ -55,13 +55,13 @@ def _get_provider_binary_name(provider_type: str) -> str:
|
||||
def _get_provider_download_name(provider_type: str) -> str:
|
||||
"""Get the remote download filename for a provider type (includes platform suffix)."""
|
||||
system = platform.system()
|
||||
|
||||
|
||||
if system == "Windows":
|
||||
platform_suffix = "windows"
|
||||
ext = ".exe"
|
||||
ext = ".zip"
|
||||
elif system == "Linux":
|
||||
platform_suffix = "linux"
|
||||
ext = ""
|
||||
ext = ".tar.gz"
|
||||
elif system == "Darwin":
|
||||
# Detect macOS architecture
|
||||
machine = platform.machine()
|
||||
@@ -69,10 +69,10 @@ def _get_provider_download_name(provider_type: str) -> str:
|
||||
platform_suffix = "macos-arm64"
|
||||
else:
|
||||
platform_suffix = "macos-x64"
|
||||
ext = ""
|
||||
ext = ".tar.gz"
|
||||
else:
|
||||
raise ValueError(f"Provider downloads not supported on {system}")
|
||||
|
||||
|
||||
return f"tts-provider-{provider_type}-{platform_suffix}{ext}"
|
||||
|
||||
|
||||
@@ -84,97 +84,131 @@ def _get_provider_download_url(provider_type: str) -> str:
|
||||
|
||||
async def download_provider(provider_type: str) -> Path:
|
||||
"""
|
||||
Download a provider binary from Cloudflare R2.
|
||||
|
||||
Download and extract a provider archive from Cloudflare R2.
|
||||
|
||||
Args:
|
||||
provider_type: Type of provider to download (e.g., "pytorch-cpu")
|
||||
|
||||
|
||||
Returns:
|
||||
Path to the downloaded provider binary
|
||||
|
||||
Path to the extracted provider binary
|
||||
|
||||
Raises:
|
||||
ValueError: If provider_type is invalid
|
||||
httpx.HTTPError: If download fails
|
||||
"""
|
||||
if provider_type not in ["pytorch-cpu", "pytorch-cuda"]:
|
||||
raise ValueError(f"Provider type {provider_type} cannot be downloaded")
|
||||
|
||||
|
||||
progress_manager = get_progress_manager()
|
||||
task_manager = get_task_manager()
|
||||
|
||||
binary_name = _get_provider_binary_name(provider_type)
|
||||
|
||||
archive_name = _get_provider_download_name(provider_type)
|
||||
download_url = _get_provider_download_url(provider_type)
|
||||
destination = _get_providers_dir() / binary_name
|
||||
|
||||
providers_dir = _get_providers_dir()
|
||||
archive_path = providers_dir / archive_name
|
||||
|
||||
# Start tracking download
|
||||
task_manager.start_download(provider_type)
|
||||
|
||||
|
||||
# Initialize progress state
|
||||
progress_manager.update_progress(
|
||||
model_name=provider_type,
|
||||
current=0,
|
||||
total=0, # Will be updated once we get Content-Length
|
||||
filename=binary_name,
|
||||
filename=archive_name,
|
||||
status="downloading",
|
||||
)
|
||||
|
||||
|
||||
try:
|
||||
# Download archive
|
||||
async with httpx.AsyncClient(timeout=300.0) as client:
|
||||
# First, get the file size
|
||||
async with client.stream("GET", download_url) as response:
|
||||
response.raise_for_status()
|
||||
|
||||
|
||||
# Get total size from Content-Length header
|
||||
total_size = int(response.headers.get("Content-Length", 0))
|
||||
|
||||
|
||||
if total_size > 0:
|
||||
progress_manager.update_progress(
|
||||
model_name=provider_type,
|
||||
current=0,
|
||||
total=total_size,
|
||||
filename=binary_name,
|
||||
filename=archive_name,
|
||||
status="downloading",
|
||||
)
|
||||
|
||||
|
||||
# Download with progress tracking
|
||||
downloaded = 0
|
||||
with open(destination, "wb") as f:
|
||||
with open(archive_path, "wb") as f:
|
||||
async for chunk in response.aiter_bytes(chunk_size=8192):
|
||||
f.write(chunk)
|
||||
downloaded += len(chunk)
|
||||
|
||||
|
||||
# Update progress
|
||||
progress_manager.update_progress(
|
||||
model_name=provider_type,
|
||||
current=downloaded,
|
||||
total=total_size if total_size > 0 else downloaded,
|
||||
filename=binary_name,
|
||||
filename=archive_name,
|
||||
status="downloading",
|
||||
)
|
||||
|
||||
|
||||
# Extract archive
|
||||
progress_manager.update_progress(
|
||||
model_name=provider_type,
|
||||
current=downloaded,
|
||||
total=downloaded,
|
||||
filename="Extracting...",
|
||||
status="downloading",
|
||||
)
|
||||
|
||||
import zipfile
|
||||
import tarfile
|
||||
|
||||
if archive_name.endswith('.zip'):
|
||||
with zipfile.ZipFile(archive_path, 'r') as zip_ref:
|
||||
zip_ref.extractall(providers_dir)
|
||||
elif archive_name.endswith('.tar.gz'):
|
||||
with tarfile.open(archive_path, 'r:gz') as tar_ref:
|
||||
tar_ref.extractall(providers_dir)
|
||||
else:
|
||||
raise ValueError(f"Unsupported archive format: {archive_name}")
|
||||
|
||||
# Remove archive after extraction
|
||||
archive_path.unlink()
|
||||
|
||||
# Get path to extracted binary
|
||||
binary_path = get_provider_binary_path(provider_type)
|
||||
if not binary_path:
|
||||
raise ValueError(f"Provider binary not found after extraction")
|
||||
|
||||
# Make executable on Unix systems
|
||||
if platform.system() != "Windows":
|
||||
binary_path.chmod(0o755)
|
||||
|
||||
# Mark as complete
|
||||
progress_manager.update_progress(
|
||||
model_name=provider_type,
|
||||
current=downloaded,
|
||||
total=downloaded,
|
||||
filename=binary_name,
|
||||
filename=_get_provider_binary_name(provider_type),
|
||||
status="complete",
|
||||
)
|
||||
task_manager.complete_download(provider_type)
|
||||
|
||||
# Make executable on Unix systems
|
||||
if platform.system() != "Windows":
|
||||
destination.chmod(0o755)
|
||||
|
||||
return destination
|
||||
|
||||
|
||||
return binary_path
|
||||
|
||||
except Exception as e:
|
||||
# Clean up archive if it exists
|
||||
if archive_path.exists():
|
||||
archive_path.unlink()
|
||||
|
||||
# Mark as error
|
||||
progress_manager.update_progress(
|
||||
model_name=provider_type,
|
||||
current=0,
|
||||
total=0,
|
||||
filename=binary_name,
|
||||
filename=archive_name,
|
||||
status="error",
|
||||
)
|
||||
task_manager.error_download(provider_type, str(e))
|
||||
@@ -184,19 +218,28 @@ async def download_provider(provider_type: str) -> Path:
|
||||
def get_provider_binary_path(provider_type: str) -> Optional[Path]:
|
||||
"""
|
||||
Get the path to an installed provider binary.
|
||||
|
||||
|
||||
Args:
|
||||
provider_type: Type of provider
|
||||
|
||||
|
||||
Returns:
|
||||
Path to provider binary, or None if not installed
|
||||
"""
|
||||
providers_dir = _get_providers_dir()
|
||||
binary_name = _get_provider_binary_name(provider_type)
|
||||
provider_path = _get_providers_dir() / binary_name
|
||||
|
||||
|
||||
# Check for --onedir structure (directory with binary inside)
|
||||
provider_dir = providers_dir / f"tts-provider-{provider_type}"
|
||||
if provider_dir.exists() and provider_dir.is_dir():
|
||||
binary_path = provider_dir / binary_name
|
||||
if binary_path.exists() and binary_path.is_file():
|
||||
return binary_path
|
||||
|
||||
# Fallback: check for direct binary (legacy)
|
||||
provider_path = providers_dir / binary_name
|
||||
if provider_path.exists() and provider_path.is_file():
|
||||
return provider_path
|
||||
|
||||
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
# User data directory
|
||||
# This directory contains:
|
||||
# - profiles/ - Voice profile audio files
|
||||
# - generations/ - Generated audio files
|
||||
# - projects/ - Audio studio project files
|
||||
# - voicebox.db - SQLite database
|
||||
# - cache/ - Voice prompt cache files
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
# Voice prompt cache files
|
||||
@@ -16,7 +16,7 @@ def build_provider():
|
||||
# PyInstaller arguments
|
||||
args = [
|
||||
'main.py',
|
||||
'--onefile',
|
||||
'--onedir', # Changed from --onefile to work around Windows extraction issues
|
||||
'--name', 'tts-provider-pytorch-cpu',
|
||||
]
|
||||
|
||||
@@ -51,6 +51,18 @@ def build_provider():
|
||||
'--collect-submodules', 'jaraco',
|
||||
'--hidden-import', 'fastapi',
|
||||
'--hidden-import', 'uvicorn',
|
||||
# Critical uvicorn imports for PyInstaller
|
||||
'--hidden-import', 'uvicorn.logging',
|
||||
'--hidden-import', 'uvicorn.loops',
|
||||
'--hidden-import', 'uvicorn.loops.auto',
|
||||
'--hidden-import', 'uvicorn.protocols',
|
||||
'--hidden-import', 'uvicorn.protocols.http',
|
||||
'--hidden-import', 'uvicorn.protocols.http.auto',
|
||||
'--hidden-import', 'uvicorn.protocols.websockets',
|
||||
'--hidden-import', 'uvicorn.protocols.websockets.auto',
|
||||
'--hidden-import', 'uvicorn.lifespan',
|
||||
'--hidden-import', 'uvicorn.lifespan.on',
|
||||
'--collect-submodules', 'uvicorn',
|
||||
'--hidden-import', 'soundfile',
|
||||
'--hidden-import', 'numpy',
|
||||
'--hidden-import', 'librosa',
|
||||
|
||||
@@ -16,7 +16,7 @@ def build_provider():
|
||||
# PyInstaller arguments
|
||||
args = [
|
||||
'main.py',
|
||||
'--onefile',
|
||||
'--onedir', # Changed from --onefile to work around Windows extraction issues
|
||||
'--name', 'tts-provider-pytorch-cuda',
|
||||
]
|
||||
|
||||
@@ -53,6 +53,18 @@ def build_provider():
|
||||
'--collect-submodules', 'jaraco',
|
||||
'--hidden-import', 'fastapi',
|
||||
'--hidden-import', 'uvicorn',
|
||||
# Critical uvicorn imports for PyInstaller
|
||||
'--hidden-import', 'uvicorn.logging',
|
||||
'--hidden-import', 'uvicorn.loops',
|
||||
'--hidden-import', 'uvicorn.loops.auto',
|
||||
'--hidden-import', 'uvicorn.protocols',
|
||||
'--hidden-import', 'uvicorn.protocols.http',
|
||||
'--hidden-import', 'uvicorn.protocols.http.auto',
|
||||
'--hidden-import', 'uvicorn.protocols.websockets',
|
||||
'--hidden-import', 'uvicorn.protocols.websockets.auto',
|
||||
'--hidden-import', 'uvicorn.lifespan',
|
||||
'--hidden-import', 'uvicorn.lifespan.on',
|
||||
'--collect-submodules', 'uvicorn',
|
||||
'--hidden-import', 'soundfile',
|
||||
'--hidden-import', 'numpy',
|
||||
'--hidden-import', 'librosa',
|
||||
|
||||
Reference in New Issue
Block a user