From 54d9c91fcbda534e06f4018ebbf56f023642a47f Mon Sep 17 00:00:00 2001 From: Vic Wong Date: Wed, 27 Dec 2023 22:54:47 -0500 Subject: [PATCH] [FEATURE] Pikaraoke should not require multiple ports (proxy http requests to ffmpeg streams) #303 --- README.md | 32 ++++++++++++++++++++++++++---- app.py | 45 ++++++++++++++++++++++++------------------- karaoke.py | 5 +++-- templates/splash.html | 4 ++-- 4 files changed, 58 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index e3bd7806..cc6fac87 100644 --- a/README.md +++ b/README.md @@ -55,9 +55,9 @@ Raspberry Pi 3 and above. Anything else will likely be too slow. Other pi considerations: - Should be running Raspberry pi desktop OS if running headed, since it requires a browser -- 32-bit version of the OS is recommended. 64-bit seemed slower in my testing, but pi4 and above can probably handle it. +- 32-bit version of the Bookworm or latest OS is recommended. There have been reported issues with Bullseye and 64-bit seemed slower on the pi 3, but pi4 and above can probably handle it. - Disable "screen blanking" in raspi-config if you want to prevent the display from turning off when idle -- Pi3 might struggle a bit with high-res video playback. Overclocking seems to help +- Pi 3 might struggle a bit with high-res video playback. Overclocking to 1300 seems to help Works fine on modern Mac, PCs, and Linux! @@ -142,7 +142,7 @@ Note that if your wifi/network is inactive pikaraoke will error out 10 seconds a ## Usage -May not be up to date, run `python3 app.py --help` for the latest: +May not be up to date, run `./pikaraoke.sh --help` for the latest: ``` usage: app.py [-h] [-p PORT] [-f FFMPEG_PORT] [-d DOWNLOAD_PATH] [-y YOUTUBEDL_PATH] [-v VOLUME] [-s SPLASH_DELAY] [-t SCREENSAVER_TIMEOUT] @@ -182,7 +182,7 @@ options: and system shutdown. If unspecified, everyone is an admin. ``` -## Troubleshooting +## FAQ / Troubleshooting ### I'm not hearing audio out of the headphone jack @@ -262,3 +262,27 @@ You'll need to add them manually by copying them to the root of your download fo ### My mp3/cdg file is not playing CDG files must have an mp3 file with a exact matching file name. They can also be bundled together in a single zip file, but the filenames in the zip must still match. They must also be placed in the root of the download directory and not stashed away in sub-directories. + +### Video playback is choppy and slow on my Raspberry Pi 3 + +The Pi 3 might struggle with full screen video playback and transcoding of user-supplied high-resolution or high-framrerate video files. The files coming from the default configuration of yt-dlp should be fine, however. There are a couple of optimizations that can be made to make it usable: + +I also found that 32-bit versions of the OS are faster than 64 bit on the pi 3. + +Overclocking to 1300 also seemed to clear up any issues for our test hardware. Ensure your pi has sufficient cooling on the CPUs and memory (headsinks or even active cooling) + +To overclock, edit your boot.config: + +``` +sudo nano /boot/config.txt +``` + +Add these lines: + +``` +arm_freq=1300 +core_freq=500 +gpu_freq=500 +over_voltage=4 +sdram_freq=500 +``` diff --git a/app.py b/app.py index d62cd040..1baee632 100644 --- a/app.py +++ b/app.py @@ -13,8 +13,9 @@ import cherrypy import flask_babel import psutil -from flask import (Flask, flash, make_response, redirect, render_template, - request, send_file, url_for) +import requests +from flask import (Flask, Response, flash, make_response, redirect, + render_template, request, send_file, url_for) from flask_babel import Babel from flask_paginate import Pagination, get_page_parameter from selenium import webdriver @@ -608,6 +609,27 @@ def expand_fs(): flash("You don't have permission to resize the filesystem", "is-danger") return redirect(url_for("home")) +# Proxy the video stream from ffmpeg to /stream/, so pikaraoke works over a single port +@app.route('/stream/', methods=["GET", "POST"]) +def redirect_to_ffmpeg_stream(path): #NOTE var :path will be unused as all path we need will be read from :request ie from flask import request + res = requests.request( # ref. https://stackoverflow.com/a/36601467/248616 + method = request.method, + url = request.url.replace(request.host_url, f'{k.ffmmpeg_url_base}/'), + headers = {k:v for k,v in request.headers if k.lower() != 'host'}, # exclude 'host' header + data = request.get_data(), + cookies = request.cookies, + allow_redirects = False, + ) + + #region exlcude some keys in :res response + excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection'] #NOTE we here exclude all "hop-by-hop headers" defined by RFC 2616 section 13.5.1 ref. https://www.rfc-editor.org/rfc/rfc2616#section-13.5.1 + headers = [ + (k,v) for k,v in res.raw.headers.items() + if k.lower() not in excluded_headers + ] + #endregion exclude some keys in :res response + response = Response(res.content, res.status_code, headers) + return response # Handle sigterm, apparently cherrypy won't shut down without explicit handling signal.signal(signal.SIGTERM, lambda signum, stack_frame: k.stop()) @@ -616,24 +638,7 @@ def get_default_youtube_dl_path(platform): if platform == "windows": return os.path.join(os.path.dirname(__file__), ".venv\Scripts\yt-dlp.exe") return os.path.join(os.path.dirname(__file__), ".venv/bin/yt-dlp") - # if platform == "windows": - # choco_ytdl_path = r"C:\ProgramData\chocolatey\bin\yt-dlp.exe" - # scoop_ytdl_path = os.path.expanduser(r"~\scoop\shims\yt-dlp.exe") - # if os.path.isfile(choco_ytdl_path): - # return choco_ytdl_path - # if os.path.isfile(scoop_ytdl_path): - # return scoop_ytdl_path - # return r"C:\Program Files\yt-dlp\yt-dlp.exe" - # default_ytdl_unix_path = "/usr/local/bin/yt-dlp" - # if platform == "osx": - # if os.path.isfile(default_ytdl_unix_path): - # return default_ytdl_unix_path - # else: - # # just a guess based on the default python 3 install in OSX monterey - # return "/Library/Frameworks/Python.framework/Versions/3.10/bin/yt-dlp" - # else: - # return default_ytdl_unix_path - + def get_default_dl_dir(platform): if is_raspberry_pi: diff --git a/karaoke.py b/karaoke.py index 85dfc454..91d781fe 100644 --- a/karaoke.py +++ b/karaoke.py @@ -360,8 +360,9 @@ def get_youtube_id_from_url(self, url): def play_file(self, file_path, semitones=0): logging.info(f"Playing file: {file_path} transposed {semitones} semitones") stream_uid = int(time.time()) - stream_url = f"{self.url_parsed.scheme}://{self.url_parsed.hostname}:{self.ffmpeg_port}/{stream_uid}" - # pass a 0.0.0.0 IP to ffmpeg which will work for both hostnames and direct IP access + # This is the stream URL that will be accessed by the splash screen client, Flask will proxy to the ffmpeg_url + stream_url = f"{self.url}/stream/{stream_uid}" + # Used by ffmpeg, 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) #The pitch value is (2^x/12), where x represents the number of semitones diff --git a/templates/splash.html b/templates/splash.html index d9cbf786..141c6639 100644 --- a/templates/splash.html +++ b/templates/splash.html @@ -333,8 +333,8 @@

{# MSG: Prompt for interaction in order to enable video autoplay. #} {% trans %}Due to limititations with browser permissions, you must interact - with the page once before it allows autoplay of videos. Pikaraoke will not - play otherwise. Click the button below to confirm.{% endtrans %} + with the page once before it allows videos to play. Click the button below + to continue.{% endtrans %}