From 4141fcd42828ea435218e50d670ec4dccf15f5fb Mon Sep 17 00:00:00 2001 From: ThisBirchWood Date: Thu, 6 Mar 2025 18:26:29 +0000 Subject: [PATCH] UPDATE: VoD Thumbnails now take a screenshot from exactly halfway through the stream, instead of the beginning --- web_server/celery_tasks/streaming.py | 16 +++++++++++----- web_server/utils/stream_utils.py | 27 ++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/web_server/celery_tasks/streaming.py b/web_server/celery_tasks/streaming.py index daa6f84..c1d4e4c 100644 --- a/web_server/celery_tasks/streaming.py +++ b/web_server/celery_tasks/streaming.py @@ -1,7 +1,7 @@ from celery import Celery, shared_task, Task from datetime import datetime from celery_tasks.preferences import user_preferences -from utils.stream_utils import generate_thumbnail, get_streamer_live_status, get_custom_thumbnail_status, remove_hls_files +from utils.stream_utils import generate_thumbnail, get_streamer_live_status, get_custom_thumbnail_status, remove_hls_files, get_video_duration from time import sleep from os import listdir, remove from utils.path_manager import PathManager @@ -10,7 +10,7 @@ import subprocess path_manager = PathManager() @shared_task -def update_thumbnail(user_id, stream_file, thumbnail_file, sleep_time) -> None: +def update_thumbnail(user_id, stream_file, thumbnail_file, sleep_time, second_capture=0) -> None: """ Updates the thumbnail of a stream periodically """ @@ -19,7 +19,7 @@ def update_thumbnail(user_id, stream_file, thumbnail_file, sleep_time) -> None: if get_streamer_live_status(user_id)['is_live'] and not get_custom_thumbnail_status(user_id)['custom_thumbnail']: print("Updating thumbnail...") generate_thumbnail(stream_file, thumbnail_file) - update_thumbnail.apply_async((user_id, stream_file, thumbnail_file, sleep_time), countdown=sleep_time) + update_thumbnail.apply_async((user_id, stream_file, thumbnail_file, sleep_time, second_capture), countdown=sleep_time) else: print(f"Stopping thumbnail updates for stream of {user_id}") @@ -28,6 +28,8 @@ def combine_ts_stream(stream_path, vods_path, vod_file_name, thumbnail_file) -> """ Combines all ts files into a single vod, and removes the ts files """ + vod_file_path = f"{vods_path}/{vod_file_name}.mp4" + ts_files = [f for f in listdir(stream_path) if f.endswith(".ts")] ts_files.sort() @@ -48,7 +50,7 @@ def combine_ts_stream(stream_path, vods_path, vod_file_name, thumbnail_file) -> f"{stream_path}/list.txt", "-c", "copy", - f"{vods_path}/{vod_file_name}.mp4" + vod_file_path ] subprocess.run(vod_command) @@ -56,8 +58,12 @@ def combine_ts_stream(stream_path, vods_path, vod_file_name, thumbnail_file) -> # Remove HLS files, even if user is not streaming remove_hls_files(stream_path) + # Get video duration and choose middle frame as thumbnail + video_duration = get_video_duration(vod_file_path) + second_capture = video_duration // 2 + # Generate thumbnail for vod - generate_thumbnail(f"{vods_path}/{vod_file_name}.mp4", thumbnail_file) + generate_thumbnail(vod_file_path, thumbnail_file, second_capture) @shared_task def convert_image_to_png(image_path, png_path): diff --git a/web_server/utils/stream_utils.py b/web_server/utils/stream_utils.py index ec0fb30..1995150 100644 --- a/web_server/utils/stream_utils.py +++ b/web_server/utils/stream_utils.py @@ -172,7 +172,7 @@ def get_user_vods(user_id: int): vods = db.fetchall("""SELECT vods.*, username, category_name FROM vods JOIN users ON vods.user_id = users.user_id JOIN categories ON vods.category_id = categories.category_id WHERE vods.user_id = ? ORDER BY vod_id DESC;""", (user_id,)) return vods -def generate_thumbnail(stream_file: str, thumbnail_file: str) -> None: +def generate_thumbnail(stream_file: str, thumbnail_file: str, second_capture) -> None: """ Generates the thumbnail of a stream """ @@ -180,6 +180,8 @@ def generate_thumbnail(stream_file: str, thumbnail_file: str) -> None: thumbnail_command = [ "ffmpeg", "-y", + "-ss", + f"{second_capture}", "-i", f"{stream_file}", "-vframes", @@ -203,6 +205,29 @@ def remove_hls_files(stream_path: str) -> None: if file.endswith(".ts") or file.endswith(".m3u8"): os.remove(os.path.join(stream_path, file)) +def get_video_duration(video_path: str) -> int: + """ + Returns the length of a video in seconds + """ + video_length_command = [ + "ffprobe", + "-v", + "error", + "-show_entries", + "format=duration", + "-of", + "default=noprint_wrappers=1:nokey=1", + video_path + ] + + try: + video_length = subprocess.check_output(video_length_command).decode("utf-8") + print(f"Video length: {video_length}") + return int(float(video_length)) + except subprocess.CalledProcessError as e: + print(f"Error getting video length: {e}") + return 0 + def get_stream_tags(user_id: int) -> Optional[List[str]]: """ Given a stream return tags associated with the user's stream