Fred808 commited on
Commit
0cc8a49
Β·
verified Β·
1 Parent(s): f1ea03c

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +123 -2
main.py CHANGED
@@ -18,9 +18,10 @@ from pathlib import Path
18
  from typing import Optional, Dict, Any, List
19
  from datetime import datetime
20
  from concurrent.futures import ThreadPoolExecutor
 
21
 
22
  from fastapi import FastAPI, HTTPException, BackgroundTasks, Request, UploadFile, File, Form
23
- from fastapi.responses import FileResponse, HTMLResponse
24
  from fastapi.middleware.cors import CORSMiddleware
25
  from pydantic import BaseModel, HttpUrl
26
  import uvicorn
@@ -549,6 +550,39 @@ class BatchYouTubeDownloader:
549
  # Global downloader instance
550
  downloader = BatchYouTubeDownloader()
551
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
552
  @app.get("/", response_class=HTMLResponse)
553
  async def read_root():
554
  """Serve the main HTML interface with batch support and cookie upload"""
@@ -688,6 +722,10 @@ async def read_root():
688
  <strong>πŸ†• Progress Tracking:</strong> Monitor batch download progress in real-time
689
  </div>
690
 
 
 
 
 
691
  <div class="feature">
692
  <strong>πŸͺ Cookie Support:</strong> Upload cookies for better success rates
693
  </div>
@@ -721,6 +759,8 @@ async def read_root():
721
  <li><code>POST /batch/info</code> - Get info for multiple videos</li>
722
  <li><code>POST /batch/download</code> - Start batch download</li>
723
  <li><code>GET /batch/status/{batch_id}</code> - Check batch progress</li>
 
 
724
  </ul>
725
  </div>
726
 
@@ -832,7 +872,8 @@ async def health_check():
832
  "Smart Retry Logic",
833
  "Enhanced Headers",
834
  "Concurrent Downloads",
835
- "Progress Tracking"
 
836
  ]
837
 
838
  return HealthResponse(
@@ -1019,6 +1060,86 @@ async def get_batch_status(batch_id: str):
1019
  status = batch_status_store[batch_id]
1020
  return BatchStatus(**status)
1021
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1022
  @app.get("/video/file/{filename}")
1023
  async def download_file(filename: str):
1024
  """Serve downloaded files"""
 
18
  from typing import Optional, Dict, Any, List
19
  from datetime import datetime
20
  from concurrent.futures import ThreadPoolExecutor
21
+ import io
22
 
23
  from fastapi import FastAPI, HTTPException, BackgroundTasks, Request, UploadFile, File, Form
24
+ from fastapi.responses import FileResponse, HTMLResponse, StreamingResponse
25
  from fastapi.middleware.cors import CORSMiddleware
26
  from pydantic import BaseModel, HttpUrl
27
  import uvicorn
 
550
  # Global downloader instance
551
  downloader = BatchYouTubeDownloader()
552
 
553
+ def generate_multipart_response(file_paths: List[str], boundary: str = None):
554
+ """Generate a multipart response with multiple files"""
555
+ if boundary is None:
556
+ boundary = f"----formdata-{uuid.uuid4().hex}"
557
+
558
+ def file_generator():
559
+ for file_path in file_paths:
560
+ if os.path.exists(file_path):
561
+ filename = os.path.basename(file_path)
562
+ file_size = os.path.getsize(file_path)
563
+
564
+ # Write multipart boundary and headers
565
+ yield f"--{boundary}\r\n".encode()
566
+ yield f"Content-Disposition: form-data; name=\"file\"; filename=\"{filename}\"\r\n".encode()
567
+ yield f"Content-Type: application/octet-stream\r\n".encode()
568
+ yield f"Content-Length: {file_size}\r\n".encode()
569
+ yield "\r\n".encode()
570
+
571
+ # Stream file content
572
+ with open(file_path, 'rb') as f:
573
+ while True:
574
+ chunk = f.read(8192)
575
+ if not chunk:
576
+ break
577
+ yield chunk
578
+
579
+ yield "\r\n".encode()
580
+
581
+ # Final boundary
582
+ yield f"--{boundary}--\r\n".encode()
583
+
584
+ return file_generator(), boundary
585
+
586
  @app.get("/", response_class=HTMLResponse)
587
  async def read_root():
588
  """Serve the main HTML interface with batch support and cookie upload"""
 
722
  <strong>πŸ†• Progress Tracking:</strong> Monitor batch download progress in real-time
723
  </div>
724
 
725
+ <div class="feature new-feature">
726
+ <strong>πŸ†• Bulk File Download:</strong> Download all files from a batch in one request
727
+ </div>
728
+
729
  <div class="feature">
730
  <strong>πŸͺ Cookie Support:</strong> Upload cookies for better success rates
731
  </div>
 
759
  <li><code>POST /batch/info</code> - Get info for multiple videos</li>
760
  <li><code>POST /batch/download</code> - Start batch download</li>
761
  <li><code>GET /batch/status/{batch_id}</code> - Check batch progress</li>
762
+ <li><code>GET /batch/download-all/{batch_id}</code> - Download all files from batch</li>
763
+ <li><code>GET /download-all</code> - Download ALL files from server</li>
764
  </ul>
765
  </div>
766
 
 
872
  "Smart Retry Logic",
873
  "Enhanced Headers",
874
  "Concurrent Downloads",
875
+ "Progress Tracking",
876
+ "Bulk File Download"
877
  ]
