gfathertech commited on
Commit
e2a54d1
·
verified ·
1 Parent(s): 78bcd1a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +88 -92
app.py CHANGED
@@ -2,6 +2,7 @@ import os
2
  import shutil
3
  import subprocess
4
  import traceback
 
5
  from uuid import uuid4
6
  from fastapi import FastAPI, UploadFile, HTTPException
7
  from markitdown import MarkItDown
@@ -11,14 +12,14 @@ import pytesseract
11
  from pdf2image import convert_from_path
12
  import riva.client as rcli
13
 
14
- app = FastAPI(title="Free MarkItDown Service with Official NVIDIA Riva ASR")
15
  md = MarkItDown()
16
 
17
  IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".bmp", ".tiff", ".webp"}
18
  AUDIO_EXTENSIONS = {".mp3", ".wav", ".m4a", ".ogg", ".flac", ".opus"}
19
  LEGACY_OFFICE_EXTENSIONS = {".doc", ".xls", ".ppt", ".rtf"}
20
 
21
- # Official NVIDIA ASR Configuration
22
  NVIDIA_STAGE_SERVER = "grpc.nvcf.nvidia.com:443"
23
  NVIDIA_PARAKEET_FUNCTION_ID = "b0e8b4a5-217c-40b7-9b96-17d84e666317"
24
 
@@ -26,7 +27,7 @@ def get_nvidia_api_keys():
26
  """Dynamically collects NVIDIA API keys from environment variables."""
27
  keys = []
28
 
29
- # Check for numbered keys: NVIDIA_API_KEY_1, NVIDIA_API_KEY_2, etc.
30
  index = 1
31
  while True:
32
  key = os.getenv(f"NVIDIA_API_KEY_{index}")
@@ -36,7 +37,7 @@ def get_nvidia_api_keys():
36
  else:
37
  break
38
 
39
- # Fallback to single NVIDIA_API_KEY
40
  if not keys:
41
  single_key = os.getenv("NVIDIA_API_KEY")
42
  if single_key and single_key.strip():
@@ -45,77 +46,83 @@ def get_nvidia_api_keys():
45
  return keys
46
 
47
  def transcribe_with_nvidia_riva(audio_path: str) -> str:
48
- """Transcribes audio using NVIDIA's standard gRPC client with multi-key rotation."""
49
  api_keys = get_nvidia_api_keys()
50
 
51
  if not api_keys:
52
  return "Audio Error: No NVIDIA API keys (NVIDIA_API_KEY_1, NVIDIA_API_KEY_2...) set in environment variables."
53
 
54
- wav_path = audio_path
55
- converted_wav = None
56
 
57
- # Convert audio to mono 16kHz WAV format (required by Riva)
58
- file_ext = os.path.splitext(audio_path)[1].lower()
59
- if file_ext != ".wav":
60
- converted_wav = f"/tmp/{uuid4()}.wav"
61
- try:
62
- subprocess.run(
63
- ["ffmpeg", "-y", "-i", audio_path, "-ac", "1", "-ar", "16000", converted_wav],
64
- check=True,
65
- stdout=subprocess.DEVNULL,
66
- stderr=subprocess.DEVNULL
67
- )
68
- wav_path = converted_wav
69
- except Exception as e:
70
- return f"Audio Conversion Error (FFmpeg): {str(e)}"
 
 
 
71
 
72
  last_error = ""
 
73
 
