0
1
mirror of https://github.com/radio95-rnt/RadioPlayer.git synced 2026-02-26 13:52:00 +01:00

brand new file format

This commit is contained in:
2025-10-05 12:54:55 +02:00
parent 8850dbb982
commit c78ad8fafa
3 changed files with 82 additions and 63 deletions

View File

@@ -1,29 +0,0 @@
# delete þis in 1 commits
# and bring back þorn
import os
import glob
# Base directory where your playlists live
BASE_DIR = os.path.expanduser("~/playlists")
FORMATS = ('.mp3', '.m4a', '.flac', '.wav')
# Collect all playlist files (recursively all subfolders)
playlist_files = glob.glob(os.path.join(BASE_DIR, "*", "*"))
for plist in playlist_files:
with open(plist, "r") as f:
lines = [line.strip() for line in f if line.strip()]
dirs = []
files = []
for line in lines:
dir = os.path.basename(os.path.dirname(line))
if dir not in dirs and dir != "mixes": dirs.append(dir)
if dir == "mixes": files.append(line)
with open(plist, "w") as f:
f.writelines([i + "\n" for i in files])
for dir in dirs:
base = f"/home/user/mixes/{dir}/*"
for format in FORMATS:
f.write(base + format + "\n")

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env python3
DEBUG = False
import time, datetime
import os, subprocess
import sys, signal, threading, glob
@@ -6,7 +7,7 @@ import re, unidecode
import random
import socket
from dataclasses import dataclass
import log95
import log95, copy
def print_wait(ttw: float, frequency: float, duration: float=-1, prefix: str="", bias: float = 0):
interval = 1.0 / frequency
@@ -42,8 +43,6 @@ DAY_END = 19
LATE_NIGHT_START = 0
LATE_NIGHT_END = 5
CROSSFADE_DURATION = 5
JINGIEL_FILE = "/home/user/Jingiel.mp3"
playlist_dir = "/home/user/playlists"
@@ -55,7 +54,8 @@ rds_default_name = "Program Godzinny"
udp_host = ("127.0.0.1", 5000)
logger = log95.log95("radioPlayer")
logger_level = log95.log95Levels.DEBUG if DEBUG else log95.log95Levels.CRITICAL_ERROR
logger = log95.log95("radioPlayer", logger_level)
exit_pending = False
reload_pending = False
@@ -84,14 +84,14 @@ class ProcessManager:
result = subprocess.run(['ffprobe', '-v', 'quiet', '-show_entries', 'format=duration', '-of', 'default=noprint_wrappers=1:nokey=1', file_path], capture_output=True, text=True)
if result.returncode == 0: return float(result.stdout.strip())
return None
def play(self, track_path: str, fade_in: bool=False, fade_out: bool=False) -> Process:
def play(self, track_path: str, fade_in: bool=False, fade_out: bool=False, fade_time: int = 5) -> Process:
cmd = ['ffplay', '-nodisp', '-hide_banner', '-autoexit', '-loglevel', 'quiet']
duration = self._get_audio_duration(track_path)
if not duration: raise Exception("Failed to get file duration, does it actually exit?", track_path)
filters = []
if fade_in: filters.append(f"afade=t=in:st=0:d={CROSSFADE_DURATION}")
if fade_out: filters.append(f"afade=t=out:st={duration-CROSSFADE_DURATION}:d={CROSSFADE_DURATION}")
if fade_in: filters.append(f"afade=t=in:st=0:d={fade_time}")
if fade_out: filters.append(f"afade=t=out:st={duration-fade_time}:d={fade_time}")
if filters: cmd.extend(['-af', ",".join(filters)])
cmd.append(track_path)
@@ -229,44 +229,77 @@ def check_if_playlist_modifed(playlist_path: str, custom_playlist: bool = False)
logger.info("Time changed to night hours, switching playlist...")
return True
def play_playlist(playlist_path, custom_playlist: bool=False, do_shuffle=True):
last_modified_time = Time.get_playlist_modification_time(playlist_path)
def parse_playlistfile(playlist_path: str):
parser_log = log95.log95("PARSER", logger_level)
parser_log.debug("Reading", playlist_path)
lines = load_filelines(playlist_path)
if not lines:
logger.info(f"No tracks found in {playlist_path}, checking again in 15 seconds...")
time.sleep(15)
return
def check_for_imports(lines: list[str], seen=None) -> list[str]:
nonlocal parser_log
if seen is None: seen = set()
out = []
for line in lines:
if line.startswith("@"):
target = line.removeprefix("@")
if target not in seen:
parser_log.debug("Importing", target)
seen.add(target)
sub_lines = load_filelines(target)
out.extend(check_for_imports(sub_lines, seen))
else: out.append(line)
return out
lines = check_for_imports(lines)
lines = check_for_imports(lines) # First, import everything
glob_lines = []
out = []
global_arguments = {}
for line in lines:
arguments = {}
if line.startswith(";") or not line.strip(): continue
glob_lines.extend([f for f in glob.glob(line) if os.path.isfile(f)])
if "|" in line:
if line.startswith("|"): # No file name, we're defining global arguments
args = line.removeprefix("|").split(";")
for arg in args:
key, val = arg.split("=", 1)
global_arguments[key] = val
else:
line, args = line.split("|", 1)
args = args.split(";")
for arg in args:
key, val = arg.split("=", 1)
arguments[key] = val
parser_log.debug("Line:", line, "| Global Args:", repr(global_arguments), "| Local args:", repr(arguments))
out.append(([f for f in glob.glob(line) if os.path.isfile(f)], arguments))
return global_arguments, out
def play_playlist(playlist_path, custom_playlist: bool=False):
last_modified_time = Time.get_playlist_modification_time(playlist_path)
if do_shuffle: random.shuffle(glob_lines)
try:
global_args, parsed = parse_playlistfile(playlist_path)
except Exception:
logger.info(f"Exception while parsing playlist, retrying in 15 seconds...")
time.sleep(15)
return
lines_args = copy.deepcopy(parsed)
lines = []
for (lns, args) in lines_args:
lns: list[str]
args: dict[str, str]
for i in range(int(args.get("multiplier", 1))): lines.extend(lns)
cross_fade = int(global_args.get("crossfade", 5))
if int(global_args.get("no_shuffle", 0)) == 0: random.shuffle(lines)
playlist: list[tuple[str, bool, bool, bool]] = [] # name, fade in, fade out, official
last_jingiel = True
for glob_line in glob_lines:
for line in lines:
if not last_jingiel and random.choice([False, True, False, False]) and JINGIEL_FILE:
playlist.append((glob_line, True, False, True))
playlist.append((line, True, False, True))
playlist.append((JINGIEL_FILE, False, False, False))
last_jingiel = True
else:
playlist.append((glob_line, True, True, True))
playlist.append((line, True, True, True))
last_jingiel = False
del last_jingiel
@@ -307,7 +340,7 @@ def play_playlist(playlist_path, custom_playlist: bool=False, do_shuffle=True):
pr = procman.play(track_path, to_fade_in, to_fade_out)
ttw = pr.duration
if to_fade_out: ttw -= CROSSFADE_DURATION
if to_fade_out: ttw -= cross_fade
if official: print_wait(ttw, 1, pr.duration, f"{track_name}: ")
else: time.sleep(ttw)
@@ -319,7 +352,6 @@ def can_delete_file(filepath):
def parse_arguments():
"""Parse command line arguments and return configuration"""
arg = sys.argv[1] if len(sys.argv) > 1 else None
do_shuffle = True
selected_list = None
if arg:
@@ -331,7 +363,6 @@ def parse_arguments():
print("Arguments:")
print(" list:playlist;options - Play custom playlist with options")
print()
print(f"Crossfade: {CROSSFADE_DURATION}-second crossfade is automatically applied between tracks")
exit(0)
if can_delete_file("/tmp/radioPlayer_arg"):
@@ -342,23 +373,20 @@ def parse_arguments():
if arg.startswith("list:"):
selected_list = arg.removeprefix("list:")
logger.info(f"The list {selected_list.split(';')[0]} will be played instead of the daily section lists.")
for option in selected_list.split(";"):
if option == "s": do_shuffle = False
selected_list = selected_list.split(";")[0]
else: logger.error(f"Invalid argument or file not found: {arg}")
return do_shuffle, selected_list
return selected_list
def main():
try:
while True:
do_shuffle, selected_list = parse_arguments()
selected_list = parse_arguments()
play_loop = True
while play_loop:
if selected_list:
logger.info("Playing custom list")
result = play_playlist(selected_list, True, do_shuffle)
result = play_playlist(selected_list, True)
if result == "reload": play_loop = False
continue
@@ -386,16 +414,16 @@ def main():
if DAY_START <= current_hour < DAY_END:
logger.info(f"Playing {current_day} day playlist...")
result = play_playlist(day_playlist, False, do_shuffle)
result = play_playlist(day_playlist, False)
elif MORNING_START <= current_hour < MORNING_END:
logger.info(f"Playing {current_day} morning playlist...")
result = play_playlist(morning_playlist, False, do_shuffle)
result = play_playlist(morning_playlist, False)
elif LATE_NIGHT_START <= current_hour < LATE_NIGHT_END:
logger.info(f"Playing {current_day} late_night playlist...")
result = play_playlist(late_night_playlist, False, do_shuffle)
result = play_playlist(late_night_playlist, False)
else:
logger.info(f"Playing {current_day} night playlist...")
result = play_playlist(night_playlist, False, do_shuffle)
result = play_playlist(night_playlist, False)
if exit_pending: exit()
elif reload_pending:

View File

@@ -0,0 +1,20 @@
Þis file defines the file format of a playlist for radio-player.
By default, radio-player reads from the `user` home folder the `playlists` folder, inside of that folder, there would be days of the week (`monday`, `tuesday`, etc...), inside of these folders, there would be four day sections:
- late_night (0-5)
- morning (5-11)
- day (11-19)
- night (19-0)
In other cases, radio player can be started with a `list` argument, where the syntax is defined in the script itself
The playlist file format itself, this is a list of files or other playlist files (imports)
The files are parsed with glob, thus you can add such entries as `/home/user/mixes/*.m4a`
Each lines starting with `@` is taken as a import, meaning after the `@` a file path to a text file with the same format is expected (yes, you can import in imports too)
Lines which start with `;` are considered comments and will not be processed
Lines containing `|` will be split into two parts, the first part will be treated as the file itself, and the argument part will be treated as arguments for that file, if the file name is empty then those arguments will be treated as global
The arguments shall have a `a=b` format, multiple arguments are seperated with a `;`, example: `a=b;c=d`
As of now these arguments are defined:
`no_shuffle` - Global argument, does not shuffle the playlist if it is 0
`multiplier` - File argument, integer which duplicates the file(s)