Skip to content

Commit

Permalink
refactor: reorganize tmpdir handling, fix buffering issues
Browse files Browse the repository at this point in the history
  • Loading branch information
vicwomg committed Dec 25, 2024
1 parent 8663bc4 commit 6b8b150
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 50 deletions.
3 changes: 2 additions & 1 deletion pikaraoke/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@

from pikaraoke import VERSION, karaoke
from pikaraoke.constants import LANGUAGES
from pikaraoke.lib.file_resolver import get_tmp_dir
from pikaraoke.lib.file_resolver import delete_tmp_dir, get_tmp_dir
from pikaraoke.lib.get_platform import get_platform, is_raspberry_pi

try:
Expand Down Expand Up @@ -1046,6 +1046,7 @@ def main():
driver.close()
cherrypy.engine.exit()

delete_tmp_dir()
sys.exit()


Expand Down
91 changes: 44 additions & 47 deletions pikaraoke/karaoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import logging
import os
import random
import shutil
import socket
import subprocess
import time
Expand All @@ -17,7 +16,7 @@
import qrcode
from unidecode import unidecode

from pikaraoke.lib.file_resolver import FileResolver, get_tmp_dir
from pikaraoke.lib.file_resolver import FileResolver, delete_tmp_dir
from pikaraoke.lib.get_platform import (
get_ffmpeg_version,
get_os_version,
Expand Down Expand Up @@ -418,11 +417,6 @@ 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"{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}"

pitch = 2 ** (
semitones / 12
Expand Down Expand Up @@ -468,7 +462,7 @@ def play_file(self, file_path, semitones=0):
output = ffmpeg.output(
audio,
video,
output_file,
fr.output_file,
vcodec="libx264",
acodec="aac",
preset="ultrafast",
Expand All @@ -483,7 +477,7 @@ def play_file(self, file_path, semitones=0):
output = ffmpeg.output(
audio,
video,
output_file,
fr.output_file,
vcodec=vcodec,
acodec=acodec,
preset="ultrafast",
Expand All @@ -508,46 +502,50 @@ def play_file(self, file_path, semitones=0):
t.start()

output_file_size = 0
buffering_threshold = 4000000 # raise this if pi3 struggles to keep up with transcoding
while self.ffmpeg_process.poll() is None:
is_transcoding_complete = False
is_buffering_complete = False
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)
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:
# 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
self.now_playing_url = stream_url
self.now_playing_user = self.queue[0]["user"]
self.is_paused = False
self.queue.pop(0)

# Pause until the stream is playing
max_retries = 100
while self.is_playing == False and max_retries > 0:
time.sleep(0.1) # prevents loop from trying to replay track
max_retries -= 1
if self.is_playing:
logging.debug("Stream is playing")
break
else:
logging.error(
"Stream was not playable! Run with debug logging to see output. Skipping track"
)
self.end_song()
break
time.sleep(0.1)
is_transcoding_complete = stream_ready_string in decode_ignore(output)
if is_transcoding_complete:
logging.debug(f"Transcoding complete. File size: {output_file_size}")
except Empty:
try:
output_file_size = os.path.getsize(fr.output_file)
is_buffering_complete = output_file_size > buffering_threshold
if is_buffering_complete:
logging.debug(f"Buffering complete. File size: {output_file_size}")
except (FileNotFoundError, AttributeError):
pass
# Check if the stream is ready to play
# Determined by completed transcode stream_ready_string match
# or the buffered file size being greater than a threshold
if is_transcoding_complete or is_buffering_complete:
logging.debug(f"Stream ready!")
self.now_playing = self.filename_from_path(file_path)
self.now_playing_filename = file_path
self.now_playing_transpose = semitones
self.now_playing_url = f"{self.url}/{fr.stream_url_path}"
self.now_playing_user = self.queue[0]["user"]
self.is_paused = False
self.queue.pop(0)
# Pause until the stream is playing
max_retries = 100
while self.is_playing == False and max_retries > 0:
time.sleep(0.1) # prevents loop from trying to replay track
max_retries -= 1
if self.is_playing:
logging.debug("Stream is playing")
break
else:
logging.error(
"Stream was not playable! Run with debug logging to see output. Skipping track"
)
self.end_song()
break

def kill_ffmpeg(self):
logging.debug("Killing ffmpeg process")
Expand All @@ -562,8 +560,7 @@ 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())
delete_tmp_dir()
logging.debug("ffmpeg process killed")

def transpose_current(self, semitones):
Expand Down
22 changes: 20 additions & 2 deletions pikaraoke/lib/file_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import re
import shutil
import zipfile
from sys import maxsize

from pikaraoke.lib.get_platform import get_platform

Expand All @@ -13,22 +14,39 @@ def get_tmp_dir():
tmp_dir = os.path.expanduser(r"~\\AppData\\Local\\Temp\\pikaraoke\\" + str(pid) + r"\\")
else:
tmp_dir = f"/tmp/pikaraoke/{pid}"
return tmp_dir


def create_tmp_dir():
tmp_dir = get_tmp_dir()
# create tmp_dir if it doesn't exist
if not os.path.exists(tmp_dir):
os.makedirs(tmp_dir)
return tmp_dir


def delete_tmp_dir():
tmp_dir = get_tmp_dir()
if os.path.exists(tmp_dir):
shutil.rmtree(tmp_dir)


def string_to_hash(s):
return hash(s) % ((maxsize + 1) * 2)


# 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
cdg_file_path = None
file_extension = None
pid = os.getpid() # for scoping tmp directories to this process

def __init__(self, file_path):
create_tmp_dir()
self.tmp_dir = get_tmp_dir()
self.resolved_file_path = self.process_file(file_path)
self.stream_uid = string_to_hash(file_path)
self.output_file = f"{self.tmp_dir}/{self.stream_uid}.mp4"
self.stream_url_path = f"stream/{self.stream_uid}"

# Extract zipped cdg + mp3 files into a temporary directory, and set the paths to both files.
def handle_zipped_cdg(self, file_path):
Expand Down

0 comments on commit 6b8b150

Please sign in to comment.