From 6bbc2e4d35fafd280f456adb395512a8ea202df7 Mon Sep 17 00:00:00 2001 From: subinps <64341611+subinps@users.noreply.github.com> Date: Sun, 10 Oct 2021 16:13:23 +0530 Subject: [PATCH] fixes and improvements --- README.md | 2 + config.py | 178 +++++---- main.py | 23 +- plugins/callback.py | 60 ++- plugins/commands.py | 9 +- plugins/controls.py | 2 +- plugins/export_import.py | 2 +- plugins/inline.py | 2 +- plugins/manage_admins.py | 4 +- plugins/player.py | 130 +++++-- plugins/recorder.py | 2 +- plugins/scheduler.py | 20 +- requirements.txt | 7 +- user.py | 2 +- userplugins/group_call.py | 10 +- utils/__init__.py | 5 + database.py => utils/database.py | 2 +- debug.py => utils/debug.py | 18 +- font.ttf => utils/font.ttf | Bin logger.py => utils/logger.py | 0 utils/pyro_dl.py | 333 ++++++++++++++++ utils.py => utils/utils.py | 630 +++++++++++++++++-------------- 22 files changed, 976 insertions(+), 465 deletions(-) create mode 100644 utils/__init__.py rename database.py => utils/database.py (99%) rename debug.py => utils/debug.py (95%) rename font.ttf => utils/font.ttf (100%) rename logger.py => utils/logger.py (100%) create mode 100644 utils/pyro_dl.py rename utils.py => utils/utils.py (79%) diff --git a/README.md b/README.md index 502c87ce..25222dd7 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Telegram bot to stream videos in telegram voicechat for both groups and channels 1. `DATABASE_URI`: MongoDB database Url, get from [mongodb](https://cloud.mongodb.com). This is an optional var, but it is recomonded to use this to experiance the full features. 2. `HEROKU_API_KEY`: Your heroku api key. Get one from [here](https://dashboard.heroku.com/account/applications/authorizations/new) 3. `HEROKU_APP_NAME`: Your heroku apps name. +4. `FILTERS`: Filter the search for channel play. Channel play means you can play all the files in a purticular channel using /cplay command. Current filters are `video document` . For searching audio files use `video document audio` . for video only search , use `video` and so on. ### Optional Vars 1. `LOG_GROUP` : Group to send Playlist, if CHAT is a Group() @@ -72,6 +73,7 @@ python3 main.py ## Features - Playlist, queue. +- Zero downtime in playing. - Supports Video Recording. - Supports Scheduling voicechats. - Cool UI for controling the player. diff --git a/config.py b/config.py index dda860bf..87da0dad 100644 --- a/config.py +++ b/config.py @@ -12,20 +12,12 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from logger import LOGGER +from utils import LOGGER try: import os import heroku3 from dotenv import load_dotenv from ast import literal_eval as is_enabled - from pytgcalls.types.input_stream.quality import ( - HighQualityVideo, - HighQualityAudio, - MediumQualityAudio, - MediumQualityVideo, - LowQualityAudio, - LowQualityVideo - ) except ModuleNotFoundError: import os @@ -79,13 +71,15 @@ class Config: PORTRAIT=is_enabled(os.environ.get("PORTRAIT", 'False')) IS_VIDEO_RECORD=is_enabled(os.environ.get("IS_VIDEO_RECORD", 'True')) DEBUG=is_enabled(os.environ.get("DEBUG", 'False')) + PTN=is_enabled(os.environ.get("PTN", "False")) #Quality vars - BITRATE=os.environ.get("BITRATE", False) - FPS=os.environ.get("FPS", False) - CUSTOM_QUALITY=os.environ.get("QUALITY", "HIGH") - + E_BITRATE=os.environ.get("BITRATE", False) + E_FPS=os.environ.get("FPS", False) + CUSTOM_QUALITY=os.environ.get("QUALITY", "100") + #Search filters for cplay + FILTERS = [filter.lower() for filter in (os.environ.get("FILTERS", "video document")).split(" ")] #Dont touch these, these are not for configuring player @@ -105,6 +99,7 @@ class Config: CALL_STATUS=False YPLAY=False YSTREAM=False + CPLAY=False STREAM_SETUP=False LISTEN=False STREAM_LINK=False @@ -141,65 +136,92 @@ class Config: REPLY_MESSAGE=None REPLY_PM=False - if BITRATE: + if E_BITRATE: try: - BITRATE=int(BITRATE) + BITRATE=int(E_BITRATE) except: LOGGER.error("Invalid bitrate specified.") - BITRATE=False + E_BITRATE=False + BITRATE=48000 + if not BITRATE >= 48000: + BITRATE=48000 else: - BITRATE=False + BITRATE=48000 - if FPS: + if E_FPS: try: - FPS=int(FPS) + FPS=int(E_FPS) except: LOGGER.error("Invalid FPS specified") - if BITRATE: - FPS=False - if not FPS <= 30: - FPS=False - else: - FPS=False - - if CUSTOM_QUALITY.lower() == 'high': - VIDEO_Q=HighQualityVideo() - AUDIO_Q=HighQualityAudio() - elif CUSTOM_QUALITY.lower() == 'medium': - VIDEO_Q=MediumQualityVideo() - AUDIO_Q=MediumQualityAudio() - elif CUSTOM_QUALITY.lower() == 'low': - VIDEO_Q=LowQualityVideo() - AUDIO_Q=LowQualityAudio() + E_FPS=False + if not FPS >= 30: + FPS=30 else: - LOGGER.warning("Invalid QUALITY specified.Defaulting to High.") - VIDEO_Q=HighQualityVideo() - AUDIO_Q=HighQualityVideo() - + FPS=30 + try: + CUSTOM_QUALITY=int(CUSTOM_QUALITY) + if CUSTOM_QUALITY > 100: + CUSTOM_QUALITY = 100 + LOGGER.warning("maximum quality allowed is 100, invalid quality specified. Quality set to 100") + elif CUSTOM_QUALITY < 10: + LOGGER.warning("Minimum Quality allowed is 10., Qulaity set to 10") + CUSTOM_QUALITY = 10 + if 66.9 < CUSTOM_QUALITY < 100: + if not E_BITRATE: + BITRATE=48000 + elif 50 < CUSTOM_QUALITY < 66.9: + if not E_BITRATE: + BITRATE=36000 + else: + if not E_BITRATE: + BITRATE=24000 + except: + if CUSTOM_QUALITY.lower() == 'high': + CUSTOM_QUALITY=100 + elif CUSTOM_QUALITY.lower() == 'medium': + CUSTOM_QUALITY=66.9 + elif CUSTOM_QUALITY.lower() == 'low': + CUSTOM_QUALITY=50 + else: + LOGGER.warning("Invalid QUALITY specified.Defaulting to High.") + CUSTOM_QUALITY=100 + + #help strings PLAY_HELP=""" __You can play using any of these options__ 1. Play a video from a YouTube link. - Command: **/play** - __You can use this as a reply to a YouTube link or pass link along command. or as a reply to message to search that in YouTube.__ +Command: **/play** +__You can use this as a reply to a YouTube link or pass link along command. or as a reply to message to search that in YouTube.__ 2. Play from a telegram file. - Command: **/play** - __Reply to a supported media(video and documents or audio file ).__ - Note: __For both the cases /fplay also can be used by admins to play the song immediately without waiting for queue to end.__ +Command: **/play** +__Reply to a supported media(video and documents or audio file ).__ +Note: __For both the cases /fplay also can be used by admins to play the song immediately without waiting for queue to end.__ + 3. Play from a YouTube playlist - Command: **/yplay** - __First get a playlist file from @GetPlaylistBot or @DumpPlaylist and reply to playlist file.__ +Command: **/yplay** +__First get a playlist file from @GetPlaylistBot or @DumpPlaylist and reply to playlist file.__ 4. Live Stream - Command: **/stream** - __Pass a live stream URL or any direct URL to play it as stream.__ +Command: **/stream** +__Pass a live stream URL or any direct URL to play it as stream.__ 5. Import an old playlist. - Command: **/import** - __Reply to a previously exported playlist file. __ +Command: **/import** +__Reply to a previously exported playlist file. __ + +6. Channel Play +Command: **/cplay** +__Use `/cplay channel username or channel id` to play all the files from the given channel. +By default both video files and documents will be played . You can add or remove the file type using `FILTERS` var. +For example , to stream audio, video and document from the channel use `/env FILTERS video document audio` . If you need only audio , you can use `/env FILTERS video audio` and so on. +To set up the files from a channel as STARTUP_STREAM, so that the files will be automatically added to playlist on startup of bot. use `/env STARTUP_STREAM channel username or channel id` + +Note that for public channels you should use username of channels along with '@' and for private channels you should use channel id. +For private channels , make sure both the bot and USER account is a member of channel.__ """ SETTINGS_HELP=""" **You can easily customize you player as per you needs. The following configurations are available:** @@ -270,41 +292,41 @@ class Config: CONTROL_HELP=""" __VCPlayer allows you to control your streams easily__ 1. Skip a song. - Command: **/skip** - __You can pass a number greater than 2 to skip the song in that position.__ +Command: **/skip** +__You can pass a number greater than 2 to skip the song in that position.__ - 2. Pause the player. - Command: **/pause** +2. Pause the player. +Command: **/pause** - 3. Resume the player. - Command: **/resume** +3. Resume the player. +Command: **/resume** - 4. Change Volume. - Command: **/volume** - __Pass the volume in between 1-200.__ +4. Change Volume. +Command: **/volume** +__Pass the volume in between 1-200.__ - 5. Leave the VC. - Command: **/leave** +5. Leave the VC. +Command: **/leave** - 6. Shuffle the playlist. - Command: **/shuffle** +6. Shuffle the playlist. +Command: **/shuffle** - 7. Clear the current playlist queue. - Command: **/clearplaylist** +7. Clear the current playlist queue. +Command: **/clearplaylist** - 8. Seek the video. - Command: **/seek** - __You can pass number of seconds to be skipped. Example: /seek 10 to skip 10 sec. /seek -10 to rewind 10 sec.__ +8. Seek the video. +Command: **/seek** +__You can pass number of seconds to be skipped. Example: /seek 10 to skip 10 sec. /seek -10 to rewind 10 sec.__ - 9. Mute the player. - Command: **/mute** +9. Mute the player. +Command: **/mute** - 10. Unmute the player. - Command : **/unmute** +10. Unmute the player. +Command : **/unmute** - 11. Shows the playlist. - Command: **/playlist** - __Use /player to show with control buttons__ +11. Shows the playlist. +Command: **/playlist** +__Use /player to show with control buttons__ """ ADMIN_HELP=""" @@ -362,7 +384,9 @@ class Config: 6. `STARTUP_STREAM` : __This will be streamed on startups and restarts of bot. You can use either any STREAM_URL or a direct link of any video or a Youtube Live link. You can also use YouTube Playlist.Find a Telegram Link for your playlist from [PlayList Dumb](https://telegram.dog/DumpPlaylist) or get a PlayList from [PlayList Extract](https://telegram.dog/GetAPlaylistbot). -The PlayList link should in form `https://t.me/DumpPlaylist/xxx`.__ +The PlayList link should in form `https://t.me/DumpPlaylist/xxx` +You can also use the files from a channel as startup stream. For that just use the channel id or channel username of channel as STARTUP_STREAM value. +For more info on channel play , read help from player section.__ **Recommended Optional Vars** @@ -372,6 +396,8 @@ class Config: 3. `HEROKU_APP_NAME`: __Your heroku app's name.__ +4. `FILTERS`: __Filters for channel play file search. Read help about cplay in player section.__ + **Other Optional Vars** 1. `LOG_GROUP` : __Group to send Playlist, if CHAT is a Group__ diff --git a/main.py b/main.py index 29a48cc5..f06268b8 100644 --- a/main.py +++ b/main.py @@ -19,22 +19,15 @@ sync_from_db ) from user import group_call, USER -from logger import LOGGER +from utils import LOGGER from config import Config from pyrogram import idle from bot import bot import asyncio import os if Config.DATABASE_URI: - from database import Database - db = Database() + from utils import db - -if not os.path.isdir("./downloads"): - os.makedirs("./downloads") -else: - for f in os.listdir("./downloads"): - os.remove(f"./downloads/{f}") async def main(): await bot.start() @@ -52,11 +45,11 @@ async def main(): pass await sync_from_db() except Exception as e: - LOGGER.error(f"Errors occured while setting up database for VCPlayerBot, check the value of DATABASE_URI. Full error - {str(e)}") + LOGGER.error(f"Errors occured while setting up database for VCPlayerBot, check the value of DATABASE_URI. Full error - {str(e)}", exc_info=True) Config.STARTUP_ERROR="Errors occured while setting up database for VCPlayerBot, check the value of DATABASE_URI. Full error - {str(e)}" LOGGER.info("Activating debug mode, you can reconfigure your bot with /env command.") await bot.stop() - from debug import debug + from utils import debug await debug.start() await idle() return @@ -64,7 +57,7 @@ async def main(): if Config.DEBUG: LOGGER.info("Debugging enabled by user, Now in debug mode.") Config.STARTUP_ERROR="Debugging enabled by user, Now in debug mode." - from debug import debug + from utils import debug await bot.stop() await debug.start() await idle() @@ -78,7 +71,7 @@ async def main(): LOGGER.error("Startup checks not passed , bot is quiting") await bot.stop() LOGGER.info("Activating debug mode, you can reconfigure your bot with /env command.") - from debug import debug + from utils import debug await debug.start() await idle() return @@ -91,10 +84,10 @@ async def main(): LOGGER.info("Loop play enabled , starting playing startup stream.") await start_stream() except Exception as e: - LOGGER.error(f"Startup was unsuccesfull, Errors - {e}") + LOGGER.error(f"Startup was unsuccesfull, Errors - {e}", exc_info=True) LOGGER.info("Activating debug mode, you can reconfigure your bot with /env command.") Config.STARTUP_ERROR=f"Startup was unsuccesfull, Errors - {e}" - from debug import debug + from utils import debug await bot.stop() await debug.start() await idle() diff --git a/plugins/callback.py b/plugins/callback.py index 2fbba3d6..abf0579f 100644 --- a/plugins/callback.py +++ b/plugins/callback.py @@ -13,7 +13,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from logger import LOGGER +from utils import LOGGER from pyrogram import Client from contextlib import suppress from config import Config @@ -108,7 +108,7 @@ async def cb_handler(client: Client, query: CallbackQuery): if query.message.chat.type != "private" and query.message.reply_to_message.from_user is None: return await query.answer("I cant help you here, since you are an anonymous admin, message me in private chat.", show_alert=True) elif query.message.chat.type != "private" and query.from_user.id != query.message.reply_to_message.from_user.id: - return await query.answer("Okda") + return await query.answer("Okda", show_alert=True) me, nyav = query.data.split("_") back=InlineKeyboardMarkup( [ @@ -156,7 +156,7 @@ async def cb_handler(client: Client, query: CallbackQuery): elif nyav == 'env': await query.message.edit(Config.ENV_HELP, reply_markup=back, disable_web_page_preview=True) - if not (query.from_user is None or query.from_user.id in admins): + if not query.from_user.id in admins: await query.answer( "šŸ˜’ Played Joji.mp3", show_alert=True @@ -167,7 +167,7 @@ async def cb_handler(client: Client, query: CallbackQuery): if query.message.chat.type != "private" and query.message.reply_to_message.from_user is None: return await query.answer("You cant use scheduling here, since you are an anonymous admin. Schedule from private chat.", show_alert=True) if query.message.chat.type != "private" and query.from_user.id != query.message.reply_to_message.from_user.id: - return await query.answer("Okda") + return await query.answer("Okda", show_alert=True) data = query.data today = datetime.datetime.now(IST) smonth=today.strftime("%B") @@ -341,9 +341,9 @@ async def cb_handler(client: Client, query: CallbackQuery): await query.message.delete() await query.message.reply_to_message.delete() - if query.data == "shuffle": + elif query.data == "shuffle": if not Config.playlist: - await query.answer("Playlist is empty.") + await query.answer("Playlist is empty.", show_alert=True) return await shuffle_playlist() await query.answer("Playlist shuffled.") @@ -353,7 +353,7 @@ async def cb_handler(client: Client, query: CallbackQuery): elif query.data.lower() == "pause": if Config.PAUSE: - await query.answer("Already Paused") + await query.answer("Already Paused", show_alert=True) else: await pause() await query.answer("Stream Paused") @@ -364,7 +364,7 @@ async def cb_handler(client: Client, query: CallbackQuery): elif query.data.lower() == "resume": if not Config.PAUSE: - await query.answer("Nothing Paused to resume") + await query.answer("Nothing Paused to resume", show_alert=True) else: await resume() await query.answer("Redumed the stream") @@ -373,7 +373,7 @@ async def cb_handler(client: Client, query: CallbackQuery): elif query.data=="skip": if not Config.playlist: - await query.answer("No songs in playlist") + await query.answer("No songs in playlist", show_alert=True) else: await query.answer("Trying to skip from playlist.") await skip() @@ -391,7 +391,7 @@ async def cb_handler(client: Client, query: CallbackQuery): elif query.data=="replay": if not Config.playlist: - await query.answer("No songs in playlist") + await query.answer("No songs in playlist", show_alert=True) else: await query.answer("trying to restart player") await restart_playout() @@ -399,20 +399,6 @@ async def cb_handler(client: Client, query: CallbackQuery): await query.message.edit_reply_markup(reply_markup=await get_buttons()) - elif query.data=="help": - buttons = [ - [ - InlineKeyboardButton('āš™ļø Update Channel', url='https://t.me/subin_works'), - InlineKeyboardButton('šŸ§© Source', url='https://github.com/subinps/VCPlayerBot'), - ] - ] - reply_markup = InlineKeyboardMarkup(buttons) - await query.message.edit( - Config.HELP, - reply_markup=reply_markup - - ) - elif query.data.lower() == "mute": if Config.MUTED: await unmute() @@ -425,7 +411,7 @@ async def cb_handler(client: Client, query: CallbackQuery): elif query.data.lower() == 'seek': if not Config.CALL_STATUS: - return await query.answer("Not Playing anything.") + return await query.answer("Not Playing anything.", show_alert=True) #if not (Config.playlist or Config.STREAM_LINK): #return await query.answer("Startup stream cant be seeked.", show_alert=True) await query.answer("trying to seek.") @@ -440,7 +426,7 @@ async def cb_handler(client: Client, query: CallbackQuery): elif query.data.lower() == 'rewind': if not Config.CALL_STATUS: - return await query.answer("Not Playing anything.") + return await query.answer("Not Playing anything.", show_alert=True) #if not (Config.playlist or Config.STREAM_LINK): #return await query.answer("Startup stream cant be seeked.", show_alert=True) await query.answer("trying to rewind.") @@ -469,15 +455,21 @@ async def cb_handler(client: Client, query: CallbackQuery): if you == "main": await query.message.edit_reply_markup(reply_markup=await volume_buttons()) if you == "add": - vol=Config.VOLUME+10 - if not (1 < vol < 200): + if 190 <= Config.VOLUME <=200: + vol=200 + else: + vol=Config.VOLUME+10 + if not (1 <= vol <= 200): return await query.answer("Only 1-200 range accepted.") await volume(vol) Config.VOLUME=vol await query.message.edit_reply_markup(reply_markup=await volume_buttons()) elif you == "less": - vol=Config.VOLUME-10 - if not (1 < vol < 200): + if 1 <= Config.VOLUME <=10: + vol=1 + else: + vol=Config.VOLUME-10 + if not (1 <= vol <= 200): return await query.answer("Only 1-200 range accepted.") await volume(vol) Config.VOLUME=vol @@ -566,19 +558,17 @@ async def cb_handler(client: Client, query: CallbackQuery): if query.from_user.id in Config.SUDO: await query.message.delete() else: - await query.answer("This can only be used by SUDO users") + await query.answer("This can only be used by SUDO users", show_alert=True) else: if query.message.chat.type != "private" and query.message.reply_to_message: if query.message.reply_to_message.from_user is None: pass elif query.from_user.id != query.message.reply_to_message.from_user.id: - return await query.answer("Okda") + return await query.answer("Okda", show_alert=True) elif query.from_user.id in Config.ADMINS: pass else: - return await query.answer("Okda") + return await query.answer("Okda", show_alert=True) await query.answer("Menu Closed") await query.message.delete() await query.answer() - - diff --git a/plugins/commands.py b/plugins/commands.py index f1b692a6..409dddc1 100644 --- a/plugins/commands.py +++ b/plugins/commands.py @@ -12,7 +12,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from logger import LOGGER +from utils import LOGGER from contextlib import suppress from config import Config import calendar @@ -49,7 +49,7 @@ ) IST = pytz.timezone(Config.TIME_ZONE) if Config.DATABASE_URI: - from database import db + from utils import db HOME_TEXT = "Hey [{}](tg://user?id={}) šŸ™‹ā€ā™‚ļø\n\nIam A Bot Built To Play or Stream Videos In Telegram VoiceChats.\nI Can Stream Any YouTube Video Or A Telegram File Or Even A YouTube Live." admin_filter=filters.create(is_admin) @@ -216,7 +216,10 @@ async def update_handler(client, message): db.add_config("RESTART", msg) else: await db.edit_config("RESTART", msg) - await message.delete() + try: + await message.delete() + except: + pass await update() @Client.on_message(filters.command(['logs', f"logs@{Config.BOT_USERNAME}"]) & admin_filter & chat_filter) diff --git a/plugins/controls.py b/plugins/controls.py index f5b377c1..7aeda3f3 100644 --- a/plugins/controls.py +++ b/plugins/controls.py @@ -12,7 +12,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from logger import LOGGER +from utils import LOGGER from pyrogram.types import Message from config import Config from pyrogram import ( diff --git a/plugins/export_import.py b/plugins/export_import.py index 3c8cdbc0..3df2d532 100644 --- a/plugins/export_import.py +++ b/plugins/export_import.py @@ -13,7 +13,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from logger import LOGGER +from utils import LOGGER import json import os from pyrogram.types import Message diff --git a/plugins/inline.py b/plugins/inline.py index 1e0a6a7f..647452e7 100644 --- a/plugins/inline.py +++ b/plugins/inline.py @@ -16,7 +16,7 @@ from pyrogram.handlers import InlineQueryHandler from youtubesearchpython import VideosSearch from config import Config -from logger import LOGGER +from utils import LOGGER from pyrogram.types import ( InlineQueryResultArticle, InputTextMessageContent, diff --git a/plugins/manage_admins.py b/plugins/manage_admins.py index faf0b0f1..e977e3cd 100644 --- a/plugins/manage_admins.py +++ b/plugins/manage_admins.py @@ -12,7 +12,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from logger import LOGGER +from utils import LOGGER from config import Config from pyrogram import ( Client, @@ -44,6 +44,7 @@ async def add_admin(client, message): user=await client.get_users(user) except Exception as e: k=await message.reply(f"I was unable to locate that user.\nError: {e}") + LOGGER.error(f"Unable to find the user - {e}", exc_info=True) await delete_messages([message, k]) return user_id=user.id @@ -86,6 +87,7 @@ async def remove_admin(client, message): user=await client.get_users(user) except Exception as e: k = await message.reply(f"I was unable to locate that user.\nError: {e}") + LOGGER.error(f"Unable to Locate user, {e}", exc_info=True) await delete_messages([message, k]) return user_id=user.id diff --git a/plugins/player.py b/plugins/player.py index f89566e4..87e1a6a6 100644 --- a/plugins/player.py +++ b/plugins/player.py @@ -12,14 +12,16 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from logger import LOGGER + +from utils import LOGGER from youtube_search import YoutubeSearch from contextlib import suppress from pyrogram.types import Message -from youtube_dl import YoutubeDL +from yt_dlp import YoutubeDL from datetime import datetime from pyrogram import filters from config import Config +from PTN import parse import re from utils import ( add_to_db_playlist, @@ -27,7 +29,7 @@ delete_messages, download, get_admins, - get_duration, + get_duration, is_admin, get_buttons, get_link, @@ -40,7 +42,8 @@ shuffle_playlist, start_stream, stream_from_link, - chat_filter + chat_filter, + c_play ) from pyrogram.types import ( InlineKeyboardMarkup, @@ -48,13 +51,17 @@ ) from pyrogram.errors import ( MessageIdInvalid, - MessageNotModified + MessageNotModified, + UserNotParticipant, + PeerIdInvalid, + ChannelInvalid ) from pyrogram import ( Client, filters ) + admin_filter=filters.create(is_admin) @Client.on_message(filters.command(["play", "fplay", f"play@{Config.BOT_USERNAME}", f"fplay@{Config.BOT_USERNAME}"]) & chat_filter) @@ -107,20 +114,8 @@ async def add_to_playlist(_, message: Message): type="youtube" yturl=query elif query.startswith("http"): - """if Config.IS_VIDEO: - try: - width, height = get_height_and_width(query) - except: - width, height = None, None - LOGGER.error("Unable to get video properties within time.") - if not width or \ - not height: - await msg.edit("This is an invalid link, provide me a direct link or a youtube link.") - await delete_messages([message, msg]) - return """ - #else: try: - has_audio_ = is_audio(query) + has_audio_ = await is_audio(query) except: has_audio_ = False LOGGER.error("Unable to get Audio properties within time.") @@ -129,7 +124,7 @@ async def add_to_playlist(_, message: Message): await delete_messages([message, msg]) return try: - dur=get_duration(query) + dur=await get_duration(query) except: dur=0 if dur == 0: @@ -152,9 +147,17 @@ async def add_to_playlist(_, message: Message): if type in ["video", "audio"]: if type == "audio": title=m_video.title + unique = f"{nyav}_{m_video.file_size}_audio" else: title=m_video.file_name - data={1:title, 2:m_video.file_id, 3:"telegram", 4:user, 5:f"{nyav}_{m_video.file_size}"} + unique = f"{nyav}_{m_video.file_size}_video" + file_id=m_video.file_id + if Config.PTN: + ny = parse(title) + title_ = ny.get("title") + if title_: + title = title_ + data={1:title, 2:file_id, 3:"telegram", 4:user, 5:unique} if message.command[0] == "fplay": pla = [data] + Config.playlist Config.playlist = pla @@ -177,12 +180,13 @@ async def add_to_playlist(_, message: Message): await msg.edit( "Song not found.\nTry inline mode.." ) - LOGGER.error(str(e)) + LOGGER.error(str(e), exc_info=True) await delete_messages([message, msg]) return else: return ydl_opts = { + "quite": True, "geo-bypass": True, "nocheckcertificate": True } @@ -190,7 +194,7 @@ async def add_to_playlist(_, message: Message): try: info = ydl.extract_info(url, False) except Exception as e: - LOGGER.error(e) + LOGGER.error(e, exc_info=True) await msg.edit( f"YouTube Download Error āŒ\nError:- {e}" ) @@ -281,13 +285,77 @@ async def clear_play_list(client, m: Message): k=await m.reply_text(f"Playlist Cleared.") await clear_db_playlist(all=True) if Config.IS_LOOP \ - and not Config.YPLAY: + and not (Config.YPLAY or Config.CPLAY): await start_stream() else: await leave_call() await delete_messages([m, k]) + +@Client.on_message(filters.command(["cplay", f"cplay@{Config.BOT_USERNAME}"]) & admin_filter & chat_filter) +async def channel_play_list(client, m: Message): + with suppress(MessageIdInvalid, MessageNotModified): + k=await m.reply("Setting up for channel play..") + if " " in m.text: + you, me = m.text.split(" ", 1) + if me.startswith("-100"): + try: + me=int(me) + except: + await k.edit("Invalid chat id given") + await delete_messages([m, k]) + return + try: + await client.get_chat_member(int(me), Config.USER_ID) + except (ValueError, PeerIdInvalid, ChannelInvalid): + LOGGER.error(f"Given channel is private and @{Config.BOT_USERNAME} is not an admin over there.", exc_info=True) + await k.edit(f"Given channel is private and @{Config.BOT_USERNAME} is not an admin over there. If channel is not private , please provide username of channel.") + await delete_messages([m, k]) + return + except UserNotParticipant: + LOGGER.error("Given channel is private and USER account is not a member of channel.") + await k.edit("Given channel is private and USER account is not a member of channel.") + await delete_messages([m, k]) + return + except Exception as e: + LOGGER.error(f"Errors occured while getting data abount channel - {e}", exc_info=True) + await k.edit(f"Something went wrong- {e}") + await delete_messages([m, k]) + return + await k.edit("Searching files from channel, this may take some time, depending on number of files in the channel.") + st, msg = await c_play(me) + if st == False: + await m.edit(msg) + else: + await k.edit(f"Succesfully added {msg} files to playlist.") + elif me.startswith("@"): + me = me.replace("@", "") + try: + chat=await client.get_chat(me) + except Exception as e: + LOGGER.error(f"Errors occured while fetching info about channel - {e}", exc_info=True) + await k.edit(f"Errors occured while getting data about channel - {e}") + await delete_messages([m, k]) + return + await k.edit("Searching files from channel, this may take some time, depending on number of files in the channel.") + st, msg=await c_play(me) + if st == False: + await k.edit(msg) + await delete_messages([m, k]) + else: + await k.edit(f"Succesfully Added {msg} files from {chat.title} to playlist") + await delete_messages([m, k]) + else: + await k.edit("The given channel is invalid. For private channels it should start with -100 and for public channels it should start with @\nExamples - `/cplay @VCPlayerFiles or /cplay -100125369865\n\nFor private channel, both bot and the USER account should be members of channel.") + await delete_messages([m, k]) + else: + await k.edit("You didn't gave me any channel. Give me a channel id or username from which i should play files . \nFor private channels it should start with -100 and for public channels it should start with @\nExamples - `/cplay @VCPlayerFiles or /cplay -100125369865\n\nFor private channel, both bot and the USER account should be members of channel.") + await delete_messages([m, k]) + + + + @Client.on_message(filters.command(["yplay", f"yplay@{Config.BOT_USERNAME}"]) & admin_filter & chat_filter) async def yt_play_list(client, m: Message): with suppress(MessageIdInvalid, MessageNotModified): @@ -343,20 +411,8 @@ async def stream(client, m: Message): return else: stream_link=link - """if Config.IS_VIDEO: - try: - width, height = get_height_and_width(stream_link) - except: - width, height = None, None - LOGGER.error("Unable to get video properties within time.") - if not width or \ - not height: - k = await msg.edit("This is an invalid link, provide me a direct link or a youtube link.") - await delete_messages([m, k]) - return""" - #else: try: - is_audio_ = is_audio(stream_link) + is_audio_ = await is_audio(stream_link) except: is_audio_ = False LOGGER.error("Unable to get Audio properties within time.") @@ -365,7 +421,7 @@ async def stream(client, m: Message): await delete_messages([m, k]) return try: - dur=get_duration(stream_link) + dur=await get_duration(stream_link) except: dur=0 if dur != 0: diff --git a/plugins/recorder.py b/plugins/recorder.py index 3d7440ff..616d7261 100644 --- a/plugins/recorder.py +++ b/plugins/recorder.py @@ -13,7 +13,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from logger import LOGGER +from utils import LOGGER from config import Config from pyrogram import ( Client, diff --git a/plugins/scheduler.py b/plugins/scheduler.py index 96c581a8..ae5821e6 100644 --- a/plugins/scheduler.py +++ b/plugins/scheduler.py @@ -12,15 +12,16 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from logger import LOGGER +from utils import LOGGER import re import calendar from datetime import datetime from contextlib import suppress import pytz from config import Config +from PTN import parse from youtube_search import YoutubeSearch -from youtube_dl import YoutubeDL +from yt_dlp import YoutubeDL from pyrogram import( Client, @@ -107,7 +108,7 @@ async def schedule_vc(bot, message): return """ #else: try: - has_audio_ = is_audio(query) + has_audio_ = await is_audio(query) except: has_audio_ = False LOGGER.error("Unable to get Audio properties within time.") @@ -131,9 +132,16 @@ async def schedule_vc(bot, message): if type in ["video", "audio"]: if type == "audio": title=m_video.title + unique = f"{nyav}_{m_video.file_size}_audio" else: title=m_video.file_name - data={'1':title, '2':m_video.file_id, '3':"telegram", '4':user, '5':f"{nyav}_{m_video.file_size}"} + unique = f"{nyav}_{m_video.file_size}_audio" + if Config.PTN: + ny = parse(title) + title_ = ny.get("title") + if title_: + title = title_ + data={'1':title, '2':m_video.file_id, '3':"telegram", '4':user, '5':unique} sid=f"{message.chat.id}_{msg.message_id}" Config.SCHEDULED_STREAM[sid] = data await sync_to_db() @@ -152,7 +160,7 @@ async def schedule_vc(bot, message): await msg.edit( "Song not found.\nTry inline mode.." ) - LOGGER.error(str(e)) + LOGGER.error(str(e), exc_info=True) await delete_messages([message, msg]) return else: @@ -166,7 +174,7 @@ async def schedule_vc(bot, message): try: info = ydl.extract_info(url, False) except Exception as e: - LOGGER.error(e) + LOGGER.error(e, exc_info=True) await msg.edit( f"YouTube Download Error āŒ\nError:- {e}" ) diff --git a/requirements.txt b/requirements.txt index e94ee41e..374d8de6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,8 @@ git+https://github.com/pyrogram/pyrogram@master -py-tgcalls==0.8.1b25 +py-tgcalls==0.8.1rc1 +parse-torrent-name tgcrypto -ffmpeg-python -wrapt_timeout_decorator -youtube_dl +yt-dlp youtube_search_python youtube_search heroku3 diff --git a/user.py b/user.py index 11967af4..14bd9cb7 100644 --- a/user.py +++ b/user.py @@ -15,7 +15,7 @@ from pytgcalls import PyTgCalls from pyrogram import Client from config import Config -from logger import LOGGER +from utils import LOGGER USER = Client( Config.SESSION, diff --git a/userplugins/group_call.py b/userplugins/group_call.py index 6e4abe34..c071536d 100644 --- a/userplugins/group_call.py +++ b/userplugins/group_call.py @@ -12,7 +12,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from logger import LOGGER +from utils import LOGGER from pyrogram.errors import BotInlineDisabled from pyrogram import Client, filters from config import Config @@ -85,7 +85,7 @@ async def reply(client, message): LOGGER.error(f"Error: Inline Mode for @{Config.BOT_USERNAME} is not enabled. Enable from @Botfather to enable PM Permit.") await message.reply(f"{Config.REPLY_MESSAGE}\n\nYou can't use this bot in your group, for that you have to make your own bot from the [SOURCE CODE](https://github.com/subinps/VCPlayerBot) below.", disable_web_page_preview=True) except Exception as e: - LOGGER.error(e) + LOGGER.error(e, exc_info=True) pass @@ -215,6 +215,12 @@ async def handler(client: PyTgCalls, update: Update): Config.CALL_STATUS = True if Config.EDIT_TITLE: await edit_title() + who=await group_call.get_participants(Config.CHAT) + you=list(filter(lambda k:k.user_id == Config.USER_ID, who)) + if you: + for me in you: + if me.volume: + Config.VOLUME=round(int(me.volume)) elif isinstance(update, LeftVoiceChat): Config.CALL_STATUS = False elif isinstance(update, PausedStream): diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 00000000..ab89ec4d --- /dev/null +++ b/utils/__init__.py @@ -0,0 +1,5 @@ +from .logger import LOGGER +from .debug import debug +from .database import db +from .utils import * +from .pyro_dl import Downloader \ No newline at end of file diff --git a/database.py b/utils/database.py similarity index 99% rename from database.py rename to utils/database.py index cd63c4b3..ca6289f6 100644 --- a/database.py +++ b/utils/database.py @@ -12,7 +12,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from logger import LOGGER +from .logger import LOGGER import motor.motor_asyncio from config import Config diff --git a/debug.py b/utils/debug.py similarity index 95% rename from debug.py rename to utils/debug.py index 20366a9a..9f01815d 100644 --- a/debug.py +++ b/utils/debug.py @@ -1,10 +1,26 @@ +#!/usr/bin/env python3 +# Copyright (C) @subinps +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from .logger import LOGGER from config import Config import os import time from threading import Thread import sys if Config.DATABASE_URI: - from database import db + from .database import db from pyrogram import ( Client, filters diff --git a/font.ttf b/utils/font.ttf similarity index 100% rename from font.ttf rename to utils/font.ttf diff --git a/logger.py b/utils/logger.py similarity index 100% rename from logger.py rename to utils/logger.py diff --git a/utils/pyro_dl.py b/utils/pyro_dl.py new file mode 100644 index 00000000..3d1d5525 --- /dev/null +++ b/utils/pyro_dl.py @@ -0,0 +1,333 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + + +#https://github.com/pyrogram/pyrogram/blob/4f585c156c1a2c6707793a8ad7f2f111515ca23b/pyrogram/client.py#L492-L518 +#https://github.com/pyrogram/pyrogram/blob/4f585c156c1a2c6707793a8ad7f2f111515ca23b/pyrogram/client.py#L806-1044 + +#Pyrogram downloader modified to suit my needs. +#Downloads the file from telegram servers and retures the path of the file without waiting for the whole download to finish. +#Copyright (C) @subinps + + +from .logger import LOGGER +import asyncio +import os +import re +import asyncio +import os +import time +from datetime import datetime +from hashlib import sha256 +from bot import bot +import pyrogram +from pyrogram import raw +from pyrogram import utils +from pyrogram.crypto import aes +from pyrogram.errors import ( + VolumeLocNotFound, + AuthBytesInvalid +) +from pyrogram.session import( + Auth, + Session +) +from pyrogram.file_id import( + FileId, + FileType, + ThumbnailSource +) +from pyrogram.file_id import ( + FileId, + FileType, + PHOTO_TYPES +) + + +DEFAULT_DOWNLOAD_DIR = "downloads/" + +class Downloader(): + def __init__( + self, + ): + super().__init__() + self.client = bot + + async def pyro_dl(self, file_id): + file_id_obj = FileId.decode(file_id) + file_type = file_id_obj.file_type + mime_type = "" + date = 0 + file_name = "" + + directory, file_name = os.path.split(file_name) + if not os.path.isabs(file_name): + directory = self.client.PARENT_DIR / (directory or DEFAULT_DOWNLOAD_DIR) + if not file_name: + guessed_extension = self.client.guess_extension(mime_type) + + if file_type in PHOTO_TYPES: + extension = ".jpg" + elif file_type == FileType.VOICE: + extension = guessed_extension or ".ogg" + elif file_type in (FileType.VIDEO, FileType.ANIMATION, FileType.VIDEO_NOTE): + extension = guessed_extension or ".mp4" + elif file_type == FileType.DOCUMENT: + extension = guessed_extension or ".zip" + elif file_type == FileType.STICKER: + extension = guessed_extension or ".webp" + elif file_type == FileType.AUDIO: + extension = guessed_extension or ".mp3" + else: + extension = ".unknown" + + file_name = "{}_{}_{}{}".format( + FileType(file_id_obj.file_type).name.lower(), + datetime.fromtimestamp(date or time.time()).strftime("%Y-%m-%d_%H-%M-%S"), + self.client.rnd_id(), + extension + ) + final_file_path = os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name))) + os.makedirs(directory, exist_ok=True) + downloaderr = self.handle_download(file_id_obj, final_file_path) + asyncio.get_event_loop().create_task(downloaderr) + return final_file_path + + async def handle_download(self, file_id_obj, final_file_path): + try: + await self.get_file( + file_id=file_id_obj, + filename=final_file_path + ) + except Exception as e: + LOGGER.error(str(e), exc_info=True) + + try: + os.remove(final_file_path) + except OSError: + pass + else: + return final_file_path or None + + async def get_file( + self, + file_id: FileId, + filename: str, + ) -> str: + dc_id = file_id.dc_id + + async with self.client.media_sessions_lock: + session = self.client.media_sessions.get(dc_id, None) + + if session is None: + if dc_id != await self.client.storage.dc_id(): + session = Session( + self.client, dc_id, await Auth(self.client, dc_id, await self.client.storage.test_mode()).create(), + await self.client.storage.test_mode(), is_media=True + ) + await session.start() + + for _ in range(3): + exported_auth = await self.client.send( + raw.functions.auth.ExportAuthorization( + dc_id=dc_id + ) + ) + + try: + await session.send( + raw.functions.auth.ImportAuthorization( + id=exported_auth.id, + bytes=exported_auth.bytes + ) + ) + except AuthBytesInvalid: + continue + else: + break + else: + await session.stop() + raise AuthBytesInvalid + else: + session = Session( + self.client, dc_id, await self.client.storage.auth_key(), + await self.client.storage.test_mode(), is_media=True + ) + await session.start() + + self.client.media_sessions[dc_id] = session + + file_type = file_id.file_type + + if file_type == FileType.CHAT_PHOTO: + if file_id.chat_id > 0: + peer = raw.types.InputPeerUser( + user_id=file_id.chat_id, + access_hash=file_id.chat_access_hash + ) + else: + if file_id.chat_access_hash == 0: + peer = raw.types.InputPeerChat( + chat_id=-file_id.chat_id + ) + else: + peer = raw.types.InputPeerChannel( + channel_id=utils.get_channel_id(file_id.chat_id), + access_hash=file_id.chat_access_hash + ) + + location = raw.types.InputPeerPhotoFileLocation( + peer=peer, + volume_id=file_id.volume_id, + local_id=file_id.local_id, + big=file_id.thumbnail_source == ThumbnailSource.CHAT_PHOTO_BIG + ) + elif file_type == FileType.PHOTO: + location = raw.types.InputPhotoFileLocation( + id=file_id.media_id, + access_hash=file_id.access_hash, + file_reference=file_id.file_reference, + thumb_size=file_id.thumbnail_size + ) + else: + location = raw.types.InputDocumentFileLocation( + id=file_id.media_id, + access_hash=file_id.access_hash, + file_reference=file_id.file_reference, + thumb_size=file_id.thumbnail_size + ) + + limit = 1024 * 1024 + offset = 0 + file_name = "" + + try: + r = await session.send( + raw.functions.upload.GetFile( + location=location, + offset=offset, + limit=limit + ), + sleep_threshold=30 + ) + + if isinstance(r, raw.types.upload.File): + #with tempfile.NamedTemporaryFile("wb", delete=False) as f: + with open(filename, 'wb') as f: + file_name = filename + while True: + chunk = r.bytes + + if not chunk: + break + + f.write(chunk) + + offset += limit + r = await session.send( + raw.functions.upload.GetFile( + location=location, + offset=offset, + limit=limit + ), + sleep_threshold=30 + ) + + elif isinstance(r, raw.types.upload.FileCdnRedirect): + async with self.client.media_sessions_lock: + cdn_session = self.client.media_sessions.get(r.dc_id, None) + + if cdn_session is None: + cdn_session = Session( + self.client, r.dc_id, await Auth(self.client, r.dc_id, await self.client.storage.test_mode()).create(), + await self.client.storage.test_mode(), is_media=True, is_cdn=True + ) + + await cdn_session.start() + + self.client.media_sessions[r.dc_id] = cdn_session + + try: + with open(filename, 'wb') as f: + file_name = f + while True: + r2 = await cdn_session.send( + raw.functions.upload.GetCdnFile( + file_token=r.file_token, + offset=offset, + limit=limit + ) + ) + + if isinstance(r2, raw.types.upload.CdnFileReuploadNeeded): + try: + await session.send( + raw.functions.upload.ReuploadCdnFile( + file_token=r.file_token, + request_token=r2.request_token + ) + ) + except VolumeLocNotFound: + break + else: + continue + + chunk = r2.bytes + + # https://core.telegram.org/cdn#decrypting-files + decrypted_chunk = aes.ctr256_decrypt( + chunk, + r.encryption_key, + bytearray( + r.encryption_iv[:-4] + + (offset // 16).to_bytes(4, "big") + ) + ) + + hashes = await session.send( + raw.functions.upload.GetCdnFileHashes( + file_token=r.file_token, + offset=offset + ) + ) + + # https://core.telegram.org/cdn#verifying-files + for i, h in enumerate(hashes): + cdn_chunk = decrypted_chunk[h.limit * i: h.limit * (i + 1)] + assert h.hash == sha256(cdn_chunk).digest(), f"Invalid CDN hash part {i}" + + f.write(decrypted_chunk) + + offset += limit + + if len(chunk) < limit: + break + except Exception as e: + LOGGER.error(e, exc_info=True) + raise e + except Exception as e: + if not isinstance(e, pyrogram.StopTransmission): + LOGGER.error(str(e), exc_info=True) + try: + os.remove(file_name) + except OSError: + pass + + return "" + else: + return file_name \ No newline at end of file diff --git a/utils.py b/utils/utils.py similarity index 79% rename from utils.py rename to utils/utils.py index 59c486cb..7118af20 100644 --- a/utils.py +++ b/utils/utils.py @@ -13,29 +13,30 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from logger import LOGGER +from .logger import LOGGER try: from pyrogram.raw.types import InputChannel - from wrapt_timeout_decorator import timeout from apscheduler.schedulers.asyncio import AsyncIOScheduler from apscheduler.jobstores.mongodb import MongoDBJobStore from apscheduler.jobstores.base import ConflictingIdError from pyrogram.raw.functions.channels import GetFullChannel from pytgcalls import StreamType - from youtube_dl import YoutubeDL + from yt_dlp import YoutubeDL from pyrogram import filters from pymongo import MongoClient from datetime import datetime from threading import Thread + from math import gcd + from .pyro_dl import Downloader from config import Config from asyncio import sleep from bot import bot + from PTN import parse import subprocess import asyncio + import json import random import re - import ffmpeg - import json import time import sys import os @@ -90,7 +91,7 @@ os.execl(sys.executable, sys.executable, *sys.argv) if Config.DATABASE_URI: - from database import db + from .database import db monclient = MongoClient(Config.DATABASE_URI) jobstores = { 'default': MongoDBJobStore(client=monclient, database=Config.DATABASE_NAME, collection='scheduler') @@ -99,20 +100,21 @@ else: scheduler = AsyncIOScheduler() scheduler.start() - +dl=Downloader() async def play(): song=Config.playlist[0] if song[3] == "telegram": file=Config.GET_FILE.get(song[5]) if not file: - await download(song) - while not file: - await sleep(1) - file=Config.GET_FILE.get(song[5]) - LOGGER.info("Downloading the file from TG") + file = await dl.pyro_dl(song[2]) + Config.GET_FILE[song[5]] = file while not os.path.exists(file): + file=Config.GET_FILE.get(song[5]) await sleep(1) + while not (os.stat(file).st_size) >= 0: + LOGGER.info("Waiting for download") + await sleep(2) elif song[3] == "url": file=song[2] else: @@ -122,7 +124,8 @@ async def play(): return await skip() else: LOGGER.error("This stream is not supported , leaving VC.") - return False + await leave_call() + return False link, seek, pic, width, height = await chek_the_media(file, title=f"{song[1]}") if not link: LOGGER.warning("Unsupported link, Skiping from queue.") @@ -130,6 +133,7 @@ async def play(): await sleep(1) if Config.STREAM_LINK: Config.STREAM_LINK=False + LOGGER.info(f"STARTING PLAYING: {song[1]}") await join_call(link, seek, pic, width, height) async def schedule_a_play(job_id, date): @@ -156,7 +160,7 @@ async def schedule_a_play(job_id, date): except ScheduleDateInvalid: LOGGER.error("Unable to schedule VideoChat, since date is invalid") except Exception as e: - LOGGER.error(f"Error in scheduling voicechat- {e}") + LOGGER.error(f"Error in scheduling voicechat- {e}", exc_info=True) await sync_to_db() async def run_schedule(job_id): @@ -219,11 +223,12 @@ async def skip(): await clear_db_playlist(song=old_track) if old_track[3] == "telegram": file=Config.GET_FILE.get(old_track[5]) - try: - os.remove(file) - except: - pass - del Config.GET_FILE[old_track[5]] + if file: + try: + os.remove(file) + except: + pass + del Config.GET_FILE[old_track[5]] if not Config.playlist \ and Config.IS_LOOP: LOGGER.info("Loop Play enabled, switching to STARTUP_STREAM, since playlist is empty.") @@ -240,7 +245,7 @@ async def skip(): await play() if len(Config.playlist) <= 1: return - await download(Config.playlist[1]) + #await download(Config.playlist[1]) async def check_vc(): @@ -258,7 +263,7 @@ async def check_vc(): await sleep(2) return True except Exception as e: - LOGGER.error(f"Unable to start new GroupCall :- {e}") + LOGGER.error(f"Unable to start new GroupCall :- {e}", exc_info=True) return False else: if Config.HAS_SCHEDULE: @@ -336,20 +341,29 @@ async def join_and_play(link, seek, pic, width, height): int(Config.CHAT), AudioPiped( link, - audio_parameters=Config.AUDIO_Q, + audio_parameters=AudioParameters( + Config.BITRATE + ), additional_ffmpeg_parameters=f'-ss {start} -atend -t {end}', ), stream_type=StreamType().pulse_stream, ) else: if pic: + cwidth, cheight = resize_ratio(1280, 720, Config.CUSTOM_QUALITY) await group_call.join_group_call( int(Config.CHAT), AudioImagePiped( link, pic, - audio_parameters=Config.AUDIO_Q, - video_parameters=Config.VIDEO_Q, + video_parameters=VideoParameters( + cwidth, + cheight, + Config.FPS, + ), + audio_parameters=AudioParameters( + Config.BITRATE, + ), additional_ffmpeg_parameters=f'-ss {start} -atend -t {end}', ), stream_type=StreamType().pulse_stream, ) @@ -362,53 +376,51 @@ async def join_and_play(link, seek, pic, width, height): else: LOGGER.error("This stream is not supported , leaving VC.") return - if Config.BITRATE and Config.FPS: - await group_call.join_group_call( - int(Config.CHAT), - AudioVideoPiped( - link, - video_parameters=VideoParameters( - width, - height, - Config.FPS, - ), - audio_parameters=AudioParameters( - Config.BITRATE - ), - additional_ffmpeg_parameters=f'-ss {start} -atend -t {end}', - ), - stream_type=StreamType().pulse_stream, - ) - else: - await group_call.join_group_call( - int(Config.CHAT), - AudioVideoPiped( - link, - video_parameters=Config.VIDEO_Q, - audio_parameters=Config.AUDIO_Q, - additional_ffmpeg_parameters=f'-ss {start} -atend -t {end}', - ), - stream_type=StreamType().pulse_stream, - ) + cwidth, cheight = resize_ratio(width, height, Config.CUSTOM_QUALITY) + await group_call.join_group_call( + int(Config.CHAT), + AudioVideoPiped( + link, + video_parameters=VideoParameters( + cwidth, + cheight, + Config.FPS, + ), + audio_parameters=AudioParameters( + Config.BITRATE + ), + additional_ffmpeg_parameters=f'-ss {start} -atend -t {end}', + ), + stream_type=StreamType().pulse_stream, + ) else: if not Config.IS_VIDEO: await group_call.join_group_call( int(Config.CHAT), AudioPiped( link, - audio_parameters=Config.AUDIO_Q, + audio_parameters=AudioParameters( + Config.BITRATE + ), ), stream_type=StreamType().pulse_stream, ) else: if pic: + cwidth, cheight = resize_ratio(1280, 720, Config.CUSTOM_QUALITY) await group_call.join_group_call( int(Config.CHAT), AudioImagePiped( link, pic, - video_parameters=Config.VIDEO_Q, - audio_parameters=Config.AUDIO_Q, + video_parameters=VideoParameters( + cwidth, + cheight, + Config.FPS, + ), + audio_parameters=AudioParameters( + Config.BITRATE, + ), ), stream_type=StreamType().pulse_stream, ) @@ -421,32 +433,22 @@ async def join_and_play(link, seek, pic, width, height): else: LOGGER.error("This stream is not supported , leaving VC.") return - if Config.FPS and Config.BITRATE: - await group_call.join_group_call( - int(Config.CHAT), - AudioVideoPiped( - link, - video_parameters=VideoParameters( - width, - height, - Config.FPS, - ), - audio_parameters=AudioParameters( - Config.BITRATE - ), + cwidth, cheight = resize_ratio(width, height, Config.CUSTOM_QUALITY) + await group_call.join_group_call( + int(Config.CHAT), + AudioVideoPiped( + link, + video_parameters=VideoParameters( + cwidth, + cheight, + Config.FPS, ), - stream_type=StreamType().pulse_stream, - ) - else: - await group_call.join_group_call( - int(Config.CHAT), - AudioVideoPiped( - link, - video_parameters=Config.VIDEO_Q, - audio_parameters=Config.AUDIO_Q + audio_parameters=AudioParameters( + Config.BITRATE ), - stream_type=StreamType().pulse_stream, - ) + ), + stream_type=StreamType().pulse_stream, + ) Config.CALL_STATUS=True return True except NoActiveGroupCall: @@ -462,25 +464,17 @@ async def join_and_play(link, seek, pic, width, height): await sleep(2) await restart_playout() except Exception as e: - LOGGER.error(f"Unable to start new GroupCall :- {e}") + LOGGER.error(f"Unable to start new GroupCall :- {e}", exc_info=True) pass except InvalidVideoProportion: - if not Config.FPS and not Config.BITRATE: - Config.FPS=20 - Config.BITRATE=48000 - await join_and_play(link, seek, pic, width, height) - Config.FPS=False - Config.BITRATE=False - return True + LOGGER.error("This video is unsupported") + if Config.playlist or Config.STREAM_LINK: + return await skip() else: - LOGGER.error("Invalid video") - if Config.playlist or Config.STREAM_LINK: - return await skip() - else: - LOGGER.error("This stream is not supported , leaving VC.") - return + LOGGER.error("This stream is not supported , leaving VC.") + return except Exception as e: - LOGGER.error(f"Errors Occured while joining, retrying Error- {e}") + LOGGER.error(f"Errors Occured while joining, retrying Error- {e}", exc_info=True) return False @@ -494,19 +488,28 @@ async def change_file(link, seek, pic, width, height): int(Config.CHAT), AudioPiped( link, - audio_parameters=Config.AUDIO_Q, + audio_parameters=AudioParameters( + Config.BITRATE + ), additional_ffmpeg_parameters=f'-ss {start} -atend -t {end}', ), ) else: if pic: + cwidth, cheight = resize_ratio(1280, 720, Config.CUSTOM_QUALITY) await group_call.change_stream( int(Config.CHAT), AudioImagePiped( link, pic, - audio_parameters=Config.AUDIO_Q, - video_parameters=Config.VIDEO_Q, + video_parameters=VideoParameters( + cwidth, + cheight, + Config.FPS, + ), + audio_parameters=AudioParameters( + Config.BITRATE, + ), additional_ffmpeg_parameters=f'-ss {start} -atend -t {end}', ), ) else: @@ -518,50 +521,50 @@ async def change_file(link, seek, pic, width, height): else: LOGGER.error("This stream is not supported , leaving VC.") return - if Config.FPS and Config.BITRATE: - await group_call.change_stream( - int(Config.CHAT), - AudioVideoPiped( - link, - video_parameters=VideoParameters( - width, - height, - Config.FPS, - ), - audio_parameters=AudioParameters( - Config.BITRATE - ), - additional_ffmpeg_parameters=f'-ss {start} -atend -t {end}', + + cwidth, cheight = resize_ratio(width, height, Config.CUSTOM_QUALITY) + await group_call.change_stream( + int(Config.CHAT), + AudioVideoPiped( + link, + video_parameters=VideoParameters( + cwidth, + cheight, + Config.FPS, ), - ) - else: - await group_call.change_stream( - int(Config.CHAT), - AudioVideoPiped( - link, - video_parameters=Config.VIDEO_Q, - audio_parameters=Config.AUDIO_Q, - additional_ffmpeg_parameters=f'-ss {start} -atend -t {end}', + audio_parameters=AudioParameters( + Config.BITRATE ), - ) + additional_ffmpeg_parameters=f'-ss {start} -atend -t {end}', + ), + ) else: if not Config.IS_VIDEO: await group_call.change_stream( int(Config.CHAT), AudioPiped( link, - audio_parameters=Config.AUDIO_Q + audio_parameters=AudioParameters( + Config.BITRATE + ), ), ) else: if pic: + cwidth, cheight = resize_ratio(1280, 720, Config.CUSTOM_QUALITY) await group_call.change_stream( int(Config.CHAT), AudioImagePiped( link, pic, - audio_parameters=Config.AUDIO_Q, - video_parameters=Config.VIDEO_Q, + video_parameters=VideoParameters( + cwidth, + cheight, + Config.FPS, + ), + audio_parameters=AudioParameters( + Config.BITRATE, + ), ), ) else: @@ -573,47 +576,31 @@ async def change_file(link, seek, pic, width, height): else: LOGGER.error("This stream is not supported , leaving VC.") return - if Config.FPS and Config.BITRATE: - await group_call.change_stream( - int(Config.CHAT), - AudioVideoPiped( - link, - video_parameters=VideoParameters( - width, - height, - Config.FPS, - ), - audio_parameters=AudioParameters( - Config.BITRATE, - ), + cwidth, cheight = resize_ratio(width, height, Config.CUSTOM_QUALITY) + await group_call.change_stream( + int(Config.CHAT), + AudioVideoPiped( + link, + video_parameters=VideoParameters( + cwidth, + cheight, + Config.FPS, ), - ) - else: - await group_call.change_stream( - int(Config.CHAT), - AudioVideoPiped( - link, - video_parameters=Config.VIDEO_Q, - audio_parameters=Config.AUDIO_Q, + audio_parameters=AudioParameters( + Config.BITRATE, ), - ) + ), + ) except InvalidVideoProportion: - if not Config.FPS and not Config.BITRATE: - Config.FPS=20 - Config.BITRATE=48000 - await join_and_play(link, seek, pic, width, height) - Config.FPS=False - Config.BITRATE=False - return True + LOGGER.error("Invalid video, skipped") + if Config.playlist or Config.STREAM_LINK: + return await skip() else: - LOGGER.error("Invalid video, skipped") - if Config.playlist or Config.STREAM_LINK: - return await skip() - else: - LOGGER.error("This stream is not supported , leaving VC.") - return + LOGGER.error("This stream is not supported , leaving VC.") + await leave_call() + return except Exception as e: - LOGGER.error(f"Error in joining call - {e}") + LOGGER.error(f"Error in joining call - {e}", exc_info=True) return False @@ -645,7 +632,7 @@ async def leave_call(): try: await group_call.leave_group_call(Config.CHAT) except Exception as e: - LOGGER.error(f"Errors while leaving call {e}") + LOGGER.error(f"Errors while leaving call {e}", exc_info=True) #Config.playlist.clear() if Config.STREAM_LINK: Config.STREAM_LINK=False @@ -671,7 +658,7 @@ async def leave_call(): except ScheduleDateInvalid: LOGGER.error("Unable to schedule VideoChat, since date is invalid") except Exception as e: - LOGGER.error(f"Error in scheduling voicechat- {e}") + LOGGER.error(f"Error in scheduling voicechat- {e}", exc_info=True) await sync_to_db() @@ -682,12 +669,12 @@ async def restart(): await group_call.leave_group_call(Config.CHAT) await sleep(2) except Exception as e: - LOGGER.error(e) + LOGGER.error(e, exc_info=True) if not Config.playlist: await start_stream() return LOGGER.info(f"- START PLAYING: {Config.playlist[0][1]}") - await sleep(2) + await sleep(1) await play() LOGGER.info("Restarting Playout") if len(Config.playlist) <= 1: @@ -719,6 +706,9 @@ async def restart_playout(): async def set_up_startup(): regex = r"^(?:https?:\/\/)?(?:www\.)?youtu\.?be(?:\.com)?\/?.*(?:watch|embed)?(?:.*v=|v\/|\/)([\w\-_]+)\&?" match = re.match(regex, Config.STREAM_URL) + Config.YSTREAM=False + Config.YPLAY=False + Config.CPLAY=False if match: Config.YSTREAM=True LOGGER.info("YouTube Stream is set as STARTUP STREAM") @@ -732,8 +722,12 @@ async def set_up_startup(): Config.STREAM_URL="http://j78dp346yq5r-hls-live.5centscdn.com/safari/live.stream/playlist.m3u8" LOGGER.error("Unable to fetch youtube playlist, starting Safari TV") pass + elif Config.STREAM_URL.startswith("@") or (str(Config.STREAM_URL)).startswith("-100"): + Config.CPLAY = True + LOGGER.info(f"Channel Play enabled from {Config.STREAM_URL}") else: - Config.STREAM_URL=Config.STREAM_URL + LOGGER.info("Direct link set as STARTUP_STREAM") + pass Config.STREAM_SETUP=True @@ -744,7 +738,10 @@ async def start_stream(): if Config.YPLAY: await y_play(Config.STREAM_URL) return - if Config.YSTREAM: + elif Config.CPLAY: + await c_play(Config.STREAM_URL) + return + elif Config.YSTREAM: link=await get_link(Config.STREAM_URL) else: link=Config.STREAM_URL @@ -780,7 +777,7 @@ async def get_link(file): try: ydl_info = ydl.extract_info(file, download=False) except Exception as e: - LOGGER.error(f"Errors occured while getting link from youtube video {e}") + LOGGER.error(f"Errors occured while getting link from youtube video {e}", exc_info=True) if Config.playlist or Config.STREAM_LINK: return await skip() else: @@ -817,11 +814,11 @@ async def download(song, msg=None): if song[3] == "telegram": if not Config.GET_FILE.get(song[5]): try: - original_file = await bot.download_media(song[2], progress=progress_bar, file_name=f'./tgdownloads/', progress_args=(int((song[5].split("_"))[1]), time.time(), msg)) - + original_file = await dl.pyro_dl(song[2]) Config.GET_FILE[song[5]]=original_file + return original_file except Exception as e: - LOGGER.error(e) + LOGGER.error(e, exc_info=True) Config.playlist.remove(song) await clear_db_playlist(song=song) if len(Config.playlist) <= 1: @@ -835,29 +832,36 @@ async def chek_the_media(link, seek=False, pic=False, title="Music"): width, height = None, None is_audio_=False try: - is_audio_ = is_audio(link) - except: + is_audio_ = await is_audio(link) + except Exception as e: + LOGGER.error(e, exc_info=True) is_audio_ = False LOGGER.error("Unable to get Audio properties within time.") if not is_audio_: Config.STREAM_LINK=False if Config.playlist or Config.STREAM_LINK: - return await skip() + await skip() + return None, None, None, None, None else: LOGGER.error("This stream is not supported , leaving VC.") return None, None, None, None, None else: - try: - width, height = get_height_and_width(link) - except: - width, height = None, None - LOGGER.error("Unable to get video properties within time.") + if os.path.isfile(link) \ + and "audio" in Config.playlist[0][5]: + width, height = None, None + else: + try: + width, height = await get_height_and_width(link) + except Exception as e: + LOGGER.error(e, exc_info=True) + width, height = None, None + LOGGER.error("Unable to get video properties within time.") if not width or \ not height: is_audio_=False try: - is_audio_ = is_audio(link) + is_audio_ = await is_audio(link) except: is_audio_ = False LOGGER.error("Unable to get Audio properties within time.") @@ -867,19 +871,20 @@ async def chek_the_media(link, seek=False, pic=False, title="Music"): if not os.path.exists(photo): photo = await pic_.download(file_name=photo) try: - dur_=get_duration(link) + dur_= await get_duration(link) except: - dur_='None' + dur_=0 pic = get_image(title, photo, dur_) else: Config.STREAM_LINK=False if Config.playlist or Config.STREAM_LINK: - return await skip() + await skip() + return None, None, None, None, None else: LOGGER.error("This stream is not supported , leaving VC.") return None, None, None, None, None try: - dur=get_duration(link) + dur= await get_duration(link) except: dur=0 Config.DATA['FILE_DATA']={"file":link, 'dur':dur} @@ -906,7 +911,7 @@ async def edit_title(): edit = EditGroupCallTitle(call=full_chat.full_chat.call, title=title) await USER.send(edit) except Exception as e: - LOGGER.error(f"Errors Occured while editing title - {e}") + LOGGER.error(f"Errors Occured while editing title - {e}", exc_info=True) pass async def stop_recording(): @@ -1140,7 +1145,7 @@ async def send_playlist(): async def send_text(text): message = await bot.send_message( - Config.LOG_GROUP, + int(Config.LOG_GROUP), text, reply_markup=await get_buttons(), disable_web_page_preview=True, @@ -1190,7 +1195,7 @@ async def import_play_list(file): pass return True except Exception as e: - LOGGER.error(f"Errors while importing playlist {e}") + LOGGER.error(f"Errors while importing playlist {e}", exc_info=True) return False @@ -1213,7 +1218,7 @@ async def y_play(playlist): if Config.SHUFFLE: await shuffle_playlist() except Exception as e: - LOGGER.error("Errors Occured While Importing Playlist", e) + LOGGER.error(f"Errors Occured While Importing Playlist - {e}", exc_info=True) Config.YSTREAM=True Config.YPLAY=False if Config.IS_LOOP: @@ -1223,6 +1228,85 @@ async def y_play(playlist): return False +async def c_play(channel): + if (str(channel)).startswith("-100"): + channel=int(channel) + else: + if channel.startswith("@"): + channel = channel.replace("@", "") + try: + chat=await USER.get_chat(channel) + LOGGER.info(f"Searching files from {chat.title}") + me=["video", "document", "audio"] + who=0 + for filter in me: + if filter in Config.FILTERS: + async for m in USER.search_messages(chat_id=channel, filter=filter): + you = await bot.get_messages(channel, m.message_id) + now = datetime.now() + nyav = now.strftime("%d-%m-%Y-%H:%M:%S") + if filter == "audio": + title=you.audio.title + file_id = you.audio.file_id + unique = f"{nyav}_{m.message_id}_audio" + elif filter == "video": + file_id = you.video.file_id + title = you.video.file_name + unique = f"{nyav}_{m.message_id}_video" + elif filter == "document": + if not "video" in you.document.mime_type: + LOGGER.info("Skiping Non-Video file") + continue + file_id=you.document.file_id + title = you.document.file_name + unique = f"{nyav}_{m.message_id}_document" + if Config.PTN: + ny = parse(title) + title_ = ny.get("title") + if title_: + title = title_ + data={1:title, 2:file_id, 3:"telegram", 4:f"[{chat.title}]({you.link})", 5:unique} + Config.playlist.append(data) + await add_to_db_playlist(data) + who += 1 + if not Config.CALL_STATUS \ + and len(Config.playlist) >= 1: + LOGGER.info(f"Downloading {title}") + await download(Config.playlist[0]) + await play() + print(f"- START PLAYING: {title}") + elif (len(Config.playlist) == 1 and Config.CALL_STATUS): + LOGGER.info(f"Downloading {title}") + await download(Config.playlist[0]) + await play() + for track in Config.playlist[:2]: + await download(track) + if who == 0: + LOGGER.warning(f"No files found in {chat.title}, Change filter settings if required. Current filters are {Config.FILTERS}") + if Config.CPLAY: + Config.CPLAY=False + Config.STREAM_URL="https://www.youtube.com/watch?v=zcrUCvBD16k" + LOGGER.warning("Seems like cplay is set as STARTUP_STREAM, Since nothing found on {chat.title}, switching to 24 News as startup stream.") + Config.STREAM_SETUP=False + await sync_to_db() + return False, f"No files found on given channel, Please check your filters.\nCurrent filters are {Config.FILTERS}" + else: + if len(Config.playlist) > 2 and Config.SHUFFLE: + await shuffle_playlist() + if Config.LOG_GROUP: + await send_playlist() + except Exception as e: + LOGGER.error(f"Errors occured while fetching songs from given channel - {e}", exc_info=True) + if Config.CPLAY: + Config.CPLAY=False + Config.STREAM_URL="https://www.youtube.com/watch?v=zcrUCvBD16k" + LOGGER.warning("Seems like cplay is set as STARTUP_STREAM, and errors occured while getting playlist from given chat. Switching to 24 news as default stream.") + Config.STREAM_SETUP=False + await sync_to_db() + return False, f"Errors occured while getting files - {e}" + else: + return True, who + async def pause(): try: await group_call.pause_stream(Config.CHAT) @@ -1231,7 +1315,7 @@ async def pause(): await restart_playout() return False except Exception as e: - LOGGER.error(f"Errors Occured while pausing -{e}") + LOGGER.error(f"Errors Occured while pausing -{e}", exc_info=True) return False @@ -1243,7 +1327,7 @@ async def resume(): await restart_playout() return False except Exception as e: - LOGGER.error(f"Errors Occured while resuming -{e}") + LOGGER.error(f"Errors Occured while resuming -{e}", exc_info=True) return False @@ -1254,7 +1338,7 @@ async def volume(volume): except BadRequest: await restart_playout() except Exception as e: - LOGGER.error(f"Errors Occured while changing volume Error -{e}") + LOGGER.error(f"Errors Occured while changing volume Error -{e}", exc_info=True) async def mute(): try: @@ -1264,7 +1348,7 @@ async def mute(): await restart_playout() return False except Exception as e: - LOGGER.error(f"Errors Occured while muting -{e}") + LOGGER.error(f"Errors Occured while muting -{e}", exc_info=True) return False async def unmute(): @@ -1275,7 +1359,7 @@ async def unmute(): await restart_playout() return False except Exception as e: - LOGGER.error(f"Errors Occured while unmuting -{e}") + LOGGER.error(f"Errors Occured while unmuting -{e}", exc_info=True) return False @@ -1290,7 +1374,7 @@ async def get_admins(chat): if not administrator.user.id in admins: admins.append(administrator.user.id) except Exception as e: - LOGGER.error(f"Errors occured while getting admin list - {e}") + LOGGER.error(f"Errors occured while getting admin list - {e}", exc_info=True) pass Config.ADMINS=admins Config.ADMIN_CACHE=True @@ -1613,83 +1697,70 @@ async def check_db(): if not await db.is_saved('HAS_SCHEDULE'): db.add_config("HAS_SCHEDULE", Config.HAS_SCHEDULE) - -async def progress_bar(current, zero, total, start, msg): - now = time.time() - if total == 0: - return - if round((now - start) % 3) == 0 or current == total: - speed = current / (now - start) - percentage = current * 100 / total - time_to_complete = round(((total - current) / speed)) * 1000 - time_to_complete = TimeFormatter(time_to_complete) - progressbar = "[{0}{1}]".format(\ - ''.join(["ā–°" for i in range(math.floor(percentage / 5))]), - ''.join(["ā–±" for i in range(20 - math.floor(percentage / 5))]) - ) - current_message = f"**Downloading** {round(percentage, 2)}% \n{progressbar}\nāš”ļø **Speed**: {humanbytes(speed)}/s\nā¬‡ļø **Downloaded**: {humanbytes(current)} / {humanbytes(total)}\nšŸ•° **Time Left**: {time_to_complete}" - if msg: - try: - await msg.edit(text=current_message) - except: - pass - LOGGER.info(f"Downloading {round(percentage, 2)}% ") - - - -@timeout(10) -def is_audio(file): - try: - k=ffmpeg.probe(file)['streams'] + +async def is_audio(file): + have_audio=False + ffprobe_cmd = ["ffprobe", "-i", file, "-v", "quiet", "-of", "json", "-show_streams"] + process = await asyncio.create_subprocess_exec( + *ffprobe_cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + output = await process.communicate() + stream = output[0].decode('utf-8') + out = json.loads(stream) + l = out.get("streams") + if not l: + return have_audio + for n in l: + k = n.get("codec_type") if k: - return True - else: - return False - except KeyError: - return False - except Exception as e: - LOGGER.error(f"Stream Unsupported {e} ") - return False + if k == "audio": + have_audio =True + break + return have_audio -@timeout(10)#wait for maximum 10 sec, temp fix for ffprobe -def get_height_and_width(file): +async def get_height_and_width(file): + ffprobe_cmd = ["ffprobe", "-v", "error", "-select_streams", "v", "-show_entries", "stream=width,height", "-of", "json", file] + process = await asyncio.create_subprocess_exec( + *ffprobe_cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + output = await process.communicate() + stream = output[0].decode('utf-8') + out = json.loads(stream) try: - k=ffmpeg.probe(file)['streams'] - width=None - height=None - for f in k: - try: - width=int(f["width"]) - height=int(f["height"]) - if height >= 256: - break - except KeyError: - continue - except: - LOGGER.error("Error, This stream is not supported.") + n = out.get("streams") + if not n: + width, height = False, False + else: + width=n[0].get("width") + height=n[0].get("height") + except Exception as e: width, height = False, False + LOGGER.error(f"Unable to get video properties {e}", exc_info=True) return width, height -@timeout(10) -def get_duration(file): +async def get_duration(file): + dur = 0 + ffprobe_cmd = ["ffprobe", "-i", file, "-v", "error", "-show_entries", "format=duration", "-of", "json", "-select_streams", "v:0"] + process = await asyncio.create_subprocess_exec( + *ffprobe_cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + output = await process.communicate() try: - total=ffmpeg.probe(file)['format']['duration'] - return total - except: - return 0 - -def humanbytes(size): - if not size: - return "" - power = 2**10 - n = 0 - Dic_powerN = {0: ' ', 1: 'K', 2: 'M', 3: 'G', 4: 'T'} - while size > power: - size /= power - n += 1 - return str(round(size, 2)) + " " + Dic_powerN[n] + 'B' + stream = output[0].decode('utf-8') + out = json.loads(stream) + if out.get("format"): + if (out.get("format")).get("duration"): + dur = int(float((out.get("format")).get("duration"))) + else: + dur = 0 + else: + dur = 0 + except Exception as e: + LOGGER.error(e, exc_info=True) + dur = 0 + return dur def get_player_string(): @@ -1731,18 +1802,6 @@ def get_volume_string(): final=f" {str(current)} / {str(200)} {progressbar} {e}" return final -def TimeFormatter(milliseconds: int) -> str: - seconds, milliseconds = divmod(int(milliseconds), 1000) - minutes, seconds = divmod(seconds, 60) - hours, minutes = divmod(minutes, 60) - days, hours = divmod(hours, 24) - tmp = ((str(days) + " days, ") if days else "") + \ - ((str(hours) + " hours, ") if hours else "") + \ - ((str(minutes) + " min, ") if minutes else "") + \ - ((str(seconds) + " sec, ") if seconds else "") + \ - ((str(milliseconds) + " millisec, ") if milliseconds else "") - return tmp[:-2] - def set_config(value): if value: return False @@ -1763,10 +1822,25 @@ def get_pause(status): else: return "Pause" +#https://github.com/pytgcalls/pytgcalls/blob/dev/pytgcalls/types/input_stream/video_tools.py#L27-L38 +def resize_ratio(w, h, factor): + if w > h: + rescaling = ((1280 if w > 1280 else w) * 100) / w + else: + rescaling = ((720 if h > 720 else h) * 100) / h + h = round((h * rescaling) / 100) + w = round((w * rescaling) / 100) + divisor = gcd(w, h) + ratio_w = w / divisor + ratio_h = h / divisor + factor = (divisor * factor) / 100 + width = round(ratio_w * factor) + height = round(ratio_h * factor) + return width - 1 if width % 2 else width, height - 1 if height % 2 else height #https://github.com/pytgcalls/pytgcalls/issues/118 def stop_and_restart(): os.system("git pull") - time.sleep(10) + time.sleep(5) os.execl(sys.executable, sys.executable, *sys.argv) @@ -1774,7 +1848,7 @@ def get_image(title, pic, dur="Live"): newimage = "converted.jpg" image = Image.open(pic) draw = ImageDraw.Draw(image) - font = ImageFont.truetype('font.ttf', 70) + font = ImageFont.truetype('./utils/font.ttf', 70) title = title[0:30] MAX_W = 1790 dur=convert(int(float(dur))) @@ -1818,7 +1892,7 @@ async def update(): async def startup_check(): if Config.LOG_GROUP: try: - k=await bot.get_chat_member(Config.LOG_GROUP, Config.BOT_USERNAME) + k=await bot.get_chat_member(int(Config.LOG_GROUP), Config.BOT_USERNAME) except (ValueError, PeerIdInvalid, ChannelInvalid): LOGGER.error(f"LOG_GROUP var Found and @{Config.BOT_USERNAME} is not a member of the group.") Config.STARTUP_ERROR=f"LOG_GROUP var Found and @{Config.BOT_USERNAME} is not a member of the group." @@ -1855,6 +1929,4 @@ async def startup_check(): pass if not Config.DATABASE_URI: LOGGER.warning("No DATABASE_URI , found. It is recommended to use a database.") - return True - - + return True \ No newline at end of file