Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Containerized branch based on 303-proxy_http_ffmpeg_streams #305

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 25 additions & 20 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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/<path>, so pikaraoke works over a single port
@app.route('/stream/<path>', 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.ffmpeg_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())
Expand All @@ -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:
Expand Down
20 changes: 20 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Use Alpine as the base image
FROM alpine:latest

# Install required packages
RUN apk add --no-cache bash zsh git gcc python3-dev musl-dev linux-headers py3-pip ffmpeg htop screen figlet

# Clone the pikaraoke repository
RUN git clone -b PiKaraoke-Docker https://github.com/vicwomg/pikaraoke.git /pikaraoke

# Run the setup script
RUN cd /pikaraoke && echo y | bash setup.sh

# Copy the entrypoint script into the container
COPY entrypoint.sh /entrypoint.sh

# Set permissions for the entrypoint script
RUN chmod +x /entrypoint.sh

# Set the entrypoint
ENTRYPOINT ["/entrypoint.sh"]
674 changes: 674 additions & 0 deletions docker/LICENSE

Large diffs are not rendered by default.

127 changes: 127 additions & 0 deletions docker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@

# Dockerized PiKaraoke

PiKaraoke is a "KTV"-style karaoke song search and queueing system, originally designed to work on Raspberry Pi, OSX, Windows, and Linux. This repository contains a Dockerized version of PiKaraoke, built on an alpine base, making it easy to set up and run on any system with Docker support.

The original project can be found <a href="https://github.com/vicwomg/pikaraoke">here</a>

## Features

The Dockerized version retains all the features of the original PiKaraoke, including:

- Web interface for multiple users to queue tracks
- Player/splash screen with connection QR code and "Next up" display
- Searching/browsing a local song library
- Adding new songs from YouTube
- mp3 + cdg support, including compressed .zip bundles
- Pause/Skip/Restart and volume control
- Advanced editing of downloaded file names
- Queue management
- Key Change / Pitch shifting
- Lock down features with admin mode

(Refer to the original PiKaraoke features for more details.)

---

## Support the Original Project

If you want to support the original PiKaraoke project with a monetary tip, it's much appreciated by the original developer:

