├── requirements.txt ├── vercel.json ├── api.py └── templates └── status.html /requirements.txt: -------------------------------------------------------------------------------- 1 | flask 2 | requests -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "builds": [ 4 | { 5 | "src": "api.py", 6 | "use": "@vercel/python" 7 | } 8 | ], 9 | "routes": [ 10 | { 11 | "src": "/(.*)", 12 | "dest": "api.py" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /api.py: -------------------------------------------------------------------------------- 1 | # Copyright @ISmartCoder 2 | # Updates Channel: https://t.me/TheSmartDevs 3 | 4 | from flask import Flask, request, jsonify, render_template, Response 5 | import requests 6 | import re 7 | import json 8 | from collections import OrderedDict 9 | 10 | app = Flask(__name__) 11 | 12 | # YouTube Data API Key 13 | YOUTUBE_API_KEY = "Get From Google AI Cloud" 14 | YOUTUBE_SEARCH_API_URL = "https://www.googleapis.com/youtube/v3/search" 15 | YOUTUBE_VIDEOS_API_URL = "https://www.googleapis.com/youtube/v3/videos" 16 | 17 | def extract_video_id(url): 18 | patterns = [ 19 | r'(?:https?:\/\/)?(?:www\.)?youtube\.com\/watch\?v=([^&?\s]+)', 20 | r'(?:https?:\/\/)?youtu\.be\/([^&?\s]+)', 21 | r'(?:https?:\/\/)?(?:www\.)?youtube\.com\/embed\/([^&?\s]+)', 22 | r'(?:https?:\/\/)?(?:www\.)?youtube\.com\/v\/([^&?\s]+)', 23 | r'(?:https?:\/\/)?(?:www\.)?youtube\.com\/shorts\/([^&?\s]+)' 24 | ] 25 | for pattern in patterns: 26 | match = re.match(pattern, url) 27 | if match: 28 | return match.group(1) 29 | 30 | # Fallback attempt with regex search (if query params included) 31 | query_match = re.search(r'v=([^&?\s]+)', url) 32 | if query_match: 33 | return query_match.group(1) 34 | 35 | return None 36 | 37 | def parse_duration(duration): 38 | """Parse ISO 8601 duration into human-readable format without isodate.""" 39 | try: 40 | # Match ISO 8601 duration (e.g., PT1H30M45S) 41 | match = re.match(r'PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?', duration) 42 | if not match: 43 | return "N/A" 44 | 45 | hours = int(match.group(1) or 0) 46 | minutes = int(match.group(2) or 0) 47 | seconds = int(match.group(3) or 0) 48 | 49 | formatted = "" 50 | if hours > 0: 51 | formatted += f"{hours}h " 52 | if minutes > 0: 53 | formatted += f"{minutes}m " 54 | if seconds > 0: 55 | formatted += f"{seconds}s" 56 | return formatted.strip() or "0s" 57 | except Exception: 58 | return "N/A" 59 | 60 | def fetch_youtube_details(video_id): 61 | """Fetch video details from YouTube Data API.""" 62 | try: 63 | api_url = f"{YOUTUBE_VIDEOS_API_URL}?part=snippet,statistics,contentDetails&id={video_id}&key={YOUTUBE_API_KEY}" 64 | response = requests.get(api_url) 65 | if response.status_code != 200: 66 | return {"error": "Failed to fetch YouTube video details."} 67 | 68 | data = response.json() 69 | if not data.get('items'): 70 | return {"error": "No video found for the provided ID."} 71 | 72 | video = data['items'][0] 73 | snippet = video['snippet'] 74 | stats = video['statistics'] 75 | content_details = video['contentDetails'] 76 | 77 | return { 78 | "title": snippet.get('title', 'N/A'), 79 | "channel": snippet.get('channelTitle', 'N/A'), 80 | "description": snippet.get('description', 'N/A'), 81 | "imageUrl": snippet.get('thumbnails', {}).get('high', {}).get('url', ''), 82 | "duration": parse_duration(content_details.get('duration', '')), 83 | "views": stats.get('viewCount', 'N/A'), 84 | "likes": stats.get('likeCount', 'N/A'), 85 | "comments": stats.get('commentCount', 'N/A') 86 | } 87 | except requests.exceptions.RequestException: 88 | return {"error": "Failed to fetch YouTube video details."} 89 | 90 | def fetch_youtube_search(query): 91 | """Fetch search results from YouTube Data API.""" 92 | try: 93 | search_api_url = f"{YOUTUBE_SEARCH_API_URL}?part=snippet&q={requests.utils.quote(query)}&type=video&maxResults=10&key={YOUTUBE_API_KEY}" 94 | search_response = requests.get(search_api_url) 95 | if search_response.status_code != 200: 96 | return {"error": "Failed to fetch search data."} 97 | 98 | search_data = search_response.json() 99 | video_ids = [item['id']['videoId'] for item in search_data.get('items', [])] 100 | 101 | if not video_ids: 102 | return {"error": "No videos found for the provided query."} 103 | 104 | # Fetch additional video statistics and duration 105 | videos_api_url = f"{YOUTUBE_VIDEOS_API_URL}?part=snippet,statistics,contentDetails&id={','.join(video_ids)}&key={YOUTUBE_API_KEY}" 106 | videos_response = requests.get(videos_api_url) 107 | if videos_response.status_code != 200: 108 | return {"error": "Failed to fetch video details."} 109 | 110 | videos_data = videos_response.json() 111 | videos_map = {video['id']: video for video in videos_data.get('items', [])} 112 | 113 | result = [] 114 | for item in search_data.get('items', []): 115 | video_id = item['id']['videoId'] 116 | snippet = item['snippet'] 117 | video = videos_map.get(video_id, {}) 118 | content_details = video.get('contentDetails', {}) 119 | stats = video.get('statistics', {}) 120 | 121 | result.append({ 122 | "title": snippet.get('title', 'N/A'), 123 | "channel": snippet.get('channelTitle', 'N/A'), 124 | "imageUrl": snippet.get('thumbnails', {}).get('high', {}).get('url', ''), 125 | "link": f"https://youtube.com/watch?v={video_id}", 126 | "duration": parse_duration(content_details.get('duration', '')), 127 | "views": stats.get('viewCount', 'N/A'), 128 | "likes": stats.get('likeCount', 'N/A'), 129 | "comments": stats.get('commentCount', 'N/A') 130 | }) 131 | 132 | return result 133 | except requests.exceptions.RequestException: 134 | return {"error": "Failed to fetch search data."} 135 | 136 | @app.route("/") 137 | def home(): 138 | return render_template("status.html") 139 | 140 | @app.route("/dl", methods=["GET"]) 141 | def download(): 142 | youtube_url = request.args.get("url", "").strip() 143 | if not youtube_url: 144 | return jsonify({ 145 | "error": "Missing 'url' parameter.", 146 | "contact": "@ISmartCoder" 147 | }), 400 148 | 149 | video_id = extract_video_id(youtube_url) 150 | if not video_id: 151 | return jsonify({ 152 | "error": "Invalid YouTube URL.", 153 | "contact": "@ISmartCoder" 154 | }), 400 155 | 156 | standard_url = f"https://www.youtube.com/watch?v={video_id}" 157 | payload = {"url": standard_url} 158 | 159 | # Fetch YouTube video details 160 | youtube_data = fetch_youtube_details(video_id) 161 | if "error" in youtube_data: 162 | youtube_data = { 163 | "title": "Unavailable", 164 | "channel": "N/A", 165 | "description": "N/A", 166 | "imageUrl": f"https://img.youtube.com/vi/{video_id}/hqdefault.jpg", 167 | "duration": "N/A", 168 | "views": "N/A", 169 | "likes": "N/A", 170 | "comments": "N/A" 171 | } 172 | 173 | # Fetch download URL from Clipto API 174 | try: 175 | response = requests.post("https://www.clipto.com/api/youtube", json=payload) 176 | if response.status_code == 200: 177 | data = response.json() 178 | title = data.get("title", youtube_data["title"]) 179 | thumbnail = data.get("thumbnail", youtube_data["imageUrl"]) 180 | fallback_thumb = f"https://img.youtube.com/vi/{video_id}/hqdefault.jpg" 181 | video_url = data.get("url", standard_url) 182 | 183 | ordered = OrderedDict() 184 | ordered["api_owner"] = "@ISmartCoder" 185 | ordered["updates_channel"] = "@TheSmartDevs" 186 | ordered["title"] = title 187 | ordered["channel"] = youtube_data["channel"] 188 | ordered["description"] = youtube_data["description"] 189 | ordered["thumbnail"] = thumbnail 190 | ordered["thumbnail_url"] = fallback_thumb 191 | ordered["url"] = video_url 192 | ordered["duration"] = youtube_data["duration"] 193 | ordered["views"] = youtube_data["views"] 194 | ordered["likes"] = youtube_data["likes"] 195 | ordered["comments"] = youtube_data["comments"] 196 | 197 | for key, value in data.items(): 198 | if key not in ordered: 199 | ordered[key] = value 200 | 201 | return Response(json.dumps(ordered, ensure_ascii=False, indent=4), mimetype="application/json") 202 | else: 203 | ordered = OrderedDict() 204 | ordered["api_owner"] = "@ISmartCoder" 205 | ordered["updates_channel"] = "@TheSmartDevs" 206 | ordered["เสียtitle"] = youtube_data["title"] 207 | ordered["channel"] = youtube_data["channel"] 208 | ordered["description"] = youtube_data["description"] 209 | ordered["thumbnail"] = youtube_data["imageUrl"] 210 | ordered["thumbnail_url"] = f"https://img.youtube.com/vi/{video_id}/hqdefault.jpg" 211 | ordered["url"] = standard_url 212 | ordered["duration"] = youtube_data["duration"] 213 | ordered["views"] = youtube_data["views"] 214 | ordered["likes"] = youtube_data["likes"] 215 | ordered["comments"] = youtube_data["comments"] 216 | ordered["error"] = "Failed to fetch download URL from Clipto API." 217 | 218 | return Response(json.dumps(ordered, ensure_ascii=False, indent=4), mimetype="application/json"), 500 219 | except requests.exceptions.RequestException: 220 | ordered = OrderedDict() 221 | ordered["api_owner"] = "@ISmartCoder" 222 | ordered["updates_channel"] = "@TheSmartDevs" 223 | ordered["title"] = youtube_data["title"] 224 | ordered["channel"] = youtube_data["channel"] 225 | ordered["description"] = youtube_data["description"] 226 | ordered["thumbnail"] = youtube_data["imageUrl"] 227 | ordered["thumbnail_url"] = f"https://img.youtube.com/vi/{video_id}/hqdefault.jpg" 228 | ordered["url"] = standard_url 229 | ordered["duration"] = youtube_data["duration"] 230 | ordered["views"] = youtube_data["views"] 231 | ordered["likes"] = youtube_data["likes"] 232 | ordered["comments"] = youtube_data["comments"] 233 | ordered["error"] = "Something went wrong. Please contact @ISmartCoder and report the bug." 234 | 235 | return Response(json.dumps(ordered, ensure_ascii=False, indent=4), mimetype="application/json"), 500 236 | 237 | @app.route("/search", methods=["GET"]) 238 | def search(): 239 | query = request.args.get("q", "").strip() 240 | if not query: 241 | return jsonify({ 242 | "error": "Missing 'q' parameter.", 243 | "contact": "@ISmartCoder" 244 | }), 400 245 | 246 | search_data = fetch_youtube_search(query) 247 | if "error" in search_data: 248 | return jsonify({ 249 | "api_owner": "@ISmartCoder", 250 | "updates_channel": "@TheSmartDevs", 251 | "error": search_data["error"] 252 | }), 500 253 | 254 | ordered = OrderedDict() 255 | ordered["api_owner"] = "@ISmartCoder" 256 | ordered["updates_channel"] = "@TheSmartDevs" 257 | ordered["result"] = search_data 258 | 259 | return Response(json.dumps(ordered, ensure_ascii=False, indent=4), mimetype="application/json") 260 | 261 | if __name__ == "__main__": 262 | app.run(host="0.0.0.0", port=5000) -------------------------------------------------------------------------------- /templates/status.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |Download YouTube videos and audio or search for videos with our fast API by @ISmartCoder.
251 | Try It Now 252 |We're verifying the API's availability.
263 |GET /dl?url={youtube_url}
https://www.youtube.com/watch?v=pCr2XfPpWjU)Success (200):
308 |
309 | {
310 | "api_owner": "@ISmartCoder",
311 | "updates_channel": "@TheSmartDevs",
312 | "title": "Video Title",
313 | "thumbnail": "Thumbnail URL",
314 | "thumbnail_url": "Fallback Thumbnail URL",
315 | "url": "Download URL or YouTube URL",
316 | "success": true,
317 | "source": "youtube",
318 | "author": "",
319 | "medias": [
320 | {
321 | "formatId": 18,
322 | "label": "mp4 (360p)",
323 | "type": "video",
324 | "ext": "mp4",
325 | "quality": "mp4 (360p)",
326 | "url": "Download URL",
327 | "bitrate": 496367,
328 | "clen": 123456789,
329 | "mimeType": "video/mp4; codecs=\"avc1.42001E, mp4a.40.2\"",
330 | "duration": 116
331 | },
332 | {
333 | "formatId": 140,
334 | "label": "m4a (137kb/s)",
335 | "type": "audio",
336 | "ext": "m4a",
337 | "quality": "m4a (137kb/s)",
338 | "url": "Download URL",
339 | "bitrate": 136906,
340 | "clen": 113199366,
341 | "mimeType": "audio/mp4; codecs=\"mp4a.40.2\"",
342 | "duration": 116
343 | }
344 | ],
345 | "type": "multiple",
346 | "error": false,
347 | "time_end": 716
348 | }
349 |
350 | Error (400 or 500):
351 |
352 | {
353 | "api_owner": "@ISmartCoder",
354 | "updates_channel": "@TheSmartDevs",
355 | "error": "Error message",
356 | "contact": "@ISmartCoder"
357 | }
358 |
359 | GET /dl?url=https://www.youtube.com/watch?v=pCr2XfPpWjU361 | 362 |
GET /search?q={search_query}
python tutorial)Success (200):
370 |
371 | {
372 | "api_owner": "@ISmartCoder",
373 | "updates_channel": "@TheSmartDevs",
374 | "result": [
375 | {
376 | "title": "Video Title",
377 | "imageUrl": "Thumbnail URL",
378 | "link": "https://youtube.com/watch?v=video_id"
379 | }
380 | ]
381 | }
382 |
383 | Error (400 or 500):
384 |
385 | {
386 | "api_owner": "@ISmartCoder",
387 | "updates_channel": "@TheSmartDevs",
388 | "error": "Error message"
389 | }
390 |
391 | GET /search?q=python+tutorial393 | 394 |