Fred808 commited on
Commit
40c8cc7
·
verified ·
1 Parent(s): 3062d44

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +42 -123
main.py CHANGED
@@ -8,7 +8,6 @@ import os
8
  import sys
9
  import subprocess
10
  import json
11
- import tempfile
12
  import random
13
  import time
14
  import asyncio
@@ -18,10 +17,9 @@ from pathlib import Path
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
@@ -50,7 +48,7 @@ class BatchDownloadRequest(BaseModel):
50
  quality: str = "best"
51
  audio_only: bool = False
52
  use_cookies: Optional[bool] = None
53
- max_concurrent: int = 2 # Limit concurrent downloads
54
 
55
  class VideoInfo(BaseModel):
56
  title: str
@@ -105,11 +103,16 @@ class HealthResponse(BaseModel):
105
  strategies_enabled: List[str]
106
  batch_support: bool
107
 
 
 
 
 
 
108
  # Initialize FastAPI app
109
  app = FastAPI(
110
  title="Batch YouTube Video Downloader",
111
  description="Download YouTube videos individually or in batches with cookie support",
112
- version="4.0.0",
113
  docs_url="/docs",
114
  redoc_url="/redoc"
115
  )
@@ -550,39 +553,6 @@ class BatchYouTubeDownloader:
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"""
@@ -723,7 +693,7 @@ async def read_root():
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">
@@ -752,6 +722,7 @@ async def read_root():
752
  <ul>
753
  <li><code>POST /video/info</code> - Get single video information</li>
754
  <li><code>POST /video/download</code> - Download single video</li>
 
755
  </ul>
756
 
757
  <h4>Batch Operations:</h4>
@@ -759,8 +730,7 @@ async def read_root():
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
 
@@ -873,7 +843,7 @@ async def health_check():
873
  "Enhanced Headers",
874
  "Concurrent Downloads",
875
  "Progress Tracking",
876
- "Bulk File Download"
877
  ]
878
 
879
  return HealthResponse(
@@ -1060,85 +1030,35 @@ async def get_batch_status(batch_id: str):
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):
@@ -1180,5 +1100,4 @@ if __name__ == "__main__":
1180
  port=port,
1181
  reload=False,
1182
  log_level="info"
1183
- )
1184
-
 
8
  import sys
9
  import subprocess
10
  import json
 
11
  import random
12
  import time
13
  import asyncio
 
17
  from typing import Optional, Dict, Any, List
18
  from datetime import datetime
19
  from concurrent.futures import ThreadPoolExecutor
 
20
 
21
+ from fastapi import FastAPI, HTTPException, BackgroundTasks, UploadFile, File
22
+ from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
23
  from fastapi.middleware.cors import CORSMiddleware
24
  from pydantic import BaseModel, HttpUrl
25
  import uvicorn
 
48
  quality: str = "best"
49
  audio_only: bool = False
50
  use_cookies: Optional[bool] = None
51
+ max_concurrent: int = 2
52
 
53
  class VideoInfo(BaseModel):
54
  title: str
 
103
  strategies_enabled: List[str]
104
  batch_support: bool
105
 
106
+ class BatchFileListResponse(BaseModel):
107
+ batch_id: str
108
+ total_files: int
109
+ files: List[Dict[str, str]]
110
+
111
  # Initialize FastAPI app
112
  app = FastAPI(
113
  title="Batch YouTube Video Downloader",
114
  description="Download YouTube videos individually or in batches with cookie support",
115
+ version="4.0.2",
116
  docs_url="/docs",
117
  redoc_url="/redoc"
118
  )
 
553
  # Global downloader instance
554
  downloader = BatchYouTubeDownloader()
555
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
556
  @app.get("/", response_class=HTMLResponse)
557
  async def read_root():
558
  """Serve the main HTML interface with batch support and cookie upload"""
 
693
  </div>
694
 
695
  <div class="feature new-feature">
696
+ <strong>🆕 Individual File Downloads:</strong> Download each file separately
697
  </div>
698
 
699
  <div class="feature">
 
722
  <ul>
723
  <li><code>POST /video/info</code> - Get single video information</li>
724
  <li><code>POST /video/download</code> - Download single video</li>
725
+ <li><code>GET /video/file/{filename}</code> - Download a specific file</li>
726
  </ul>
727
 
728
  <h4>Batch Operations:</h4>
 
730
  <li><code>POST /batch/info</code> - Get info for multiple videos</li>
731
  <li><code>POST /batch/download</code> - Start batch download</li>
732
  <li><code>GET /batch/status/{batch_id}</code> - Check batch progress</li>
733
+ <li><code>GET /batch/files/{batch_id}</code> - Get list of downloadable files</li>
 
734
  </ul>
735
  </div>
736
 
 
843
  "Enhanced Headers",
844
  "Concurrent Downloads",
845
  "Progress Tracking",
846
+ "Individual File Downloads"
847
  ]
848
 
849
  return HealthResponse(
 
1030
  status = batch_status_store[batch_id]
1031
  return BatchStatus(**status)
1032
 
1033
+ @app.get("/batch/files/{batch_id}", response_model=BatchFileListResponse)
1034
+ async def get_batch_files(batch_id: str):
1035
+ """Get list of downloadable files for a batch"""
1036
+ if batch_id not in batch_status_store:
1037
+ raise HTTPException(status_code=404, detail="Batch not found")
1038
+
1039
+ batch_status = batch_status_store[batch_id]
1040
+
1041
+ if batch_status["status"] != "completed":
1042
+ raise HTTPException(
1043
+ status_code=400,
1044
+ detail=f"Batch is not completed yet. Current status: {batch_status['status']}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1045
  )
1046
+
1047
+ files = []
1048
+ for result in batch_status["results"]:
1049
+ if result.get("success") and result.get("download_path"):
1050
+ file_path = result["download_path"]
1051
+ if os.path.exists(file_path):
1052
+ files.append({
1053
+ "filename": os.path.basename(file_path),
1054
+ "url": f"/video/file/{os.path.basename(file_path)}"
1055
+ })
1056
+
1057
+ return BatchFileListResponse(
1058
+ batch_id=batch_id,
1059
+ total_files=len(files),
1060
+ files=files
1061
+ )
1062
 
1063
  @app.get("/video/file/{filename}")
1064
  async def download_file(filename: str):
 
1100
  port=port,
1101
  reload=False,
1102
  log_level="info"
1103
+ )