|
import sys |
|
import time |
|
from fastapi import FastAPI, BackgroundTasks, Request, HTTPException |
|
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 mutagen.mp4 import MP4, MP4Cover, MP4FreeForm |
|
from mutagen.mp3 import MP3 |
|
from mutagen.id3 import ID3, USLT, SYLT, Encoding, APIC, TIT2, TPE1, TALB, TPE2, TDRC, TCON, TRCK, COMM |
|
from mutagen.oggvorbis import OggVorbis |
|
from mutagen.oggopus import OggOpus |
|
from mutagen.flac import FLAC, Picture |
|
from PIL import Image |
|
from io import BytesIO |
|
from pathlib import Path |
|
from fastapi.staticfiles import StaticFiles |
|
from collections import defaultdict |
|
import logging |
|
|
|
tmp_dir = tempfile.gettempdir() |
|
BASE_URL = "https://chrunos-multi.hf.space" |
|
|
|
MP4_TAGS_MAP = { |
|
"album": "\xa9alb", |
|
"album_artist": "aART", |
|
"artist": "\xa9ART", |
|
"composer": "\xa9wrt", |
|
"copyright": "cprt", |
|
"lyrics": "\xa9lyr", |
|
"comment": "desc", |
|
"media_type": "stik", |
|
"producer": "\xa9prd", |
|
"rating": "rtng", |
|
"release_date": "\xa9day", |
|
"title": "\xa9nam", |
|
"url": "\xa9url", |
|
} |
|
def env_to_cookies(env_content: str, output_file: str) -> None: |
|
"""Convert environment variable content back 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) |
|
|
|
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) |
|
|
|
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() |
|
env_content = os.getenv('FIREFOX_COOKIES') |
|
|
|
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') |
|
|
|
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() |
|
|
|
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() |
|
|
|
ydl_opts = { |
|
'format': 'best', |
|
'quiet': True, |
|
|
|
'max_filesize': 50 * 1024 * 1024 |
|
} |
|
|
|
|
|
@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") |
|
|
|
ydl_opts["cookiefile"] = "firefox-cookies.txt" |
|
|
|
|
|
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)) |
|
|
|
|
|
|
|
|
|
global_download_dir = tempfile.mkdtemp() |
|
|
|
|
|
request_counts = defaultdict(lambda: {"count": 0, "reset_time": datetime.now() + timedelta(days=1)}) |
|
MAX_REQUESTS_PER_DAY = 50 |
|
|
|
def get_user_ip(request: Request) -> str: |
|
"""Helper function to get user's IP address.""" |
|
return request.client.host |
|
|
|
@app.post("/maxs") |
|
async def download_high_quality_video(request: Request): |
|
user_ip = get_user_ip(request) |
|
user_info = request_counts[user_ip] |
|
|
|
|
|
if datetime.now() > user_info["reset_time"]: |
|
user_info["count"] = 0 |
|
user_info["reset_time"] = datetime.now() + timedelta(days=1) |
|
|
|
|
|
if user_info["count"] >= MAX_REQUESTS_PER_DAY: |
|
error_message = "You have exceeded the maximum number of requests per day. Please try again tomorrow." |
|
return {"error": error_message, "url": "https://t.me/chrunoss"} |
|
|
|
data = await request.json() |
|
video_url = data.get('url') |
|
quality = data.get('quality', '1080') |
|
|
|
|
|
if int(quality) > 1080: |
|
error_message = "Quality above 1080p is for Premium Members Only. Please check the URL for more information." |
|
help_url = "https://chrunos.com/premium-shortcuts/" |
|
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') |
|
|
|
|
|
height_map = { |
|
'480': 480, |
|
'720': 720, |
|
'1080': 1080 |
|
} |
|
max_height = height_map.get(quality, 1080) |
|
|
|
|
|
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}" |
|
|
|
|
|
user_info["count"] += 1 |
|
|
|
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') |
|
|
|
|
|
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/" |
|
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') |
|
|
|
|
|
height_map = { |
|
'480': 480, |
|
'720': 720, |
|
'1080': 1080 |
|
} |
|
max_height = height_map.get(quality, 1080) |
|
|
|
|
|
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}" |
|
return {"url": download_url} |
|
|
|
|
|
@app.post("/audio") |
|
async def download_audio(request: Request): |
|
data = await request.json() |
|
video_url = data.get('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') |
|
|
|
ydl_opts = { |
|
'format': 'bestaudio/best', |
|
'outtmpl': output_template, |
|
'quiet': True, |
|
'no_warnings': True, |
|
'noprogress': True, |
|
'cookiefile': cookiefile, |
|
'postprocessors': [{ |
|
'key': 'FFmpegExtractAudio', |
|
'preferredcodec': 'mp3', |
|
'preferredquality': '192' |
|
}] |
|
|
|
} |
|
|
|
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}" |
|
return {"url": download_url} |
|
|
|
|
|
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)s_{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}") |
|
|
|
|
|
logging.info("Preparing to send response back to the client") |
|
return JSONResponse(content={"url": download_url}, status_code=200) |
|
|
|
|
|
app.mount("/file", StaticFiles(directory=global_download_dir), name="downloads") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.middleware("http") |
|
async def set_mime_type_middleware(request: Request, call_next): |
|
response = await call_next(request) |
|
if request.url.path.endswith(".mp4"): |
|
response.headers["Content-Type"] = "video/mp4" |
|
return response |
|
|
|
|