github-actions[bot]
commited on
Commit
·
53878a2
1
Parent(s):
eec3fb0
Auto-deploy from GitHub: dc2dcd799144f3d7cf9e0b0bef529a1486c14375
Browse files- app.py +81 -3
- index.html +27 -2
app.py
CHANGED
|
@@ -203,24 +203,75 @@ def upload_audio():
|
|
| 203 |
'message': 'File uploaded successfully'
|
| 204 |
}), 201
|
| 205 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
@app.route('/api/files', methods=['GET'])
|
| 207 |
def get_files():
|
| 208 |
conn = sqlite3.connect('audio_captions.db')
|
| 209 |
conn.row_factory = sqlite3.Row
|
| 210 |
c = conn.cursor()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 211 |
c.execute('SELECT * FROM audio_files ORDER BY created_at DESC')
|
| 212 |
rows = c.fetchall()
|
| 213 |
conn.close()
|
| 214 |
|
| 215 |
files = []
|
| 216 |
for row in rows:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
files.append({
|
| 218 |
'id': row['id'],
|
| 219 |
'filename': row['filename'],
|
| 220 |
'status': row['status'],
|
| 221 |
'caption': row['caption'],
|
| 222 |
'created_at': row['created_at'],
|
| 223 |
-
'processed_at': row['processed_at']
|
|
|
|
|
|
|
| 224 |
})
|
| 225 |
|
| 226 |
return jsonify(files)
|
|
@@ -232,18 +283,45 @@ def get_file(file_id):
|
|
| 232 |
c = conn.cursor()
|
| 233 |
c.execute('SELECT * FROM audio_files WHERE id = ?', (file_id,))
|
| 234 |
row = c.fetchone()
|
| 235 |
-
conn.close()
|
| 236 |
|
| 237 |
if row is None:
|
|
|
|
| 238 |
return jsonify({'error': 'File not found'}), 404
|
| 239 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
return jsonify({
|
| 241 |
'id': row['id'],
|
| 242 |
'filename': row['filename'],
|
| 243 |
'status': row['status'],
|
| 244 |
'caption': row['caption'],
|
| 245 |
'created_at': row['created_at'],
|
| 246 |
-
'processed_at': row['processed_at']
|
|
|
|
|
|
|
| 247 |
})
|
| 248 |
|
| 249 |
@app.route('/health', methods=['GET'])
|
|
|
|
| 203 |
'message': 'File uploaded successfully'
|
| 204 |
}), 201
|
| 205 |
|
| 206 |
+
def get_average_processing_time(cursor):
|
| 207 |
+
"""Calculate average processing time from completed files in seconds"""
|
| 208 |
+
cursor.execute('''SELECT created_at, processed_at FROM audio_files
|
| 209 |
+
WHERE status = 'completed' AND processed_at IS NOT NULL
|
| 210 |
+
ORDER BY processed_at DESC LIMIT 20''')
|
| 211 |
+
completed_rows = cursor.fetchall()
|
| 212 |
+
|
| 213 |
+
if not completed_rows:
|
| 214 |
+
return 30.0 # Default estimate: 30 seconds per file
|
| 215 |
+
|
| 216 |
+
total_seconds = 0
|
| 217 |
+
count = 0
|
| 218 |
+
for r in completed_rows:
|
| 219 |
+
try:
|
| 220 |
+
created = datetime.fromisoformat(r['created_at'])
|
| 221 |
+
processed = datetime.fromisoformat(r['processed_at'])
|
| 222 |
+
duration = (processed - created).total_seconds()
|
| 223 |
+
if duration > 0:
|
| 224 |
+
total_seconds += duration
|
| 225 |
+
count += 1
|
| 226 |
+
except:
|
| 227 |
+
continue
|
| 228 |
+
|
| 229 |
+
return total_seconds / count if count > 0 else 30.0
|
| 230 |
+
|
| 231 |
@app.route('/api/files', methods=['GET'])
|
| 232 |
def get_files():
|
| 233 |
conn = sqlite3.connect('audio_captions.db')
|
| 234 |
conn.row_factory = sqlite3.Row
|
| 235 |
c = conn.cursor()
|
| 236 |
+
|
| 237 |
+
# Get average processing time
|
| 238 |
+
avg_time = get_average_processing_time(c)
|
| 239 |
+
|
| 240 |
+
# Get queue (files waiting to be processed, ordered by creation time)
|
| 241 |
+
c.execute('''SELECT id FROM audio_files
|
| 242 |
+
WHERE status = 'not_started'
|
| 243 |
+
ORDER BY created_at ASC''')
|
| 244 |
+
queue_ids = [row['id'] for row in c.fetchall()]
|
| 245 |
+
|
| 246 |
+
# Check if there's a file currently processing
|
| 247 |
+
c.execute('''SELECT COUNT(*) as count FROM audio_files WHERE status = 'processing' ''')
|
| 248 |
+
processing_count = c.fetchone()['count']
|
| 249 |
+
|
| 250 |
c.execute('SELECT * FROM audio_files ORDER BY created_at DESC')
|
| 251 |
rows = c.fetchall()
|
| 252 |
conn.close()
|
| 253 |
|
| 254 |
files = []
|
| 255 |
for row in rows:
|
| 256 |
+
# Calculate queue position (1-based) for files in queue
|
| 257 |
+
queue_position = None
|
| 258 |
+
estimated_start_seconds = None
|
| 259 |
+
|
| 260 |
+
if row['status'] == 'not_started' and row['id'] in queue_ids:
|
| 261 |
+
queue_position = queue_ids.index(row['id']) + 1
|
| 262 |
+
# Estimate = (files ahead + currently processing) * avg time
|
| 263 |
+
files_ahead = queue_position - 1 + processing_count
|
| 264 |
+
estimated_start_seconds = round(files_ahead * avg_time)
|
| 265 |
+
|
| 266 |
files.append({
|
| 267 |
'id': row['id'],
|
| 268 |
'filename': row['filename'],
|
| 269 |
'status': row['status'],
|
| 270 |
'caption': row['caption'],
|
| 271 |
'created_at': row['created_at'],
|
| 272 |
+
'processed_at': row['processed_at'],
|
| 273 |
+
'queue_position': queue_position,
|
| 274 |
+
'estimated_start_seconds': estimated_start_seconds
|
| 275 |
})
|
| 276 |
|
| 277 |
return jsonify(files)
|
|
|
|
| 283 |
c = conn.cursor()
|
| 284 |
c.execute('SELECT * FROM audio_files WHERE id = ?', (file_id,))
|
| 285 |
row = c.fetchone()
|
|
|
|
| 286 |
|
| 287 |
if row is None:
|
| 288 |
+
conn.close()
|
| 289 |
return jsonify({'error': 'File not found'}), 404
|
| 290 |
|
| 291 |
+
# Calculate queue position and estimated time if file is waiting
|
| 292 |
+
queue_position = None
|
| 293 |
+
estimated_start_seconds = None
|
| 294 |
+
|
| 295 |
+
if row['status'] == 'not_started':
|
| 296 |
+
# Get average processing time
|
| 297 |
+
avg_time = get_average_processing_time(c)
|
| 298 |
+
|
| 299 |
+
# Count files ahead in queue
|
| 300 |
+
c.execute('''SELECT COUNT(*) as position FROM audio_files
|
| 301 |
+
WHERE status = 'not_started' AND created_at < ?''',
|
| 302 |
+
(row['created_at'],))
|
| 303 |
+
position_row = c.fetchone()
|
| 304 |
+
queue_position = position_row['position'] + 1 # 1-based position
|
| 305 |
+
|
| 306 |
+
# Check if there's a file currently processing
|
| 307 |
+
c.execute('''SELECT COUNT(*) as count FROM audio_files WHERE status = 'processing' ''')
|
| 308 |
+
processing_count = c.fetchone()['count']
|
| 309 |
+
|
| 310 |
+
# Estimate = (files ahead + currently processing) * avg time
|
| 311 |
+
files_ahead = queue_position - 1 + processing_count
|
| 312 |
+
estimated_start_seconds = round(files_ahead * avg_time)
|
| 313 |
+
|
| 314 |
+
conn.close()
|
| 315 |
+
|
| 316 |
return jsonify({
|
| 317 |
'id': row['id'],
|
| 318 |
'filename': row['filename'],
|
| 319 |
'status': row['status'],
|
| 320 |
'caption': row['caption'],
|
| 321 |
'created_at': row['created_at'],
|
| 322 |
+
'processed_at': row['processed_at'],
|
| 323 |
+
'queue_position': queue_position,
|
| 324 |
+
'estimated_start_seconds': estimated_start_seconds
|
| 325 |
})
|
| 326 |
|
| 327 |
@app.route('/health', methods=['GET'])
|
index.html
CHANGED
|
@@ -627,6 +627,7 @@
|
|
| 627 |
<tr>
|
| 628 |
<th>Filename</th>
|
| 629 |
<th>Status</th>
|
|
|
|
| 630 |
<th>Caption</th>
|
| 631 |
<th>Created</th>
|
| 632 |
<th>Processed</th>
|
|
@@ -634,7 +635,7 @@
|
|
| 634 |
</thead>
|
| 635 |
<tbody id="filesTable">
|
| 636 |
<tr>
|
| 637 |
-
<td colspan="
|
| 638 |
</td>
|
| 639 |
</tr>
|
| 640 |
</tbody>
|
|
@@ -754,7 +755,7 @@
|
|
| 754 |
const tbody = document.getElementById('filesTable');
|
| 755 |
|
| 756 |
if (files.length === 0) {
|
| 757 |
-
tbody.innerHTML = '<tr><td colspan="
|
| 758 |
return;
|
| 759 |
}
|
| 760 |
|
|
@@ -762,11 +763,35 @@
|
|
| 762 |
const captionPreview = file.caption ?
|
| 763 |
(file.caption.length > 50 ? file.caption.substring(0, 50) + '...' : file.caption) :
|
| 764 |
'—';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 765 |
|
| 766 |
return `
|
| 767 |
<tr>
|
| 768 |
<td><strong>${file.filename}</strong></td>
|
| 769 |
<td><span class="status status-${file.status}">${file.status.replace('_', ' ')}</span></td>
|
|
|
|
| 770 |
<td class="caption-cell">
|
| 771 |
${file.caption ?
|
| 772 |
`<button class="btn btn-small btn-secondary" onclick='showCaption(${JSON.stringify(file.caption)})' style="margin-left: 0.5rem;">Show</button>`
|
|
|
|
| 627 |
<tr>
|
| 628 |
<th>Filename</th>
|
| 629 |
<th>Status</th>
|
| 630 |
+
<th>Est. Wait</th>
|
| 631 |
<th>Caption</th>
|
| 632 |
<th>Created</th>
|
| 633 |
<th>Processed</th>
|
|
|
|
| 635 |
</thead>
|
| 636 |
<tbody id="filesTable">
|
| 637 |
<tr>
|
| 638 |
+
<td colspan="6" class="empty-state">No files uploaded yet. Start by uploading an audio file!
|
| 639 |
</td>
|
| 640 |
</tr>
|
| 641 |
</tbody>
|
|
|
|
| 755 |
const tbody = document.getElementById('filesTable');
|
| 756 |
|
| 757 |
if (files.length === 0) {
|
| 758 |
+
tbody.innerHTML = '<tr><td colspan="6" class="empty-state">No files uploaded yet. Start by uploading an audio file!</td></tr>';
|
| 759 |
return;
|
| 760 |
}
|
| 761 |
|
|
|
|
| 763 |
const captionPreview = file.caption ?
|
| 764 |
(file.caption.length > 50 ? file.caption.substring(0, 50) + '...' : file.caption) :
|
| 765 |
'—';
|
| 766 |
+
|
| 767 |
+
// Format estimated wait time
|
| 768 |
+
let estWait = '—';
|
| 769 |
+
if (file.status === 'not_started' && file.estimated_start_seconds !== null) {
|
| 770 |
+
const seconds = file.estimated_start_seconds;
|
| 771 |
+
if (seconds < 60) {
|
| 772 |
+
estWait = `${seconds}s`;
|
| 773 |
+
} else if (seconds < 3600) {
|
| 774 |
+
const mins = Math.floor(seconds / 60);
|
| 775 |
+
const secs = seconds % 60;
|
| 776 |
+
estWait = secs > 0 ? `${mins}m ${secs}s` : `${mins}m`;
|
| 777 |
+
} else {
|
| 778 |
+
const hours = Math.floor(seconds / 3600);
|
| 779 |
+
const mins = Math.floor((seconds % 3600) / 60);
|
| 780 |
+
estWait = mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
|
| 781 |
+
}
|
| 782 |
+
// Add queue position
|
| 783 |
+
if (file.queue_position) {
|
| 784 |
+
estWait = `#${file.queue_position} (${estWait})`;
|
| 785 |
+
}
|
| 786 |
+
} else if (file.status === 'processing') {
|
| 787 |
+
estWait = '⏳ Processing...';
|
| 788 |
+
}
|
| 789 |
|
| 790 |
return `
|
| 791 |
<tr>
|
| 792 |
<td><strong>${file.filename}</strong></td>
|
| 793 |
<td><span class="status status-${file.status}">${file.status.replace('_', ' ')}</span></td>
|
| 794 |
+
<td>${estWait}</td>
|
| 795 |
<td class="caption-cell">
|
| 796 |
${file.caption ?
|
| 797 |
`<button class="btn btn-small btn-secondary" onclick='showCaption(${JSON.stringify(file.caption)})' style="margin-left: 0.5rem;">Show</button>`
|