MichaelChou0806 commited on
Commit
1ebe0ef
ยท
verified ยท
1 Parent(s): 20943a5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +97 -66
app.py CHANGED
@@ -2,6 +2,8 @@ import os, shutil, base64, uuid, mimetypes, json
2
  from pydub import AudioSegment
3
  from openai import OpenAI
4
  import gradio as gr
 
 
5
 
6
  # ====== ๅŸบๆœฌ่จญๅฎš ======
7
  PASSWORD = os.getenv("APP_PASSWORD", "chou")
@@ -26,87 +28,75 @@ MIME_EXT = {
26
  }
27
 
28
  def _dataurl_to_file(data_url: str, orig_name: str | None = None) -> str:
29
- # data_url: "data:audio/mp4;base64,AAAA..."
30
  try:
31
  header, b64 = data_url.split(",", 1)
32
  except ValueError:
33
- raise ValueError("data URL ๆ ผๅผ้Œฏ่ชค(็ผบๅฐ‘้€—่™Ÿ)ใ€‚")
34
- # ๅ– MIME
35
  mime = header.split(";")[0].split(":", 1)[-1].strip()
36
  ext = MIME_EXT.get(mime) or (mimetypes.guess_extension(mime) or "m4a").lstrip(".")
37
- # ่‡จๆ™‚ๆช”ๅ
38
  fname = orig_name if (orig_name and "." in orig_name) else f"upload_{uuid.uuid4().hex}.{ext}"
39
  with open(fname, "wb") as f:
40
  f.write(base64.b64decode(b64))
41
- print(f"โœ… ๅทฒๅพž data URL ็”Ÿๆˆๆช”ๆกˆ:{fname}, ๅคงๅฐ:{os.path.getsize(fname)} bytes")
42
  return fname
43
 
44
  def _extract_effective_path(file_obj) -> str:
45
- """
46
- ๅพž Gradio ็š„ File ่ผธๅ…ฅไธญ,ๅพ—ๅˆฐ็œŸๆญฃๅญ˜ๅœจ็š„ๆช”ๆกˆ่ทฏๅพ‘ใ€‚
47
- ๐Ÿ”ด ๅŠ ๅผท็‰ˆ:่ฉณ็ดฐ่จ˜้Œ„ๆ‰€ๆœ‰ๅ˜—่ฉฆ้Ž็จ‹
48
- """
49
- print(f"\n๐Ÿ” ้–‹ๅง‹่งฃๆžๆช”ๆกˆ...")
50
- print(f"๐Ÿ“ฆ ๆ”ถๅˆฐ็š„้กžๅž‹: {type(file_obj)}")
51
- print(f"๐Ÿ“ฆ ๆ”ถๅˆฐ็š„ๅ…งๅฎน: {file_obj}")
52
 
53
- # ๆƒ…ๆณ A:ๅญ—ไธฒ(ๅฏ่ƒฝๆ˜ฏ่ทฏๅพ‘ๆˆ– data:URL)
54
  if isinstance(file_obj, str):
55
  s = file_obj.strip().strip('"')
56
- print(f" โ†’ ๆƒ…ๆณA:ๅญ—ไธฒๆจกๅผ")
57
  if s.startswith("data:"):
58
- print(f" โ†’ ๅตๆธฌๅˆฐ data URL,้•ทๅบฆ:{len(s)}")
59
  return _dataurl_to_file(s, None)
60
  if os.path.isfile(s):
61
- print(f" โ†’ ๆ‰พๅˆฐๆœ‰ๆ•ˆ่ทฏๅพ‘:{s}")
62
  return s
63
- print(f" โ†’ ๅญ—ไธฒ็„กๆ•ˆ,็นผ็บŒๅ˜—่ฉฆ...")
64
 
65
- # ๆƒ…ๆณ B:dict
66
  if isinstance(file_obj, dict):
67
- print(f" โ†’ ๆƒ…ๆณB:ๅญ—ๅ…ธๆจกๅผ")
68
- print(f" โ†’ ๅญ—ๅ…ธ็š„ keys: {list(file_obj.keys())}")
69
 
70
- # ๐Ÿ”ด ๅ„ชๅ…ˆๆชขๆŸฅ data(Base64 ๆจกๅผ)
71
  data = file_obj.get("data")