74
- # Rotate through keys if an error/rate limit occurs
75
- for idx, key in enumerate(api_keys, start=1):
76
- try:
77
- # Construct standard NVIDIA gRPC metadata headers
78
- auth = rcli.Auth(
79
- use_ssl=True,
80
- uri=NVIDIA_STAGE_SERVER,
81
- metadata_args=[
82
- ("authorization", f"Bearer {key}"),
83
- ("function-id", NVIDIA_PARAKEET_FUNCTION_ID)
84
- ]
85
- )
86
-
87
- asr_service = rcli.ASRService(auth)
88
- config = rcli.RecognitionConfig(
89
- encoding=rcli.AudioEncoding.LINEAR_PCM,
90
- sample_rate_hertz=16000,
91
- language_code="en-US",
92
- max_alternatives=1
93
- )
94
-
95
- with open(wav_path, "rb") as fh:
96
- audio_data = fh.read()
97
-
98
- response = asr_service.offline_recognize(audio_data, config)
99
-
100
- # Process returned transcript
101
- results = response.results
102
- transcripts = [res.alternatives[0].transcript for res in results if res.alternatives]
103
- text = " ".join(transcripts).strip()
104
-
105
- if converted_wav and os.path.exists(converted_wav):
106
- os.remove(converted_wav)
107
-
108
- return f"## Audio Transcription\n\n{text}" if text else "Audio processed, but no speech was detected."
109
-
110
- except Exception as e:
111
- last_error = str(e)
112
- print(f"Key #{idx} failed with error: {last_error}. Retrying with next key...")
113
- continue
114
 
115
- if converted_wav and os.path.exists(converted_wav):
116
- os.remove(converted_wav)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
- return f"Audio Transcription Error: All provided NVIDIA API keys failed. Last error: {last_error}"
 
 
119
 
120
  def run_free_ocr(file_path: str) -> str:
121
  """Fallback OCR pipeline using Tesseract (images and PDFs only)."""
@@ -174,7 +181,7 @@ def process_input(source_path_or_url: str, original_filename: str = None) -> str
174
  temp_converted_office = None
175
 
176
  try:
177
- # Route 1: Audio files -> Standard NVIDIA gRPC ASR
178
  if ext in AUDIO_EXTENSIONS:
179
  return transcribe_with_nvidia_riva(working_path)
180
 
@@ -244,58 +251,47 @@ async def convert_file_api(file: UploadFile = None, url: str = None):
244
  os.remove(temp_path)
245
 
246
  # -------------------------------------------------------------
247
- # Updated Gradio Web UI
248
  # -------------------------------------------------------------
249
- def gradio_handler(file_obj, mic_file_path, url_input):
250
  """
251
- Priority Order:
252
- 1. Recorded Audio (Microphone)
253
- 2. Uploaded File
254
- 3. URL Input
255
  """
256
- # Case 1: Real-time recording from the microphone
257
  if mic_file_path is not None:
258
  return process_input(mic_file_path, original_filename="recorded_audio.wav")
259
-
260
- # Case 2: Standard file upload (.pdf, .doc, .mp3, .opus, etc.)
261
- elif file_obj is not None:
262
- orig_name = getattr(file_obj, "orig_name", file_obj.name)
263
- return process_input(file_obj.name, original_filename=orig_name)
264
-
265
- # Case 3: URL string
266
  elif url_input and url_input.strip():
267
  return process_input(url_input)
268
-
269
- return "Please record audio, upload a file, or enter a valid URL."
270
-
271
 
272
- with gr.Blocks(title="Free MarkItDown & Audio Converter") as demo:
273
- gr.Markdown("# 📄 Document & Real-Time Audio Converter")
274
 
275
  with gr.Row():
276
  with gr.Column():
277
- gr.Markdown("### Option 1: Record Audio Live")
278
- # Microphone component (saves recorded audio to temporary .wav file automatically)
279
  mic_input = gr.Audio(
280
  sources=["microphone"],
281
  type="filepath",
282
- label="Click red icon to record voice"
283
  )
284
 
285
  gr.Markdown("---")
286
  gr.Markdown("### Option 2: Upload File or URL")
287
- file_input = gr.File(label="Upload Document or Audio (.pdf, .mp3, .opus, etc.)")
288
  url_input = gr.Textbox(label="Or enter URL", placeholder="https://example.com/document.pdf")
289
 
290
- submit_btn = gr.Button("Process & Convert", variant="primary")
291
 
292
  with gr.Column():
