github-actions[bot]
commited on
Commit
Β·
ea14d5d
1
Parent(s):
4116bc7
Auto-deploy from GitHub: 07597185080a5be8bd74c45bc69a05d84f5e6604
Browse files- Dockerfile +2 -7
- app.py +145 -1
- worker.py +24 -11
Dockerfile
CHANGED
|
@@ -25,10 +25,5 @@ RUN mkdir -p uploads temp_dir
|
|
| 25 |
# Expose port
|
| 26 |
EXPOSE 7860
|
| 27 |
|
| 28 |
-
#
|
| 29 |
-
|
| 30 |
-
python worker.py &\n\
|
| 31 |
-
python app.py' > /app/start.sh && chmod +x /app/start.sh
|
| 32 |
-
|
| 33 |
-
# Run the application
|
| 34 |
-
CMD ["/app/start.sh"]
|
|
|
|
| 25 |
# Expose port
|
| 26 |
EXPOSE 7860
|
| 27 |
|
| 28 |
+
# Run only the Flask app (worker starts automatically on first upload)
|
| 29 |
+
CMD ["python", "app.py"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.py
CHANGED
|
@@ -5,6 +5,9 @@ import os
|
|
| 5 |
import uuid
|
| 6 |
from datetime import datetime
|
| 7 |
from werkzeug.utils import secure_filename
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
app = Flask(__name__)
|
| 10 |
CORS(app)
|
|
@@ -15,6 +18,10 @@ ALLOWED_EXTENSIONS = {'wav', 'mp3', 'flac', 'ogg', 'm4a', 'aac'}
|
|
| 15 |
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
| 16 |
os.makedirs('temp_dir', exist_ok=True)
|
| 17 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
def init_db():
|
| 19 |
conn = sqlite3.connect('audio_captions.db')
|
| 20 |
c = conn.cursor()
|
|
@@ -32,6 +39,129 @@ def init_db():
|
|
| 32 |
def allowed_file(filename):
|
| 33 |
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
| 34 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
@app.route('/')
|
| 36 |
def index():
|
| 37 |
return send_from_directory('.', 'index.html')
|
|
@@ -63,6 +193,9 @@ def upload_audio():
|
|
| 63 |
conn.commit()
|
| 64 |
conn.close()
|
| 65 |
|
|
|
|
|
|
|
|
|
|
| 66 |
return jsonify({
|
| 67 |
'id': file_id,
|
| 68 |
'filename': filename,
|
|
@@ -115,10 +248,21 @@ def get_file(file_id):
|
|
| 115 |
|
| 116 |
@app.route('/health', methods=['GET'])
|
| 117 |
def health():
|
| 118 |
-
return jsonify({
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
|
| 120 |
if __name__ == '__main__':
|
| 121 |
init_db()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
# Use PORT environment variable for Hugging Face compatibility
|
| 123 |
port = int(os.environ.get('PORT', 7860))
|
| 124 |
app.run(debug=False, host='0.0.0.0', port=port)
|
|
|
|
| 5 |
import uuid
|
| 6 |
from datetime import datetime
|
| 7 |
from werkzeug.utils import secure_filename
|
| 8 |
+
import threading
|
| 9 |
+
import subprocess
|
| 10 |
+
import time
|
| 11 |
|
| 12 |
app = Flask(__name__)
|
| 13 |
CORS(app)
|
|
|
|
| 18 |
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
| 19 |
os.makedirs('temp_dir', exist_ok=True)
|
| 20 |
|
| 21 |
+
# Worker state
|
| 22 |
+
worker_thread = None
|
| 23 |
+
worker_running = False
|
| 24 |
+
|
| 25 |
def init_db():
|
| 26 |
conn = sqlite3.connect('audio_captions.db')
|
| 27 |
c = conn.cursor()
|
|
|
|
| 39 |
def allowed_file(filename):
|
| 40 |
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
| 41 |
|
| 42 |
+
def start_worker():
|
| 43 |
+
"""Start the worker thread if not already running"""
|
| 44 |
+
global worker_thread, worker_running
|
| 45 |
+
|
| 46 |
+
if not worker_running:
|
| 47 |
+
worker_running = True
|
| 48 |
+
worker_thread = threading.Thread(target=worker_loop, daemon=True)
|
| 49 |
+
worker_thread.start()
|
| 50 |
+
print("β
Worker thread started")
|
| 51 |
+
|
| 52 |
+
def worker_loop():
|
| 53 |
+
"""Main worker loop that processes audio files"""
|
| 54 |
+
print("π€ STT Worker started. Monitoring for new audio files...")
|
| 55 |
+
|
| 56 |
+
CWD = "./"
|
| 57 |
+
PYTHON_PATH = "stt-transcribe"
|
| 58 |
+
STT_MODEL_NAME = "fasterwhispher"
|
| 59 |
+
POLL_INTERVAL = 3 # seconds
|
| 60 |
+
|
| 61 |
+
import shlex
|
| 62 |
+
import json
|
| 63 |
+
|
| 64 |
+
while worker_running:
|
| 65 |
+
try:
|
| 66 |
+
# Get next unprocessed file
|
| 67 |
+
conn = sqlite3.connect('audio_captions.db')
|
| 68 |
+
conn.row_factory = sqlite3.Row
|
| 69 |
+
c = conn.cursor()
|
| 70 |
+
c.execute('''SELECT * FROM audio_files
|
| 71 |
+
WHERE status = 'not_started'
|
| 72 |
+
ORDER BY created_at ASC
|
| 73 |
+
LIMIT 1''')
|
| 74 |
+
row = c.fetchone()
|
| 75 |
+
conn.close()
|
| 76 |
+
|
| 77 |
+
if row:
|
| 78 |
+
file_id = row['id']
|
| 79 |
+
filepath = row['filepath']
|
| 80 |
+
filename = row['filename']
|
| 81 |
+
|
| 82 |
+
print(f"\n{'='*60}")
|
| 83 |
+
print(f"π΅ Processing: {filename}")
|
| 84 |
+
print(f"π ID: {file_id}")
|
| 85 |
+
print(f"{'='*60}")
|
| 86 |
+
|
| 87 |
+
# Update status to processing
|
| 88 |
+
update_status(file_id, 'processing')
|
| 89 |
+
|
| 90 |
+
try:
|
| 91 |
+
# Run STT command
|
| 92 |
+
print(f"π Running STT on: {os.path.abspath(filepath)}")
|
| 93 |
+
command = f"""cd {CWD} && {PYTHON_PATH} --input {shlex.quote(os.path.abspath(filepath))} --model {STT_MODEL_NAME}"""
|
| 94 |
+
|
| 95 |
+
subprocess.run(
|
| 96 |
+
command,
|
| 97 |
+
shell=True,
|
| 98 |
+
executable="/bin/bash",
|
| 99 |
+
check=True,
|
| 100 |
+
cwd=CWD,
|
| 101 |
+
env={
|
| 102 |
+
**os.environ,
|
| 103 |
+
'PYTHONUNBUFFERED': '1',
|
| 104 |
+
'CUDA_LAUNCH_BLOCKING': '1',
|
| 105 |
+
'USE_CPU_IF_POSSIBLE': 'true'
|
| 106 |
+
}
|
| 107 |
+
)
|
| 108 |
+
|
| 109 |
+
# Read transcription result
|
| 110 |
+
output_path = f'{CWD}/temp_dir/output_transcription.json'
|
| 111 |
+
with open(output_path, 'r') as file:
|
| 112 |
+
result = json.loads(file.read().strip())
|
| 113 |
+
|
| 114 |
+
# Extract caption text
|
| 115 |
+
caption = result.get('text', '') or result.get('transcription', '') or str(result)
|
| 116 |
+
|
| 117 |
+
print(f"β
Successfully processed: {filename}")
|
| 118 |
+
print(f"π Caption preview: {caption[:100]}...")
|
| 119 |
+
|
| 120 |
+
# Update database with success
|
| 121 |
+
update_status(file_id, 'completed', caption=caption)
|
| 122 |
+
|
| 123 |
+
# Delete the audio file after successful processing
|
| 124 |
+
if os.path.exists(filepath):
|
| 125 |
+
os.remove(filepath)
|
| 126 |
+
print(f"ποΈ Deleted audio file: {filepath}")
|
| 127 |
+
|
| 128 |
+
except Exception as e:
|
| 129 |
+
print(f"β Failed to process: {filename}")
|
| 130 |
+
print(f"Error: {str(e)}")
|
| 131 |
+
update_status(file_id, 'failed', error=str(e))
|
| 132 |
+
|
| 133 |
+
# Don't delete file on failure (for debugging)
|
| 134 |
+
# Optionally delete after some time or manual review
|
| 135 |
+
|
| 136 |
+
else:
|
| 137 |
+
# No files to process, sleep for a bit
|
| 138 |
+
time.sleep(POLL_INTERVAL)
|
| 139 |
+
|
| 140 |
+
except Exception as e:
|
| 141 |
+
print(f"β οΈ Worker error: {str(e)}")
|
| 142 |
+
time.sleep(POLL_INTERVAL)
|
| 143 |
+
|
| 144 |
+
def update_status(file_id, status, caption=None, error=None):
|
| 145 |
+
"""Update the status of a file in the database"""
|
| 146 |
+
conn = sqlite3.connect('audio_captions.db')
|
| 147 |
+
c = conn.cursor()
|
| 148 |
+
|
| 149 |
+
if status == 'completed':
|
| 150 |
+
c.execute('''UPDATE audio_files
|
| 151 |
+
SET status = ?, caption = ?, processed_at = ?
|
| 152 |
+
WHERE id = ?''',
|
| 153 |
+
(status, caption, datetime.now().isoformat(), file_id))
|
| 154 |
+
elif status == 'failed':
|
| 155 |
+
c.execute('''UPDATE audio_files
|
| 156 |
+
SET status = ?, caption = ?, processed_at = ?
|
| 157 |
+
WHERE id = ?''',
|
| 158 |
+
(status, f"Error: {error}", datetime.now().isoformat(), file_id))
|
| 159 |
+
else:
|
| 160 |
+
c.execute('UPDATE audio_files SET status = ? WHERE id = ?', (status, file_id))
|
| 161 |
+
|
| 162 |
+
conn.commit()
|
| 163 |
+
conn.close()
|
| 164 |
+
|
| 165 |
@app.route('/')
|
| 166 |
def index():
|
| 167 |
return send_from_directory('.', 'index.html')
|
|
|
|
| 193 |
conn.commit()
|
| 194 |
conn.close()
|
| 195 |
|
| 196 |
+
# Start worker on first upload
|
| 197 |
+
start_worker()
|
| 198 |
+
|
| 199 |
return jsonify({
|
| 200 |
'id': file_id,
|
| 201 |
'filename': filename,
|
|
|
|
| 248 |
|
| 249 |
@app.route('/health', methods=['GET'])
|
| 250 |
def health():
|
| 251 |
+
return jsonify({
|
| 252 |
+
'status': 'healthy',
|
| 253 |
+
'service': 'audio-caption-generator',
|
| 254 |
+
'worker_running': worker_running
|
| 255 |
+
})
|
| 256 |
|
| 257 |
if __name__ == '__main__':
|
| 258 |
init_db()
|
| 259 |
+
print("\n" + "="*60)
|
| 260 |
+
print("π Audio Caption Generator API Server")
|
| 261 |
+
print("="*60)
|
| 262 |
+
print("π Worker will start automatically on first upload")
|
| 263 |
+
print("ποΈ Audio files will be deleted after successful processing")
|
| 264 |
+
print("="*60 + "\n")
|
| 265 |
+
|
| 266 |
# Use PORT environment variable for Hugging Face compatibility
|
| 267 |
port = int(os.environ.get('PORT', 7860))
|
| 268 |
app.run(debug=False, host='0.0.0.0', port=port)
|
worker.py
CHANGED
|
@@ -9,12 +9,12 @@ from datetime import datetime
|
|
| 9 |
CWD = "./"
|
| 10 |
PYTHON_PATH = "stt-transcribe"
|
| 11 |
STT_MODEL_NAME = "fasterwhispher"
|
| 12 |
-
POLL_INTERVAL =
|
| 13 |
|
| 14 |
def process_audio(file_id, filepath):
|
| 15 |
"""Process audio file using STT and return the transcription"""
|
| 16 |
try:
|
| 17 |
-
print(f"
|
| 18 |
|
| 19 |
# Run STT command
|
| 20 |
command = f"""cd {CWD} && {PYTHON_PATH} --input {shlex.quote(os.path.abspath(filepath))} --model {STT_MODEL_NAME}"""
|
|
@@ -44,7 +44,7 @@ def process_audio(file_id, filepath):
|
|
| 44 |
return caption, None
|
| 45 |
|
| 46 |
except Exception as e:
|
| 47 |
-
print(f"Error processing file {file_id}: {str(e)}")
|
| 48 |
return None, str(e)
|
| 49 |
|
| 50 |
def update_status(file_id, status, caption=None, error=None):
|
|
@@ -70,7 +70,8 @@ def update_status(file_id, status, caption=None, error=None):
|
|
| 70 |
|
| 71 |
def worker_loop():
|
| 72 |
"""Main worker loop that processes audio files"""
|
| 73 |
-
print("STT Worker started.
|
|
|
|
| 74 |
|
| 75 |
while True:
|
| 76 |
try:
|
|
@@ -91,8 +92,8 @@ def worker_loop():
|
|
| 91 |
filename = row['filename']
|
| 92 |
|
| 93 |
print(f"\n{'='*60}")
|
| 94 |
-
print(f"Processing: {filename}")
|
| 95 |
-
print(f"ID: {file_id}")
|
| 96 |
print(f"{'='*60}")
|
| 97 |
|
| 98 |
# Update status to processing
|
|
@@ -102,24 +103,36 @@ def worker_loop():
|
|
| 102 |
caption, error = process_audio(file_id, filepath)
|
| 103 |
|
| 104 |
if caption:
|
| 105 |
-
print(f"
|
| 106 |
-
print(f"Caption: {caption[:100]}...")
|
| 107 |
update_status(file_id, 'completed', caption=caption)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
else:
|
| 109 |
-
print(f"
|
| 110 |
print(f"Error: {error}")
|
| 111 |
update_status(file_id, 'failed', error=error)
|
|
|
|
| 112 |
else:
|
| 113 |
# No files to process, sleep for a bit
|
| 114 |
time.sleep(POLL_INTERVAL)
|
| 115 |
|
| 116 |
except Exception as e:
|
| 117 |
-
print(f"Worker error: {str(e)}")
|
| 118 |
time.sleep(POLL_INTERVAL)
|
| 119 |
|
| 120 |
if __name__ == '__main__':
|
| 121 |
# Initialize database if it doesn't exist
|
| 122 |
if not os.path.exists('audio_captions.db'):
|
| 123 |
-
print("Database not found. Please run app.py first to initialize.")
|
| 124 |
else:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
worker_loop()
|
|
|
|
| 9 |
CWD = "./"
|
| 10 |
PYTHON_PATH = "stt-transcribe"
|
| 11 |
STT_MODEL_NAME = "fasterwhispher"
|
| 12 |
+
POLL_INTERVAL = 3 # seconds
|
| 13 |
|
| 14 |
def process_audio(file_id, filepath):
|
| 15 |
"""Process audio file using STT and return the transcription"""
|
| 16 |
try:
|
| 17 |
+
print(f"π Running STT on: {os.path.abspath(filepath)}")
|
| 18 |
|
| 19 |
# Run STT command
|
| 20 |
command = f"""cd {CWD} && {PYTHON_PATH} --input {shlex.quote(os.path.abspath(filepath))} --model {STT_MODEL_NAME}"""
|
|
|
|
| 44 |
return caption, None
|
| 45 |
|
| 46 |
except Exception as e:
|
| 47 |
+
print(f"β Error processing file {file_id}: {str(e)}")
|
| 48 |
return None, str(e)
|
| 49 |
|
| 50 |
def update_status(file_id, status, caption=None, error=None):
|
|
|
|
| 70 |
|
| 71 |
def worker_loop():
|
| 72 |
"""Main worker loop that processes audio files"""
|
| 73 |
+
print("π€ STT Worker started. Monitoring for new audio files...")
|
| 74 |
+
print("ποΈ Audio files will be deleted after successful processing\n")
|
| 75 |
|
| 76 |
while True:
|
| 77 |
try:
|
|
|
|
| 92 |
filename = row['filename']
|
| 93 |
|
| 94 |
print(f"\n{'='*60}")
|
| 95 |
+
print(f"π΅ Processing: {filename}")
|
| 96 |
+
print(f"π ID: {file_id}")
|
| 97 |
print(f"{'='*60}")
|
| 98 |
|
| 99 |
# Update status to processing
|
|
|
|
| 103 |
caption, error = process_audio(file_id, filepath)
|
| 104 |
|
| 105 |
if caption:
|
| 106 |
+
print(f"β
Successfully processed: {filename}")
|
| 107 |
+
print(f"π Caption preview: {caption[:100]}...")
|
| 108 |
update_status(file_id, 'completed', caption=caption)
|
| 109 |
+
|
| 110 |
+
# Delete the audio file after successful processing
|
| 111 |
+
if os.path.exists(filepath):
|
| 112 |
+
os.remove(filepath)
|
| 113 |
+
print(f"ποΈ Deleted audio file: {filepath}")
|
| 114 |
else:
|
| 115 |
+
print(f"β Failed to process: {filename}")
|
| 116 |
print(f"Error: {error}")
|
| 117 |
update_status(file_id, 'failed', error=error)
|
| 118 |
+
# Don't delete file on failure (for debugging)
|
| 119 |
else:
|
| 120 |
# No files to process, sleep for a bit
|
| 121 |
time.sleep(POLL_INTERVAL)
|
| 122 |
|
| 123 |
except Exception as e:
|
| 124 |
+
print(f"β οΈ Worker error: {str(e)}")
|
| 125 |
time.sleep(POLL_INTERVAL)
|
| 126 |
|
| 127 |
if __name__ == '__main__':
|
| 128 |
# Initialize database if it doesn't exist
|
| 129 |
if not os.path.exists('audio_captions.db'):
|
| 130 |
+
print("β Database not found. Please run app.py first to initialize.")
|
| 131 |
else:
|
| 132 |
+
print("\n" + "="*60)
|
| 133 |
+
print("π Starting STT Worker (Standalone Mode)")
|
| 134 |
+
print("="*60)
|
| 135 |
+
print("β οΈ Note: Worker is now embedded in app.py")
|
| 136 |
+
print("β οΈ This standalone mode is for testing/debugging only")
|
| 137 |
+
print("="*60 + "\n")
|
| 138 |
worker_loop()
|