trying to fix crashes

This commit is contained in:
2026-04-08 01:04:27 -06:00
parent 38ca9cfbad
commit 2406b0a2e7
8 changed files with 109 additions and 13 deletions

View File

@ -2,12 +2,15 @@ import logging
import os import os
import stat import stat
import sys import sys
import tempfile
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from pathlib import Path from pathlib import Path
from fastapi import FastAPI, Query, Request, HTTPException from fastapi import FastAPI, Query, Request, HTTPException
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse, FileResponse
import ffmpeg
from routers import transcribe, export, ai, captions, audio from routers import transcribe, export, ai, captions, audio
@ -61,20 +64,69 @@ MIME_MAP = {
@app.get("/file") @app.get("/file")
async def serve_local_file(request: Request, path: str = Query(...)): async def serve_local_file(request: Request, path: str = Query(...), format: str = Query(None)):
"""Stream a local file with HTTP Range support (required for video seeking).""" """Stream a local file with HTTP Range support (required for video seeking).
Optionally transcode audio files to MP3 for better browser compatibility."""
file_path = Path(path) file_path = Path(path)
if not file_path.is_file(): if not file_path.is_file():
logger.warning(f"[serve_file] File not found: {path}") logger.warning(f"[serve_file] File not found: {path}")
raise HTTPException(status_code=404, detail=f"File not found: {path}") raise HTTPException(status_code=404, detail=f"File not found: {path}")
file_size = file_path.stat().st_size file_size = file_path.stat().st_size
content_type = MIME_MAP.get(file_path.suffix.lower(), "application/octet-stream") original_ext = file_path.suffix.lower()
# Check if we should transcode this file
should_transcode = (
original_ext == '.wav' and
(format == 'mp3' or file_size > 10 * 1024 * 1024) # Transcode WAV if > 10MB or explicitly requested
)
if should_transcode:
logger.info(f"[serve_file] Transcoding {file_path.name} to MP3 (size: {file_size})")
# Create cache directory
cache_dir = Path(__file__).parent / "cache"
cache_dir.mkdir(exist_ok=True)
# Create cache filename
import hashlib
file_hash = hashlib.md5(str(file_path).encode()).hexdigest()
cache_path = cache_dir / f"{file_hash}.mp3"
# Check if cached version exists
if not cache_path.exists():
logger.info(f"[serve_file] Creating cached MP3: {cache_path}")
try:
# Transcode to MP3 using ffmpeg
(
ffmpeg
.input(str(file_path))
.output(str(cache_path), acodec='libmp3lame', ab='128k')
.run(overwrite_output=True, quiet=True)
)
except ffmpeg.Error as e:
logger.error(f"[serve_file] Transcoding failed: {e}")
# Fall back to original file
cache_path = file_path
else:
logger.info(f"[serve_file] Transcoding completed: {cache_path}")
else:
logger.info(f"[serve_file] Using cached MP3: {cache_path}")
# Use the transcoded file
file_path = cache_path
file_size = file_path.stat().st_size
content_type = "audio/mpeg"
else:
content_type = MIME_MAP.get(original_ext, "application/octet-stream")
range_header = request.headers.get("range") range_header = request.headers.get("range")
logger.info( logger.info(
f"[serve_file] {file_path.name} | size={file_size} | " f"[serve_file] Serving {file_path.name} | size={file_size} | "
f"type={content_type} | range={range_header or 'none'}" f"type={content_type} | range={range_header or 'none'} | transcoded={should_transcode}"
) )
if content_type == "application/octet-stream": if content_type == "application/octet-stream":

View File

@ -3,10 +3,7 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' data: https://fonts.gstatic.com; connect-src 'self' ipc: http://ipc.localhost http://localhost:* http://127.0.0.1:* ws://localhost:* ws://127.0.0.1:*; media-src 'self' file: blob: http://localhost:* http://127.0.0.1:*; img-src 'self' data: blob:;" /> <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; font-src 'self' data:; connect-src 'self' ipc: http://ipc.localhost http://localhost:* http://127.0.0.1:* ws://localhost:* ws://127.0.0.1:*; media-src 'self' file: blob: http://localhost:* http://127.0.0.1:*; img-src 'self' data: blob:;" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
<title>TalkEdit</title> <title>TalkEdit</title>
</head> </head>
<body class="bg-editor-bg text-editor-text antialiased"> <body class="bg-editor-bg text-editor-text antialiased">

View File

@ -8,6 +8,8 @@
"name": "talkedit-frontend", "name": "talkedit-frontend",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@fontsource/inter": "^5.2.8",
"@fontsource/jetbrains-mono": "^5.2.8",
"@tauri-apps/api": "^2", "@tauri-apps/api": "^2",
"@tauri-apps/plugin-dialog": "^2", "@tauri-apps/plugin-dialog": "^2",
"@tauri-apps/plugin-fs": "^2", "@tauri-apps/plugin-fs": "^2",
@ -768,6 +770,24 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@fontsource/inter": {
"version": "5.2.8",
"resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-5.2.8.tgz",
"integrity": "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@fontsource/jetbrains-mono": {
"version": "5.2.8",
"resolved": "https://registry.npmjs.org/@fontsource/jetbrains-mono/-/jetbrains-mono-5.2.8.tgz",
"integrity": "sha512-6w8/SG4kqvIMu7xd7wt6x3idn1Qux3p9N62s6G3rfldOUYHpWcc2FKrqf+Vo44jRvqWj2oAtTHrZXEP23oSKwQ==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13", "version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",

View File

@ -10,6 +10,8 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@fontsource/inter": "^5.2.8",
"@fontsource/jetbrains-mono": "^5.2.8",
"@tauri-apps/api": "^2", "@tauri-apps/api": "^2",
"@tauri-apps/plugin-dialog": "^2", "@tauri-apps/plugin-dialog": "^2",
"@tauri-apps/plugin-fs": "^2", "@tauri-apps/plugin-fs": "^2",

View File

@ -72,6 +72,7 @@ export default function VideoPlayer() {
src={videoUrl} src={videoUrl}
className="max-w-full max-h-full" className="max-w-full max-h-full"
controls={false} controls={false}
preload="none"
onClick={togglePlay} onClick={togglePlay}
onError={(e) => { onError={(e) => {
console.error('Audio load error:', e); console.error('Audio load error:', e);
@ -80,6 +81,9 @@ export default function VideoPlayer() {
onLoadStart={() => console.log('Audio load start:', videoUrl)} onLoadStart={() => console.log('Audio load start:', videoUrl)}
onLoadedData={() => console.log('Audio loaded data')} onLoadedData={() => console.log('Audio loaded data')}
onCanPlay={() => console.log('Audio can play')} onCanPlay={() => console.log('Audio can play')}
onProgress={() => console.log('Audio progress event')}
onStalled={() => console.log('Audio stalled')}
onSuspend={() => console.log('Audio suspended')}
/> />
) : ( ) : (
<video <video
@ -87,6 +91,7 @@ export default function VideoPlayer() {
src={videoUrl} src={videoUrl}
className="max-w-full max-h-full object-contain" className="max-w-full max-h-full object-contain"
playsInline playsInline
preload="none"
onClick={togglePlay} onClick={togglePlay}
onError={(e) => { onError={(e) => {
console.error('Video load error:', e); console.error('Video load error:', e);
@ -95,6 +100,9 @@ export default function VideoPlayer() {
onLoadStart={() => console.log('Video load start:', videoUrl)} onLoadStart={() => console.log('Video load start:', videoUrl)}
onLoadedData={() => console.log('Video loaded data')} onLoadedData={() => console.log('Video loaded data')}
onCanPlay={() => console.log('Video can play')} onCanPlay={() => console.log('Video can play')}
onProgress={() => console.log('Video progress event')}
onStalled={() => console.log('Video stalled')}
onSuspend={() => console.log('Video suspended')}
/> />
)} )}
</div> </div>

View File

@ -1,3 +1,11 @@
@import '@fontsource/inter/300.css';
@import '@fontsource/inter/400.css';
@import '@fontsource/inter/500.css';
@import '@fontsource/inter/600.css';
@import '@fontsource/inter/700.css';
@import '@fontsource/jetbrains-mono/400.css';
@import '@fontsource/jetbrains-mono/500.css';
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;

View File

@ -116,7 +116,13 @@ export const useEditorStore = create<EditorState & EditorActions>()(
loadVideo: (path) => { loadVideo: (path) => {
const backend = get().backendUrl; const backend = get().backendUrl;
const url = `${backend}/file?path=${encodeURIComponent(path)}`; const buildMediaUrl = (filePath: string) => {
const isWav = filePath.toLowerCase().endsWith('.wav');
return isWav
? `${backend}/file?path=${encodeURIComponent(filePath)}&format=mp3`
: `${backend}/file?path=${encodeURIComponent(filePath)}`;
};
const url = buildMediaUrl(path);
set({ set({
...initialState, ...initialState,
backendUrl: backend, backendUrl: backend,
@ -304,7 +310,10 @@ export const useEditorStore = create<EditorState & EditorActions>()(
loadProject: (data) => { loadProject: (data) => {
const backend = get().backendUrl; const backend = get().backendUrl;
const url = `${backend}/file?path=${encodeURIComponent(data.videoPath)}`; const isWav = data.videoPath.toLowerCase().endsWith('.wav');
const url = isWav
? `${backend}/file?path=${encodeURIComponent(data.videoPath)}&format=mp3`
: `${backend}/file?path=${encodeURIComponent(data.videoPath)}`;
let globalIdx = 0; let globalIdx = 0;
const annotatedSegments = (data.segments || []).map((seg: Segment) => { const annotatedSegments = (data.segments || []).map((seg: Segment) => {

View File

@ -23,7 +23,7 @@
} }
], ],
"security": { "security": {
"csp": "default-src 'self'; connect-src 'self' http://127.0.0.1:* http://localhost:* ws://127.0.0.1:* ws://localhost:*; media-src 'self' http://127.0.0.1:* http://localhost:* file: blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' data: https://fonts.gstatic.com; img-src 'self' data: blob:" "csp": "default-src 'self'; connect-src 'self' http://127.0.0.1:* http://localhost:* ws://127.0.0.1:* ws://localhost:*; media-src 'self' http://127.0.0.1:* http://localhost:* file: blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; font-src 'self' data:; img-src 'self' data: blob:"
} }
}, },
"bundle": { "bundle": {