293
- output_text = gr.Textbox(label="Converted Output / Transcription", lines=22)
294
 
295
- # Trigger processing on button click
296
  submit_btn.click(
297
  fn=gradio_handler,
298
- inputs=[file_input, mic_input, url_input],
299
  outputs=output_text
300
  )
301
 
 
2
  import shutil
3
  import subprocess
4
  import traceback
5
+ import wave
6
  from uuid import uuid4
7
  from fastapi import FastAPI, UploadFile, HTTPException
8
  from markitdown import MarkItDown
 
12
  from pdf2image import convert_from_path
13
  import riva.client as rcli
14
 
15
+ app = FastAPI(title="Free MarkItDown & Real-Time Voice Processing")
16
  md = MarkItDown()
17
 
18
  IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".bmp", ".tiff", ".webp"}
19
  AUDIO_EXTENSIONS = {".mp3", ".wav", ".m4a", ".ogg", ".flac", ".opus"}
20
  LEGACY_OFFICE_EXTENSIONS = {".doc", ".xls", ".ppt", ".rtf"}
21
 
22
+ # NVIDIA Riva gRPC Configuration Endpoint
23
  NVIDIA_STAGE_SERVER = "grpc.nvcf.nvidia.com:443"
24
  NVIDIA_PARAKEET_FUNCTION_ID = "b0e8b4a5-217c-40b7-9b96-17d84e666317"
25
 
 
27
  """Dynamically collects NVIDIA API keys from environment variables."""
28
  keys = []
29
 
30
+ # Check for numbered environment keys: NVIDIA_API_KEY_1, NVIDIA_API_KEY_2, etc.
31
  index = 1
32
  while True:
33
  key = os.getenv(f"NVIDIA_API_KEY_{index}")
 
37
  else:
38
  break
39
 
40
+ # Fallback to single NVIDIA_API_KEY if numbered keys aren't set
41
  if not keys:
42
  single_key = os.getenv("NVIDIA_API_KEY")
43
  if single_key and single_key.strip():
 
46
  return keys
47
 
48
  def transcribe_with_nvidia_riva(audio_path: str) -> str:
49
+ """Transcribes audio using NVIDIA's gRPC client with multi-key failover."""
50
  api_keys = get_nvidia_api_keys()
51
 
52
  if not api_keys:
53
  return "Audio Error: No NVIDIA API keys (NVIDIA_API_KEY_1, NVIDIA_API_KEY_2...) set in environment variables."
54
 
55
+ converted_wav = f"/tmp/{uuid4()}.wav"
 
56
 
57
+ # 1. Convert any input format (MP3, OPUS, uploaded WAV, recorded WAV)
58
+ # into strictly 16kHz, Mono, 16-bit PCM WAV using FFmpeg
59
+ try:
60
+ subprocess.run(
61
+ [
62
+ "ffmpeg", "-y", "-i", audio_path,
63
+ "-ac", "1",
64
+ "-ar", "16000",
65
+ "-sample_fmt", "s16",
66
+ converted_wav
67
+ ],
68
+ check=True,
69
+ stdout=subprocess.DEVNULL,
70
+ stderr=subprocess.DEVNULL
71
+ )
72
+ except Exception as e:
73
+ return f"Audio Pre-processing Error (FFmpeg): {str(e)}"
74
 
75
  last_error = ""
76
+ audio_data = None
77
 
