Przemyslaw Chachaj commited on
Commit
6b35c70
·
1 Parent(s): a98fb2d

Fix: remove diffusion; minimal torch; enable Habit Agent boot

Browse files
app.py CHANGED
@@ -8,6 +8,18 @@ import gradio as gr
8
  import yt_dlp
9
  from openai import OpenAI
10
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  # =====================================
12
  # CONFIG
13
  # =====================================
@@ -16,6 +28,9 @@ DATA_DIR = "/data/contracts"
16
  os.makedirs(DATA_DIR, exist_ok=True)
17
 
18
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
 
 
 
19
  client = OpenAI(api_key=OPENAI_API_KEY)
20
 
21
 
@@ -43,18 +58,13 @@ def load_contract(uid: str) -> Dict[str, Any]:
43
 
44
 
45
  # =====================================
46
- # CORE TOOLS
47
  # =====================================
48
 
49
- def transcribe_reel(link: str) -> str:
50
  """
51
- Download a reel/video from a link (e.g. Instagram) and transcribe audio using OpenAI Whisper.
52
- Returns plain text transcript.
53
  """
54
- if not OPENAI_API_KEY:
55
- raise ValueError("OPENAI_API_KEY not set in environment.")
56
-
57
- # Download audio/video with yt-dlp
58
  tmp_id = str(uuid.uuid4())
59
  out_tmpl = f"/tmp/{tmp_id}.%(ext)s"
60
 
@@ -67,28 +77,53 @@ def transcribe_reel(link: str) -> str:
67
  with yt_dlp.YoutubeDL(ydl_opts) as ydl:
68
  info = ydl.extract_info(link, download=True)
69
  filename = ydl.prepare_filename(info)
 
70
 
71
- # Transcription via OpenAI
72
- with open(filename, "rb") as f:
73
- transcript = client.audio.transcriptions.create(
74
- model="gpt-4o-transcribe",
75
- file=f
76
- )
77
 
78
- text = transcript.text if hasattr(transcript, "text") else str(transcript)
79
- return text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
 
81
 
 
 
 
 
82
  def extract_habits(transcript: str, category: str = "general") -> Dict[str, Any]:
83
  """
84
- Analyze transcript and produce:
85
  - goal (one sentence)
86
- - 7 simple actionable steps (list of strings)
87
  """
88
- if not OPENAI_API_KEY:
89
- raise ValueError("OPENAI_API_KEY not set in environment.")
90
-
91
- if not transcript or not transcript.strip():
92
  # Fallback generic plan
