|
|
|
from fastapi import FastAPI, HTTPException, Response |
|
from fastapi.staticfiles import StaticFiles |
|
from pydantic import BaseModel |
|
import subprocess |
|
import os |
|
import shutil |
|
import logging |
|
from datetime import datetime |
|
import tempfile |
|
from pathlib import Path |
|
from dotenv import load_dotenv |
|
from urllib.parse import quote |
|
|
|
|
|
logging.basicConfig(level=logging.INFO) |
|
logger = logging.getLogger(__name__) |
|
|
|
def env_to_cookies(env_content: str, output_file: str) -> None: |
|
"""Convert environment variable content to cookie file""" |
|
try: |
|
|
|
if '="' not in env_content: |
|
raise ValueError("Invalid env content format") |
|
content = env_content.split('="', 1)[1].strip('"') |
|
|
|
|
|
cookie_content = content.replace('\\n', '\n') |
|
|
|
|
|
with open(output_file, 'w') as f: |
|
f.write(cookie_content) |
|
|
|
logger.info(f"Successfully created cookie file at {output_file}") |
|
except Exception as e: |
|
logger.error(f"Error creating cookie file: {str(e)}") |
|
raise ValueError(f"Error converting to cookie file: {str(e)}") |
|
|
|
def get_cookies() -> str: |
|
"""Get cookies from environment variable""" |
|
load_dotenv() |
|
cookie_content = os.getenv('COOKIES') |
|
if not cookie_content: |
|
raise ValueError("COOKIES environment variable not set") |
|
return cookie_content |
|
|
|
def env_to_cookies_from_env(output_file: str) -> None: |
|
"""Convert environment variable from .env file to cookie file""" |
|
try: |
|
load_dotenv() |
|
env_content = os.getenv('COOKIES') |
|
logger.info("Retrieved cookies from environment variable") |
|
|
|
if not env_content: |
|
raise ValueError("COOKIES not found in environment variables") |
|
|
|
env_to_cookies(f'COOKIES="{env_content}"', output_file) |
|
except Exception as e: |
|
logger.error(f"Error creating cookie file from env: {str(e)}") |
|
raise ValueError(f"Error converting to cookie file: {str(e)}") |
|
|
|
app = FastAPI( |
|
title="GAMDL API", |
|
description="API for downloading Google Drive files using gamdl", |
|
version="1.0.0" |
|
) |
|
|
|
|
|
|
|
|
|
DOWNLOADS_DIR = "downloads" |
|
os.makedirs(DOWNLOADS_DIR, exist_ok=True) |
|
|
|
|
|
class CacheControlStaticFiles(StaticFiles): |
|
def __init__(self, directory: str): |
|
super().__init__(directory=directory) |
|
|
|
async def get_response(self, path: str, scope): |
|
response = await super().get_response(path, scope) |
|
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate" |
|
response.headers["Pragma"] = "no-cache" |
|
response.headers["Expires"] = "0" |
|
return response |
|
|
|
app.mount("/files", CacheControlStaticFiles(directory=DOWNLOADS_DIR), name="files") |
|
|
|
class DownloadRequest(BaseModel): |
|
url: str |
|
|
|
class DownloadResponse(BaseModel): |
|
success: bool |
|
message: str |
|
filename: str = None |
|
download_url: str = None |
|
file_size: int = None |
|
|
|
|
|
@app.post("/download", response_model=DownloadResponse) |
|
async def download_file(request: DownloadRequest): |
|
try: |
|
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") |
|
download_subdir = os.path.join(DOWNLOADS_DIR, timestamp) |
|
os.makedirs(download_subdir, exist_ok=True) |
|
|
|
|
|
logger.info(f"Current working directory: {os.getcwd()}") |
|
logger.info(f"Download directory: {download_subdir}") |
|
|
|
|
|
cookie_path = os.path.join(download_subdir, "cookies.txt") |
|
logger.info(f"Creating cookies file at: {cookie_path}") |
|
env_to_cookies_from_env(cookie_path) |
|
|
|
|
|
original_dir = os.getcwd() |
|
os.chdir(download_subdir) |
|
|
|
|
|
cmd = ["votify", "--audio-quality", "vorbis-low", request.url] |
|
logger.info(f"Executing command: {' '.join(cmd)}") |
|
|
|
|
|
process = subprocess.run( |
|
cmd, |
|
capture_output=True, |
|
text=True |
|
) |
|
|
|
|
|
logger.info(f"Command stdout: {process.stdout}") |
|
logger.info(f"Command stderr: {process.stderr}") |
|
|
|
|
|
process.check_returncode() |
|
|
|
|
|
files = [f for f in os.listdir() if f != "cookies.txt"] |
|
logger.info(f"Files in download directory: {files}") |
|
|
|
if not files: |
|
raise Exception("No files found in download directory after download attempt") |
|
|
|
downloaded_file = files[0] |
|
logger.info(f"Downloaded file: {downloaded_file}") |
|
|
|
|
|
file_size = os.path.getsize(downloaded_file) |
|
|
|
|
|
space_url = os.getenv("SPACE_URL", "https://chrunos-vob.hf.space") |
|
encoded_filename = quote(downloaded_file) |
|
download_url = f"{space_url}/files/{timestamp}/{encoded_filename}" |
|
logger.info(f"Generated download URL: {download_url}") |
|
|
|
|
|
|
|
|
|
return DownloadResponse( |
|
success=True, |
|
message="File downloaded successfully", |
|
filename=downloaded_file, |
|
download_url=download_url, |
|
file_size=file_size |
|
) |
|
|
|
except subprocess.CalledProcessError as e: |
|
logger.error(f"Download process failed: stdout={e.stdout}, stderr={e.stderr}") |
|
raise HTTPException( |
|
status_code=400, |
|
detail=f"Failed to download: {e.stderr or e.stdout or str(e)}" |
|
) |
|
except Exception as e: |
|
logger.error(f"Unexpected error: {str(e)}", exc_info=True) |
|
raise HTTPException( |
|
status_code=500, |
|
detail=f"Error: {str(e)}" |
|
) |
|
finally: |
|
|
|
if 'original_dir' in locals(): |
|
os.chdir(original_dir) |
|
|
|
|
|
|
|
@app.get("/test") |
|
async def test(): |
|
"""Test endpoint to verify setup""" |
|
try: |
|
|
|
temp_cookie = os.path.join(DOWNLOADS_DIR, "test_cookies.txt") |
|
env_to_cookies_from_env(temp_cookie) |
|
|
|
|
|
process = subprocess.run(["votify", "--version"], capture_output=True, text=True) |
|
|
|
return { |
|
"gamdl_version": process.stdout.strip(), |
|
"cookies_created": os.path.exists(temp_cookie), |
|
"cookies_size": os.path.getsize(temp_cookie) if os.path.exists(temp_cookie) else 0, |
|
"installed": True, |
|
"error": process.stderr if process.stderr else None |
|
} |
|
except Exception as e: |
|
return { |
|
"installed": False, |
|
"error": str(e) |
|
} |