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

Feat: Background music, score and limit songs by user #455

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ dist/
songs/
qrcode.png
.DS_Store
config.ini
docker-compose.yml
89 changes: 89 additions & 0 deletions pikaraoke/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import hashlib
import json
import logging
import mimetypes
import os
import signal
import subprocess
Expand All @@ -16,6 +17,7 @@
from flask import (
Flask,
flash,
jsonify,
make_response,
redirect,
render_template,
Expand Down Expand Up @@ -425,6 +427,13 @@ 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()
Expand Down Expand Up @@ -545,6 +554,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 @@ -594,6 +606,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 @@ -690,6 +706,33 @@ def expand_fs():
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 @@ -721,6 +764,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 @@ -868,6 +912,38 @@ 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 @@ -894,6 +970,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 @@ -915,6 +999,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
106 changes: 96 additions & 10 deletions pikaraoke/karaoke.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import configparser
import contextlib
import json
import logging
Expand All @@ -14,6 +15,7 @@

import ffmpeg
import qrcode
from flask_babel import _
from unidecode import unidecode

from pikaraoke.lib.file_resolver import FileResolver
Expand Down Expand Up @@ -62,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 @@ -74,6 +77,8 @@ class Karaoke:
raspberry_pi = is_raspberry_pi()
os_version = get_os_version()

config_obj = configparser.ConfigParser()

def __init__(
self,
port=5555,
Expand All @@ -94,7 +99,18 @@ 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 @@ -112,17 +128,18 @@ 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 @@ -142,6 +159,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 @@ -151,6 +173,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 @@ -192,6 +215,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 @@ -569,10 +637,28 @@ def is_song_in_queue(self, 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 @@ -581,12 +667,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
Loading