72
  if isinstance(data, str) and data.startswith("data:"):
73
- print(f" โ†’ โœ… ๆ‰พๅˆฐ data URL! ้•ทๅบฆ:{len(data)}")
74
  orig_name = file_obj.get("orig_name")
75
- print(f" โ†’ ๆช”ๅ:{orig_name}")
76
  return _dataurl_to_file(data, orig_name)
77
 
78
- # ๅ†ๆชขๆŸฅ path
79
  p = str(file_obj.get("path") or "").strip().strip('"')
80
  if p and os.path.isfile(p):
81
- print(f" โ†’ ๆ‰พๅˆฐ path:{p}")
82
  return p
83
 
84
- # ๆชขๆŸฅ url
85
  u = str(file_obj.get("url") or "").strip().strip('"')
86
  if u and os.path.isfile(u):
87
- print(f" โ†’ ๆ‰พๅˆฐ url:{u}")
88
  return u
89
-
90
- print(f" โ†’ โŒ ๅญ—ๅ…ธๅ…งๆฒ’ๆœ‰ๆœ‰ๆ•ˆ็š„ data/path/url")
91
 
92
- # ๆƒ…ๆณ C:็‰ฉไปถ
93
- print(f" โ†’ ๆƒ…ๆณC:็‰ฉไปถๆจกๅผ")
94
  for attr in ("name", "path"):
95
  p = getattr(file_obj, attr, None)
96
  if isinstance(p, str):
97
  s = p.strip().strip('"')
98
  if os.path.isfile(s):
99
- print(f" โ†’ ๆ‰พๅˆฐ็‰ฉไปถๅฑฌๆ€ง {attr}:{s}")
100
  return s
101
 
102
- # ็‰ฉไปถไธŠ็š„ data:URL
103
  data = getattr(file_obj, "data", None)
104
  if isinstance(data, str) and data.startswith("data:"):
105
- print(f" โ†’ ๆ‰พๅˆฐ็‰ฉไปถ็š„ data URL")
106
  return _dataurl_to_file(data, getattr(file_obj, "orig_name", None))
107
 
108
- print(f" โ†’ โŒ ๆ‰€ๆœ‰ๆ–นๆณ•้ƒฝๅคฑๆ•—!")
109
- raise FileNotFoundError(f"็„กๆณ•่งฃๆžไธŠๅ‚ณๆช”ๆกˆใ€‚ๆ”ถๅˆฐ็š„้กžๅž‹:{type(file_obj)}, ๅ…งๅฎน:{str(file_obj)[:200]}")
110
 
111
  # ====== ๅˆ†ๆฎต่™•็† ======
112
  def split_audio(path):
@@ -125,14 +115,13 @@ def split_audio(path):
125
 
126
  # ====== ่ฝ‰้Œ„ๆ ธๅฟƒ ======
127
  def transcribe_core(path, model="whisper-1"):
128
- # iPhone LINE ๅธธ่ฆ‹:mp4(ๅ…ถๅฏฆๆ˜ฏ้Ÿณ่จŠๅฎนๅ™จ)
129
  if path.lower().endswith(".mp4"):
130
  fixed = path[:-4] + ".m4a"
131
  try:
132
  shutil.copy(path, fixed)
133
  path = fixed
134
  except Exception as e:
135
- print(f"โš ๏ธ mp4โ†’m4a ๅคฑๆ•—: {e}")
136
 
137
  chunks = split_audio(path)
138
  raw = []
@@ -146,7 +135,6 @@ def transcribe_core(path, model="whisper-1"):
146
  raw.append(txt)
147
  raw_txt = "\n".join(raw)
148
 
