#!/usr/bin/env python3 """ REST API for YouTube CLI application """ import subprocess from flask import Flask, jsonify, request from youtube_cli.main import YouTubeCLI app = Flask(__name__) # Initialize YouTube CLI cli = YouTubeCLI() def search_youtube_api(query, page=1): """Search YouTube and return structured results for API""" try: # Use yt-dlp directly to get search results cmd = [ "yt-dlp", "--flat-playlist", # Get video info without downloading "--dump-single-json", # Output as JSON single item f"--playlist-start={15 * (page - 1) + 1}", f"--playlist-end={15 * page}", "--no-warnings", "--no-progress", "--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", f"ytsearch{15 * page}:{query}", ] result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) if result.returncode != 0: return {"error": f"Error searching videos: {result.stderr}"} # Parse JSON output import json try: data = json.loads(result.stdout.strip()) except json.JSONDecodeError as e: return {"error": f"Error parsing search results: {e}"} # Process videos into our format videos = [] if isinstance(data, list): videos_data = data elif "entries" in data: videos_data = data["entries"] else: videos_data = [data] for entry in videos_data: if not entry: continue title = entry.get("title", "Unknown Title") author = entry.get("uploader", "Unknown Author") duration = entry.get("duration", 0) url = entry.get("url", "") or entry.get("webpage_url", "") view_count = entry.get("view_count", None) # Format duration length = format_duration(duration) # Check if this is a short video is_short = "/shorts/" in url or "/shorts" in url # Check if this is a playlist (look for playlist-specific attributes) is_playlist = "playlist" in url.lower() or "list=" in url # Validate URL before adding to videos list if not url or url.strip() == "": continue # Skip videos with invalid/missing URLs # Create video object videos.append( { "title": title, "author": author, "length": length, "url": url, "is_short": is_short, "is_playlist": is_playlist, "id": entry.get("id", ""), "thumbnail": entry.get("thumbnail", ""), "view_count": view_count, } ) return { "query": query, "page": page, "videos": videos, "total": len(videos), } except Exception as e: return {"error": str(e)} def format_duration(seconds): """Convert seconds to MM:SS or HH:MM:SS format.""" if not seconds: return "0:00" hours = int(seconds // 3600) minutes = int((seconds % 3600) // 60) secs = int(seconds % 60) if hours > 0: return f"{hours}:{minutes:02d}:{secs:02d}" else: return f"{minutes}:{secs:02d}" @app.route("/search", methods=["GET"]) def search_videos(): """Search YouTube videos""" query = request.args.get("q", "") page = int(request.args.get("page", 1)) if not query: return jsonify({"error": 'Query parameter "q" is required'}), 400 try: result = search_youtube_api(query, page) if "error" in result: return jsonify(result), 500 return jsonify(result) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route("/download", methods=["POST"]) def download_video(): """Download a video by URL""" data = request.get_json() url = data.get("url", "") if not url: return jsonify({"error": "URL is required"}), 400 try: # For now, return a placeholder response indicating download would start # In a real implementation, this would call the actual download functionality return jsonify( { "status": "download_started", "url": url, "message": "Download process initiated (not implemented in this demo)", } ) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route("/health", methods=["GET"]) def health_check(): """Health check endpoint""" return jsonify({"status": "healthy", "service": "youtube-cli-api"}) @app.route("/version", methods=["GET"]) def get_version(): """Get API version""" return jsonify({"version": "1.0.0"}) @app.route("/capabilities", methods=["GET"]) def get_capabilities(): """MCP capabilities endpoint""" return jsonify( { "name": "YouTube CLI API", "version": "1.0.0", "description": "YouTube CLI API for searching and downloading videos", "endpoints": [ { "path": "/search", "method": "GET", "description": "Search YouTube videos", }, { "path": "/download", "method": "POST", "description": "Download a video by URL", }, { "path": "/health", "method": "GET", "description": "Health check endpoint", }, { "path": "/version", "method": "GET", "description": "Get API version", }, { "path": "/capabilities", "method": "GET", "description": "MCP capabilities endpoint", }, { "path": "/openapi.json", "method": "GET", "description": "OpenAPI specification", }, ], "features": [ "Video search", "Video download", "Health monitoring", "Version information", "MCP compliance", ], } ) @app.route("/openapi.json", methods=["GET"]) def get_openapi(): """Serve the OpenAPI specification file""" try: # Read the openapi.json file from the filesystem # Try multiple locations to handle Docker vs local execution import json import os # Check if we're in Docker (working directory is /app) current_dir = os.getcwd() if current_dir == "/app": # In Docker, the file should be in /app file_path = "/app/openapi.json" else: # Local execution file_path = "openapi.json" with open(file_path, "r") as f: spec = json.load(f) return jsonify(spec) except Exception as e: return jsonify( {"error": f"OpenAPI specification not found: {str(e)}"} ), 404 if __name__ == "__main__": # Fix the port issue by using a different port app.run(host="0.0.0.0", port=4096, debug=True)