hamza2923 commited on
Commit
c6a04bd
·
verified ·
1 Parent(s): fd621c0

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +196 -0
app.py ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify, Response
2
+ from faster_whisper import WhisperModel
3
+ import torch
4
+ import io
5
+ import time
6
+ import datetime
7
+ from threading import Semaphore
8
+ import os
9
+ from werkzeug.utils import secure_filename
10
+ import tempfile
11
+ from moviepy.editor import VideoFileClip
12
+ import firebase_admin
13
+ from firebase_admin import credentials, messaging # Added for FCM
14
+
15
+ app = Flask(__name__)
16
+
17
+ # Configuration
18
+ MAX_CONCURRENT_REQUESTS = 2
19
+ MAX_FILE_DURATION = 60 * 30
20
+ TEMPORARY_FOLDER = tempfile.gettempdir()
21
+ ALLOWED_AUDIO_EXTENSIONS = {'mp3', 'wav', 'ogg', 'm4a', 'flac', 'aac', 'wma', 'opus', 'aiff'}
22
+ ALLOWED_VIDEO_EXTENSIONS = {'mp4', 'avi', 'mov', 'mkv', 'webm', 'flv', 'wmv', 'mpeg', 'mpg', '3gp'}
23
+ ALLOWED_EXTENSIONS = ALLOWED_AUDIO_EXTENSIONS.union(ALLOWED_VIDEO_EXTENSIONS)
24
+
25
+
26
+ # Initialize Firebase Admin SDK using environment variables
27
+ firebase_credentials = {
28
+ "type": "service_account",
29
+ "project_id": os.getenv("FIREBASE_PROJECT_ID"),
30
+ "private_key_id": os.getenv("FIREBASE_PRIVATE_KEY_ID"),
31
+ "private_key": os.getenv("FIREBASE_PRIVATE_KEY").replace("\\n", "\n"),
32
+ "client_email": os.getenv("FIREBASE_CLIENT_EMAIL"),
33
+ "client_id": os.getenv("FIREBASE_CLIENT_ID"),
34
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
35
+ "token_uri": "https://oauth2.googleapis.com/token",
36
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
37
+ "client_x509_cert_url": f"https://www.googleapis.com/robot/v1/metadata/x509/{os.getenv('FIREBASE_CLIENT_EMAIL')}"
38
+ }
39
+ cred = credentials.Certificate(firebase_credentials)
40
+ firebase_admin.initialize_app(cred)
41
+
42
+ # Device check for faster-whisper
43
+ device = "cuda" if torch.cuda.is_available() else "cpu"
44
+ compute_type = "float16" if device == "cuda" else "int8"
45
+ print(f"Using device: {device} with compute_type: {compute_type}")
46
+
47
+ # Faster Whisper setup
48
+ beamsize = 2
49
+ wmodel = WhisperModel(
50
+ "guillaumekln/faster-whisper-small",
51
+ device=device,
52
+ compute_type=compute_type,
53
+ download_root="./model_cache"
54
+ )
55
+
56
+ # Concurrency control
57
+ request_semaphore = Semaphore(MAX_CONCURRENT_REQUESTS)
58
+ active_requests = 0
59
+
60
+ def allowed_file(filename):
61
+ return '.' in filename and \
62
+ filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
63
+
64
+ def cleanup_temp_files(*file_paths):
65
+ for file_path in file_paths:
66
+ try:
67
+ if file_path and os.path.exists(file_path):
68
+ os.remove(file_path)
69
+ except Exception as e:
70
+ print(f"Error cleaning up temp file {file_path}: {str(e)}")
71
+
72
+ def extract_audio_from_video(video_path, output_audio_path):
73
+ try:
74
+ video = VideoFileClip(video_path)
75
+ if video.duration > MAX_FILE_DURATION:
76
+ video.close()
77
+ raise ValueError(f"Video duration exceeds {MAX_FILE_DURATION} seconds")
78
+ video.audio.write_audiofile(output_audio_path)
79
+ video.close()
80
+ return output_audio_path
81
+ except Exception as e:
82
+ raise Exception(f"Failed to extract audio from video: {str(e)}")
83
+
84
+ def send_fcm_data_message(fcm_token, transcription, file_type, created_date, transcription_name):
85
+ """Send an FCM message with transcription details and a notification"""
86
+ try:
87
+ message = messaging.Message(
88
+ notification=messaging.Notification(
89
+ title=transcription_name,
90
+ body="Successfully downloaded"
91
+ ),
92
+ data={
93
+ 'transcription': transcription,
94
+ 'file_type': file_type,
95
+ 'created_date': created_date,
96
+ 'transcription_name': transcription_name
97
+ },
98
+ token=fcm_token
99
+ )
100
+ response = messaging.send(message)
101
+ print(f"FCM message sent: {response}")
102
+ return True
103
+ except Exception as e:
104
+ print(f"Error sending FCM message: {str(e)}")
105
+ return False
106
+
107
+ @app.route("/health", methods=["GET"])
108
+ def health_check():
109
+ return jsonify({
110
+ 'status': 'API is running',
111
+ 'timestamp': datetime.datetime.now().isoformat(),
112
+ 'device': device,
113
+ 'compute_type': compute_type,
114
+ 'active_requests': active_requests,
115
+ 'max_duration_supported': MAX_FILE_DURATION,
116
+ 'supported_formats': list(ALLOWED_EXTENSIONS)
117
+ })
118
+
119
+ @app.route("/status/busy", methods=["GET"])
120
+ def server_busy():
121
+ is_busy = active_requests >= MAX_CONCURRENT_REQUESTS
122
+ return jsonify({
123
+ 'is_busy': is_busy,
124
+ 'active_requests': active_requests,
125
+ 'max_capacity': MAX_CONCURRENT_REQUESTS
126
+ })
127
+
128
+ @app.route("/whisper_transcribe", methods=["POST"])
129
+ def transcribe():
130
+ global active_requests
131
+
132
+ if not request_semaphore.acquire(blocking=False):
133
+ return jsonify({'error': 'Server busy'}), 503
134
+
135
+ active_requests += 1
136
+ start_time = time.time()
137
+ temp_file_path = None
138
+ temp_audio_path = None
139
+
140
+ try:
141
+ if 'file' not in request.files or 'fcm_token' not in request.form:
142
+ return jsonify({'error': 'Missing file or FCM token'}), 400
143
+
144
+ file = request.files['file']
145
+ fcm_token = request.form['fcm_token']
146
+ created_date = request.form['created_date']
147
+ transcription_name = request.form['transcription_name']
148
+ if not (file and allowed_file(file.filename)):
149
+ return jsonify({'error': f'Invalid file format. Supported: {", ".join(ALLOWED_EXTENSIONS)}'}), 400
150
+
151
+ # Save uploaded file
152
+ temp_file_path = os.path.join(TEMPORARY_FOLDER, secure_filename(file.filename))
153
+ file.save(temp_file_path)
154
+
155
+ # Handle video/audio
156
+ file_extension = file.filename.rsplit('.', 1)[1].lower()
157
+ if file_extension in ALLOWED_VIDEO_EXTENSIONS:
158
+ temp_audio_path = os.path.join(TEMPORARY_FOLDER, f"temp_audio_{int(time.time())}.wav")
159
+ extract_audio_from_video(temp_file_path, temp_audio_path)
160
+ transcription_file = temp_audio_path
161
+ else:
162
+ transcription_file = temp_file_path
163
+
164
+ # Transcribe
165
+ segments, _ = wmodel.transcribe(
166
+ transcription_file,
167
+ beam_size=beamsize,
168
+ vad_filter=True,
169
+ without_timestamps=True,
170
+ compression_ratio_threshold=2.4,
171
+ word_timestamps=False
172
+ )
173
+
174
+ full_text = " ".join(segment.text for segment in segments)
175
+ file_type = 'video' if file_extension in ALLOWED_VIDEO_EXTENSIONS else 'audio'
176
+
177
+ # Send FCM data message
178
+ # Send FCM data message
179
+ send_fcm_data_message(fcm_token, full_text, file_type, created_date, transcription_name)
180
+
181
+ return jsonify({}), 200
182
+
183
+ except Exception as e:
184
+ return jsonify({'error': str(e)}), 500
185
+
186
+ finally:
187
+ cleanup_temp_files(temp_file_path, temp_audio_path)
188
+ active_requests -= 1
189
+ request_semaphore.release()
190
+ print(f"Processed in {time.time()-start_time:.2f}s (Active: {active_requests})")
191
+
192
+ if __name__ == "__main__":
193
+ if not os.path.exists(TEMPORARY_FOLDER):
194
+ os.makedirs(TEMPORARY_FOLDER)
195
+
196
+ app.run(host="0.0.0.0", port=7860, threaded=True)