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