Skip to content

Commit 7ecbf73

Browse files
committed
fix: batch 29 third-pass audit — 12 issues fixed across 8 files
chat_editor.py: multi-turn chat was completely broken — query_llm() doesn't accept messages kwarg (TypeError at runtime). Rewrote to embed conversation history in the prompt. Added _sessions_lock for thread-safe session dict access. Bare ffmpeg/ffprobe (3 final): styled_captions.py Popen used bare "ffmpeg" (bypasses run_ffmpeg auto-resolve), audio_enhance.py used bare "ffprobe" despite resolving path, color_match.py fallback path used bare "ffmpeg" via subprocess.run. system.py: removed double rate_limit_release on validation error in install_whisper and whisper_reinstall (acquired flag handles it). animated_captions.py: bare "ffmpeg" replaced with get_ffmpeg_path(), _group_words_into_lines bare dict access changed to .get() defaults. engine_registry.py: clear_cache() now acquires self._lock. server.py: PID file open() calls now have encoding="utf-8".
1 parent d52c36e commit 7ecbf73

8 files changed

Lines changed: 55 additions & 36 deletions

File tree

opencut/core/animated_captions.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import tempfile
1818
from typing import Callable, Dict, List, Optional
1919

20-
from opencut.helpers import ensure_package, get_video_info, run_ffmpeg
20+
from opencut.helpers import ensure_package, get_ffmpeg_path, get_video_info, run_ffmpeg
2121

2222
logger = logging.getLogger("opencut")
2323

