Skip to content

Commit

Permalink
Feat: Background music, Score and Limit user songs in queue
Browse files Browse the repository at this point in the history
  • Loading branch information
lvmasterrj committed Jan 1, 2025
1 parent 6af808a commit 1f4b9f2
Show file tree
Hide file tree
Showing 15 changed files with 611 additions and 72 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ dist/
songs/
qrcode.png
.DS_Store
config.ini
89 changes: 85 additions & 4 deletions pikaraoke/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import sys
import threading
import time
import mimetypes

import cherrypy
import flask_babel
Expand All @@ -22,6 +23,7 @@
request,
send_file,
url_for,
jsonify,
)
from flask_babel import Babel
from flask_paginate import Pagination, get_page_parameter
Expand Down Expand Up @@ -417,18 +419,21 @@ def download():
def qrcode():
return send_file(k.qr_code_path, mimetype="image/png")


@app.route("/logo")
def logo():
return send_file(k.logo_path, mimetype="image/png")


@app.route("/background_music")
def background_music():
music_path = k.bg_music_path
mime_type, _ = mimetypes.guess_type(music_path)
return send_file(k.bg_music_path, mimetype=mime_type)

@app.route("/end_song", methods=["GET"])
def end_song():
k.end_song()
return "ok"


@app.route("/start_song", methods=["GET"])
def start_song():
k.start_song()
Expand Down Expand Up @@ -542,6 +547,9 @@ def splash():
hide_url=k.hide_url,
hide_overlay=k.hide_overlay,
screensaver_timeout=k.screensaver_timeout,
disable_bg_music=k.disable_bg_music,
disable_score=k.disable_score,
bg_music_volume=k.bg_music_volume,
)


Expand Down Expand Up @@ -590,6 +598,10 @@ def info():
pikaraoke_version=VERSION,
admin=is_admin(),
admin_enabled=admin_password != None,
disable_bg_music=k.disable_bg_music,
bg_music_volume=int(100 * k.bg_music_volume),
disable_score=k.disable_score,
limit_user_songs_by=k.limit_user_songs_by,
)


Expand Down Expand Up @@ -684,7 +696,31 @@ def expand_fs():
else:
flash("You don't have permission to resize the filesystem", "is-danger")
return redirect(url_for("home"))


@app.route("/change_preferences", methods=["GET"])
def change_preferences():
if is_admin():
preference = request.args["pref"]
val = request.args["val"]

rc = k.change_preferences(preference, val)

return jsonify(rc)
else:
flash(_("You don't have permission to define audio output"), "is-danger")
return redirect(url_for("info"))

@app.route("/clear_preferences", methods=["GET"])
def clear_preferences():
if is_admin():
rc = k.clear_preferences()
if rc[0]:
flash(rc[1], "is-success")
else:
flash(rc[1], "is-danger")
else:
flash(_("You don't have permission to define audio output"), "is-danger")
return redirect(url_for("home"))

# Handle sigterm, apparently cherrypy won't shut down without explicit handling
signal.signal(signal.SIGTERM, lambda signum, stack_frame: k.stop())
Expand Down Expand Up @@ -717,6 +753,7 @@ def main():
default_screensaver_delay = 300
default_log_level = logging.INFO
default_prefer_hostname = False
default_bg_music_volume = 0.3

default_dl_dir = get_default_dl_dir(platform)
default_youtubedl_path = "yt-dlp"
Expand Down Expand Up @@ -864,6 +901,37 @@ def main():
default=None,
required=False,
),
parser.add_argument(
"--disable-bg-music",
action="store_true",
help="Disable background music on splash screen",
required=False,
),
parser.add_argument(
"--bg-music-volume",
default=default_bg_music_volume,
help="Set the volume of background music on splash screen. A value between 0 and 1. (default: %s)" % default_bg_music_volume,
required=False,
),
parser.add_argument(
"--bg-music-path",
nargs="+",
help="Path to a custom background music for the splash screen. (.mp3, .wav or .ogg)",
default=None,
required=False,
),
parser.add_argument(
"--disable-score",
help="Disable the score screen after each song",
action="store_true",
required=False,
),
parser.add_argument(
"--limit-user-songs-by",
help="Limit the number of songs a user can add to queue (default: 0 = illimited)",
default="0",
required=False,
),