149
- # ็ฐก่ฝ‰็น(ไธๆ„่ญฏ)
150
  conv = client.chat.completions.create(
151
  model="gpt-4o-mini",
152
  messages=[
@@ -157,7 +145,6 @@ def transcribe_core(path, model="whisper-1"):
157
  )
158
  trad = conv.choices[0].message.content.strip()
159
 
160
- # ๆ‘˜่ฆ(ๅ…งๅฎนๅคšโ†’ๆขๅˆ—;ๅ…งๅฎนๅฐ‘โ†’ไธ€ๅฅ่ฉฑ)
161
  summ = client.chat.completions.create(
162
  model="gpt-4o-mini",
163
  messages=[
@@ -168,55 +155,99 @@ def transcribe_core(path, model="whisper-1"):
168
  )
169
  return trad, summ.choices[0].message.content.strip()
170
 
171
- # ====== ๅฐๅค–ๅ‡ฝๅผ(UI / API ๅ…ฑ็”จ) ======
172
- def transcribe(password, file):
173
  print("\n" + "="*50)
174
- print("๐ŸŽฏ ๆ–ฐ็š„่ฝ‰้Œ„่ซ‹ๆฑ‚")
175
- print(f"๐Ÿ”‘ ๅฏ†็ขผ: {password[:2]}*** (้•ทๅบฆ:{len(password)})")
176
- print(f"๐Ÿ“ ๆช”ๆกˆ: {type(file)}")
177
  print("="*50)
178
 
179
  if password.strip() != PASSWORD:
180
- print("โŒ ๅฏ†็ขผ้ฉ—่ญ‰ๅคฑๆ•—")
181
  return "โŒ Password incorrect", "", ""
182
-
183
  if not file:
184
- print("โŒ ๆœชๆ”ถๅˆฐๆช”ๆกˆ")
185
  return "โš ๏ธ No file uploaded", "", ""
186
 
187
  try:
188
  path = _extract_effective_path(file)
189
- print(f"โœ… ๆˆๅŠŸ่งฃๆžๆช”ๆกˆ:{path}")
190
- print(f"๐Ÿ“Š ๆช”ๆกˆๅคงๅฐ:{os.path.getsize(path)} bytes")
191
- except Exception as e:
192
- import traceback
193
- error_msg = traceback.format_exc()
194
- print(f"โŒ ๆช”ๆกˆ่งฃๆžๅคฑๆ•—:\n{error_msg}")
195
- return f"โŒ File parsing failed: {e}", "", ""
196
-
197
- try:
198
  text, summary = transcribe_core(path)
199
- print("โœ… ่ฝ‰้Œ„ๅฎŒๆˆ")
200
  return "โœ… Transcription completed", text, summary
201
  except Exception as e:
202
  import traceback
203
- error_msg = traceback.format_exc()
204
- print(f"โŒ ่ฝ‰้Œ„ๅคฑๆ•—:\n{error_msg}")
205
- return f"โŒ Transcription failed: {e}", "", ""
206
 
207
  # ====== Gradio UI ======
208
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
209
- gr.Markdown("## ๐ŸŽง LINE Audio Transcription & Summary (Enhanced Debug)")
210
  pw = gr.Textbox(label="Password", type="password")
211
  f = gr.File(label="Upload Audio File")
212
  run = gr.Button("Start Transcription ๐Ÿš€")
213
  s = gr.Textbox(label="Status", interactive=False)
214
  t = gr.Textbox(label="Transcription Result", lines=10)
215
  su = gr.Textbox(label="AI Summary", lines=8)
216
- # ้—œ้ต:queue=False ่ฎ“ API ็›ดๆŽฅๅ›žๆ‡‰
217
- run.click(transcribe, [pw, f], [s, t, su], queue=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
 
219
- app = demo
 
220
 
221
  if __name__ == "__main__":
222
- demo.launch(server_name="0.0.0.0", server_port=7860)
 
 
2
  from pydub import AudioSegment
3
  from openai import OpenAI
4
  import gradio as gr
5
+ from fastapi import FastAPI, Request, HTTPException
6
+ from fastapi.responses import JSONResponse
7
 
8
  # ====== ๅŸบๆœฌ่จญๅฎš ======
9
  PASSWORD = os.getenv("APP_PASSWORD", "chou")
 
28
  }
29
 
30
  def _dataurl_to_file(data_url: str, orig_name: str | None = None) -> str:
 
31
  try:
32
  header, b64 = data_url.split(",", 1)
33
  except ValueError:
34
+ raise ValueError("data URL format error (missing comma).")
 
35
  mime = header.split(";")[0].split(":", 1)[-1].strip()
36
  ext = MIME_EXT.get(mime) or (mimetypes.guess_extension(mime) or "m4a").lstrip(".")
 
37
  fname = orig_name if (orig_name and "." in orig_name) else f"upload_{uuid.uuid4().hex}.{ext}"
38
  with open(fname, "wb") as f:
39
  f.write(base64.b64decode(b64))
40
+ print(f"โœ… Created file from data URL: {fname}, size: {os.path.getsize(fname)} bytes")
41
  return fname
42
 
43
  def _extract_effective_path(file_obj) -> str:
44
+ """ๅพžๅ„็จฎๆ ผๅผไธญๆๅ–ๆœ‰ๆ•ˆๆช”ๆกˆ่ทฏๅพ‘"""
45
+ print(f"\n๐Ÿ” Starting file parsing...")
46
+ print(f"๐Ÿ“ฆ Received type: {type(file_obj)}")
47
+ print(f"๐Ÿ“ฆ Content: {str(file_obj)[:200]}...")
 
 
 
48
 
49
+ # ๅญ—ไธฒๆจกๅผ
50
  if isinstance(file_obj, str):
51
  s = file_obj.strip().strip('"')
52
+ print(f" โ†’ Mode A: String")
53
  if s.startswith("data:"):
54
+ print(f" โ†’ Detected data URL, length: {len(s)}")
55
  return _dataurl_to_file(s, None)
56
  if os.path.isfile(s):
57
+ print(f" โ†’ Found valid path: {s}")
58
  return s
 
59
 
60
+ # ๅญ—ๅ…ธๆจกๅผ
61
  if isinstance(file_obj, dict):
62
+ print(f" โ†’ Mode B: Dictionary")
63
+ print(f" โ†’ Keys: {list(file_obj.keys())}")
64
 
65
+ # ๅ„ชๅ…ˆ data URL
66
  data = file_obj.get("data")
67
  if isinstance(data, str) and data.startswith("data:"):
68
+ print(f" โ†’ โœ… Found data URL! Length: {len(data)}")
69
  orig_name = file_obj.get("orig_name")
 
70
  return _dataurl_to_file(data, orig_name)
71
 
72
+ # path
73
  p = str(file_obj.get("path") or "").strip().strip('"')
74
  if p and os.path.isfile(p):
75
+ print(f" โ†’ Found path: {p}")
76
  return p
77
 
78
+ # url
79
  u = str(file_obj.get("url") or "").strip().strip('"')
80
  if u and os.path.isfile(u):
81
+ print(f" โ†’ Found url: {u}")
82
  return u
 
 
83
 
84
+ # ็‰ฉไปถๆจกๅผ
85
+ print(f" โ†’ Mode C: Object")
86
  for attr in ("name", "path"):
87
  p = getattr(file_obj, attr, None)
88
  if isinstance(p, str):
89
  s = p.strip().strip('"')
90
  if os.path.isfile(s):
91
+ print(f" โ†’ Found object attr {attr}: {s}")
92
  return s
93
 
 
94
  data = getattr(file_obj, "data", None)
95
  if isinstance(data, str) and data.startswith("data:"):
96
+ print(f" โ†’ Found object data URL")
97
  return _dataurl_to_file(data, getattr(file_obj, "orig_name", None))
98
 
99
+ raise FileNotFoundError(f"Cannot parse uploaded file. Type: {type(file_obj)}")
 
100
 
101
  # ====== ๅˆ†ๆฎต่™•็† ======
102
  def split_audio(path):
 
115
 
116
  # ====== ่ฝ‰้Œ„ๆ ธๅฟƒ ======
117
  def transcribe_core(path, model="whisper-1"):
 
118
  if path.lower().endswith(".mp4"):
119
  fixed = path[:-4] + ".m4a"
120
  try:
121
  shutil.copy(path, fixed)
122
  path = fixed
123
  except Exception as e:
124
+ print(f"โš ๏ธ mp4โ†’m4a failed: {e}")
125
 
126
  chunks = split_audio(path)
127
  raw = []
 
135
  raw.append(txt)
136
  raw_txt = "\n".join(raw)
137
 
 
138
  conv = client.chat.completions.create(
139
  model="gpt-4o-mini",
140
  messages=[
 
145
  )
146
  trad = conv.choices[0].message.content.strip()
147
 
 
148
  summ = client.chat.completions.create(
149
  model="gpt-4o-mini",
150
  messages=[
 
155
  )
156
  return trad, summ.choices[0].message.content.strip()
157
 
158
+ # ====== Gradio UI ๅ‡ฝๅผ ======
159
+ def transcribe_ui(password, file):
160
  print("\n" + "="*50)
161
+ print("๐ŸŽฏ New transcription request (UI)")
162
+ print(f"๐Ÿ”‘ Password: {password[:2]}*** (length: {len(password)})")
163
+ print(f"๐Ÿ“ File: {type(file)}")
164
  print("="*50)
165
 
166
  if password.strip() != PASSWORD:
 
167
  return "โŒ Password incorrect", "", ""
 
168
  if not file:
 
169
  return "โš ๏ธ No file uploaded", "", ""
170
 
171
  try:
172
  path = _extract_effective_path(file)
 
 
 
 
 
 
 
 
 
173
  text, summary = transcribe_core(path)
 
174
  return "โœ… Transcription completed", text, summary
175
  except Exception as e:
176
  import traceback
177
+ print(traceback.format_exc())
178
+ return f"โŒ Error: {e}", "", ""
 
179
 
180
  # ====== Gradio UI ======
181
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
182
+ gr.Markdown("## ๐ŸŽง LINE Audio Transcription & Summary")
183
  pw = gr.Textbox(label="Password", type="password")
184
  f = gr.File(label="Upload Audio File")
185
  run = gr.Button("Start Transcription ๐Ÿš€")
186
  s = gr.Textbox(label="Status", interactive=False)
187
  t = gr.Textbox(label="Transcription Result", lines=10)
188
  su = gr.Textbox(label="AI Summary", lines=8)
189
+ run.click(transcribe_ui, [pw, f], [s, t, su], queue=False)
190
+
191
+ # ====== ๐Ÿ”ด ๆ–ฐๅขž:่‡ช่จ‚ API ็ซฏ้ปž(็นž้Ž Gradio validation) ======
192
+ app = FastAPI()
193
+
194
+ @app.post("/api/transcribe")
195
+ async def transcribe_api(request: Request):
196
+ """
197
+ ็›ดๆŽฅๆŽฅๅ— Base64 data URL ็š„่‡ช่จ‚ API
198
+
199
+ ่ซ‹ๆฑ‚ๆ ผๅผ:
200
+ {
201
+ "password": "chou",
202
+ "file": {
203
+ "data": "data:audio/m4a;base64,UklGR...",
204
+ "orig_name": "recording.m4a"
205
+ }
206
+ }
207
+ """
208
+ try:
209
+ body = await request.json()
210
+ print("\n" + "="*50)
211
+ print("๐ŸŽฏ New transcription request (Custom API)")
212
+ print(f"๐Ÿ“ฆ Request body keys: {list(body.keys())}")
213
+ print("="*50)
214
+
215
+ password = body.get("password", "")
216
+ if password.strip() != PASSWORD:
217
+ return JSONResponse(
218
+ status_code=401,
219
+ content={"error": "Password incorrect"}
220
+ )
221
+
222
+ file_obj = body.get("file")
223
+ if not file_obj:
224
+ return JSONResponse(
225
+ status_code=400,
226
+ content={"error": "No file provided"}
227
+ )
228
+
229
+ # ็›ดๆŽฅ่™•็† Base64
230
+ path = _extract_effective_path(file_obj)
231
+ text, summary = transcribe_core(path)
232
+
233
+ return JSONResponse(content={
234
+ "status": "success",
235
+ "transcription": text,
236
+ "summary": summary
237
+ })
238
+
239
+ except Exception as e:
240
+ import traceback
241
+ error_detail = traceback.format_exc()
242
+ print(f"โŒ API Error:\n{error_detail}")
243
+ return JSONResponse(
244
+ status_code=500,
245
+ content={"error": str(e), "detail": error_detail}
246
+ )
247
 
248
+ # ๆŽ›่ผ‰ Gradio ๅˆฐ FastAPI
249
+ app = gr.mount_gradio_app(app, demo, path="/")
250
 
251
  if __name__ == "__main__":
252
+ import uvicorn
253
+ uvicorn.run(app, host="0.0.0.0", port=7860)