import os import zipfile import aiofiles import httpx import yaml from fastapi import APIRouter, Request # 导入FastAPI组件 from starlette.responses import FileResponse from app.api.models.APIResponseModel import ErrorResponseModel # 导入响应模型 from crawlers.hybrid.hybrid_crawler import HybridCrawler # 导入混合数据爬虫 router = APIRouter() HybridCrawler = HybridCrawler() # 读取上级再上级目录的配置文件 config_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))), 'config.yaml') with open(config_path, 'r', encoding='utf-8') as file: config = yaml.safe_load(file) async def fetch_data(url: str): headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' } async with httpx.AsyncClient() as client: response = await client.get(url, headers=headers) response.raise_for_status() # 确保响应是成功的 return response @router.get("/download", summary="在线下载抖音|TikTok视频/图片/Online download Douyin|TikTok video/image") async def download_file_hybrid(request: Request, url: str, prefix: bool = True, with_watermark: bool = False): # 是否开启此端点/Whether to enable this endpoint if not config["API"]["Download_Switch"]: code = 400 message = "Download endpoint is disabled." return ErrorResponseModel(code=code, message=message, router=request.url.path, params=dict(request.query_params)) # 开始解析数据/Start parsing data try: data = await HybridCrawler.hybrid_parsing_single_video(url, minimal=True) except Exception as e: code = 400 return ErrorResponseModel(code=code, message=str(e), router=request.url.path, params=dict(request.query_params)) # 开始下载文件/Start downloading files try: data_type = data.get('type') platform = data.get('platform') aweme_id = data.get('aweme_id') file_prefix = config.get("API").get("Download_File_Prefix") if prefix else '' download_path = os.path.join(config.get("API").get("Download_Path"), f"{platform}_{data_type}") # 确保目录存在/Ensure the directory exists os.makedirs(download_path, exist_ok=True) # 下载视频文件/Download video file if data_type == 'video': file_name = f"{file_prefix}{platform}_{aweme_id}.mp4" if not with_watermark else f"{file_prefix}{platform}_{aweme_id}_watermark.mp4" url = data.get('video_data').get('nwm_video_url_HQ') if not with_watermark else data.get('video_data').get( 'wm_video_url_HQ') file_path = os.path.join(download_path, file_name) # 判断文件是否存在,存在就直接返回 if os.path.exists(file_path): return FileResponse(path=file_path, media_type='video/mp4', filename=file_name) # 获取视频文件 response = await fetch_data(url) # 保存文件 async with aiofiles.open(file_path, 'wb') as out_file: await out_file.write(response.content) # 返回文件内容 return FileResponse(path=file_path, filename=file_name, media_type="video/mp4") # 下载图片文件/Download image file elif data_type == 'image': # 压缩文件属性/Compress file properties zip_file_name = f"{file_prefix}{platform}_{aweme_id}_images.zip" if not with_watermark else f"{file_prefix}{platform}_{aweme_id}_images_watermark.zip" zip_file_path = os.path.join(download_path, zip_file_name) # 判断文件是否存在,存在就直接返回、 if os.path.exists(zip_file_path): return FileResponse(path=zip_file_path, filename=zip_file_name, media_type="application/zip") # 获取图片文件/Get image file urls = data.get('image_data').get('no_watermark_image_list') if not with_watermark else data.get( 'image_data').get('watermark_image_list') image_file_list = [] for url in urls: # 请求图片文件/Request image file response = await fetch_data(url) index = int(urls.index(url)) content_type = response.headers.get('content-type') file_format = content_type.split('/')[1] file_name = f"{file_prefix}{platform}_{aweme_id}_{index + 1}.{file_format}" if not with_watermark else f"{file_prefix}{platform}_{aweme_id}_{index + 1}_watermark.{file_format}" file_path = os.path.join(download_path, file_name) image_file_list.append(file_path) # 保存文件/Save file async with aiofiles.open(file_path, 'wb') as out_file: await out_file.write(response.content) # 压缩文件/Compress file with zipfile.ZipFile(zip_file_path, 'w') as zip_file: for image_file in image_file_list: zip_file.write(image_file, os.path.basename(image_file)) # 返回压缩文件/Return compressed file return FileResponse(path=zip_file_path, filename=zip_file_name, media_type="application/zip") # 异常处理/Exception handling except Exception as e: code = 400 return ErrorResponseModel(code=code, message=str(e), router=request.url.path, params=dict(request.query_params))