import sys import time from fastapi import FastAPI, BackgroundTasks, Request, HTTPException, Security from fastapi.security import APIKeyHeader from fastapi.responses import FileResponse from fastapi.concurrency import run_in_threadpool import yt_dlp import ffmpeg import urllib.parse import os from datetime import datetime, timedelta import schedule import requests import uvicorn import subprocess import json from dotenv import load_dotenv import mimetypes import tempfile from PIL import Image from io import BytesIO from pathlib import Path from fastapi.staticfiles import StaticFiles from collections import defaultdict from starlette.responses import JSONResponse import logging import gc from typing import Dict, Any import re import asyncio import cloudscraper tmp_dir = tempfile.gettempdir() BASE_URL = "https://chrunos-mmmm.hf.space" logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) def env_to_cookies(env_content: str, output_file: str) -> None: """Convert environment variable content back to cookie file""" try: # Extract content from env format if '="' not in env_content: raise ValueError("Invalid env content format") content = env_content.split('="', 1)[1].strip('"') # Replace escaped newlines with actual newlines cookie_content = content.replace('\\n', '\n') # Write to cookie file with open(output_file, 'w') as f: f.write(cookie_content) except Exception as e: raise ValueError(f"Error converting to cookie file: {str(e)}") def save_to_env_file(env_content: str, env_file: str = '.env') -> None: """Save environment variable content to .env file""" try: with open(env_file, 'w') as f: f.write(env_content) #print(f"Successfully saved to {env_file}") except Exception as e: raise ValueError(f"Error saving to env file: {str(e)}") def env_to_cookies_from_env(output_file: str) -> None: """Convert environment variable from .env file to cookie file""" try: load_dotenv() # Load from .env file env_content = os.getenv('FIREFOX_COOKIES') #print(f"Printing env content: \n{env_content}") if not env_content: raise ValueError("FIREFOX_COOKIES not found in .env file") env_to_cookies(f'FIREFOX_COOKIES="{env_content}"', output_file) except Exception as e: raise ValueError(f"Error converting to cookie file: {str(e)}") def get_cookies(): """Get cookies from environment variable""" load_dotenv() cookie_content = os.getenv('FIREFOX_COOKIES') #print(cookie_content) if not cookie_content: raise ValueError("FIREFOX_COOKIES environment variable not set") return cookie_content def create_temp_cookie_file(): """Create temporary cookie file from environment variable""" temp_cookie = tempfile.NamedTemporaryFile(mode='w+', delete=False, suffix='.txt') try: cookie_content = get_cookies() # Replace escaped newlines with actual newlines cookie_content = cookie_content.replace('\\n', '\n') temp_cookie.write() temp_cookie.flush() return Path(temp_cookie.name) finally: temp_cookie.close() load_dotenv() app = FastAPI() @app.get('/') def main(): return "API Is Running. If you want to use this API, contact Cody from chrunos.com" @app.get("/get_video_url") async def get_video_url(youtube_url: str): try: cookiefile = "firefox-cookies.txt" env_to_cookies_from_env("firefox-cookies.txt") # Add cookies ydl_opts["cookiefile"] = "firefox-cookies.txt" #create_temp_cookie_file() with yt_dlp.YoutubeDL(ydl_opts) as ydl: info = ydl.extract_info(youtube_url, download=False) return info except Exception as e: raise HTTPException(status_code=500, detail=str(e)) # Define a global temporary download directory global_download_dir = tempfile.mkdtemp() # Rate limiting dictionary class RateLimiter: def __init__(self, max_requests: int, time_window: timedelta): self.max_requests = max_requests self.time_window = time_window self.requests: Dict[str, list] = defaultdict(list) def _cleanup_old_requests(self, user_ip: str) -> None: """Remove requests that are outside the time window.""" current_time = time.time() self.requests[user_ip] = [ timestamp for timestamp in self.requests[user_ip] if current_time - timestamp < self.time_window.total_seconds() ] def is_rate_limited(self, user_ip: str) -> bool: """Check if the user has exceeded their rate limit.""" self._cleanup_old_requests(user_ip) # Get current count after cleanup current_count = len(self.requests[user_ip]) # Add current request timestamp (incrementing the count) current_time = time.time() self.requests[user_ip].append(current_time) # Check if user has exceeded the maximum requests return (current_count + 1) > self.max_requests def get_current_count(self, user_ip: str) -> int: """Get the current request count for an IP.""" self._cleanup_old_requests(user_ip) return len(self.requests[user_ip]) # Initialize rate limiter with 100 requests per day rate_limiter = RateLimiter( max_requests=12, time_window=timedelta(days=1) ) def get_user_ip(request: Request) -> str: """Helper function to get user's IP address.""" forwarded = request.headers.get("X-Forwarded-For") if forwarded: return forwarded.split(",")[0] return request.client.host class ApiRotator: def __init__(self, apis): self.apis = apis self.last_successful_index = None def get_prioritized_apis(self): if self.last_successful_index is not None: # Move the last successful API to the front rotated_apis = ( [self.apis[self.last_successful_index]] + self.apis[:self.last_successful_index] + self.apis[self.last_successful_index+1:] ) return rotated_apis return self.apis def update_last_successful(self, index): self.last_successful_index = index # In your function: api_rotator = ApiRotator([ "https://cobalt-api.kwiatekmiki.com", "http://34.107.254.11", "https://dwnld.nichind.dev", "http://2.56.214.74:9000", "http://109.107.189.229:9000", "https://dl01.yt-dl.click", "https://cobalt-api.ayo.tf" ]) async def get_track_download_url(video_url: str, quality: str) -> str: apis = api_rotator.get_prioritized_apis() session = cloudscraper.create_scraper() # Requires cloudscraper package headers = { "Accept": "application/json", "Content-Type": "application/json", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" } quality_lower = quality.lower() if quality_lower == "mp3": body_json = {"url": video_url, "audioFormat": "mp3", "downloadMode": "audio"} else: body_json = {"url": video_url, "videoQuality": quality, "filenameStyle": "pretty", "youtubeVideoCodec": "h264"} for i, api_url in enumerate(apis): try: logger.info(f"Attempting to get download URL from: {api_url}") y_url = video_url response = session.post( api_url, timeout=20, json=body_json, headers=headers ) logger.info(f"Response status: {response.status_code}") logger.info(f"Response content: {response.content}") if response.headers.get('content-type', '').startswith('application/json'): json_response = response.json() error_code = json_response.get("error", {}).get("code", "") if error_code == "error.api.content.video.age": logger.warning(f"Video unavailable error from {api_url}") break # Only break for specific error if "url" in json_response: api_rotator.update_last_successful(i) return json_response["url"] except Exception as e: logger.error(f"Failed with {api_url}: {str(e)}") continue logger.error(f"No download URL found") return {"error": "Download URL not found"} restricted_domain = "chrunos.com" @app.post("/maxs") async def download_high_quality_video(request: Request): user_ip = get_user_ip(request) if rate_limiter.is_rate_limited(user_ip): current_count = rate_limiter.get_current_count(user_ip) raise HTTPException( status_code=429, detail={ "error": "You have exceeded the maximum number of requests per day. Please try again tomorrow.", "url": "https://t.me/chrunoss" } ) data = await request.json() video_url = data.get('url') is_youtube_url = re.search(r'(youtube\.com|youtu\.be)', video_url) is not None if "t.me" in video_url or "chrunos.com" in video_url: return {"error": f"{video_url} is not supported", "url": "https://t.me/chrunoss"} quality = data.get('quality', '720') # Default to 720 if not specified logger.info(f'input: {video_url}, {quality}') # Check if the requested quality is above 1080p if int(quality) > 720: error_message = "Quality above 720p is for Premium Members Only. Please check the URL for more information." help_url = "https://chrunos.com/premium-shortcuts/" # Replace with your actual URL return {"error": error_message, "url": help_url} #cookiefile = "firefox-cookies.txt" #env_to_cookies_from_env("firefox-cookies.txt") timestamp = datetime.now().strftime('%Y%m%d%H%M%S') output_template = str(Path(global_download_dir) / f'%(title).70s_{timestamp}.%(ext)s') # Convert quality string to height height_map = { '480': 480, '720': 720, '1080': 1080 } max_height = height_map.get(quality, 1080) # Use the quality variable correctly # Determine format string based on quality format_str = f'bestvideo[height<={max_height}][vcodec^=avc]+bestaudio/best' ydl_opts = { 'format': format_str, 'outtmpl': output_template, 'quiet': True, 'no_warnings': True, 'noprogress': True, 'merge_output_format': 'mp4' } if is_youtube_url: dl_url = await get_track_download_url(video_url, quality) if dl_url and "http" in dl_url: return {"url": dl_url, "requests_remaining": rate_limiter.max_requests - rate_limiter.get_current_count(user_ip)} else: return { "error": "Failed to Fetch the video." } else: await run_in_threadpool(lambda: yt_dlp.YoutubeDL(ydl_opts).download([video_url])) downloaded_files = list(Path(global_download_dir).glob(f"*_{timestamp}.mp4")) if not downloaded_files: return {"error": "Download failed. Report error on Telegram", "url": "https://t.me/chrunoss"} downloaded_file = downloaded_files[0] encoded_filename = urllib.parse.quote(downloaded_file.name) download_url = f"{BASE_URL}/file/{encoded_filename}" gc.collect() return {"url": download_url} TRACT_API = os.getenv("EXTRACT_API") ALT_API = os.getenv("ALT_API") def extract_video_info(video_url: str) -> str: if "pornhub.com" in video_url: EXTRACT_API = TRACT_API else: EXTRACT_API = ALT_API api_url = f'{EXTRACT_API}?url={video_url}' logger.info(api_url) session = cloudscraper.create_scraper() try: response = session.get(api_url, timeout=20) if response.status_code == 200: json_response = response.json() result = [] # 检查 formats 列表是否存在且不为空 if 'formats' in json_response: for format_item in json_response['formats']: format_url = format_item.get('url') format_id = format_item.get('format_id') p_cookies = format_item.get('cookies') if format_id and format_url: result.append({ "url": format_url, "format_id": format_id, "cookies": p_cookies }) title = json_response.get('title') logger.info(title) if "pornhub.com" in video_url: p_result = [item for item in result if 'hls' in item['format_id']] last_item = p_result[-1] second_last_item = p_result[-2] last_item["format_id"] = f'{last_item["format_id"]} - Chrunos Shortcuts Premium Only' last_item["url"] = 'https://chrunos.com/premium-shortcuts/' second_last_item["format_id"] = f'{second_last_item["format_id"]} - Chrunos Shortcuts Premium Only' second_last_item["url"] = 'https://chrunos.com/premium-shortcuts/' return p_result else: new_result = result # Check if new_result has more than one item if len(new_result) > 3: for i in range(3, len(new_result)): item = new_result[i] item["format_id"] = f'{item["format_id"]} - Chrunos Shortcuts Premium Only' item["url"] = 'https://chrunos.com/premium-shortcuts/' elif 2 <= len(new_result) <= 3: last_item = new_result[-1] last_item["format_id"] = f'{last_item["format_id"]} - Chrunos Shortcuts Premium Only' last_item["url"] = 'https://chrunos.com/premium-shortcuts/' elif len(new_result) == 1: new_item = {"url": "https://chrunos.com/premium-shortcuts/", "format_id": "Best Qaulity Video - Chrunos Shortcuts Premium Only" } new_result.append(new_item) return new_result else: if 'url' in json_response: download_url = json_response.get('url') thumbnail_url = json_response.get('thumbnail') return [ {"url": download_url, "format_id": "Normal Quality Video" }, {"url": thumbnail_url, "format_id": "thumbnail"}, {"url": "https://chrunos.com/premium-shortcuts/", "format_id": "Best Qaulity Video - Chrunos Shortcuts Premium Only"} ] return {"error": "No formats available. Report Error on Telegram"} else: return {"error": f"Request failed with status code {response.status_code}, API: {api_url}"} except Exception as e: logger.error(f"An error occurred: {e}") return {"error": str(e)} @app.post("/test") async def test_download(request: Request): user_ip = get_user_ip(request) if rate_limiter.is_rate_limited(user_ip): current_count = rate_limiter.get_current_count(user_ip) raise HTTPException( status_code=429, detail={ "error": "You have exceeded the maximum number of requests per day. Please try again tomorrow.", "url": "https://t.me/chrunoss" } ) data = await request.json() video_url = data.get('url') response = extract_video_info(video_url) return response @app.post("/hls") async def download_hls_video(request: Request): data = await request.json() hls_url = data.get('url') timestamp = datetime.now().strftime('%Y%m%d%H%M%S') output_template = str(Path(global_download_dir) / f'%(title)s_{timestamp}.%(ext)s') ydl_opts = { 'format': 'best', 'outtmpl': output_template, 'quiet': True, 'no_warnings': True, 'noprogress': True, 'merge_output_format': 'mp4' } try: await run_in_threadpool(lambda: yt_dlp.YoutubeDL(ydl_opts).download([hls_url])) except Exception as e: return {"error": f"Download failed: {str(e)}"} downloaded_files = list(Path(global_download_dir).glob(f"*_{timestamp}.mp4")) if not downloaded_files: return {"error": "Download failed"} downloaded_file = downloaded_files[0] encoded_filename = urllib.parse.quote(downloaded_file.name) download_url = f"{BASE_URL}/file/{encoded_filename}" gc.collect() return {"url": download_url} @app.post("/max") async def download_high_quality_video(request: Request): data = await request.json() video_url = data.get('url') quality = data.get('quality', '1080') # Default to 1080p if not specified # Check if the requested quality is above 1080p #if int(quality) > 1080: # error_message = "Quality above 1080p is for premium users. Please check the URL for more information." # help_url = "https://chrunos.com/premium-shortcuts/" # Replace with your actual URL # return {"error": error_message, "url": help_url} cookiefile = "firefox-cookies.txt" env_to_cookies_from_env("firefox-cookies.txt") timestamp = datetime.now().strftime('%Y%m%d%H%M%S') output_template = str(Path(global_download_dir) / f'%(title)s_{timestamp}.%(ext)s') # Convert quality string to height height_map = { '480': 480, '720': 720, '1080': 1080, '1440': 1440, '2160': 2160 } max_height = height_map.get(quality, 1080) # Use the quality variable correctly # Determine format string based on quality format_str = f'bestvideo[height<={max_height}][vcodec^=avc]+bestaudio/best' ydl_opts = { 'format': format_str, 'outtmpl': output_template, 'quiet': True, 'no_warnings': True, 'noprogress': True, 'merge_output_format': 'mp4', 'cookiefile': cookiefile } await run_in_threadpool(lambda: yt_dlp.YoutubeDL(ydl_opts).download([video_url])) downloaded_files = list(Path(global_download_dir).glob(f"*_{timestamp}.mp4")) if not downloaded_files: return {"error": "Download failed"} downloaded_file = downloaded_files[0] encoded_filename = urllib.parse.quote(downloaded_file.name) download_url = f"{BASE_URL}/file/{encoded_filename}" gc.collect() return {"url": download_url} @app.post("/audio") async def download_audio(request: Request): user_ip = get_user_ip(request) if rate_limiter.is_rate_limited(user_ip): current_count = rate_limiter.get_current_count(user_ip) raise HTTPException( status_code=429, detail={ "error": "You have exceeded the maximum number of requests per day. Please try again tomorrow.", "url": "https://t.me/chrunoss" } ) data = await request.json() video_url = data.get('url') is_youtube_url = re.search(r'(youtube\.com|youtu\.be)', video_url) is not None #cookiefile = "firefox-cookies.txt" #env_to_cookies_from_env("firefox-cookies.txt") timestamp = datetime.now().strftime('%Y%m%d%H%M%S') output_template = str(Path(global_download_dir) / f'%(title).70s_{timestamp}.%(ext)s') ydl_opts = { 'format': 'bestaudio/best', 'outtmpl': output_template, 'quiet': True, 'no_warnings': True, 'noprogress': True, 'postprocessors': [{ 'key': 'FFmpegExtractAudio', 'preferredcodec': 'mp3', 'preferredquality': '192' }] } if is_youtube_url: dl_url = await get_track_download_url(video_url, 'mp3') if dl_url and "http" in dl_url: return {"url": dl_url, "requests_remaining": rate_limiter.max_requests - rate_limiter.get_current_count(user_ip)} else: return { "error": "Failed to Fetch the audio." } else: await run_in_threadpool(lambda: yt_dlp.YoutubeDL(ydl_opts).download([video_url])) downloaded_files = list(Path(global_download_dir).glob(f"*_{timestamp}.*")) if not downloaded_files: return {"error": "Download failed"} downloaded_file = downloaded_files[0] encoded_filename = urllib.parse.quote(downloaded_file.name) download_url = f"{BASE_URL}/file/{encoded_filename}" gc.collect() return {"url": download_url, "requests_remaining": rate_limiter.max_requests - rate_limiter.get_current_count(user_ip)} # Configure logging logging.basicConfig(level=logging.INFO) @app.post("/search") async def search_and_download_song(request: Request): data = await request.json() song_name = data.get('songname') artist_name = data.get('artist') if artist_name: search_query = f"ytsearch:{song_name} {artist_name}" else: search_query = f"ytsearch:{song_name}" logging.info(f"Search query: {search_query}") cookiefile = "firefox-cookies.txt" env_to_cookies_from_env("firefox-cookies.txt") timestamp = datetime.now().strftime('%Y%m%d%H%M%S') output_template = str(Path(global_download_dir) / f'%(title).70s_{timestamp}.%(ext)s') ydl_opts = { 'format': 'bestaudio/best', 'outtmpl': output_template, 'quiet': True, 'no_warnings': True, 'noprogress': True, 'postprocessors': [{ 'key': 'FFmpegExtractAudio', 'preferredcodec': 'mp3', 'preferredquality': '192' }], 'cookiefile': cookiefile } try: logging.info("Starting yt-dlp search and download...") await run_in_threadpool(lambda: yt_dlp.YoutubeDL(ydl_opts).download([search_query])) logging.info("yt-dlp search and download completed") except yt_dlp.utils.DownloadError as e: error_message = str(e) logging.error(f"yt-dlp error: {error_message}") return JSONResponse(content={"error": error_message}, status_code=500) except Exception as e: error_message = str(e) logging.error(f"General error: {error_message}") return JSONResponse(content={"error": error_message}, status_code=500) downloaded_files = list(Path(global_download_dir).glob(f"*_{timestamp}.mp3")) if not downloaded_files: logging.error("Download failed: No MP3 files found") return JSONResponse(content={"error": "Download failed"}, status_code=500) downloaded_file = downloaded_files[0] encoded_filename = urllib.parse.quote(downloaded_file.name) download_url = f"{BASE_URL}/file/{encoded_filename}" logging.info(f"Download URL: {download_url}") # Log just before returning the response logging.info("Preparing to send response back to the client") gc.collect() return JSONResponse(content={"url": download_url}, status_code=200) # Mount the static files directory app.mount("/file", StaticFiles(directory=global_download_dir), name="downloads")