78
+ # 2. Extract ONLY raw PCM frames, stripping the 44-byte WAV header
79
+ try:
80
+ with wave.open(converted_wav, "rb") as wf:
81
+ audio_data = wf.readframes(wf.getnframes())
82
+ except Exception as e:
83
+ last_error = f"WAV Parse Error: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
+ try:
86
+ if audio_data:
87
+ # 3. Rotate through API keys upon rate-limiting or errors
88
+ for idx, key in enumerate(api_keys, start=1):
89
+ try:
90
+ auth = rcli.Auth(
91
+ use_ssl=True,
92
+ uri=NVIDIA_STAGE_SERVER,
93
+ metadata_args=[
94
+ ("authorization", f"Bearer {key}"),
95
+ ("function-id", NVIDIA_PARAKEET_FUNCTION_ID)
96
+ ]
97
+ )
98
+
99
+ asr_service = rcli.ASRService(auth)
100
+ config = rcli.RecognitionConfig(
101
+ encoding=rcli.AudioEncoding.LINEAR_PCM,
102
+ sample_rate_hertz=16000,
103
+ language_code="en-US",
104
+ max_alternatives=1,
105
+ audio_channel_count=1
106
+ )
107
+
108
+ response = asr_service.offline_recognize(audio_data, config)
109
+
110
+ results = response.results
111
+ transcripts = [res.alternatives[0].transcript for res in results if res.alternatives]
112
+ text = " ".join(transcripts).strip()
113
+
114
+ return f"## Audio Transcription\n\n{text}" if text else "Audio processed, but no speech was detected."
115
+
116
+ except Exception as e:
117
+ last_error = str(e)
118
+ print(f"NVIDIA API Key #{idx} failed: {last_error}. Trying next key...")
119
+ continue
120
+
121
+ return f"Audio Transcription Error: All provided NVIDIA API keys failed. Last error: {last_error}"
122
 
123
+ finally:
124
+ if os.path.exists(converted_wav):
125
+ os.remove(converted_wav)
126
 
127
  def run_free_ocr(file_path: str) -> str:
128
  """Fallback OCR pipeline using Tesseract (images and PDFs only)."""
 
181
  temp_converted_office = None
182
 
183
  try:
184
+ # Route 1: Audio files (Uploaded or Recorded) -> NVIDIA Riva gRPC
185
  if ext in AUDIO_EXTENSIONS:
186
  return transcribe_with_nvidia_riva(working_path)
187
 
 
251
  os.remove(temp_path)
252
 
253
  # -------------------------------------------------------------
254
+ # Gradio UI with Real-time Recording & File Handling
255
  # -------------------------------------------------------------
256
+ def gradio_handler(mic_file_path, uploaded_file_obj, url_input):
257
  """
258
+ Evaluates inputs with priority given to live microphone recordings.
 
 
 
259
  """
 
260
  if mic_file_path is not None:
261
  return process_input(mic_file_path, original_filename="recorded_audio.wav")
262
+ elif uploaded_file_obj is not None:
263
+ orig_name = getattr(uploaded_file_obj, "orig_name", uploaded_file_obj.name)
264
+ return process_input(uploaded_file_obj.name, original_filename=orig_name)
 
 
 
 
265
  elif url_input and url_input.strip():
266
  return process_input(url_input)
267
+
268
+ return "Please record voice, upload a file, or provide a URL."
 
269
 
270
+ with gr.Blocks(title="Free Converter & Voice Transcriber") as demo:
271
+ gr.Markdown("# 📄 Document & Real-Time Voice Transcriber")
272
 
273
  with gr.Row():
274
  with gr.Column():
275
+ gr.Markdown("### Option 1: Record Live Voice")
 
276
  mic_input = gr.Audio(
277
  sources=["microphone"],
278
  type="filepath",
279
+ label="Click microphone to start recording"
280
  )
281
 
282
  gr.Markdown("---")
283
  gr.Markdown("### Option 2: Upload File or URL")
284
+ file_input = gr.File(label="Upload File (.pdf, .doc, .mp3, .wav, .png, etc.)")
285
  url_input = gr.Textbox(label="Or enter URL", placeholder="https://example.com/document.pdf")
286
 
287
+ submit_btn = gr.Button("Process & Transcribe", variant="primary")
288
 
289
  with gr.Column():
290
+ output_text = gr.Textbox(label="Result Output / Markdown", lines=24)
291
 
 
292
  submit_btn.click(
293
  fn=gradio_handler,
294
+ inputs=[mic_input, file_input, url_input],
295
  outputs=output_text
296
  )
297