Repository: aryanvikash/Youtube-Downloader-Bot Branch: master Commit: 9910023d39d3 Files: 17 Total size: 15.1 KB Directory structure: gitextract_chqsk1l4/ ├── .gitignore ├── Procfile ├── README.md ├── app.json ├── bot/ │ ├── __init__.py │ └── __main__.py ├── config.py ├── helper/ │ ├── ffmfunc.py │ └── ytdlfunc.py ├── plugins/ │ ├── help.py │ ├── start.py │ ├── youtube.py │ └── youtube_callback_data.py ├── requirements.txt ├── runtime.txt ├── start.sh └── utils/ └── util.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ downloads/* *.session __pycache__/ *.pyc log.txt venv/ .idea/ pythonenv3.8/ .vscode/ ================================================ FILE: Procfile ================================================ worker : chmod +x start.sh && bash start.sh ================================================ FILE: README.md ================================================ # Youtube Dl bot 😉 ## Prerequisite ffmpeg ## install dependencies pip3 install -r requirements.txt ## Setup Bot - Change configuration config.py File - install dependencies - python3 -m bot ## Thanks ❤️ * [Spechide](https://telegram.dog/SpEcHIDe) for his [AnyDlBot](https://github.com/SpEcHiDe/AnyDLBot) * [HasibulKabir](https://telegram.dog/HasibulKabir) [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/aryanvikash/Youtube-Downloader-Bot/tree/master) ================================================ FILE: app.json ================================================ { "name": "YouTube Downloader Bot", "description": "A telegram bot To Download Youtube Files ", "logo": "https://telegra.ph/file/6a3b1febade2313dd0dca.jpg", "keywords": [ "Youtube","YoutubeDownloader" ], "repository": "https://github.com/aryanvikash/Youtube-Downloader-Bot", "success_url": "https://t.me/youtubdlbot", "website": "https://github.com/aryanvikash/Youtube-Downloader-Bot", "env": { "API_ID": {"description": "Get this value from https://my.telegram.org", "required": true}, "API_HASH": {"description": "Get this value from https://my.telegram.org" , "required": true}, "BOT_TOKEN": {"description": "Get Bot Token From BotFather Bot","required": true} }, "buildpacks": [ {"url": "https://github.com/jonathanong/heroku-buildpack-ffmpeg-latest.git"}, {"url": "heroku/python"} ], "formation": { "worker": { "quantity": 1, "size": "free" } }, "stack": "heroku-20" } ================================================ FILE: bot/__init__.py ================================================ users ={} user_time = {} ================================================ FILE: bot/__main__.py ================================================ from pyrogram import Client import config DOWNLOAD_LOCATION = "./Downloads" BOT_TOKEN = config.BOT_TOKEN APP_ID = config.APP_ID API_HASH = config.API_HASH plugins = dict( root="plugins", ) Client( "YouTubeDlBot", bot_token=BOT_TOKEN, api_id=APP_ID, api_hash=API_HASH, plugins=plugins, workers=100 ).run() ================================================ FILE: config.py ================================================ import os BOT_TOKEN = os.environ.get("BOT_TOKEN") APP_ID = int(os.environ.get("API_ID")) API_HASH = os.environ.get("API_HASH") youtube_next_fetch = 0 # time in minute EDIT_TIME = 5 ================================================ FILE: helper/ffmfunc.py ================================================ import subprocess as sp import json def probe(vid_file_path): """ Give a json from ffprobe command line @vid_file_path : The absolute (full) path of the video file, string. """ if type(vid_file_path) != str: raise Exception('Give ffprobe a full file path of the file') command = ["ffprobe", "-loglevel", "quiet", "-print_format", "json", "-show_format", "-show_streams", vid_file_path ] pipe = sp.Popen(command, stdout=sp.PIPE, stderr=sp.STDOUT) out, err = pipe.communicate() return json.loads(out) def duration(vid_file_path): """ Video's duration in seconds, return a float number """ _json = probe(vid_file_path) if 'format' in _json: if 'duration' in _json['format']: return float(_json['format']['duration']) if 'streams' in _json: # commonly stream 0 is the video for s in _json['streams']: if 'duration' in s: return float(s['duration']) raise Exception('duration Not found') if __name__ == "__main__": print(duration("examplefile.mp4")) # 10.008 ================================================ FILE: helper/ytdlfunc.py ================================================ from __future__ import unicode_literals from pyrogram import Client, Filters, StopPropagation, InlineKeyboardButton, InlineKeyboardMarkup import youtube_dl from utils.util import humanbytes import asyncio def buttonmap(item): quality = item['format'] if "audio" in quality: return [InlineKeyboardButton(f"{quality} 🎵 {humanbytes(item['filesize'])}", callback_data=f"ytdata||audio||{item['format_id']}||{item['yturl']}")] else: return [InlineKeyboardButton(f"{quality} 📹 {humanbytes(item['filesize'])}", callback_data=f"ytdata||video||{item['format_id']}||{item['yturl']}")] # Return a array of Buttons def create_buttons(quailitylist): return map(buttonmap, quailitylist) # extract Youtube info def extractYt(yturl): ydl = youtube_dl.YoutubeDL() with ydl: qualityList = [] r = ydl.extract_info(yturl, download=False) for format in r['formats']: # Filter dash video(without audio) if not "dash" in str(format['format']).lower(): qualityList.append( {"format": format['format'], "filesize": format['filesize'], "format_id": format['format_id'], "yturl": yturl}) return r['title'], r['thumbnail'], qualityList # Need to work on progress # def downloadyt(url, fmid, custom_progress): # ydl_opts = { # 'format': f"{fmid}+bestaudio", # "outtmpl": "test+.%(ext)s", # 'noplaylist': True, # 'progress_hooks': [custom_progress], # } # with youtube_dl.YoutubeDL(ydl_opts) as ydl: # ydl.download([url]) # https://github.com/SpEcHiDe/AnyDLBot async def downloadvideocli(command_to_exec): process = await asyncio.create_subprocess_exec( *command_to_exec, # stdout must a pipe to be accessible as process.stdout stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) stdout, stderr = await process.communicate() e_response = stderr.decode().strip() t_response = stdout.decode().strip() print(e_response) filename = t_response.split("Merging formats into")[-1].split('"')[1] return filename async def downloadaudiocli(command_to_exec): process = await asyncio.create_subprocess_exec( *command_to_exec, # stdout must a pipe to be accessible as process.stdout stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) stdout, stderr = await process.communicate() e_response = stderr.decode().strip() t_response = stdout.decode().strip() print("Download error:", e_response) return t_response.split("Destination")[-1].split("Deleting")[0].split(":")[-1].strip() ================================================ FILE: plugins/help.py ================================================ from pyrogram import Client, Filters @Client.on_message(Filters.command(["help"])) async def start(client, message): helptxt = f"Currently Only supports Youtube Single (No playlist) Just Send Youtube Url" await message.reply_text(helptxt) ================================================ FILE: plugins/start.py ================================================ from pyrogram import Client, Filters, StopPropagation, InlineKeyboardButton, InlineKeyboardMarkup @Client.on_message(Filters.command(["start"]), group=-2) async def start(client, message): # return joinButton = InlineKeyboardMarkup([ [InlineKeyboardButton("Channel", url="https://t.me/aryan_bots")], [InlineKeyboardButton( "Report Bugs 😊", url="https://t.me/aryanvikash")] ]) welcomed = f"Hey {message.from_user.first_name}\n/help for More info" await message.reply_text(welcomed, reply_markup=joinButton) raise StopPropagation ================================================ FILE: plugins/youtube.py ================================================ from datetime import datetime, timedelta from pyrogram import Client, Filters, InlineKeyboardMarkup, InlineKeyboardButton from bot import user_time from config import youtube_next_fetch from helper.ytdlfunc import extractYt, create_buttons import wget import os from PIL import Image ytregex = r"^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$" @Client.on_message(Filters.regex(ytregex)) async def ytdl(_, message): userLastDownloadTime = user_time.get(message.chat.id) try: if userLastDownloadTime > datetime.now(): wait_time = round((userLastDownloadTime - datetime.now()).total_seconds() / 60, 2) await message.reply_text(f"`Wait {wait_time} Minutes before next Request`") return except: pass url = message.text.strip() await message.reply_chat_action("typing") try: title, thumbnail_url, formats = extractYt(url) now = datetime.now() user_time[message.chat.id] = now + \ timedelta(minutes=youtube_next_fetch) except Exception: await message.reply_text("`Failed To Fetch Youtube Data... 😔 \nPossible Youtube Blocked server ip \n#error`") return buttons = InlineKeyboardMarkup(list(create_buttons(formats))) sentm = await message.reply_text("Processing Youtube Url 🔎 🔎 🔎") try: # Todo add webp image support in thumbnail by default not supported by pyrogram # https://www.youtube.com/watch?v=lTTajzrSkCw img = wget.download(thumbnail_url) im = Image.open(img).convert("RGB") output_directory = os.path.join(os.getcwd(), "downloads", str(message.chat.id)) if not os.path.isdir(output_directory): os.makedirs(output_directory) thumb_image_path = f"{output_directory}.jpg" im.save(thumb_image_path,"jpeg") await message.reply_photo(thumb_image_path, caption=title, reply_markup=buttons) await sentm.delete() except Exception as e: print(e) try: thumbnail_url = "https://telegra.ph/file/ce37f8203e1903feed544.png" await message.reply_photo(thumbnail_url, caption=title, reply_markup=buttons) except Exception as e: await sentm.edit( f"{e} #Error") ================================================ FILE: plugins/youtube_callback_data.py ================================================ import asyncio import os from pyrogram import (Client, InlineKeyboardButton, InlineKeyboardMarkup, ContinuePropagation, InputMediaDocument, InputMediaVideo, InputMediaAudio) from helper.ffmfunc import duration from helper.ytdlfunc import downloadvideocli, downloadaudiocli from PIL import Image from hachoir.metadata import extractMetadata from hachoir.parser import createParser @Client.on_callback_query() async def catch_youtube_fmtid(c, m): cb_data = m.data if cb_data.startswith("ytdata||"): yturl = cb_data.split("||")[-1] format_id = cb_data.split("||")[-2] media_type = cb_data.split("||")[-3].strip() print(media_type) if media_type == 'audio': buttons = InlineKeyboardMarkup([[InlineKeyboardButton( "Audio", callback_data=f"{media_type}||{format_id}||{yturl}"), InlineKeyboardButton("Document", callback_data=f"docaudio||{format_id}||{yturl}")]]) else: buttons = InlineKeyboardMarkup([[InlineKeyboardButton( "Video", callback_data=f"{media_type}||{format_id}||{yturl}"), InlineKeyboardButton("Document", callback_data=f"docvideo||{format_id}||{yturl}")]]) await m.edit_message_reply_markup(buttons) else: raise ContinuePropagation @Client.on_callback_query() async def catch_youtube_dldata(c, q): cb_data = q.data.strip() #print(q.message.chat.id) # Callback Data Check yturl = cb_data.split("||")[-1] format_id = cb_data.split("||")[-2] thumb_image_path = "/app/downloads" + \ "/" + str(q.message.chat.id) + ".jpg" print(thumb_image_path) if os.path.exists(thumb_image_path): width = 0 height = 0 metadata = extractMetadata(createParser(thumb_image_path)) #print(metadata) if metadata.has("width"): width = metadata.get("width") if metadata.has("height"): height = metadata.get("height") img = Image.open(thumb_image_path) if cb_data.startswith(("audio", "docaudio", "docvideo")): img.resize((320, height)) else: img.resize((90, height)) img.save(thumb_image_path, "JPEG") # print(thumb_image_path) if not cb_data.startswith(("video", "audio", "docaudio", "docvideo")): print("no data found") raise ContinuePropagation filext = "%(title)s.%(ext)s" userdir = os.path.join(os.getcwd(), "downloads", str(q.message.chat.id)) if not os.path.isdir(userdir): os.makedirs(userdir) await q.edit_message_reply_markup( InlineKeyboardMarkup([[InlineKeyboardButton("Downloading...", callback_data="down")]])) filepath = os.path.join(userdir, filext) # await q.edit_message_reply_markup([[InlineKeyboardButton("Processing..")]]) audio_command = [ "youtube-dl", "-c", "--prefer-ffmpeg", "--extract-audio", "--audio-format", "mp3", "--audio-quality", format_id, "-o", filepath, yturl, ] video_command = [ "youtube-dl", "-c", "--embed-subs", "-f", f"{format_id}+bestaudio", "-o", filepath, "--hls-prefer-ffmpeg", yturl] loop = asyncio.get_event_loop() med = None if cb_data.startswith("audio"): filename = await downloadaudiocli(audio_command) med = InputMediaAudio( media=filename, thumb=thumb_image_path, caption=os.path.basename(filename), title=os.path.basename(filename) ) if cb_data.startswith("video"): filename = await downloadvideocli(video_command) dur = round(duration(filename)) med = InputMediaVideo( media=filename, duration=dur, width=width, height=height, thumb=thumb_image_path, caption=os.path.basename(filename), supports_streaming=True ) if cb_data.startswith("docaudio"): filename = await downloadaudiocli(audio_command) med = InputMediaDocument( media=filename, thumb=thumb_image_path, caption=os.path.basename(filename), ) if cb_data.startswith("docvideo"): filename = await downloadvideocli(video_command) dur = round(duration(filename)) med = InputMediaDocument( media=filename, thumb=thumb_image_path, caption=os.path.basename(filename), ) if med: loop.create_task(send_file(c, q, med, filename)) else: print("med not found") async def send_file(c, q, med, filename): print(med) try: await q.edit_message_reply_markup( InlineKeyboardMarkup([[InlineKeyboardButton("Uploading...", callback_data="down")]])) await c.send_chat_action(chat_id=q.message.chat.id, action="upload_document") # this one is not working await q.edit_message_media(media=med) except Exception as e: print(e) await q.edit_message_text(e) finally: try: os.remove(filename) os.remove(thumb_image_path) except: pass ================================================ FILE: requirements.txt ================================================ TgCrypto https://raw.githubusercontent.com/HasibulKabir/pyrogramarchive/master/pyrogramasyncv0.18.0.zip git+https://github.com/ytdl-org/youtube-dl wget pillow hachoir ================================================ FILE: runtime.txt ================================================ python-3.8.7 ================================================ FILE: start.sh ================================================ python3 -m bot ================================================ FILE: utils/util.py ================================================ def humanbytes(num, suffix='B'): if num is None: num = 0 else: num = int(num) for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']: if abs(num) < 1024.0: return "%3.1f%s%s" % (num, unit, suffix) num /= 1024.0 return "%.1f%s%s" % (num, 'Yi', suffix)