args = parser.parse_args()

Expand All @@ -890,6 +958,14 @@ def main():
)
parsed_volume = default_volume

parsed_bg_volume = float(args.bg_music_volume)
if parsed_bg_volume > 1 or parsed_bg_volume < 0:
# logging.warning("BG music volume must be between 0 and 1. Setting to default: %s" % default_bg_volume)
print(
f"[ERROR] Volume: {args.bg_music_volume} must be between 0 and 1. Setting to default: {default_bg_music_volume}"
)
parsed_bg_volume = default_bg_music_volume

# Configure karaoke process
global k
k = karaoke.Karaoke(
Expand All @@ -911,6 +987,11 @@ def main():
url=args.url,
ffmpeg_url=args.ffmpeg_url,
prefer_hostname=args.prefer_hostname,
disable_bg_music=args.disable_bg_music,
bg_music_volume=parsed_bg_volume,
bg_music_path=arg_path_parse(args.bg_music_path),
disable_score=args.disable_score,
limit_user_songs_by=args.limit_user_songs_by
)
k.upgrade_youtubedl()

Expand Down
100 changes: 90 additions & 10 deletions pikaraoke/karaoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import json
import logging
import os
import math
import random
import socket
import subprocess
Expand All @@ -11,6 +12,8 @@
from subprocess import CalledProcessError, check_output
from threading import Thread
from urllib.parse import urlparse
import configparser
from flask_babel import _

import ffmpeg
import qrcode
Expand Down Expand Up @@ -61,6 +64,7 @@ class Karaoke:
volume = None
loop_interval = 500 # in milliseconds
default_logo_path = os.path.join(base_path, "logo.png")
default_bg_music_path = os.path.join(base_path, "static/sounds/bg-music.ogg")
screensaver_timeout = 300 # in seconds

ffmpeg_process = None
Expand All @@ -72,6 +76,8 @@ class Karaoke:
raspberry_pi = is_raspberry_pi()
os_version = get_os_version()

config_obj = configparser.ConfigParser()

