Fred808 commited on
Commit
04bc5ce
Β·
verified Β·
1 Parent(s): 7450624

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +132 -96
app.py CHANGED
@@ -284,34 +284,39 @@ def process_video_frames_and_upload(extracted_rar_folder: str, processed_video_c
284
 
285
  log_message(f"🎬 Processing videos in extracted RAR folder: {course_folder_name}")
286
 
287
- mp4_files_found = []
288
  for root, _, files in os.walk(extracted_rar_folder):
289
  for file in files:
290
- if file.lower().endswith(".mp4"):
291
- mp4_files_found.append(os.path.join(root, file))
 
292
 
293
- if not mp4_files_found:
294
- log_message(f"⚠️ No MP4 files found in {course_folder_name}. Skipping video frame extraction.")
295
  return False # Indicate no video processing was done
296
 
297
  course_video_extract_dir = os.path.join(VIDEO_FRAMES_EXTRACT_FOLDER, course_folder_name)
298
  os.makedirs(course_video_extract_dir, exist_ok=True)
299
 
300
- for mp4_path in mp4_files_found:
301
- video_basename = os.path.splitext(os.path.basename(mp4_path))[0]
 
302
  # Create a unique output folder for frames from this video within the course's frame directory
303
  video_output_folder = os.path.join(course_video_extract_dir, video_basename)
304
 
305
- if not extract_frames(mp4_path, video_output_folder, VIDEO_FRAME_FPS):
306
- log_message(f"❌ Failed to extract frames from {os.path.basename(mp4_path)}. Continuing with other videos.")
 
 
307
  # Clean up partially extracted frames for this video
308
  if os.path.exists(video_output_folder):
309
  shutil.rmtree(video_output_folder)
310
 
311
  # Check if any frames were extracted for the entire course folder
312
- if not os.listdir(course_video_extract_dir):
313
  log_message(f"⚠️ No frames extracted for any video in {course_folder_name}. Skipping zipping and upload to BG3.")
314
- shutil.rmtree(course_video_extract_dir)
 
315
  return False
316
 
317
  course_zip_path = os.path.join(ZIPPED_FRAMES_FOLDER, f"{course_folder_name}_frames.zip")
@@ -345,24 +350,32 @@ def extract_and_upload_rar(rar_path: str, processed_rars_set: set, uploaded_fold
345
  current_extract_folder = os.path.join(EXTRACT_FOLDER, f"{folder_name}_extracted")
346
 
347
  # Check if RAR is already processed (uploaded to BG2 or video frames processed)
348
- if filename in processed_rars_set and (not DEST_REPO_ID_RAR or get_folder_hash(folder_name) in uploaded_folders_set) and folder_name in processed_video_courses_set:
 
 
 
 
 
349
  log_message(f"⏩ {filename} already fully processed, skipping.")
350
  return True
351
 
352
- # Generate folder name and hash for tracking
353
- folder_hash = get_folder_hash(folder_name)
354
-
355
  # If BG2 upload is enabled and folder already uploaded to BG2, skip RAR extraction/upload to BG2
356
- if DEST_REPO_ID_RAR and folder_hash in uploaded_folders_set:
357
- log_message(f"πŸ”’ Folder \'{folder_name}\' already uploaded to BG2 (hash: {folder_hash[:8]}...), skipping RAR upload.")
358
- processed_rars_set.add(filename)
359
- save_processed_files_state(processed_rars_set)
360
- # Proceed to video processing if not already done
361
- if not os.path.exists(current_extract_folder): # If extracted folder doesn't exist, we can't process videos
362
- log_message(f"⚠️ Extracted folder {current_extract_folder} not found for video processing. Skipping.")
363
- return False
364
- process_video_frames_and_upload(current_extract_folder, processed_video_courses_set)
365
- return True
 
 
 
 
 
 
366
 
367
  log_message(f"πŸ“¦ Attempting to extract: {filename}")
368
  os.makedirs(current_extract_folder, exist_ok=True)
@@ -399,31 +412,50 @@ def extract_and_upload_rar(rar_path: str, processed_rars_set: set, uploaded_fold
399
  log_message(f"⬆️ Uploading to BG2: {path_in_repo}")
400
 
401
  try:
402
- upload_file_to_hf(
403
  local_path=local_path,
404
  path_in_repo=path_in_repo,
405
  repo_id=DEST_REPO_ID_RAR
406
- )
407
- upload_count += 1
 
 
 
 
 
408
  except Exception as upload_error:
409
  log_message(f"❌ Failed to upload {path_in_repo} to BG2: {upload_error}")
410
- raise upload_error
411
 
412
- log_message(f"βœ… Successfully uploaded {upload_count} files from {filename} to BG2")
413
-
414
- # Mark folder as uploaded to BG2 using hash
415
- uploaded_folders_set.add(folder_hash)
416
- save_uploaded_folders(uploaded_folders_set)
417
- processing_status["uploaded_rar_folders"] = len(uploaded_folders_set)
418
-
419
- log_message(f"πŸ”’ Folder \'{folder_name}\' locked in BG2 repo (hash: {folder_hash[:8]}...)")
 
420
  else:
421
  log_message("Skipping upload to BG2 as DEST_REPO_ID_RAR is not set.")
422
 
423
  # Now process video frames from the extracted content and upload to BG3
424
- process_video_frames_and_upload(current_extract_folder, processed_video_courses_set)
425
 
426
- return True
 
 
 
 
 
 
 
 
 
 
 
 
 
427
 
428
  except subprocess.CalledProcessError as e:
429
  error_msg = f"RAR extraction failed (exit {e.returncode}): {e.stderr.strip()}"
@@ -471,49 +503,51 @@ def continuous_processing(start_download_index: Optional[int] = None):
471
  downloaded_rar_paths, next_download_index = download_rar_files(download_start_index, CHUNK_SIZE)
472
  save_download_state(next_download_index)
473
 
474
- if not downloaded_rar_paths:
475
- # Check if there are any local RAR files to process
476
- all_local_rars = sorted([os.path.join(DOWNLOAD_FOLDER, f) for f in os.listdir(DOWNLOAD_FOLDER) if f.endswith(".rar")])
477
- processed_rars = load_processed_files_state()
478
- unprocessed_rars = [rar for rar in all_local_rars if os.path.basename(rar) not in processed_rars]
479
-
480
- if not unprocessed_rars:
481
- log_message("βœ… No more RAR files to download. Stopping...")
482
- break
483
- else:
484
- log_message(f"πŸ“‹ Found {len(unprocessed_rars)} unprocessed local RAR files")
485
-
486
  # 2. Process all available RAR files (downloaded + existing)
487
  all_local_rars = sorted([os.path.join(DOWNLOAD_FOLDER, f) for f in os.listdir(DOWNLOAD_FOLDER) if f.endswith(".rar")])
488
  processed_rars = load_processed_files_state()
489
  processing_status["total_files"] = len(all_local_rars)
 
490
  processing_status["processed_files"] = len(processed_rars)
491
 
 
 
492
  for rar_file_path in all_local_rars:
 
 
 
 
 
 
 
 
 
 
 
 
 
493
  if not processing_status["is_running"]:
494
  break
495
 
496
  filename = os.path.basename(rar_file_path)
497
- if filename not in processed_rars:
498
- success = extract_and_upload_rar(rar_file_path, processed_rars, uploaded_folders, processed_video_courses)
499
- if success:
500
- processed_rars.add(filename)
501
- save_processed_files_state(processed_rars)
502
- processing_status["processed_files"] += 1
503
-
504
- # Delete the RAR file after successful processing
505
- log_message(f"πŸ—‘οΈ Deleting processed RAR: {filename}")
506
- try:
507
- os.remove(rar_file_path)
508
- log_message(f"βœ… Deleted RAR file: {filename}")
509
- except Exception as e:
510
- log_message(f"⚠️ Could not delete {rar_file_path}: {e}")
511
 
512
- # Add delay between processing files
513
- time.sleep(PROCESSING_DELAY)
 
 
 
 
 
 
 
 
514
 
515
  # If no new files were downloaded and all local files are processed, we're done
516
- if not downloaded_rar_paths:
517
  break
518
 
519
  except Exception as e:
@@ -531,8 +565,8 @@ async def root():
531
  <html>
532
  <head>
533
  <title>RAR & Video Processing Service</title>
534
- <meta charset="utf-8">
535
- <meta name="viewport" content="width=device-width, initial-scale=1">
536
  <style>
537
  body { font-family: Arial, sans-serif; margin: 20px; background-color: #f5f5f5; }
538
  .container { max-width: 1200px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
@@ -546,59 +580,59 @@ async def root():
546
  .stats { display: flex; gap: 20px; margin: 20px 0; }
547
  .stat-item { background: #f0f0f0; padding: 10px; border-radius: 5px; text-align: center; flex: 1; }
548
  .start-form { margin-top: 20px; padding: 15px; border: 1px solid #ddd; border-radius: 5px; background: #f9f9f9; }
549
- .start-form input[type="number"] { width: calc(100% - 120px); padding: 8px; margin-right: 10px; border: 1px solid #ccc; border-radius: 4px; }
550
  .start-form button { padding: 8px 15px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; }
551
  .start-form button:hover { background: #45a049; }
552
  </style>
553
  </head>
554
  <body>
555
- <div class="container">
556
  <h1>πŸ”„ RAR & Video Processing Service</h1>
557
  <p>Automated extraction and upload of RAR files from BG1 to BG2 dataset, and video frame extraction/upload to BG3 dataset</p>
558
 
559
- <div class="status-card">
560
- <h3>Status: <span id="status">Stopped</span></h3>
561
- <p>Current File: <span id="current-file">None</span></p>
562
- <p>Last Update: <span id="last-update">Never</span></p>
563
  </div>
564
 
565
- <div class="stats">
566
- <div class="stat-item">
567
  <h4>Total Files (RARs)</h4>
568
- <span id="total-files">0</span>
569
  </div>
570
- <div class="stat-item">
571
  <h4>Processed (RARs)</h4>
572
- <span id="processed-files">0</span>
573
  </div>
574
- <div class="stat-item">
575
  <h4>Uploaded Folders (BG2)</h4>
576
- <span id="uploaded-rar-folders">0</span>
577
  </div>
578
- <div class="stat-item">
579
  <h4>Uploaded Video Courses (BG3)</h4>
580
- <span id="uploaded-video-courses">0</span>
581
  </div>
582
- <div class="stat-item">
583
  <h4>Failed</h4>
584
- <span id="failed-files">0</span>
585
  </div>
586
  </div>
587
 
588
- <div class="start-form">
589
  <h3>Start Processing from Specific Download Index</h3>
590
- <input type="number" id="start-index-input" placeholder="Enter start index (e.g., 0)" value="0">
591
- <button onclick="startProcessingWithIndex()">Start from Index</button>
592
  </div>
593
 
594
  <div>
595
- <button class="button" onclick="startProcessing()" id="start-btn">Start Processing (from last saved index)</button>
596
- <button class="button stop-button" onclick="stopProcessing()" id="stop-btn" disabled>Stop Processing</button>
597
- <button class="button" onclick="refreshStatus()">Refresh Status</button>
598
  </div>
599
 
600
  <h3>Logs</h3>
601
- <div class="logs" id="logs">Loading...</div>
602
  </div>
603
 
604
  <script>
@@ -615,7 +649,7 @@ async def root():
615
 
616
  async function startProcessingWithIndex() {
617
  const index = document.getElementById(\"start-index-input\").value;
618
- if (index === "" || isNaN(index)) {
619
  alert(\"Please enter a valid number for the start index.\");
620
  return;
621
  }
@@ -733,4 +767,6 @@ async def get_processed_video_courses():
733
  return {"processed_video_course_count": len(processed_video_courses), "course_names": list(processed_video_courses)}
734
 
735
  if __name__ == "__main__":
736
- uvicorn.run(app, host="0.0.0.0", port=7860)
 
 
 
284
 
285
  log_message(f"🎬 Processing videos in extracted RAR folder: {course_folder_name}")
286
 
287
+ video_files_found = []
288
  for root, _, files in os.walk(extracted_rar_folder):
289
  for file in files:
290
+ # Check for common video file extensions
291
+ if file.lower().endswith(('.mp4', '.avi', '.mov', '.mkv', '.flv', '.wmv')):
292
+ video_files_found.append(os.path.join(root, file))
293
 
294
+ if not video_files_found:
295
+ log_message(f"⚠️ No video files found in {course_folder_name}. Skipping video frame extraction.")
296
  return False # Indicate no video processing was done
297
 
298
  course_video_extract_dir = os.path.join(VIDEO_FRAMES_EXTRACT_FOLDER, course_folder_name)
299
  os.makedirs(course_video_extract_dir, exist_ok=True)
300
 
301
+ frames_extracted_count = 0
302
+ for video_path in video_files_found:
303
+ video_basename = os.path.splitext(os.path.basename(video_path))[0]
304
  # Create a unique output folder for frames from this video within the course's frame directory
305
  video_output_folder = os.path.join(course_video_extract_dir, video_basename)
306
 
307
+ if extract_frames(video_path, video_output_folder, VIDEO_FRAME_FPS):
308
+ frames_extracted_count += 1
309
+ else:
310
+ log_message(f"❌ Failed to extract frames from {os.path.basename(video_path)}. Continuing with other videos.")
311
  # Clean up partially extracted frames for this video
312
  if os.path.exists(video_output_folder):
313
  shutil.rmtree(video_output_folder)
314
 
315
  # Check if any frames were extracted for the entire course folder
316
+ if frames_extracted_count == 0:
317
  log_message(f"⚠️ No frames extracted for any video in {course_folder_name}. Skipping zipping and upload to BG3.")
318
+ if os.path.exists(course_video_extract_dir):
319
+ shutil.rmtree(course_video_extract_dir)
320
  return False
321
 
322
  course_zip_path = os.path.join(ZIPPED_FRAMES_FOLDER, f"{course_folder_name}_frames.zip")
 
350
  current_extract_folder = os.path.join(EXTRACT_FOLDER, f"{folder_name}_extracted")
351
 
352
  # Check if RAR is already processed (uploaded to BG2 or video frames processed)
353
+ # This logic needs to be careful. If BG2 is not set, we only care about video processing.
354
+ # If video processing is not needed, we only care about BG2 upload.
355
+ is_bg2_processed = (not DEST_REPO_ID_RAR) or (get_folder_hash(folder_name) in uploaded_folders_set)
356
+ is_bg3_processed = (folder_name in processed_video_courses_set)
357
+
358
+ if filename in processed_rars_set and is_bg2_processed and is_bg3_processed:
359
  log_message(f"⏩ {filename} already fully processed, skipping.")
360
  return True
361
 
 
 
 
362
  # If BG2 upload is enabled and folder already uploaded to BG2, skip RAR extraction/upload to BG2
363
+ # but still proceed to video processing if not already done.
364
+ if DEST_REPO_ID_RAR and get_folder_hash(folder_name) in uploaded_folders_set and not is_bg3_processed:
365
+ log_message(f"πŸ”’ Folder \'{folder_name}\' already uploaded to BG2 (hash: {get_folder_hash(folder_name)[:8]}...), skipping RAR upload.")
366
+ # If the extracted folder doesn't exist, we can't process videos from it.
367
+ # This scenario might happen if the previous run was interrupted after BG2 upload but before video processing cleanup.
368
+ if not os.path.exists(current_extract_folder):
369
+ log_message(f"⚠️ Extracted folder {current_extract_folder} not found for video processing. Attempting re-extraction for video processing.")
370
+ # Fall through to re-extract and process videos
371
+ else:
372
+ # Proceed to video processing if not already done
373
+ log_message(f"Continuing with video processing for {filename}.")
374
+ video_processed = process_video_frames_and_upload(current_extract_folder, processed_video_courses_set)
375
+ if video_processed:
376
+ processed_rars_set.add(filename)
377
+ save_processed_files_state(processed_rars_set)
378
+ return video_processed
379
 
380
  log_message(f"πŸ“¦ Attempting to extract: {filename}")
381
  os.makedirs(current_extract_folder, exist_ok=True)
 
412
  log_message(f"⬆️ Uploading to BG2: {path_in_repo}")
413
 
414
  try:
415
+ if upload_file_to_hf(
416
  local_path=local_path,
417
  path_in_repo=path_in_repo,
418
  repo_id=DEST_REPO_ID_RAR
419
+ ):
420
+ upload_count += 1
421
+ else:
422
+ log_message(f"❌ Failed to upload {path_in_repo} to BG2. Skipping remaining uploads for this RAR.")
423
+ # Consider if you want to fail the whole RAR processing here or continue.
424
+ # For now, we'll continue but log the failure.
425
+
426
  except Exception as upload_error:
427
  log_message(f"❌ Failed to upload {path_in_repo} to BG2: {upload_error}")
428
+ # Don't re-raise, allow other files to be attempted
429
 
430
+ if upload_count > 0: # Only mark as uploaded if at least one file was successfully uploaded
431
+ log_message(f"βœ… Successfully uploaded {upload_count} files from {filename} to BG2")
432
+ # Mark folder as uploaded to BG2 using hash
433
+ uploaded_folders_set.add(folder_hash)
434
+ save_uploaded_folders(uploaded_folders_set)
435
+ processing_status["uploaded_rar_folders"] = len(uploaded_folders_set)
436
+ log_message(f"πŸ”’ Folder \'{folder_name}\' locked in BG2 repo (hash: {folder_hash[:8]}...)")
437
+ else:
438
+ log_message(f"⚠️ No files were successfully uploaded from {filename} to BG2.")
439
  else:
440
  log_message("Skipping upload to BG2 as DEST_REPO_ID_RAR is not set.")
441
 
442
  # Now process video frames from the extracted content and upload to BG3
443
+ video_processed = process_video_frames_and_upload(current_extract_folder, processed_video_courses_set)
444
 
445
+ # Mark RAR as processed only if both BG2 (if enabled) and BG3 processing are successful
446
+ # Or if BG2 is not enabled, only BG3 processing needs to be successful
447
+ if (not DEST_REPO_ID_RAR or (folder_hash in uploaded_folders_set)) and video_processed:
448
+ return True
449
+ elif DEST_REPO_ID_RAR and not (folder_hash in uploaded_folders_set):
450
+ log_message(f"❌ RAR processing failed for {filename}: BG2 upload was not successful.")
451
+ return False
452
+ elif not video_processed:
453
+ log_message(f"❌ RAR processing failed for {filename}: Video frame processing was not successful.")
454
+ return False
455
+ else:
456
+ # This case should ideally not be reached if the above logic is exhaustive
457
+ log_message(f"❌ RAR processing failed for {filename}: Unknown reason.")
458
+ return False
459
 
460
  except subprocess.CalledProcessError as e:
461
  error_msg = f"RAR extraction failed (exit {e.returncode}): {e.stderr.strip()}"
 
503
  downloaded_rar_paths, next_download_index = download_rar_files(download_start_index, CHUNK_SIZE)
504
  save_download_state(next_download_index)
505
 
 
 
 
 
 
 
 
 
 
 
 
 
506
  # 2. Process all available RAR files (downloaded + existing)
507
  all_local_rars = sorted([os.path.join(DOWNLOAD_FOLDER, f) for f in os.listdir(DOWNLOAD_FOLDER) if f.endswith(".rar")])
508
  processed_rars = load_processed_files_state()
509
  processing_status["total_files"] = len(all_local_rars)
510
+ # Recalculate processed_files based on actual processed_rars_set to be accurate
511
  processing_status["processed_files"] = len(processed_rars)
512
 
513
+ # Filter out RARs that are already fully processed based on current state
514
+ rars_to_process = []
515
  for rar_file_path in all_local_rars:
516
+ filename = os.path.basename(rar_file_path)
517
+ folder_name = filename.replace(".rar", "")
518
+ is_bg2_processed = (not DEST_REPO_ID_RAR) or (get_folder_hash(folder_name) in uploaded_folders)
519
+ is_bg3_processed = (folder_name in processed_video_courses)
520
+
521
+ if not (filename in processed_rars and is_bg2_processed and is_bg3_processed):
522
+ rars_to_process.append(rar_file_path)
523
+
524
+ if not downloaded_rar_paths and not rars_to_process:
525
+ log_message("βœ… No more RAR files to download or process. Stopping...")
526
+ break
527
+
528
+ for rar_file_path in rars_to_process:
529
  if not processing_status["is_running"]:
530
  break
531
 
532
  filename = os.path.basename(rar_file_path)
533
+ success = extract_and_upload_rar(rar_file_path, processed_rars, uploaded_folders, processed_video_courses)
534
+ if success:
535
+ # processed_rars.add(filename) is handled inside extract_and_upload_rar now for better atomicity
536
+ # processing_status["processed_files"] += 1 is also handled inside
 
 
 
 
 
 
 
 
 
 
537
 
538
+ # Delete the RAR file after successful processing
539
+ log_message(f"πŸ—‘οΈ Deleting processed RAR: {filename}")
540
+ try:
541
+ os.remove(rar_file_path)
542
+ log_message(f"βœ… Deleted RAR file: {filename}")
543
+ except Exception as e:
544
+ log_message(f"⚠️ Could not delete {rar_file_path}: {e}")
545
+
546
+ # Add delay between processing files
547
+ time.sleep(PROCESSING_DELAY)
548
 
549
  # If no new files were downloaded and all local files are processed, we're done
550
+ if not downloaded_rar_paths and not rars_to_process:
551
  break
552
 
553
  except Exception as e:
 
565
  <html>
566
  <head>
567
  <title>RAR & Video Processing Service</title>
568
+ <meta charset=\"utf-8\">
569
+ <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
570
  <style>
571
  body { font-family: Arial, sans-serif; margin: 20px; background-color: #f5f5f5; }
572
  .container { max-width: 1200px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
 
580
  .stats { display: flex; gap: 20px; margin: 20px 0; }
581
  .stat-item { background: #f0f0f0; padding: 10px; border-radius: 5px; text-align: center; flex: 1; }
582
  .start-form { margin-top: 20px; padding: 15px; border: 1px solid #ddd; border-radius: 5px; background: #f9f9f9; }
583
+ .start-form input[type=\"number\"] { width: calc(100% - 120px); padding: 8px; margin-right: 10px; border: 1px solid #ccc; border-radius: 4px; }
584
  .start-form button { padding: 8px 15px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; }
585
  .start-form button:hover { background: #45a049; }
586
  </style>
587
  </head>
588
  <body>
589
+ <div class=\"container\">
590
  <h1>πŸ”„ RAR & Video Processing Service</h1>
591
  <p>Automated extraction and upload of RAR files from BG1 to BG2 dataset, and video frame extraction/upload to BG3 dataset</p>
592
 
593
+ <div class=\"status-card\">
594
+ <h3>Status: <span id=\"status\">Stopped</span></h3>
595
+ <p>Current File: <span id=\"current-file\">None</span></p>
596
+ <p>Last Update: <span id=\"last-update\">Never</span></p>
597
  </div>
598
 
599
+ <div class=\"stats\">
600
+ <div class=\"stat-item\">
601
  <h4>Total Files (RARs)</h4>
602
+ <span id=\"total-files\">0</span>
603
  </div>
604
+ <div class=\"stat-item\">
605
  <h4>Processed (RARs)</h4>
606
+ <span id=\"processed-files\">0</span>
607
  </div>
608
+ <div class=\"stat-item\">
609
  <h4>Uploaded Folders (BG2)</h4>
610
+ <span id=\"uploaded-rar-folders\">0</span>
611
  </div>
612
+ <div class=\"stat-item\">
613
  <h4>Uploaded Video Courses (BG3)</h4>
614
+ <span id=\"uploaded-video-courses\">0</span>
615
  </div>
616
+ <div class=\"stat-item\">
617
  <h4>Failed</h4>
618
+ <span id=\"failed-files\">0</span>
619
  </div>
620
  </div>
621
 
622
+ <div class=\"start-form\">
623
  <h3>Start Processing from Specific Download Index</h3>
624
+ <input type=\"number\" id=\"start-index-input\" placeholder=\"Enter start index (e.g., 0)\" value=\"0\">
625
+ <button onclick=\"startProcessingWithIndex()\">Start from Index</button>
626
  </div>
627
 
628
  <div>
629
+ <button class=\"button\" onclick=\"startProcessing()\" id=\"start-btn\">Start Processing (from last saved index)</button>
630
+ <button class=\"button stop-button\" onclick=\"stopProcessing()\" id=\"stop-btn\" disabled>Stop Processing</button>
631
+ <button class=\"button\" onclick=\"refreshStatus()\">Refresh Status</button>
632
  </div>
633
 
634
  <h3>Logs</h3>
635
+ <div class=\"logs\" id=\"logs\">Loading...</div>
636
  </div>
637
 
638
  <script>
 
649
 
650
  async function startProcessingWithIndex() {
651
  const index = document.getElementById(\"start-index-input\").value;
652
+ if (index === \"\" || isNaN(index)) {
653
  alert(\"Please enter a valid number for the start index.\");
654
  return;
655
  }
 
767
  return {"processed_video_course_count": len(processed_video_courses), "course_names": list(processed_video_courses)}
768
 
769
  if __name__ == "__main__":
770
+ uvicorn.run(app, host="0.0.0.0", port=7860)
771
+
772
+