93
  return {
94
  "goal": f"Improve your {category} through one small action per day.",
@@ -129,7 +164,7 @@ def extract_habits(transcript: str, category: str = "general") -> Dict[str, Any]
129
 
130
  raw = resp.choices[0].message.content
131
 
132
- # Try to parse JSON even if model wrapped it in text/code fences
133
  raw = raw.strip()
134
  start = raw.find("{")
135
  end = raw.rfind("}")
@@ -139,12 +174,10 @@ def extract_habits(transcript: str, category: str = "general") -> Dict[str, Any]
139
 
140
  data = json.loads(json_str)
141
 
142
- # Minimal validation / normalization
143
  goal = data.get("goal") or f"Improve your {category}."
144
  steps = data.get("steps") or []
145
  steps = [s for s in steps if isinstance(s, str)]
146
  if len(steps) < 7:
147
- # pad if needed
148
  while len(steps) < 7:
149
  steps.append("Repeat a small action that moves you forward.")
150
  else:
@@ -153,6 +186,45 @@ def extract_habits(transcript: str, category: str = "general") -> Dict[str, Any]
153
  return {"goal": goal, "steps": steps}
154
 
155
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  def create_contract(
157
  link: str,
158
  category: str = "general",
@@ -160,25 +232,28 @@ def create_contract(
160
  auto_transcribe: bool = True
161
  ) -> Dict[str, Any]:
162
  """
163
- Create a habit contract from a video link.
164
 
165
- - Downloads + transcribes the video (if auto_transcribe=True)
166
- - Extracts goal + 7 habit steps
167
- - Saves contract to storage
168
- - Returns public contract summary
 
 
169
  """
170
  if uid is None or not uid.strip():
171
  uid = f"{category}_{uuid.uuid4().hex[:8]}"
172
 
173
  transcript = ""
174
  if auto_transcribe:
175
- try:
176
- transcript = transcribe_reel(link)
177
- except Exception as e:
178
- # Fallback: no transcript, but do not fail hard
179
- transcript = ""
180
- print(f"[WARN] Transcription failed: {e}")
181
 
 
182
  habits = extract_habits(transcript, category=category)
183
 
184
  contract = {
@@ -190,6 +265,10 @@ def create_contract(
190
  "current_index": 0,
191
  "history": [],
192
  "created_at": datetime.datetime.utcnow().isoformat() + "Z",
 
 
 
 
193
  }
194
 
195
  save_contract(contract)
@@ -201,12 +280,14 @@ def create_contract(
201
  "goal": contract["goal"],
202
  "first_step": contract["steps"][0],
203
  "total_steps": len(contract["steps"]),
 
 
204
  }
205
 
206
 
207
  def next_step(uid: str) -> Dict[str, Any]:
208
  """
209
- Return the next actionable step for given contract.
210
  """
211
  contract = load_contract(uid)
212
  idx = contract.get("current_index", 0)
@@ -218,6 +299,7 @@ def next_step(uid: str) -> Dict[str, Any]:
218
  "done": True,
219
  "message": "All steps completed.",
220
  "goal": contract.get("goal", ""),
 
221
  }
222
 
223
  return {
@@ -227,12 +309,14 @@ def next_step(uid: str) -> Dict[str, Any]:
227
  "step_index": idx,
228
  "step": steps[idx],
229
  "remaining_steps": len(steps) - idx,
 
 
230
  }
231
 
232
 
233
  def mark_done(uid: str) -> Dict[str, Any]:
234
  """
235
- Mark current step as done and advance to next one.
236
  """
237
  contract = load_contract(uid)
238
  idx = contract.get("current_index", 0)
@@ -253,7 +337,7 @@ def mark_done(uid: str) -> Dict[str, Any]:
253
 
254
  def report(uid: str) -> Dict[str, Any]:
255
  """
256
- Return a summary of contract progress.
257
  """
258
  contract = load_contract(uid)
259
  steps = contract.get("steps", [])
@@ -275,18 +359,23 @@ def report(uid: str) -> Dict[str, Any]:
275
  "progress_percent": round(progress, 1),
276
  "history": history,
277
  "current_step": steps[idx] if idx < total else None,
 
 
278
  }
279
 
280
 
281
  # =====================================
282
- # GRADIO UI + MCP EXPOSURE
283
  # =====================================
284
 
285
  with gr.Blocks() as demo:
286
  gr.Markdown(
287
  "# Behavior Changer – Habit Agent (MCP)\n"
288
- "Tworzy kontrakty nawyków z linków do Reels / video, "
289
- "rozbija na kroki i udostępnia jako MCP tools."
 
 
 
290
  )
291
 
292
  with gr.Tab("Create Contract"):
 
8
  import yt_dlp
9
  from openai import OpenAI
10
 
11
+ # opcjonalny fallback na lokalny Whisper z reels-agent
12
+ try:
13
+ from modules import transcriber as local_transcriber # transcribe_from_url(url)
14
+ except Exception:
15
+ local_transcriber = None
16
+
17
+ # opcjonalny summarizer z Nebiusa (reels-agent)
18
+ try:
19
+ from modules import summarizer as nebius_summarizer # summarize(text) -> {"insight","action"}
20
+ except Exception:
21
+ nebius_summarizer = None
22
+
23
  # =====================================
24
  # CONFIG
25
  # =====================================
 
28
  os.makedirs(DATA_DIR, exist_ok=True)
29
 
30
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
31
+ if not OPENAI_API_KEY:
32
+ raise RuntimeError("Brak OPENAI_API_KEY w secrets Spaces.")
33
+
34
  client = OpenAI(api_key=OPENAI_API_KEY)
35
 
36
 
 
58
 
59
 
60
  # =====================================
61
+ # TRANSKRYPCJA: OpenAI → fallback local Whisper
62
  # =====================================
63
 
64
+ def _download_video_with_ytdlp(link: str) -> str:
65
  """
66
+ Pobiera wideo/audio do /tmp i zwraca ścieżkę do pliku.
 
67
  """
 
 
 
 
68
  tmp_id = str(uuid.uuid4())
69
  out_tmpl = f"/tmp/{tmp_id}.%(ext)s"
70
 
 
77
  with yt_dlp.YoutubeDL(ydl_opts) as ydl:
78
  info = ydl.extract_info(link, download=True)
79
  filename = ydl.prepare_filename(info)
80
+ return filename
81
 
 
 
 
 
 
 
82
 
83
+ def transcribe_reel(link: str) -> str:
84
+ """
85
+ 1) Próbuje transkrypcji w OpenAI (gpt-4o-transcribe)
86
+ 2) Jeśli to padnie i mamy local_transcriber → używa Whisper lokalnie
87
+ 3) Jeśli wszystko padnie → zwraca 'TRANSCRIPTION_FAILED'
88
+ """
89
+ # --- krok 1: OpenAI audio.transcriptions ---
90
+ try:
91
+ filename = _download_video_with_ytdlp(link)
92
+ with open(filename, "rb") as f:
93
+ transcript = client.audio.transcriptions.create(
94
+ model="gpt-4o-transcribe",
95
+ file=f
96
+ )
97
+ text = transcript.text if hasattr(transcript, "text") else str(transcript)
98
+ if text and text.strip():
99
+ return text.strip()
100
+ except Exception as e:
101
+ print(f"[WARN] OpenAI audio transcription failed: {e}")
102
+
103
+ # --- krok 2: fallback – lokalny Whisper z reels-agent ---
104
+ if local_transcriber is not None and hasattr(local_transcriber, "transcribe_from_url"):
105
+ try:
106
+ text = local_transcriber.transcribe_from_url(link) # type: ignore[attr-defined]
107
+ if text and text.strip():
108
+ return text.strip()
109
+ except Exception as e:
110
+ print(f"[WARN] Local Whisper transcription failed: {e}")
111
+
112
+ # --- krok 3: totalna porażka ---
113
+ return "TRANSCRIPTION_FAILED"
114
 
115
 
116
+ # =====================================
117
+ # EXTRACT HABITS (OpenAI – 7 kroków)
118
+ # =====================================
119
+
120
  def extract_habits(transcript: str, category: str = "general") -> Dict[str, Any]:
121
  """
122
+ Analizuje transkrypcję i produkuje:
123
  - goal (one sentence)
124
+ - steps: 7 prostych kroków (list[str])
125
  """
126
+ if not transcript or transcript == "TRANSCRIPTION_FAILED":
 
 
 
127
  # Fallback generic plan
128
  return {
129
  "goal": f"Improve your {category} through one small action per day.",
 
164
 
165
  raw = resp.choices[0].message.content
166
 
167
+ # Try to parse JSON even if model wrapped it
168
  raw = raw.strip()
169
  start = raw.find("{")
170
  end = raw.rfind("}")
 
174
 
175
  data = json.loads(json_str)
176
 
 
177
  goal = data.get("goal") or f"Improve your {category}."
178
  steps = data.get("steps") or []
179
  steps = [s for s in steps if isinstance(s, str)]
180
  if len(steps) < 7:
 
181
  while len(steps) < 7:
182
  steps.append("Repeat a small action that moves you forward.")
183
  else:
 
186
  return {"goal": goal, "steps": steps}
187
 
188
 
189
+ # =====================================
190
+ # NEBIUS SUMMARY (INSIGHT + TODAY ACTION)
191
+ # =====================================
192
+
193
+ def summarize_with_nebius(transcript: str) -> Dict[str, Optional[str]]:
194
+ """
195
+ Używa modules/summarizer.py z reels-agent (Nebius).
196
+ Zwraca dict: {"insight": str | None, "action": str | None}
197
+ Jeśli coś padnie → zwraca domyślne fallbacki.
198
+ """
199
+ default_insight = "Mały krok dzisiaj zmienia trajektorię tygodnia."
200
+ default_action = "Obejrzyj materiał i zapisz 1 konkretną rzecz, którą dziś wdrożysz."
201
+
202
+ if not transcript or transcript == "TRANSCRIPTION_FAILED":
203
+ return {"insight": default_insight, "action": default_action}
204
+
205
+ if nebius_summarizer is None or not hasattr(nebius_summarizer, "summarize"):
206
+ return {"insight": default_insight, "action": default_action}
207
+
208
+ try:
209
+ res = nebius_summarizer.summarize(transcript) # type: ignore[attr-defined]
210
+ insight = res.get("insight") if isinstance(res, dict) else None
211
+ action = res.get("action") if isinstance(res, dict) else None
212
+ except Exception as e:
213
+ print(f"[WARN] Nebius summarizer failed: {e}")
214
+ insight, action = None, None
215
+
216
+ if not insight or not insight.strip():
217
+ insight = default_insight
218
+ if not action or not action.strip():
219
+ action = default_action
220
+
221
+ return {"insight": insight.strip(), "action": action.strip()}
222
+
223
+
224
+ # =====================================
225
+ # CONTRACT API (MCP tools)
226
+ # =====================================
227
+
228
  def create_contract(
229
  link: str,
230
  category: str = "general",
 
232
  auto_transcribe: bool = True
233
  ) -> Dict[str, Any]:
234
  """
235
+ Tworzy kontrakt nawyku z linku:
236
 
237
+ - transkrypcja (OpenAI → Whisper fallback)
238
+ - Nebius: INSIGHT + TODAY ACTION
239
+ - OpenAI: goal + 7 kroków
240
+ - zapis do /data/contracts
241
+
242
+ Zwraca skrócone podsumowanie kontraktu.
243
  """
244
  if uid is None or not uid.strip():
245
  uid = f"{category}_{uuid.uuid4().hex[:8]}"
246
 
247
  transcript = ""
248
  if auto_transcribe:
249
+ transcript = transcribe_reel(link)
250
+
251
+ # Nebius: INSIGHT + AKCJA DNIA
252
+ nebius_summary = summarize_with_nebius(transcript)
253
+ insight = nebius_summary["insight"]
254
+ today_action = nebius_summary["action"]
255
 
256
+ # OpenAI: goal + 7 kroków
257
  habits = extract_habits(transcript, category=category)
258
 
259
  contract = {
 
265
  "current_index": 0,
266
  "history": [],
267
  "created_at": datetime.datetime.utcnow().isoformat() + "Z",
268
+ "insight": insight,
269
+ "today_action": today_action,
270
+ # trzymamy tylko preview, żeby nie zabić storage
271
+ "transcript_preview": None if not transcript else transcript[:2000],
272
  }
273
 
274
  save_contract(contract)
 
280
  "goal": contract["goal"],
281
  "first_step": contract["steps"][0],
282
  "total_steps": len(contract["steps"]),
283
+ "insight": insight,
284
+ "today_action": today_action,
285
  }
286
 
287
 
288
  def next_step(uid: str) -> Dict[str, Any]:
289
  """
290
+ Zwraca kolejny krok dla danego kontraktu.
291
  """
292
  contract = load_contract(uid)
293
  idx = contract.get("current_index", 0)
 
299
  "done": True,
300
  "message": "All steps completed.",
301
  "goal": contract.get("goal", ""),
302
+ "insight": contract.get("insight", ""),
303
  }