[![Buy Me A Coffee](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/vicwomg)

---
honestlai marked this conversation as resolved.
Show resolved Hide resolved


## Running Dockerized PiKaraoke

### Prerequisites

- Docker and Docker Compose installed on your system

### Installation and Launch


## Using Docker Compose Command Line

1. **Clone the Repository**:
First, clone the repository from GitHub to get the `docker-compose.yml` file and any other necessary configurations:
```bash
git clone https://github.com/honestlai/pikaraoke-docker.git
honestlai marked this conversation as resolved.
Show resolved Hide resolved
cd pikaraoke-docker
```

2. **Running the Container**:
Use Docker Compose to pull the image from Docker Hub and start the container in detached mode:
```bash
docker-compose up -d
```

## Using Portainer

1. **Access Portainer**:
Open Portainer and navigate to the 'Stacks' section in the left sidebar.

2. **Add a New Stack**:
Click on '+ Add stack'. Enter a name for the stack in the 'Name' field.

3. **Compose File**:
Clone the repository from GitHub or copy the contents of the `docker-compose.yml` file into the 'Web editor'. The repository can be cloned using:
```bash
git clone https://github.com/honestlai/pikaraoke-docker.git
honestlai marked this conversation as resolved.
Show resolved Hide resolved
```

4. **Environment Variables**:
Below the web editor, use the 'Add an environment variable' button to add necessary environment variables. The primary variables you might need are:
- `URL`: The URL for the pikaraoke service.
- `PASSWORD`: The admin password for pikaraoke (optional).

5. **Deploy the Stack**:
After setting up your compose file and environment variables, click on 'Deploy the stack'. Portainer will pull the image from Docker Hub and start the container based on your configurations.

## Building the container locally

1. **Clone this Dockerized repository:**

```bash
git clone https://github.com/honestlai/pikaraoke-docker.git
honestlai marked this conversation as resolved.
Show resolved Hide resolved
cd pikaraoke-docker
```

2. **Build and run the Docker container:**

```bash
docker-compose up --build
```

This command builds the Docker image and starts the PiKaraoke server.

3. **Accessing PiKaraoke:**

After the container is up and running, PiKaraoke will be accessible at `http://localhost:8888`. You can connect to this address from any device on the same network to access the PiKaraoke web interface.

### Customizing Your Setup

- Configuration options can be adjusted through environment variables in the `docker-compose.yml` file.
- Songs are stored within a docker volume, and you can manage this storage as per your requirements.

---
honestlai marked this conversation as resolved.
Show resolved Hide resolved

## Screenshots

### TV

<p float="left">
<img width="400" alt="pikaraoke-tv1" src="https://user-images.githubusercontent.com/4107190/95813571-06645600-0ccd-11eb-8341-021a20813990.png">
<img width="400" alt="pikaraoke-tv2" src="https://user-images.githubusercontent.com/4107190/95813564-019fa200-0ccd-11eb-95e1-57a002c357a3.png">
</p>

### Web interface

<div style="display: flex">
<img width="250" alt="pikaraoke-nowplaying" src="https://user-images.githubusercontent.com/4107190/95813193-2cd5c180-0ccc-11eb-89f4-11a69676dc6f.png">
<img width="250" alt="pikaraoke-queue" src="https://user-images.githubusercontent.com/4107190/95813195-2d6e5800-0ccc-11eb-8f00-1369350a8a1c.png">
<img width="250" alt="pikaraoke-browse" src="https://user-images.githubusercontent.com/4107190/95813182-27787700-0ccc-11eb-82c8-fde7f0a631c1.png">
<img width="250" alt="pikaraoke-search1" src="https://user-images.githubusercontent.com/4107190/95813197-2e06ee80-0ccc-11eb-9bf9-ddb24d988332.png">
<img width="250" alt="pikaraoke-search2" src="https://user-images.githubusercontent.com/4107190/95813190-2ba49480-0ccc-11eb-84e3-f902cbd489a2.png">
</div>
18 changes: 18 additions & 0 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
version: '3'

services:
pikaraoke:
image: honestlai/pikaraoke-docker:latest
container_name: PiKaraoke
volumes:
- pikaraoke-songs:/pikaraoke-songs
environment:
URL: #https://karaoke.yourdomain.com
PASSWORD: #optionalpassword
restart: unless-stopped
ports:
- "5555:5555"

volumes:
pikaraoke-songs:
# Define your volume specifics here, if any.
22 changes: 22 additions & 0 deletions docker/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/sh

# Correcting the URL format
URL=$(echo "$URL" | sed 's|/$||') # Remove trailing slash if exists
if ! echo "$URL" | grep -q "^https://"; then
URL="https://$URL" # Add https:// if not present
fi
honestlai marked this conversation as resolved.
Show resolved Hide resolved

# Exporting the corrected URL
export URL

# Run pikaraoke with environment variables
if [ -z "$PASSWORD" ]; then
figlet PiKaraoke
honestlai marked this conversation as resolved.
Show resolved Hide resolved
/pikaraoke/pikaraoke.sh -d /pikaraoke-songs/ --headless -u $URL
honestlai marked this conversation as resolved.
Show resolved Hide resolved
else
figlet PiKaraoke
/pikaraoke/pikaraoke.sh -d /pikaraoke-songs/ --headless -u $URL --admin-password $PASSWORD
fi

# Keep the container running
tail -f /dev/null
8 changes: 5 additions & 3 deletions karaoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def __init__(
# override with supplied constructor args if provided
self.port = port
self.ffmpeg_port = ffmpeg_port
self.ffmpeg_url_base = f"http://0.0.0.0:{self.ffmpeg_port}"
self.hide_url = hide_url
self.hide_raspiwifi_instructions = hide_raspiwifi_instructions
self.hide_splash_screen = hide_splash_screen
Expand Down Expand Up @@ -360,9 +361,10 @@ 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
ffmpeg_url = f"http://0.0.0.0:{self.ffmpeg_port}/{stream_uid}"
# This is the stream URL that will be accessed by the splash screen client, Flask will
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"{self.ffmpeg_url_base}/{stream_uid}"

pitch = 2**(semitones/12) #The pitch value is (2^x/12), where x represents the number of semitones

Expand Down
43 changes: 41 additions & 2 deletions templates/splash.html
honestlai marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,27 @@
}
});
});

