From 8663bc48344ac802be1b0d431d5ded5d4d41241c Mon Sep 17 00:00:00 2001 From: Vic Wong Date: Wed, 25 Dec 2024 01:50:19 -0800 Subject: [PATCH] better handling of temp dirs, remove debug logging, better stream readiness detection --- pikaraoke/app.py | 9 +++------ pikaraoke/karaoke.py | 29 +++++++++++++++++++---------- pikaraoke/lib/file_resolver.py | 21 ++++++++++++++------- 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/pikaraoke/app.py b/pikaraoke/app.py index 045ce287..3cdca25a 100644 --- a/pikaraoke/app.py +++ b/pikaraoke/app.py @@ -40,6 +40,7 @@ from pikaraoke import VERSION, karaoke from pikaraoke.constants import LANGUAGES +from pikaraoke.lib.file_resolver import get_tmp_dir from pikaraoke.lib.get_platform import get_platform, is_raspberry_pi try: @@ -697,7 +698,7 @@ def expand_fs(): # Streams the file in chunks from the filesystem (chrome supports it, safari does not) @app.route("/stream/") def stream(id): - file_path = f"/tmp/pikaraoke/{id}.mp4" + file_path = f"{get_tmp_dir()}/{id}.mp4" def generate(): previous_size = -1 @@ -708,15 +709,12 @@ def generate(): current_size = os.path.getsize(file_path) if current_size == previous_size: # File size has stabilized, break the loop - print(f"**FILE SIZE STABILIZED {current_size}") break file.seek(position) # Move to the last read position while True: chunk = file.read(10240 * 100 * 30) # Read in 3mb chunks if not chunk: - print(f"**CHUNK BREAK {len(chunk)}") break # End of file reached - print(f"**YIELD CHUNK {len(chunk)}") yield chunk position += len(chunk) # Update the position with the size of the chunk previous_size = current_size @@ -729,7 +727,7 @@ def generate(): # (Safari compatible, but requires the ffmpeg transcoding to be complete to know file size) @app.route("/stream/full/") def stream_full(id): - file_path = f"/tmp/pikaraoke/{id}.mp4" + file_path = f"{get_tmp_dir()}/{id}.mp4" try: file_size = os.path.getsize(file_path) range_header = request.headers.get("Range", None) @@ -745,7 +743,6 @@ def stream_full(id): start = int(start) end = int(end) if end else file_size - 1 - print(f"***range header: {range_header} FILE_SIZE: {file_size}") # Generate response with part of file with open(file_path, "rb") as file: file.seek(start) diff --git a/pikaraoke/karaoke.py b/pikaraoke/karaoke.py index bdbcba02..bf4391a0 100644 --- a/pikaraoke/karaoke.py +++ b/pikaraoke/karaoke.py @@ -3,11 +3,12 @@ import logging import os import random +import shutil import socket import subprocess import time from pathlib import Path -from queue import Queue +from queue import Empty, Queue from subprocess import CalledProcessError, check_output from threading import Thread from urllib.parse import urlparse @@ -16,7 +17,7 @@ import qrcode from unidecode import unidecode -from pikaraoke.lib.file_resolver import FileResolver +from pikaraoke.lib.file_resolver import FileResolver, get_tmp_dir from pikaraoke.lib.get_platform import ( get_ffmpeg_version, get_os_version, @@ -418,7 +419,7 @@ def log_ffmpeg_output(self): def play_file(self, file_path, semitones=0): logging.info(f"Playing file: {file_path} transposed {semitones} semitones") stream_uid = int(time.time()) - output_file = f"/tmp/pikaraoke/{stream_uid}.mp4" + output_file = f"{get_tmp_dir()}/{stream_uid}.mp4" stream_url = f"{self.url}/stream/{stream_uid}" # pass a 0.0.0.0 IP to ffmpeg which will work for both hostnames and direct IP access ffmpeg_url = f"http://0.0.0.0:{self.ffmpeg_port}/{stream_uid}" @@ -453,13 +454,11 @@ def play_file(self, file_path, semitones=0): # normalize the audio audio = audio.filter("loudnorm", i=-16, tp=-1.5, lra=11) if self.normalize_audio else audio - # Ffmpeg outputs "Stream #0" when the stream is ready to consume - stream_ready_string = "Stream #" + # Ffmpeg outputs "out#0" when the stream is done transcoding + stream_ready_string = "out#0/mp4" if fr.cdg_file_path != None: # handle CDG files logging.info("Playing CDG/MP3 file: " + file_path) - # Ffmpeg outputs "Video: cdgraphics" when the stream is ready to consume - stream_ready_string = "Video: cdgraphics" # copyts helps with sync issues, fps=25 prevents ffmpeg from needlessly encoding cdg at 300fps cdg_input = ffmpeg.input(fr.cdg_file_path, copyts=None) video = cdg_input.video.filter("fps", fps=25) @@ -512,13 +511,20 @@ def play_file(self, file_path, semitones=0): while self.ffmpeg_process.poll() is None: try: # Add a loop to check the size of output_file + output = self.ffmpeg_log.get_nowait() output_file_size = os.path.getsize(output_file) - except FileNotFoundError: + logging.debug("[FFMPEG] " + decode_ignore(output)) + logging.debug(f"Output file size: {output_file_size}") + except (FileNotFoundError, Empty, AttributeError): # Handle the case where the file might not exist yet + time.sleep(0.1) pass else: - if output_file_size > 1048576: # 1MB in bytes - logging.debug("Stream ready!") + # Check if the stream is ready to play + # Determined by completed transcode stream_ready_string match + # or the file size being greater than a threshold + if (stream_ready_string in decode_ignore(output)) or (output_file_size > 4048576): + logging.debug(f"Stream ready! File size: {output_file_size}") self.now_playing = self.filename_from_path(file_path) self.now_playing_filename = file_path self.now_playing_transpose = semitones @@ -541,6 +547,7 @@ def play_file(self, file_path, semitones=0): ) self.end_song() break + time.sleep(0.1) def kill_ffmpeg(self): logging.debug("Killing ffmpeg process") @@ -555,6 +562,8 @@ def end_song(self): logging.info(f"Song ending: {self.now_playing}") self.reset_now_playing() self.kill_ffmpeg() + # delete the tmp dir + shutil.rmtree(get_tmp_dir()) logging.debug("ffmpeg process killed") def transpose_current(self, semitones): diff --git a/pikaraoke/lib/file_resolver.py b/pikaraoke/lib/file_resolver.py index 68c221fc..9ab3824f 100644 --- a/pikaraoke/lib/file_resolver.py +++ b/pikaraoke/lib/file_resolver.py @@ -6,6 +6,19 @@ from pikaraoke.lib.get_platform import get_platform +def get_tmp_dir(): + # Determine tmp directories (for things like extracted cdg files) + pid = os.getpid() # for scoping tmp directories to this process + if get_platform() == "windows": + tmp_dir = os.path.expanduser(r"~\\AppData\\Local\\Temp\\pikaraoke\\" + str(pid) + r"\\") + else: + tmp_dir = f"/tmp/pikaraoke/{pid}" + # create tmp_dir if it doesn't exist + if not os.path.exists(tmp_dir): + os.makedirs(tmp_dir) + return tmp_dir + + # Processes a given file path and determines the file format and file path, extracting zips into cdg + mp3 if necessary. class FileResolver: file_path = None @@ -14,13 +27,7 @@ class FileResolver: pid = os.getpid() # for scoping tmp directories to this process def __init__(self, file_path): - # Determine tmp directories (for things like extracted cdg files) - if get_platform() == "windows": - self.tmp_dir = os.path.expanduser( - r"~\\AppData\\Local\\Temp\\pikaraoke\\" + str(self.pid) + r"\\" - ) - else: - self.tmp_dir = f"/tmp/pikaraoke/{self.pid}" + self.tmp_dir = get_tmp_dir() self.resolved_file_path = self.process_file(file_path) # Extract zipped cdg + mp3 files into a temporary directory, and set the paths to both files.