Skip to content

Commit

Permalink
Draft implementation of file based radio streaming
Browse files Browse the repository at this point in the history
  • Loading branch information
evgenii-m committed Jul 25, 2024
1 parent 7337b6e commit 7dffb3a
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 97 deletions.
103 changes: 27 additions & 76 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,75 +1,46 @@
# Telegram Radio Player V3 [![Mentioned in Awesome Telegram Calls](https://awesome.re/mentioned-badge-flat.svg)](https://github.com/tgcalls/awesome-tgcalls)
![GitHub Repo stars](https://img.shields.io/github/stars/AsmSafone/RadioPlayerV3?color=blue&style=flat)
![GitHub forks](https://img.shields.io/github/forks/AsmSafone/RadioPlayerV3?color=green&style=flat)
![GitHub issues](https://img.shields.io/github/issues/AsmSafone/RadioPlayerV3)
![GitHub closed issues](https://img.shields.io/github/issues-closed/AsmSafone/RadioPlayerV3)
![GitHub pull requests](https://img.shields.io/github/issues-pr/AsmSafone/RadioPlayerV3)
![GitHub closed pull requests](https://img.shields.io/github/issues-pr-closed/AsmSafone/RadioPlayerV3)
![GitHub contributors](https://img.shields.io/github/contributors/AsmSafone/RadioPlayerV3?style=flat)
![GitHub repo size](https://img.shields.io/github/repo-size/AsmSafone/RadioPlayerV3?color=red)
![GitHub commit activity](https://img.shields.io/github/commit-activity/m/AsmSafone/RadioPlayerV3)
![GitHub](https://img.shields.io/github/license/AsmSafone/RadioPlayerV3)
[![Bot Updates](https://img.shields.io/badge/RadioPlayerV3-Updates%20Channel-green)](https://t.me/AsmSafone)
[![Bot Support](https://img.shields.io/badge/RadioPlayerV3-Support%20Group-blue)](https://t.me/AsmSupport)


An Advanced Telegram Bot to Play Nonstop Radio/Music/YouTube Live in Channel or Group Voice Chats.

This is also the source code of the bot which is being used for playing
Radio in [AsmSafone](https://t.me/AsmSafone) Channel & Music in [AsmSupport](https://t.me/AsmSupport) Group.
# Telega Radio Broadcasting Application

The original implementation is taken from [AsmSafone/RadioPlayerV3](https://github.com/AsmSafone/RadioPlayerV3)

## Special Features

- Playlist, queue, 24x7 radio stream
- Supports Live streaming from youtube
- Starts Radio after if no songs in playlist
- Automatic playback even if heroku restarts
- Show current playing position of the audio
- Control with buttons and commands
- Download songs from youtube as audio
- Change Voice chat title to current playing song name
- Automatically downloads audio for the first two tracks in the playlist to ensure smooth playing
- Radio broadcasting to Telegram channels by stream URL or local playlist (from files)
- Bot interface for playlist managment

## Deploy to Heroku
## Initialize Telegram session with Pyrogram

<p><a href="https://deploy.safone.tech/"><img src="https://img.shields.io/badge/Deploy%20To%20Heroku-blueviolet?style=for-the-badge&logo=heroku" width="200""/></a></p>
Pyrogram session initialization required for streaming to Telegram channels feature, that allowed only for userbot (Telegram user account).
Run script `pyrogram_session_gen.py` and follow instructions.

NOTE: Change the app region to Europe (it will help to make the bot stable)

## Deploy to Railway
## Deploy

<p><a href="https://railway.app/new/template?template=https%3A%2F%2Fgithub.com%2FAsmSafone%2FRadioPlayerV3&envs=API_ID%2CAPI_HASH%2CBOT_TOKEN%2CSESSION_STRING%2CCHAT_ID%2CLOG_GROUP%2CADMINS%2CADMIN_ONLY%2CMAXIMUM_DURATION%2CSTREAM_URL%2CREPLY_MESSAGE&optionalEnvs=LOG_GROUP%2CADMIN_ONLY%2CMAXIMUM_DURATION%2CSTREAM_URL%2CREPLY_MESSAGE&API_IDDesc=Your+Telegram+API_ID+get+it+from+my.telegram.org%2Fapps&API_HASHDesc=Your+Telegram+API_HASH+get+it+from+my.telegram.org%2Fapps&BOT_TOKENDesc=Bot+token+of+your+bot%2C+get+from+%40Botfather&SESSION_STRINGDesc=Session+string%2C+use+%40genStr_robot+to+generate+pyrogram+session+string&CHAT_IDDesc=ID+of+Channel+or+Group+where+the+Bot+plays+Radio%2FMusic%2FYouTube+Lives&LOG_GROUPDesc=ID+of+the+group+to+send+playlist+if+CHAT+is+a+Group%2C+if+channel+then+leave+blank&ADMINSDesc=ID+of+Users+who+can+use+Admin+commands+%28for+multiple+users+seperated+by+space%29&ADMIN_ONLYDesc=Change+it+to+%27True%27+If+you+want+to+make+%2Fplay+commands+only+for+admins+of+CHAT.+By+default+%2Fplay+is+available+for+all.&MAXIMUM_DURATIONDesc=Maximum+duration+of+song+to+be+played+using+%2Fplay+command&STREAM_URLDesc=URL+of+Radio+station+or+Youtube+Live+video+url+to+stream+with+%2Fradio+command&REPLY_MESSAGEDesc=A+reply+message+to+those+who+message+the+USER+account+in+PM.+Make+it+blank+if+you+do+not+need+this+feature.&MAXIMUM_DURATIONDefault=15&ADMIN_ONLYDefault=False&STREAM_URLDefault=https://youtu.be/5qap5aO4i9A&REPLY_MESSAGEDefault=Hello Sir, I'm a bot to play radio/music/youtube live on telegram voice chat, not having time to chat with you 😂!"> <img src="https://img.shields.io/badge/Deploy%20To%20Railway-blueviolet?style=for-the-badge&logo=railway" width="200""/></a></p>
You can deploy application in VDS/VPS or local machine by using Docker.
First create `.env` file in source root based on `.env.sample` file.
Then run this script from project source root.

NOTE: Make Sure You Have Started A Voice Chat In Your Channel/Group Before Deploying!
```sh
$ pip3 install -r requirements.txt
$
$
```

## Config Vars:
1. `API_ID` : Get it from https://my.telegram.org/apps
2. `API_HASH` : Get it from https://my.telegram.org/apps
3. `BOT_TOKEN` : Get it from [@Botfather](https://t.me/botfather) XD
4. `SESSION_STRING` : Generate from [@genStr robot](http://t.me/genStr_robot) or [![genStr](https://img.shields.io/badge/repl.it-genStr-yellowgreen)](https://repl.it/@AsmSafone/genStr)
5. `CHAT_ID` : ID of Channel/Group where the bot plays Music/Radio.
6. `LOG_GROUP` : ID of Group to send Playlist, if CHAT_ID is a Group.
3. `BOT_TOKEN` : Get it from [@Botfather](https://t.me/botfather)
4. `SESSION_NAME` : Name of session generated by `pyrogram_session_gen.py`
5. `CHAT_ID` : ID of Channel/Group to play radio.
6. `CHAT_NAME` : Name (started with @) of Channel/Group to play radio.
7. `AUTH_USERS` : ID of Auth Users who can use Admin commands. (for multiple users seperated by space)
8. `STREAM_URL` : Stream URL of radio station or a youtube live video to stream when the bot starts or with /radio command. Here is [Some Live Streaming Links](https://telegra.ph/Live-Radio-Stream-Links-05-17).
9. `MAXIMUM_DURATION` : Maximum duration of song to play.(Optional)
10. `REPLY_MESSAGE` : A reply to those who message the USER account in PM. Leave it blank if you do not need this feature.
11. `ADMIN_ONLY` : Pass 'True' If you want to make /play commands only for admins of CHAT. By default /play is available for all.
12. `HEROKU_API_KEY`: Your Heroku api key. Get it from [here](https://dashboard.heroku.com/account)
13. `HEROKU_APP_NAME`: Name of your Heroku app if deploying to heroku.

- Enable the worker after deploy the project to Heroku.
- Bot will starts radio automatically in given `CHAT_ID` with given `STREAM_URL` after deploy.
- 24x7 Music even if heroku restarts, radio stream restarts automatically.
- To play a song use /play as a reply to audio file or a youtube link or use /play [song name].
- To download audio you can use [@SafoneMusicBot](http://t.me/SafoneMusicBot) or `/song` command to the bot.
- Use `/help` to know about other commands & their usage.
8. `STREAM_URL` : Stream URL of radio station.

## Requirements

- Python 3.6 or higher.
- Python 3.11 or higher.
- [Telegram API Key](https://docs.pyrogram.org/intro/quickstart#enjoy-the-api)
- [FFmpeg Python](https://www.ffmpeg.org/)
- Telegram [String Session](http://t.me/genStr_robot) of the account.
- Telegram [Pyrogram Session](http://t.me/genStr_robot) of the account.
- User Accounts Needs To Be An Admin In The Channel or Group.
- Must Start A Voice Chat In Channel/Group Before Running The Bot.

Expand All @@ -86,29 +57,9 @@ $ python3 main.py
```


## License
```sh
RadioPlayerV3, Telegram Voice Chat Bot
Copyright (c) 2021 Asm Safone <https://github.com/AsmSafone>

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 <https://www.gnu.org/licenses/>
```

## Credits

- [Me](https://github.com/AsmSafone) for [Noting](https://github.com/AsmSafone/RadioPlayerV3) 😬
- [Dan](https://github.com/delivrance) for [Pyrogram](https://github.com/pyrogram/pyrogram) ❤️
- [MarshalX](https://github.com/MarshalX) for [pytgcalls](https://github.com/MarshalX/tgcalls) ❤️
- And Thanks To All [Contributors](https://github.com/AsmSafone/RadioPlayerV3/graphs/contributors)! ❤️
- [AsmSafone](https://github.com/AsmSafone) for [Base implementation](https://github.com/AsmSafone/RadioPlayerV3)
- [Dan](https://github.com/delivrance) for [Pyrogram](https://github.com/pyrogram/pyrogram)
- [MarshalX](https://github.com/MarshalX) for [pytgcalls](https://github.com/MarshalX/tgcalls)

10 changes: 7 additions & 3 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@

async def main():
async with bot:
await mp.start_radio(DEFAULT_STREAM_URL)
await mp.start_radio_by_stream_url(DEFAULT_STREAM_URL)

def stop_and_restart():
bot.stop()
Expand All @@ -64,8 +64,12 @@ def stop_and_restart():
description="Start The Bot"
),
BotCommand(
command="radio",
description="Start Radio Stream by URL"
command="stream",
description="Start Radio by Stream URL"
),
BotCommand(
command="file",
description="Start Radio by Aduio File"
),
BotCommand(
command="stopradio",
Expand Down
47 changes: 38 additions & 9 deletions plugins/bot/radio.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,29 +39,59 @@ async def is_admin(_, client, message: Message):
ADMINS_FILTER = filters.create(is_admin)


allcmd = ["start", "radio", "stopradio", "help"]
allcmd = ["start", "stream", "file", "stopradio", "help"]

@Client.on_message(filters.command(allcmd) & filters.group & ~filters.chat(CHAT_ID))
async def not_chat(_, m: Message):
k=await m.reply_text("*** Sorry, You Can't Use This Bot!")
await mp.delete(m)


@Client.on_message(filters.command(["radio", f"radio@{USERNAME}"]) & ADMINS_FILTER & (filters.chat(CHAT_ID) | filters.private))
async def start_radio(_, message: Message):
@Client.on_message(filters.command(["stream"]) & ADMINS_FILTER & (filters.chat(CHAT_ID) | filters.private))
async def start_radio_by_stream_url(_, message: Message):
if 1 in RADIO:
k=await message.reply_text(f"{emoji.ROBOT} **Please Stop Existing Radio Stream!**")
await mp.delete(k)
await message.delete()
return
station_stream_url = message.text.split()[-1]
await mp.start_radio(station_stream_url)
k=await message.reply_text(f"{emoji.CHECK_MARK_BUTTON} **Radio Stream Started :** \n<code>{station_stream_url}</code>")
await mp.start_radio_by_stream_url(station_stream_url)
k=await message.reply_text(f"**Radio Stream Started by URL:** \n<code>{station_stream_url}</code>")
await mp.delete(k)
await mp.delete(message)


@Client.on_message(filters.command(["stopradio", f"stopradio@{USERNAME}"]) & ADMINS_FILTER & (filters.chat(CHAT_ID) | filters.private))
@Client.on_message(filters.command(["file"]) & ADMINS_FILTER & (filters.chat(CHAT_ID) | filters.private))
async def start_radio_by_file(_, message: Message):
if 1 in RADIO:
k=await message.reply_text(f"{emoji.ROBOT} **Please Stop Existing Radio Stream!**")
await mp.delete(k)
await message.delete()
return
if message.audio:
audio = message.audio
file_id = audio.file_id
await mp.start_radio_by_file(file_id)
k=await message.reply_text(
f"**Radio Stream Started by file:** \n<code>{audio.title} ({audio.file_id})</code>")
await mp.delete(k)
await mp.delete(message)
elif message.reply_to_message and message.reply_to_message.audio:
audio = message.reply_to_message.audio
file_id = audio.file_id
await mp.start_radio_by_file(file_id)
k=await message.reply_text(
f"**Radio Stream Started by file:** \n<code>{audio.title} ({audio.file_id})</code>")
await mp.delete(k)
await mp.delete(message)
else:
k=await message.reply_text(f"**Audio file attachment is needed")
await mp.delete(k)
await mp.delete(message)



@Client.on_message(filters.command(["stopradio"]) & ADMINS_FILTER & (filters.chat(CHAT_ID) | filters.private))
async def stop_radio(_, message: Message):
if 0 in RADIO:
k=await message.reply_text(f"{emoji.ROBOT} **Please Start A Radio Stream First!**")
Expand All @@ -74,15 +104,14 @@ async def stop_radio(_, message: Message):
await mp.delete(message)



@Client.on_message(filters.command(["start", f"start@{USERNAME}"]))
@Client.on_message(filters.command(["start"]) & ADMINS_FILTER & (filters.chat(CHAT_ID) | filters.private))
async def start(client, message):
m=await message.reply_text("Bot started...")
await mp.delete(m)
await mp.delete(message)


@Client.on_message(filters.command(["help", f"help@{USERNAME}"]))
@Client.on_message(filters.command(["help"]) & ADMINS_FILTER & (filters.chat(CHAT_ID) | filters.private))
async def help(client, message):
if msg.get('help') is not None:
await msg['help'].delete()
Expand Down
67 changes: 58 additions & 9 deletions utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,21 +67,35 @@
playlist=Config.playlist
msg=Config.msg

ydl_opts = {
"format": "bestaudio[ext=m4a]",
"geo-bypass": True,
"nocheckcertificate": True,
"outtmpl": "downloads/%(id)s.%(ext)s",
}
ydl = YoutubeDL(ydl_opts)
playlistItems = {}

class PlaylistItem:
def __init__(
self,
title: str,
file_id: int,
user: str
):
self.title = title
self.file_id = file_id
self.user = user

def __repr__(self):
return f"PlaylistItem(title={self.title!r}, file_id={self.file_id!r}, user={self.user!r})"

def __eq__(self, other):
if other.__class__ is self.__class__:
return (self.title, self.file_id, self.user) == (other.title, other.file_id, other.user)
return NotImplemented



class MusicPlayer(object):
def __init__(self):
self.group_call = GroupCallFactory(USER, GroupCallFactory.MTPROTO_CLIENT_TYPE.PYROGRAM).get_file_group_call()


async def start_radio(self, station_stream_url):
async def start_radio_by_stream_url(self, station_stream_url):
group_call = self.group_call
if group_call.is_connected:
playlist.clear()
Expand Down Expand Up @@ -134,6 +148,41 @@ async def start_radio(self, station_stream_url):
continue


async def start_radio_by_file(self, file_id):
group_call = self.group_call
client = group_call.client
raw_file = os.path.join(client.workdir, DEFAULT_DOWNLOAD_DIR, f"{file_id}.raw")
if not os.path.isfile(raw_file):
original_file = await bot.download_media(f"{file_id}")
ffmpeg.input(original_file).output(
raw_file,
format='s16le',
acodec='pcm_s16le',
ac=2,
ar='48k',
loglevel='error'
).overwrite_output().run()
if 1 in RADIO:
if group_call:
group_call.input_filename = ''
RADIO.remove(1)
RADIO.add(0)
process = FFMPEG_PROCESSES.get(CHAT_ID)
if process:
try:
process.send_signal(SIGINT)
except subprocess.TimeoutExpired:
process.kill()
except Exception as e:
print(e)
pass
FFMPEG_PROCESSES[CHAT_ID] = ""
if not group_call.is_connected:
await mp.start_call()
group_call.input_filename = raw_file
print(f"- START PLAYING: {raw_file}")


async def stop_radio(self):
group_call = self.group_call
if group_call:
Expand Down Expand Up @@ -232,4 +281,4 @@ async def on_network_changed(call, is_connected):

@mp.group_call.on_playout_ended
async def playout_ended_handler(_, __):
await mp.start_radio()
await mp.start_radio_from_stream()

0 comments on commit 7dffb3a

Please sign in to comment.