def __init__(
self,
port=5555,
Expand All @@ -92,7 +98,19 @@ def __init__(
url=None,
ffmpeg_url=None,
prefer_hostname=True,
disable_bg_music=False,
bg_music_volume=0.3,
bg_music_path=None,
disable_score=False,
limit_user_songs_by=0,
):

logging.basicConfig(
format="[%(asctime)s] %(levelname)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
level=int(log_level),
)

# override with supplied constructor args if provided
self.port = port
self.ffmpeg_port = ffmpeg_port
Expand All @@ -110,17 +128,16 @@ def __init__(
self.screensaver_timeout = screensaver_timeout
self.url_override = url
self.prefer_hostname = prefer_hostname
self.disable_bg_music = self.get_user_preference("disable_bg_music") or disable_bg_music
self.bg_music_volume = self.get_user_preference("bg_music_volume") or bg_music_volume
self.bg_music_path = self.default_bg_music_path if bg_music_path == None else bg_music_path
self.disable_score = self.get_user_preference("disable_score") or disable_score
self.limit_user_songs_by = self.get_user_preference("limit_user_songs_by") or limit_user_songs_by

# other initializations
self.platform = get_platform()
self.screen = None

logging.basicConfig(
format="[%(asctime)s] %(levelname)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
level=int(log_level),
)

logging.debug(
f"""
http port: {self.port}
Expand All @@ -140,6 +157,11 @@ def __init__(
logo path: {self.logo_path}
log_level: {log_level}
hide overlay: {self.hide_overlay}
disable bg music: {self.disable_bg_music}
bg music volume: {self.bg_music_volume}
bg music path: {self.bg_music_path}
disable score: {self.disable_score}
limit user songs by: {self.limit_user_songs_by}
platform: {self.platform}
os version: {self.os_version}
Expand All @@ -148,6 +170,7 @@ def __init__(
youtubedl-version: {self.get_youtubedl_version()}
"""
)

# Generate connection URL and QR code,
if self.raspberry_pi:
# retry in case pi is still starting up
Expand Down Expand Up @@ -189,6 +212,51 @@ def __init__(

self.generate_qr_code()

# def get_user_preferences(self, preference):
def get_user_preference(self, preference, default_value=False):
# Try to read the config file
try:
self.config_obj.read("config.ini")
except FileNotFoundError:
return default_value

# Check if the section exists
if not self.config_obj.has_section("USERPREFERENCES"):
return default_value

# Try to get the value
try:
return self.config_obj.get("USERPREFERENCES", preference)
except (configparser.NoOptionError, ValueError):
return default_value

def change_preferences(self, preference, val):
"""Makes changes in the config.ini file that stores the user preferences.
Receives the preference and it's new value"""

logging.debug("Changing user preference << %s >> to %s" % (preference, val))
try:
if "USERPREFERENCES" not in self.config_obj:
self.config_obj.add_section("USERPREFERENCES")

userprefs = self.config_obj["USERPREFERENCES"]
userprefs[preference] = str(val)
setattr(self,preference,eval(str(val)))
with open("config.ini", "w") as conf:
self.config_obj.write(conf)
self.changed_preferences = True
return [True, _("Your preferences were changed successfully")]
except Exception as e:
logging.debug("Failed to change user preference << %s >>: %s", preference, e)
return [False, _("Something went wrong! Your preferences were not changed")]

def clear_preferences(self):
try:
os.remove("config.ini")
return [True, _("Your preferences were cleared successfully")]
except OSError:
return [False, _("Something went wrong! Your preferences were not cleared")]

def get_ip(self):
# python socket.connect will not work on android, access denied. Workaround: use ifconfig which is installed to termux by default, iirc.
if self.platform == "android":
Expand Down Expand Up @@ -565,11 +633,23 @@ def is_song_in_queue(self, song_path):
if each["file"] == song_path:
return True
return False

def is_user_limited(self, user):
#Returns if a user needs to be limited or not if the limitation is on and if the user reached the limit of songs in queue
if self.limit_user_songs_by == "0" or user == "Pikaraoke":
return False
cont = len([i for i in self.queue if i['user'] == user]) + (1 if self.now_playing_user == user else 0)
return True if cont >= int(self.limit_user_songs_by) else False

def enqueue(self, song_path, user="Pikaraoke", semitones=0, add_to_front=False):
#Check if the song is already in the queue, if not add it
if self.is_song_in_queue(song_path):
logging.warn("Song is already in queue, will not add: " + song_path)
return False
return [False, _("Song is already in queue, will not add: ") + song_path]
#check if the user has reached the limit of songs in queue
elif self.is_user_limited(user):
logging.debug("User limitted by: " + str(self.limit_user_songs_by))
return [False, _("You reached the limit of %s song(s) from an user in queue!") % (str(self.limit_user_songs_by))]
else:
queue_item = {
"user": user,
Expand All @@ -578,12 +658,12 @@ def enqueue(self, song_path, user="Pikaraoke", semitones=0, add_to_front=False):
"semitones": semitones,
}
if add_to_front:
logging.info("'%s' is adding song to front of queue: %s" % (user, song_path))
logging.info(_("'%s' is adding song to front of queue: %s") % (user, song_path))
self.queue.insert(0, queue_item)
else:
logging.info("'%s' is adding song to queue: %s" % (user, song_path))
logging.info(_("'%s' is adding song to queue: %s") % (user, song_path))
self.queue.append(queue_item)
return True
return [True, _("Song added to the queue: %s") % (self.filename_from_path(song_path))]

def queue_add_random(self, amount):
logging.info("Adding %d random songs to queue" % amount)
Expand Down
Loading

0 comments on commit 1f4b9f2

Please sign in to comment.