304
 
305
  return {
 
309
  "step_index": idx,
310
  "step": steps[idx],
311
  "remaining_steps": len(steps) - idx,
312
+ "insight": contract.get("insight", ""),
313
+ "today_action": contract.get("today_action", ""),
314
  }
315
 
316
 
317
  def mark_done(uid: str) -> Dict[str, Any]:
318
  """
319
+ Oznacza bieżący krok jako wykonany i przechodzi do kolejnego.
320
  """
321
  contract = load_contract(uid)
322
  idx = contract.get("current_index", 0)
 
337
 
338
  def report(uid: str) -> Dict[str, Any]:
339
  """
340
+ Zwraca pełny raport z progresu.
341
  """
342
  contract = load_contract(uid)
343
  steps = contract.get("steps", [])
 
359
  "progress_percent": round(progress, 1),
360
  "history": history,
361
  "current_step": steps[idx] if idx < total else None,
362
+ "insight": contract.get("insight", ""),
363
+ "today_action": contract.get("today_action", ""),
364
  }
365
 
366
 
367
  # =====================================
368
+ # GRADIO UI + MCP
369
  # =====================================
370
 
371
  with gr.Blocks() as demo:
372
  gr.Markdown(
373
  "# Behavior Changer – Habit Agent (MCP)\n"
374
+ "Kontrakty nawyków z linków do Reels / video:\n"
375
+ "- transkrypcja (OpenAI → Whisper fallback)\n"
376
+ "- Nebius INSIGHT + mikro-akcja\n"
377
+ "- 7 kroków z OpenAI\n"
378
+ "- MCP tools do użycia z Claude/Cursor."
379
  )
