Define rec and stop command along with helper functions for the recording

This commit is contained in:
deflax 2025-02-08 02:46:51 +02:00
parent 3da7f7761a
commit e2a4061583

View file

@ -40,7 +40,6 @@ logger_discord.setLevel(log_level)
database = {} database = {}
rec_path = "/recordings" rec_path = "/recordings"
rechead = {}
# Bot functions # Bot functions
@bot.event @bot.event
@ -53,12 +52,12 @@ async def on_ready():
@has_role(worshipper_role_name) @has_role(worshipper_role_name)
async def hello(ctx): async def hello(ctx):
author_name = ctx.author.name author_name = ctx.author.name
await ctx.channel.send(f'hi, {author_name} :blush:') await ctx.channel.send(f'Hi, {author_name} :blush:')
@hello.error @hello.error
async def hello_error(ctx, error): async def hello_error(ctx, error):
if isinstance(error, CheckFailure): if isinstance(error, CheckFailure):
await ctx.channel.send('do I know you?') await ctx.channel.send('Do I know you?')
@bot.command(name='time', help='Show current time in UTC') @bot.command(name='time', help='Show current time in UTC')
async def time(ctx): async def time(ctx):
@ -85,20 +84,45 @@ async def epg(ctx):
@bot.command(name='now', help='Displays whats playing right now') @bot.command(name='now', help='Displays whats playing right now')
async def now(ctx): async def now(ctx):
head = await query_playhead() playhead = await query_playhead()
stream_name = playhead['name']
stream_prio = playhead['prio']
return f'Now playing {stream_name} (prio={stream_prio})'
await ctx.channel.send(head) await ctx.channel.send(head)
@bot.command(name='rec', help='Start the recorder') @bot.command(name='rec', help='Start the recorder')
@has_role(boss_role_name) @has_role(boss_role_name)
async def rec(ctx): async def rec(ctx):
await ctx.channel.send('soon...') playhead = await query_playhead()
stream_name = playhead['name']
stream_prio = playhead['prio']
# Check if the recorder job already exists
if scheduler.get_job('recorder') is None:
await ctx.channel.send(f'Recording from {stream_name} (prio={stream_prio})')
scheduler.add_job(func=recorder, id='recorder', args=(playhead))
else:
await ctx.channel.send(f'Recorder is busy!')
@rec.error @rec.error
async def rec_error(ctx, error): async def rec_error(ctx, error):
if isinstance(error, CheckFailure): if isinstance(error, CheckFailure):
await ctx.channel.send('access denied') await ctx.channel.send('Access denied!')
@bot.command(name='stop', help='Stop the recorder')
@has_role(boss_role_name)
async def stop(ctx):
# Check if the recorder job already exists
if scheduler.get_job('recorder') is not None:
await ctx.channel.send(f'Shutting down recorder')
scheduler.remove_job('recorder')
else:
await ctx.channel.send(f'Recorder is stopped.')
@stop.error
async def stop_error(ctx, error):
if isinstance(error, CheckFailure):
await ctx.channel.send('Access denied!')
# Helper functions
async def query_playhead(): async def query_playhead():
head_url = f'https://{scheduler_hostname}/playhead' head_url = f'https://{scheduler_hostname}/playhead'
if requests.get(head_url).status_code == 200: if requests.get(head_url).status_code == 200:
@ -107,13 +131,10 @@ async def query_playhead():
playhead = response.json() playhead = response.json()
else: else:
logger_discord.error('Cannot connect to the playhead!') logger_discord.error('Cannot connect to the playhead!')
head_name = playhead['name'] return playhead
head_prio = playhead['prio']
return f'now playing {head_name}'
async def query_database(): async def query_database():
global database global database
global rechead
db_url = f'https://{scheduler_hostname}/database' db_url = f'https://{scheduler_hostname}/database'
try: try:
if requests.get(db_url).status_code == 200: if requests.get(db_url).status_code == 200:
@ -133,29 +154,18 @@ async def query_database():
logger_discord.error('Database is empty!') logger_discord.error('Database is empty!')
return return
# Search for live streams # Search for live streams and announce them
for key, value in database.items(): for key, value in database.items():
stream_name = value['name'] stream_name = value['name']
stream_start_at = value['start_at'] stream_start_at = value['start_at']
stream_meta = value['meta'] stream_meta = value['meta']
if stream_start_at == 'now': if stream_start_at == 'now':
# Check if the job already exists # Check if the announement job already exists
if scheduler.get_job('announce_live_channel') is None: if scheduler.get_job('announce_live_channel') is None:
# Job doesn't exist, so add it # Job doesn't exist, so add it
logger_discord.info(f'{stream_name} live stream detected!') logger_discord.info(f'{stream_name} live stream detected!')
scheduler.add_job(func=announce_live_channel, trigger='interval', minutes=int(live_channel_update), id='announce_live_channel', args=(stream_name, stream_meta)) scheduler.add_job(func=announce_live_channel, trigger='interval', minutes=int(live_channel_update), id='announce_live_channel', args=(stream_name, stream_meta))
# Manually execute the job immediately
scheduler.get_job('announce_live_channel').modify(next_run_time=datetime.now()) scheduler.get_job('announce_live_channel').modify(next_run_time=datetime.now())
# Set global rechead
rec_url = f'https://{scheduler_hostname}/rechead'
if requests.get(rec_url).status_code == 200:
response = requests.get(rec_url)
response.raise_for_status()
rechead = response.json()
# Exit since we found one
return return
else: else:
# Exit since we already have a live announcement job # Exit since we already have a live announcement job
@ -166,65 +176,28 @@ async def query_database():
scheduler.remove_job('announce_live_channel') scheduler.remove_job('announce_live_channel')
if live_channel_id != 0: if live_channel_id != 0:
live_channel = bot.get_channel(int(live_channel_id)) live_channel = bot.get_channel(int(live_channel_id))
if rechead != {}: await live_channel.send(f'{stream_name} is offline.')
rec_stream_name = rechead['name'] logger_discord.info(f'{stream_name} is offline.')
video_filename = rechead['video']
thumb_filename = rechead['thumb']
# Reset the rechead
rechead = {}
# Creating an embed
img_url = f'https://{scheduler_hostname}/static/images'
thumb_url = f'https://{scheduler_hostname}/thumb/{thumb_filename}'
video_download_url = f'https://{scheduler_hostname}/video/download/{video_filename}'
video_filename_no_extension = video_filename.split('.')[0]
video_watch_url = f'https://{scheduler_hostname}/video/watch/{video_filename_no_extension}'
embed = discord.Embed(title=f'VOD: {video_filename_no_extension}',
url=f'{video_watch_url}',
description=f'{rec_stream_name}',
colour=0x00b0f4,
timestamp=datetime.now())
embed.add_field(name="Download",
value=f'[mp4 file]({video_download_url})',
inline=True)
embed.add_field(name="Watch",
value=f'[plyr.js player]({video_watch_url}) :]',
inline=True)
embed.set_image(url=thumb_url)
#embed.set_thumbnail(url=f'{img_url}/logo-96.png')
embed.set_footer(text="DeflaxTV",
icon_url=f'{img_url}/logo-96.png')
# Sending the embed to the channel
await live_channel.send(embed=embed)
logger_discord.info(f'{rec_stream_name} is offline. VOD: {video_filename_no_extension}')
else:
await live_channel.send('Stream is offline.')
logger_discord.info('Stream is offline.')
async def announce_live_channel(stream_name, stream_meta): async def announce_live_channel(stream_name, stream_meta):
logger_discord.info(f'{stream_name} is live! {stream_meta}')
if live_channel_id != 0: if live_channel_id != 0:
live_channel = bot.get_channel(int(live_channel_id)) live_channel = bot.get_channel(int(live_channel_id))
await live_channel.send(f'{stream_name} is live! :satellite_orbital: {stream_meta}') await live_channel.send(f'{stream_name} is live! :satellite_orbital: {stream_meta}')
logger_discord.info(f'{stream_name} is live! {stream_meta}')
# Execute recorder # Execute recorder
async def exec_recorder(stream_id, stream_name, stream_hls_url): async def exec_recorder(playhead):
global rechead stream_id = playhead['id']
current_datetime = datetime.now().strftime("%Y%m%d_%H%M%S-%f") stream_name = playhead['name']
stream_hls_url = playhead['head']
current_datetime = datetime.now().strftime("%Y%m%d_%H%M%S")
video_file = current_datetime + ".mp4" video_file = current_datetime + ".mp4"
thumb_file = current_datetime + ".png" thumb_file = current_datetime + ".png"
if rechead != {}:
logger_job.error('Recorder is already started. Refusing to start another job.')
else:
logger_job.warning(f'Recording {video_file} started.')
rechead = { 'id': stream_id,
'name': stream_name,
'video': video_file,
'thumb': thumb_file }
video_output = f'{rec_path}/live/{video_file}' video_output = f'{rec_path}/live/{video_file}'
thumb_output = f'{rec_path}/live/{thumb_file}' thumb_output = f'{rec_path}/live/{thumb_file}'
try: try:
logger_discord.info(f'Recording video {video_file}')
# Record a mp4 file # Record a mp4 file
ffmpeg = ( ffmpeg = (
FFmpeg() FFmpeg()
@ -237,11 +210,11 @@ async def exec_recorder(stream_id, stream_name, stream_hls_url):
def on_progress(progress: Progress): def on_progress(progress: Progress):
print(progress) print(progress)
ffmpeg.execute() ffmpeg.execute()
logger_job.warning(f'Recording of {video_file} finished.') logger_discord.info(f'Recording of {video_file} finished.')
except Exception as joberror: except Exception as joberror:
logger_job.error(f'Recording of {video_file} failed!') logger_discord.error(f'Recording of {video_file} failed!')
logger_job.error(joberror) logger_discord.error(joberror)
else: else:
# Show Metadata # Show Metadata
@ -252,18 +225,20 @@ async def exec_recorder(stream_id, stream_name, stream_hls_url):
show_streams=None,) show_streams=None,)
) )
media = json.loads(ffmpeg_metadata.execute()) media = json.loads(ffmpeg_metadata.execute())
logger_job.warning(f"# Video") logger_discord.info(f"# Video")
logger_job.warning(f"- Codec: {media['streams'][0]['codec_name']}") logger_discord.info(f"- Codec: {media['streams'][0]['codec_name']}")
logger_job.warning(f"- Resolution: {media['streams'][0]['width']} X {media['streams'][0]['height']}") logger_discord.info(f"- Resolution: {media['streams'][0]['width']} X {media['streams'][0]['height']}")
logger_job.warning(f"- Duration: {media['streams'][0]['duration']}") logger_discord.info(f"- Duration: {media['streams'][0]['duration']}")
logger_job.warning(f"# Audio") logger_discord.info(f"# Audio")
logger_job.warning(f"- Codec: {media['streams'][1]['codec_name']}") logger_discord.info(f"- Codec: {media['streams'][1]['codec_name']}")
logger_job.warning(f"- Sample Rate: {media['streams'][1]['sample_rate']}") logger_discord.info(f"- Sample Rate: {media['streams'][1]['sample_rate']}")
logger_job.warning(f"- Duration: {media['streams'][1]['duration']}") logger_discord.info(f"- Duration: {media['streams'][1]['duration']}")
thumb_skip_time = float(media['streams'][0]['duration']) // 2 thumb_skip_time = float(media['streams'][0]['duration']) // 2
thumb_width = media['streams'][0]['width'] thumb_width = media['streams'][0]['width']
try:
logger_discord.info(f'Generating thumb {thumb_file}')
# Generate thumbnail image from the recorded mp4 file # Generate thumbnail image from the recorded mp4 file
ffmpeg_thumb = ( ffmpeg_thumb = (
FFmpeg() FFmpeg()
@ -271,17 +246,17 @@ async def exec_recorder(stream_id, stream_name, stream_hls_url):
.output(thumb_output, vf='scale={}:{}'.format(thumb_width, -1), vframes=1) .output(thumb_output, vf='scale={}:{}'.format(thumb_width, -1), vframes=1)
) )
ffmpeg_thumb.execute() ffmpeg_thumb.execute()
logger_job.warning(f'Thumbnail {thumb_file} created.') logger_discord.info(f'Thumbnail {thumb_file} created.')
except Exception as joberror:
logger_discord.error(f'Generating thumb {thumb_file} failed!')
logger_discord.error(joberror)
# When ready, move the recorded from the live dir to the archives and reset the rec head # When ready, move the recorded from the live dir to the archives and reset the rec head
os.rename(f'{video_output}', f'{rec_path}/vod/{video_file}') os.rename(f'{video_output}', f'{rec_path}/vod/{video_file}')
os.rename(f'{thumb_output}', f'{rec_path}/thumb/{thumb_file}') os.rename(f'{thumb_output}', f'{rec_path}/thumb/{thumb_file}')
await create_embed(stream_name, video_output, thumb_output)
finally: logger_discord.info('Recording job done in {rec_job_time} minutes')
# Reset the rechead
time.sleep(5)
rechead = {}
logger_job.warning(f'Rechead reset.')
# HLS Converter # HLS Converter
async def hls_converter(): async def hls_converter():
@ -297,10 +272,33 @@ async def hls_converter():
if entry.lower().endswith('.mp4'): if entry.lower().endswith('.mp4'):
input_file = file_path input_file = file_path
break break
#logger_job.warning(f'{input_file} found. Converting to HLS...') #logger_discord.info(f'{input_file} found. Converting to HLS...')
except Exception as e: except Exception as e:
logger_job.error(e) logger_discord.error(e)
async def create_embed(stream_name, video_filename, thumb_filename):
# Creating an embed
img_url = f'https://{scheduler_hostname}/static/images'
thumb_url = f'https://{scheduler_hostname}/thumb/{thumb_filename}'
video_download_url = f'https://{scheduler_hostname}/video/download/{video_filename}'
video_filename_no_extension = video_filename.split('.')[0]
video_watch_url = f'https://{scheduler_hostname}/video/watch/{video_filename_no_extension}'
embed = discord.Embed(title=f'VOD: {video_filename_no_extension}',
url=f'{video_watch_url}',
description=f'{stream_name}',
colour=0x00b0f4,
timestamp=datetime.now())
embed.add_field(name="Download",
value=f'[mp4 file]({video_download_url})',
inline=True)
embed.add_field(name="Watch",
value=f'[plyr.js player]({video_watch_url}) :]',
inline=True)
embed.set_image(url=thumb_url)
#embed.set_thumbnail(url=f'{img_url}/logo-96.png')
embed.set_footer(text="DeflaxTV",
icon_url=f'{img_url}/logo-96.png')
await live_channel.send(embed=embed)
# Run the bot with your token # Run the bot with your token
asyncio.run(bot.run(bot_token)) asyncio.run(bot.run(bot_token))