878
 
879
  return HealthResponse(
 
1060
  status = batch_status_store[batch_id]
1061
  return BatchStatus(**status)
1062
 
1063
+ @app.get("/batch/download-all/{batch_id}")
1064
+ async def download_all_batch_files(batch_id: str):
1065
+ """Download all files from a completed batch as multipart response"""
1066
+ try:
1067
+ if batch_id not in batch_status_store:
1068
+ raise HTTPException(status_code=404, detail="Batch not found")
1069
+
1070
+ batch_status = batch_status_store[batch_id]
1071
+
1072
+ if batch_status["status"] != "completed":
1073
+ raise HTTPException(
1074
+ status_code=400,
1075
+ detail=f"Batch is not completed yet. Current status: {batch_status['status']}"
1076
+ )
1077
+
1078
+ # Get all successfully downloaded files
1079
+ file_paths = []
1080
+ for result in batch_status["results"]:
1081
+ if result.get("success") and result.get("download_path"):
1082
+ file_path = result["download_path"]
1083
+ if os.path.exists(file_path):
1084
+ file_paths.append(file_path)
1085
+
1086
+ if not file_paths:
1087
+ raise HTTPException(status_code=404, detail="No files found for this batch")
1088
+
1089
+ # Generate multipart response
1090
+ file_generator, boundary = generate_multipart_response(file_paths)
1091
+
1092
+ headers = {
1093
+ "Content-Type": f"multipart/form-data; boundary={boundary}",
1094
+ "Content-Disposition": f"attachment; filename=\"batch_{batch_id}_files.multipart\""
1095
+ }
1096
+
1097
+ return StreamingResponse(
1098
+ file_generator,
1099
+ headers=headers,
1100
+ media_type=f"multipart/form-data; boundary={boundary}"
1101
+ )
1102
+
1103
+ except HTTPException:
1104
+ raise
1105
+ except Exception as e:
1106
+ logger.error(f"Error downloading batch files: {e}")
1107
+ raise HTTPException(status_code=500, detail=str(e))
1108
+
1109
+ @app.get("/download-all")
1110
+ async def download_all_files():
1111
+ """Download all files from the server as multipart response"""
1112
+ try:
1113
+ # Get all files from the download directory
1114
+ download_dir = Path(downloader.download_dir)
1115
+ all_files = [f for f in download_dir.glob("*") if f.is_file()]
1116
+
1117
+ if not all_files:
1118
+ raise HTTPException(status_code=404, detail="No files found on server")
1119
+
1120
+ # Convert to string paths
1121
+ file_paths = [str(f) for f in all_files]
1122
+
1123
+ # Generate multipart response
1124
+ file_generator, boundary = generate_multipart_response(file_paths)
1125
+
1126
+ headers = {
1127
+ "Content-Type": f"multipart/form-data; boundary={boundary}",
1128
+ "Content-Disposition": f"attachment; filename=\"all_server_files.multipart\""
1129
+ }
1130
+
1131
+ return StreamingResponse(
1132
+ file_generator,
1133
+ headers=headers,
1134
+ media_type=f"multipart/form-data; boundary={boundary}"
1135
+ )
1136
+
1137
+ except HTTPException:
1138
+ raise
1139
+ except Exception as e:
1140
+ logger.error(f"Error downloading all files: {e}")
1141
+ raise HTTPException(status_code=500, detail=str(e))
1142
+
1143
  @app.get("/video/file/{filename}")
1144
  async def download_file(filename: str):
1145
  """Serve downloaded files"""