380
 
381
  with gr.Tab("Create Contract"):
modal/sms_webhook.py ADDED
File without changes
modules/__pycache__/summarizer.cpython-312.pyc ADDED
Binary file (2.35 kB). View file
 
modules/__pycache__/transcriber.cpython-312.pyc ADDED
Binary file (2.65 kB). View file
 
modules/plan_generator.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # engine/plan_generator.py
2
+ import os
3
+ from openai import OpenAI
4
+
5
+ client = OpenAI(
6
+ base_url="https://api.tokenfactory.nebius.com/v1/",
7
+ api_key=os.getenv("NEBIUS_API_KEY")
8
+ )
9
+
10
+ def generate_plan(insight:str, category:str, steps:int=10):
11
+ prompt = f"""
12
+ Stwórz serię {steps} ultra-małych kroków nawykowych.
13
+ Kroki muszą być tak małe, że wykonanie ich zajmuje <2 minuty.
14
+
15
+ FORMAT WYJŚCIA (dokładny):
16
+ 1. ...
17
+ 2. ...
18
+ 3. ...
19
+
20
+ INSIGHT użytkownika: {insight}
21
+ Kategoria: {category}
22
+ """
23
+
24
+ resp = client.chat.completions.create(
25
+ model="deepseek-ai/DeepSeek-R1-0528",
26
+ messages=[{"role":"user","content":prompt}]
27
+ ).choices[0].message.content
28
+
29
+ return [x[3:] for x in resp.split("\n") if x[:2].isdigit()]
modules/session.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # engine/session.py
2
+ import json, os, time
3
+
4
+ PATH="state/"
5
+
6
+ def load(uid):
7
+ f = PATH+uid+".json"
8
+ return json.load(open(f)) if os.path.exists(f) else {
9
+ "insight":None,
10
+ "category":None,
11
+ "plan":[],
12
+ "done":[],
13
+ "created":time.time()
14
+ }
15
+
16
+ def save(uid,data):
17
+ os.makedirs(PATH,exist_ok=True)
18
+ json.dump(data,open(PATH+uid+".json","w"),indent=2)
19
+
20
+ def next_step(uid):
21
+ s=load(uid)
22
+ remaining=[x for x in s["plan"] if x not in s["done"]]
23
+ return remaining[0] if remaining else None
24
+
25
+ def mark_done(uid,step):
26
+ s=load(uid)
27
+ s["done"].append(step)
28
+ save(uid,s)
modules/sms_logic.py ADDED
File without changes
modules/summarizer.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from openai import OpenAI
3
+
4
+ client = OpenAI(
5
+ base_url="https://api.tokenfactory.nebius.com/v1/",
6
+ api_key=os.environ["NEBIUS_API_KEY"]
7
+ )
8
+
9
+ def summarize(text:str) -> dict:
10
+ prompt = f"""
11
+ Streść najważniejszą myśl i podaj jedną mikro-akcję na dziś dla użytkownika.
12
+ Ultra krótko. Maks 2 zdania. Po polsku.
13
+
14
+ Format odpowiedzi bez etykiet:
15
+
16
+ INSIGHT: <jedno zdanie sedna, maks 140 znaków>
17
+ AKCJA: <jedna mikroczynność do wykonania dziś na podsstawie transkrypcji, maks 120 znaków>
18
+
19
+ Transkrypcja:
20
+ {text}
21
+ """
22
+
23
+ r = client.chat.completions.create(
24
+ model="moonshotai/Kimi-K2-Instruct",
25
+ messages=[{"role":"user","content":prompt}],
26
+ max_tokens=200
27
+ ).choices[0].message.content.strip()
28
+
29
+ # Wyciągamy dwie rzeczy nawet bez idealnego formatu
30
+ import re
31
+ insight_match = re.search(r'INSIGHT:(.*)', r, re.IGNORECASE)
32
+ action_match = re.search(r'AKCJA:(.*)', r, re.IGNORECASE)
33
+
34
+ insight = insight_match.group(1).strip()[:140] if insight_match else None
35
+ action = action_match.group(1).strip()[:120] if action_match else None
36
+
37
+ # jeśli brak akcji → próbujemy z heurystyk
38
+ if not action:
39
+ # wyłuskanie najkrótszego zdania z tekstu
40
+ parts = [p.strip() for p in r.split("\n") if len(p.strip())<160]
41
+ action = parts[-1] if len(parts)>1 else "Zrób najmniejszą możliwą wersję działania."
42
+
43
+ if not insight:
44
+ insight = "Mały krok dzisiaj zmieni trajektorię tygodnia."
45
+
46
+ return {"insight": insight, "action": action}
modules/transcriber.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ # 🔥 ładowanie modelu tylko raz → x3 szybciej przy wielu SMS
4
+ # model = whisper.load_model("base")
5
+
6
+ # def download_audio(url, out="temp.mp3"):
7
+ # cmd = [
8
+ # "yt-dlp",
9
+ # "-x", "--audio-format", "mp3",
10
+ # "--ffmpeg-location", "/usr/bin/ffmpeg",
11
+ # "-o", out,
12
+ # url
13
+ # ]
14
+ # try:
15
+ # subprocess.run(cmd, check=True, timeout=30) # timeout = mniej zwisów
16
+ # return out
17
+ # except Exception as e:
18
+ # print("AUDIO ERROR:", e)
19
+ # return None
20
+ import os
21
+ import shutil
22
+ import subprocess
23
+ import whisper
24
+
25
+ TEMP_AUDIO = "temp.mp3"
26
+ _model = whisper.load_model("base") # cache modelu, żeby nie ładować przy każdym wywołaniu
27
+
28
+
29
+ def _get_ffmpeg_path() -> str:
30
+ """
31
+ Zwraca ścieżkę do ffmpeg albo rzuca czytelny wyjątek,
32
+ jeśli ffmpeg nie jest zainstalowany.
33
+ """
34
+ path = shutil.which("ffmpeg")
35
+ if path is None:
36
+ raise RuntimeError(
37
+ "ffmpeg nie jest dostępny w PATH.\n"
38
+ "Na Macu zainstaluj: brew install ffmpeg"
39
+ )
40
+ return path
41
+
42
+
43
+ def download_audio(url: str, out: str = TEMP_AUDIO) -> str:
44
+ """
45
+ Pobiera audio z URL do pliku MP3.
46
+ Rzuca RuntimeError jeśli yt-dlp padnie.
47
+ """
48
+ if os.path.exists(out):
49
+ os.remove(out)
50
+
51
+ ffmpeg_path = _get_ffmpeg_path()
52
+
53
+ cmd = [
54
+ "yt-dlp",
55
+ "-x",
56
+ "--audio-format", "mp3",
57
+ "--ffmpeg-location", ffmpeg_path,
58
+ "-o", out,
59
+ url,
60
+ ]
61
+
62
+ try:
63
+ subprocess.run(cmd, check=True)
64
+ except subprocess.CalledProcessError as e:
65
+ raise RuntimeError(f"yt-dlp nie udało się pobrać audio: {e}") from e
66
+
67
+ if not os.path.exists(out):
68
+ raise RuntimeError("Pobieranie się udało, ale plik audio nie powstał.")
69
+
70
+ return out
71
+
72
+
73
+ def _get_model():
74
+ global _model
75
+ if _model is None:
76
+ # możesz wrócić do "small", jeśli chcesz szybciej / taniej
77
+ _model = whisper.load_model("base")
78
+ return _model
79
+
80
+
81
+ def transcribe_from_url(url):
82
+ audio = download_audio(url)
83
+ model = _get_model()
84
+ if not audio: # 🔥 nowy soft fallback zamiast crasha
85
+ return "TRANSCRIPTION_FAILED"
86
+
87
+ try:
88
+ result = model.transcribe(audio)
89
+ return result.get("text", "")
90
+ except Exception as e:
91
+ print("TRANSCRIBE ERROR:", e)
92
+ return "TRANSCRIPTION_FAILED"
packages ADDED
@@ -0,0 +1 @@
 
 
1
+ ffmpeg
requirements.txt CHANGED
@@ -1,4 +1,7 @@
1
  gradio[mcp]
2
  yt-dlp
3
  openai>=1.40.0
4
- requests
 
 
 
 
1
  gradio[mcp]
2
  yt-dlp
3
  openai>=1.40.0
4
+ requests
5
+ openai-whisper
6
+ tiktoken
7
+ torch==2.1.0 --extra-index-url https://download.pytorch.org/whl/cpu
storage/.keep ADDED
File without changes