@@ -264,7 +264,7 @@ def render_animated_captions(
264264

265265
try:
266266
run_ffmpeg([
267-
"ffmpeg", "-hide_banner", "-loglevel", "error", "-y",
267+
get_ffmpeg_path(), "-hide_banner", "-loglevel", "error", "-y",
268268
"-i", tmp_video, "-i", video_path,
269269
"-map", "0:v", "-map", "1:a?",
270270
"-c:v", "libx264", "-crf", "18", "-preset", "medium",
@@ -295,16 +295,16 @@ def _group_words_into_lines(words: List[Dict], max_per_line: int) -> List[Dict]:
295295
if len(current_words) >= max_per_line:
296296
lines.append({
297297
"words": current_words,
298-
"start": current_words[0]["start"],
299-
"end": current_words[-1]["end"],
298+
"start": current_words[0].get("start", 0),
299+
"end": current_words[-1].get("end", 0),
300300
})
301301
current_words = []
302302

303303
if current_words:
304304
lines.append({
305305
"words": current_words,
306-
"start": current_words[0]["start"],
307-
"end": current_words[-1]["end"],
306+
"start": current_words[0].get("start", 0),
307+
"end": current_words[-1].get("end", 0),
308308
})
309309

310310
return lines

opencut/core/audio_enhance.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def _is_video(filepath):
5050
try:
5151
ffprobe_path = get_ffprobe_path()
5252
result = subprocess.run(
53-
["ffprobe", "-v", "quiet", "-select_streams", "v:0",
53+
[ffprobe_path, "-v", "quiet", "-select_streams", "v:0",
5454
"-show_entries", "stream=codec_type", "-of", "csv=p=0", filepath],
5555
capture_output=True, text=True, timeout=10, env=_binary_env(ffprobe_path), check=False,
5656
)

opencut/core/chat_editor.py

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"""
2626

2727
import logging
28+
import threading
2829
import time
2930
from dataclasses import dataclass, field
3031
from typing import Dict, List, Optional
@@ -105,14 +106,15 @@ def get_llm_messages(self) -> List[Dict]:
105106

106107
# Active sessions (in-memory, keyed by session_id)
107108
_sessions: Dict[str, ChatSession] = {}
109+
_sessions_lock = threading.Lock()
108110

109111
# Session TTL: evict sessions inactive for more than 2 hours
110112
_SESSION_TTL = 2 * 60 * 60 # seconds
111113
_MAX_SESSIONS = 100
112114

113115

114116
def _evict_stale_sessions():
115-
"""Remove sessions that haven't been active within the TTL."""
117+
"""Remove sessions that haven't been active within the TTL. Must hold _sessions_lock."""
116118
now = time.time()
117119
stale = [
118120
sid for sid, s in _sessions.items()
@@ -136,28 +138,30 @@ def get_or_create_session(
136138
clip_info: Optional[Dict] = None,
137139
) -> ChatSession:
138140
"""Get an existing chat session or create a new one."""
139-
_evict_stale_sessions()
140-
141-
if session_id in _sessions:
142-
session = _sessions[session_id]
143-
# Update filepath if changed
144-
if filepath and filepath != session.filepath:
145-
session.filepath = filepath
146-
session.context = clip_info or {}
141+
with _sessions_lock:
142+
_evict_stale_sessions()
143+
144+
if session_id in _sessions:
145+
session = _sessions[session_id]
146+
# Update filepath if changed
147+
if filepath and filepath != session.filepath:
148+
session.filepath = filepath
149+
session.context = clip_info or {}
150+
return session
151+
152+
session = ChatSession(
153+
session_id=session_id,
154+
filepath=filepath,
155+
context=clip_info or {},
156+
)
157+
_sessions[session_id] = session
147158
return session
148159

149-
session = ChatSession(
150-
session_id=session_id,
151-
filepath=filepath,
152-
context=clip_info or {},
153-
)
154-
_sessions[session_id] = session
155-
return session
156-
157160

158161
def clear_session(session_id: str):
159162
"""Clear a chat session's history."""
160-
_sessions.pop(session_id, None)
163+
with _sessions_lock:
164+
_sessions.pop(session_id, None)
161165

162166

163167
def chat(
@@ -209,12 +213,24 @@ def chat(
209213
# Query LLM with full conversation history
210214
messages = session.get_llm_messages()
211215

216+
# Build a combined prompt with conversation context
217+
# query_llm() only supports single prompt + system_prompt,
218+
# so we include recent history in the prompt itself
219+
context_msgs = messages[1:-1] # Skip system and current user msg
220+
if context_msgs:
221+
history_text = "\n".join(
222+
f"{'User' if m['role'] == 'user' else 'Assistant'}: {m['content']}"
223+
for m in context_msgs[-10:] # Last 10 messages for context
224+
)
225+
full_prompt = f"Previous conversation:\n{history_text}\n\nUser: {user_message}"
226+
else:
227+
full_prompt = user_message
228+
212229
try:
213230
response = query_llm(
214-
prompt=user_message,
231+
prompt=full_prompt,
215232
config=llm_config,
216233
system_prompt=system_prompt,
217-
messages=messages[1:], # Skip system (passed separately)
218234
)
219235
response_text = response.text if hasattr(response, "text") else str(response)
220236
except Exception as e:

opencut/core/color_match.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@
1111
from typing import List
1212

1313
try:
14-
from ..helpers import run_ffmpeg
14+
from ..helpers import get_ffmpeg_path, run_ffmpeg
1515
except ImportError:
1616
try:
17-
from opencut.helpers import run_ffmpeg
17+
from opencut.helpers import get_ffmpeg_path, run_ffmpeg
1818
except ImportError:
1919
run_ffmpeg = None # type: ignore
20+
get_ffmpeg_path = None # type: ignore
2021

2122
logger = logging.getLogger("opencut")
2223

@@ -311,8 +312,9 @@ def color_match_video(
311312
logger.info("Merging audio from source into color-matched output")
312313

313314
# Merge original audio from source using FFmpeg
315+
_ffmpeg = get_ffmpeg_path() if get_ffmpeg_path else "ffmpeg"
314316
cmd = [
315-
"ffmpeg", "-hide_banner", "-loglevel", "error", "-y",
317+
_ffmpeg, "-hide_banner", "-loglevel", "error", "-y",
316318
"-i", temp_video,
317319
"-i", source_path,
318320
"-map", "0:v:0",

opencut/core/engine_registry.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,8 @@ def export_preferences(self) -> Dict[str, str]:
203203

204204
def clear_cache(self):
205205
"""Clear the availability cache."""
206-
self._availability_cache.clear()
206+
with self._lock:
207+
self._availability_cache.clear()
207208

208209

209210
# ---------------------------------------------------------------------------

opencut/core/styled_captions.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
from dataclasses import dataclass
2020
from typing import Callable, Dict, List, Optional, Set, Tuple
2121

22+
from opencut.helpers import get_ffmpeg_path
23+
2224
from .captions import TranscriptionResult, Word
2325

2426
logger = logging.getLogger("opencut.styled_captions")
@@ -992,7 +994,7 @@ def render_styled_caption_video(
992994
# Pipe raw RGBA frames to FFmpeg
993995
# -------------------------------------------------------------------
994996
cmd = [
995-
"ffmpeg", "-hide_banner", "-loglevel", "warning", "-y",
997+
get_ffmpeg_path(), "-hide_banner", "-loglevel", "warning", "-y",
996998
"-f", "rawvideo",
997999
"-pix_fmt", "rgba",
9981000
"-s", f"{video_width}x{video_height}",

opencut/routes/system.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -749,7 +749,6 @@ def install_whisper(job_id, filepath, data):
749749
backend = data.get("backend", "faster-whisper")
750750
allowed = {"faster-whisper", "openai-whisper", "whisperx"}
751751
if backend not in allowed:
752-
rate_limit_release("model_install")
753752
raise ValueError(f"Unknown backend: {backend}")
754753

755754
try:
@@ -1050,7 +1049,6 @@ def whisper_reinstall(job_id, filepath, data):
10501049
backend = data.get("backend", "faster-whisper")
10511050
allowed_backends = {"faster-whisper", "openai-whisper", "whisperx"}
10521051
if backend not in allowed_backends:
1053-
rate_limit_release("model_install")
10541052
raise ValueError(f"Unknown backend: {backend}")
10551053

10561054
cpu_mode = data.get("cpu_mode", False)

opencut/server.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ def _write_pid(port: int):
310310
# Atomic write: temp file + rename to prevent partial reads
311311
fd, tmp_path = tempfile.mkstemp(dir=pid_dir, suffix=".tmp", prefix="server.pid.")
312312
try:
313-
with os.fdopen(fd, "w") as f:
313+
with os.fdopen(fd, "w", encoding="utf-8") as f:
314314
f.write(f"{os.getpid()}\n{port}\n")
315315
os.replace(tmp_path, PID_FILE)
316316
except BaseException:
@@ -326,7 +326,7 @@ def _read_pid():
326326
"""Read PID and port from file. Returns (pid, port) or (None, None)."""
327327
try:
328328
if os.path.exists(PID_FILE):
329-
with open(PID_FILE, "r") as f:
329+
with open(PID_FILE, "r", encoding="utf-8") as f:
330330
lines = f.read().strip().split("\n")
331331
pid = int(lines[0]) if lines else None
332332
port = int(lines[1]) if len(lines) > 1 else None

0 commit comments

Comments
 (0)