// Script for forcing browser to go full screen
function handleConfirmation() {
$("#permissions-modal").removeClass("is-active");
confirmationDismissed = true;
goFullScreen();
honestlai marked this conversation as resolved.
Show resolved Hide resolved
}

function goFullScreen() {
var elem = document.documentElement; // Get the document's root element
if (elem.requestFullscreen) {
elem.requestFullscreen();
} else if (elem.mozRequestFullScreen) { /* Firefox */
elem.mozRequestFullScreen();
} else if (elem.webkitRequestFullscreen) { /* Chrome, Safari & Opera */
elem.webkitRequestFullscreen();
} else if (elem.msRequestFullscreen) { /* IE/Edge */
elem.msRequestFullscreen();
}
}

</script>
{% endblock %} {% block body %}

Expand Down Expand Up @@ -389,16 +410,25 @@
body {
background-color: black;
}
.backdrop-blur {
-webkit-backdrop-filter: blur(10px); /* For Safari */
backdrop-filter: blur(10px); /* Standard syntax */
}
#now-playing, #qr-code, #up-next {
background-color: rgba(100, 100, 100, 0.3);
-webkit-backdrop-filter: blur(6px); /* For Safari */
backdrop-filter: blur(6px); /* Standard syntax */
}

#top-container,
#bottom-container {
position: absolute;
z-index: 1;
padding: 10px 20px;
}
#top-container {
top: 0px;
right: 0px;
max-width: 75%;
max-width: 75%;
}
#bottom-container {
bottom: 0px;
Expand All @@ -408,11 +438,20 @@
justify-content: space-between;
align-items: flex-end;
}
#now-playing {
padding: 10px 20px 10px 20px;
border-bottom-left-radius: 20px;
}
#qr-code {
max-width: 25%;
border-top-right-radius: 20px; /* Rounded corner */
text-align: center; /* Center QR code */
Copy link
Owner

@vicwomg vicwomg Dec 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer for this to remain left-aligned so it's less likely to cover karaoke video text.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can do, not a biggie. It looked a little awkward when I had a long url there... I have one instance spun up on my personal server, and I know it would look odd to have the QR code left aligned.... buuuut even centered, the long URL + the QR doesn't look the greatest graphically either.

Been pondering a way to make it look more visually appealing... or maybe make the url text disappear altogether once a certain character count is reached.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I agree its not great either way, maybe lowering the text size of the url would make it look more symmetrical. Or it might be worth adding a command line option to hide the url, but show the QR code, for minimalists. If that were the case a corner-aligned QR code would look pretty clean. But that's another ticket

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

This might be worth adding as a tick box option in the admin area.

padding: 20px;
}
#up-next {
max-width: 75%;
border-top-left-radius: 20px; /* Rounded corner */
padding: 10px 20px 10px 20px;
Comment on lines +464 to +477
Copy link
Owner

@vicwomg vicwomg Dec 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a look and I'm not crazy about these boxes. They cover up much more screen real estate than the text alone, much of it being blank space. If the goal is to make the text more readable, I feel that adding a black or semi-transparent stroke effect to them would achieve the same thing and minimize how much of the video this is all covering up. https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-text-stroke

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On second thought, stroke is not great because it's an internal, not external stroke so it makes the text thinner. But an 8-axis shadow should work:
https://stackoverflow.com/a/47511171/2909171

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm.... I really like the look, to me it makes the interface very polished looking, and on most my screens it's rare they cover up important words.

I think what I might try to do is configure this so by default it's off, but ad a check box in the admin area so it can be turned on and off dynamically.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do like the general look, I think it's just the excessive blank space that bugs me. Maybe if the singer name goes on the same line as the song title, it would be cleaner. Then give it a max-width so it will wrap if necessary.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw, I don't think it's trivial to add admin settings checkboxes that will affect client side frontend. You'd probably have to dip into flask

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another thought, maybe the project in general should allow css overrides for custom "skinning". There's no way any interface will make everyone happy.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ya, I'm definitely feeling that with the QR code area at the moment.
image

}
.video-container {
position: absolute;
Expand Down