mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-26 21:55:15 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7f749f082 | ||
|
|
d42e926e5c | ||
|
|
32768ea874 | ||
|
|
b585e18ccf | ||
|
|
655910457f | ||
|
|
d6984f1057 | ||
|
|
a637aebe69 |
+1
-1
@@ -1,5 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 0.2.1
|
||||
current_version = 0.2.2
|
||||
commit = True
|
||||
tag = True
|
||||
tag_name = v{new_version}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@voicebox/app",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -246,13 +246,18 @@ export function GpuAcceleration() {
|
||||
{/* CUDA download section - only show when no GPU is active (native or CUDA) */}
|
||||
{!hasNativeGpu && !isCurrentlyCuda && (
|
||||
<>
|
||||
{/* Download progress */}
|
||||
{/* Download progress (manual download or auto-update) */}
|
||||
{cudaDownloading && downloadProgress && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
<span>{downloadProgress.filename || 'Downloading CUDA backend...'}</span>
|
||||
<span>
|
||||
{downloadProgress.filename ||
|
||||
(cudaAvailable
|
||||
? 'Updating CUDA backend...'
|
||||
: 'Downloading CUDA backend...')}
|
||||
</span>
|
||||
</div>
|
||||
{downloadProgress.total > 0 && (
|
||||
<span className="text-muted-foreground">
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
# Backend package
|
||||
|
||||
__version__ = "0.2.1"
|
||||
__version__ = "0.2.2"
|
||||
|
||||
@@ -68,6 +68,14 @@ def build_server(cuda=False):
|
||||
'--hidden-import', 'backend.utils.effects',
|
||||
'--hidden-import', 'backend.versions',
|
||||
'--hidden-import', 'pedalboard',
|
||||
'--hidden-import', 'chatterbox',
|
||||
'--hidden-import', 'chatterbox.tts_turbo',
|
||||
'--hidden-import', 'chatterbox.mtl_tts',
|
||||
'--hidden-import', 'backend.backends.chatterbox_backend',
|
||||
'--hidden-import', 'backend.backends.chatterbox_turbo_backend',
|
||||
'--hidden-import', 'backend.backends.luxtts_backend',
|
||||
'--hidden-import', 'zipvoice',
|
||||
'--hidden-import', 'zipvoice.luxvoice',
|
||||
'--hidden-import', 'torch',
|
||||
'--hidden-import', 'transformers',
|
||||
'--hidden-import', 'fastapi',
|
||||
|
||||
@@ -199,6 +199,56 @@ async def download_cuda_binary(version: Optional[str] = None):
|
||||
raise
|
||||
|
||||
|
||||
def get_cuda_binary_version() -> Optional[str]:
|
||||
"""Get the version of the installed CUDA binary, or None if not installed."""
|
||||
import subprocess
|
||||
cuda_path = get_cuda_binary_path()
|
||||
if not cuda_path:
|
||||
return None
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[str(cuda_path), "--version"],
|
||||
capture_output=True, text=True, timeout=30,
|
||||
)
|
||||
# Output format: "voicebox-server 0.2.0"
|
||||
for line in result.stdout.strip().splitlines():
|
||||
if "voicebox-server" in line:
|
||||
return line.split()[-1]
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not get CUDA binary version: {e}")
|
||||
return None
|
||||
|
||||
|
||||
async def check_and_update_cuda_binary():
|
||||
"""Check if the CUDA binary is outdated and auto-download if so.
|
||||
|
||||
Called on server startup. If a CUDA binary exists but its version
|
||||
doesn't match the current app version, triggers a background download
|
||||
of the updated CUDA binary. The download progress is visible to the
|
||||
frontend via the existing SSE progress endpoint.
|
||||
"""
|
||||
cuda_path = get_cuda_binary_path()
|
||||
if not cuda_path:
|
||||
return # No CUDA binary installed, nothing to update
|
||||
|
||||
cuda_version = get_cuda_binary_version()
|
||||
current_version = __version__
|
||||
|
||||
if cuda_version == current_version:
|
||||
logger.info(f"CUDA binary is up to date (v{current_version})")
|
||||
return
|
||||
|
||||
logger.info(
|
||||
f"CUDA binary version mismatch: binary=v{cuda_version}, app=v{current_version}. "
|
||||
f"Auto-downloading updated CUDA backend..."
|
||||
)
|
||||
|
||||
try:
|
||||
await download_cuda_binary()
|
||||
except Exception as e:
|
||||
logger.error(f"Auto-update of CUDA binary failed: {e}")
|
||||
|
||||
|
||||
async def delete_cuda_binary() -> bool:
|
||||
"""Delete the downloaded CUDA binary. Returns True if deleted."""
|
||||
path = get_cuda_binary_path()
|
||||
|
||||
@@ -3104,6 +3104,10 @@ async def startup_event():
|
||||
print(f"Backend: {backend_type.upper()}")
|
||||
print(f"GPU available: {_get_gpu_status()}")
|
||||
|
||||
# Auto-update CUDA binary if installed but outdated
|
||||
from .cuda_download import check_and_update_cuda_binary
|
||||
_create_background_task(check_and_update_cuda_binary())
|
||||
|
||||
# Initialize progress manager with main event loop for thread-safe operations
|
||||
try:
|
||||
progress_manager = get_progress_manager()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@voicebox/landing",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"description": "Landing page for voicebox.sh",
|
||||
"scripts": {
|
||||
"dev": "bun --bun next dev --turbo",
|
||||
|
||||
@@ -115,6 +115,20 @@
|
||||
animation: fadeUp 0.6s ease-out forwards;
|
||||
}
|
||||
|
||||
.hero-glow-fade {
|
||||
opacity: 0;
|
||||
animation: fadeIn 2s ease-out 0.3s forwards;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Noise texture overlay for hero glow */
|
||||
/* .hero-glow::after {
|
||||
content: "";
|
||||
|
||||
+14
-10
@@ -13,6 +13,8 @@ import type { DownloadLinks } from '@/lib/releases';
|
||||
|
||||
export default function Home() {
|
||||
const [downloadLinks, setDownloadLinks] = useState<DownloadLinks>(DOWNLOAD_LINKS);
|
||||
const [version, setVersion] = useState<string | null>(null);
|
||||
const [totalDownloads, setTotalDownloads] = useState<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetch('/api/releases')
|
||||
@@ -22,6 +24,8 @@ export default function Home() {
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.downloadLinks) setDownloadLinks(data.downloadLinks);
|
||||
if (data.version) setVersion(data.version);
|
||||
if (data.totalDownloads != null) setTotalDownloads(data.totalDownloads);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to fetch release info:', error);
|
||||
@@ -35,7 +39,7 @@ export default function Home() {
|
||||
{/* ── Hero Section ─────────────────────────────────────────────── */}
|
||||
<section className="relative pt-32 pb-16">
|
||||
{/* Background glow */}
|
||||
<div className="hero-glow pointer-events-none absolute inset-0 -top-32">
|
||||
<div className="hero-glow hero-glow-fade pointer-events-none absolute inset-0 -top-32">
|
||||
<div className="absolute left-1/2 top-0 -translate-x-1/2 w-[800px] h-[600px] rounded-full bg-accent/15 blur-[150px]" />
|
||||
<div className="absolute left-1/2 top-12 -translate-x-1/2 w-[500px] h-[400px] rounded-full bg-accent/10 blur-[80px]" />
|
||||
</div>
|
||||
@@ -44,23 +48,19 @@ export default function Home() {
|
||||
{/* Logo */}
|
||||
<div
|
||||
className="fade-in mx-auto mb-8 h-[120px] w-[120px] md:h-[160px] md:w-[160px]"
|
||||
style={{
|
||||
animationDelay: '0ms',
|
||||
filter:
|
||||
'drop-shadow(0 0 20px hsl(43 60% 50% / 0.4)) drop-shadow(0 0 60px hsl(43 60% 50% / 0.2))',
|
||||
}}
|
||||
style={{ animationDelay: '0ms' }}
|
||||
>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src="/voicebox-logo-app.webp"
|
||||
alt="Voicebox"
|
||||
className="h-full w-full object-contain mix-blend-lighten"
|
||||
className="h-full w-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Headline */}
|
||||
<div className="fade-in relative" style={{ animationDelay: '100ms' }}>
|
||||
<h1 className="text-5xl font-bold tracking-tighter leading-[0.9] text-foreground drop-shadow-[0_16px_50px_rgba(0,0,0,0.95)] md:text-7xl lg:text-8xl">
|
||||
<h1 className="text-5xl font-bold tracking-tighter leading-[0.9] text-foreground md:text-7xl lg:text-8xl">
|
||||
Your voice, your machine.
|
||||
</h1>
|
||||
</div>
|
||||
@@ -96,12 +96,16 @@ export default function Home() {
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* Version */}
|
||||
{/* Version + downloads */}
|
||||
<p
|
||||
className="fade-in mt-4 text-xs text-muted-foreground/50"
|
||||
style={{ animationDelay: '400ms' }}
|
||||
>
|
||||
Free and open source · macOS, Windows, Linux
|
||||
{version ?? ''}
|
||||
{version && totalDownloads != null ? ' \u00b7 ' : ''}
|
||||
{totalDownloads != null ? `${totalDownloads.toLocaleString()} downloads` : ''}
|
||||
{version || totalDownloads != null ? ' \u00b7 ' : ''}
|
||||
macOS, Windows, Linux
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ export interface DownloadLinks {
|
||||
export interface ReleaseInfo {
|
||||
version: string;
|
||||
downloadLinks: DownloadLinks;
|
||||
totalDownloads: number;
|
||||
}
|
||||
|
||||
const GITHUB_REPO = 'jamiepine/voicebox';
|
||||
@@ -72,11 +73,15 @@ export async function getLatestRelease(): Promise<ReleaseInfo> {
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch total downloads across ALL releases
|
||||
const totalDownloads = await getTotalDownloads();
|
||||
|
||||
// Fallback: construct URLs if not found in assets
|
||||
const baseUrl = `https://github.com/${GITHUB_REPO}/releases/download/${version}`;
|
||||
|
||||
const releaseInfo: ReleaseInfo = {
|
||||
version,
|
||||
totalDownloads,
|
||||
downloadLinks: {
|
||||
macArm: downloadLinks.macArm || `${baseUrl}/voicebox_aarch64.app.tar.gz`,
|
||||
macIntel: downloadLinks.macIntel || `${baseUrl}/voicebox_x64.app.tar.gz`,
|
||||
@@ -97,6 +102,56 @@ export async function getLatestRelease(): Promise<ReleaseInfo> {
|
||||
}
|
||||
}
|
||||
|
||||
// Cache for total download count
|
||||
let cachedTotalDownloads: number | null = null;
|
||||
let downloadsCacheTimestamp: number = 0;
|
||||
|
||||
/**
|
||||
* Fetches download counts across ALL releases (paginated)
|
||||
*/
|
||||
async function getTotalDownloads(): Promise<number> {
|
||||
const now = Date.now();
|
||||
if (cachedTotalDownloads !== null && now - downloadsCacheTimestamp < CACHE_DURATION) {
|
||||
return cachedTotalDownloads;
|
||||
}
|
||||
|
||||
let total = 0;
|
||||
let page = 1;
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const response = await fetch(
|
||||
`${GITHUB_API_BASE}/repos/${GITHUB_REPO}/releases?per_page=100&page=${page}`,
|
||||
{
|
||||
headers: { Accept: 'application/vnd.github.v3+json' },
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) break;
|
||||
|
||||
const releases = await response.json();
|
||||
if (!Array.isArray(releases) || releases.length === 0) break;
|
||||
|
||||
for (const release of releases) {
|
||||
for (const asset of release.assets || []) {
|
||||
total += asset.download_count || 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (releases.length < 100) break;
|
||||
page++;
|
||||
}
|
||||
|
||||
cachedTotalDownloads = total;
|
||||
downloadsCacheTimestamp = now;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch total downloads:', error);
|
||||
if (cachedTotalDownloads !== null) return cachedTotalDownloads;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the star count for the repo from GitHub
|
||||
*/
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "voicebox",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"app",
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@voicebox/tauri",
|
||||
"private": true,
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "voicebox"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
description = "A production-quality desktop app for Qwen3-TTS voice cloning and generation"
|
||||
authors = ["you"]
|
||||
license = ""
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Voicebox",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"identifier": "sh.voicebox.app",
|
||||
"build": {
|
||||
"beforeDevCommand": "bun run dev",
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@voicebox/web",
|
||||
"private": true,
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
Reference in New Issue
Block a user