init liquidsoap. We thank our uncle Claude, gave him 1.74$ so he can buy a beer. He got drunk on wine and we had to clean the mess ourselves. Sacré oncle Claude !
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,2 @@
|
|||||||
*~
|
*~
|
||||||
state.json
|
*.log
|
||||||
*.xml
|
|
||||||
|
|||||||
20
Dockerfile
20
Dockerfile
@@ -1,38 +1,32 @@
|
|||||||
# docker build . -t radio-bullshit && docker run -it --rm -p 8000:8000 -v `pwd`/jingles:/jingles -v `pwd`/songs:/songs radio-bullshit
|
# docker build . -t radio-bullshit && docker run -it --rm -p 8000:8000 -v `pwd`/jingles:/jingles -v `pwd`/songs:/songs radio-bullshit
|
||||||
FROM python:3.9
|
FROM python:3.14
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
RUN apt-get install -y icecast2 ices2 ffmpeg
|
RUN apt-get install -y ffmpeg liquidsoap
|
||||||
RUN pip install jinja2 j2cli
|
|
||||||
|
|
||||||
WORKDIR /config
|
WORKDIR /config
|
||||||
|
|
||||||
RUN adduser zambla
|
RUN adduser zambla
|
||||||
|
|
||||||
COPY icecast.xml.jinja .
|
COPY radio.liq /opt/radio.liq
|
||||||
COPY ices.xml.jinja .
|
|
||||||
COPY index.html /usr/share/icecast2/web
|
|
||||||
COPY favicon.ico /usr/share/icecast2/web
|
|
||||||
|
|
||||||
COPY next_song.py /opt
|
|
||||||
COPY yt_sync.py /opt
|
COPY yt_sync.py /opt
|
||||||
COPY ultrasync.sh /opt
|
COPY ultrasync.sh /opt
|
||||||
|
COPY je_te_met_en_pls.py /opt
|
||||||
|
|
||||||
RUN curl -fsSL https://deno.land/install.sh | sh
|
RUN curl -fsSL https://deno.land/install.sh | sh
|
||||||
ENV DENO_INSTALL="/$HOME/.deno"
|
ENV DENO_INSTALL="/$HOME/.deno"
|
||||||
ENV PATH="$DENO_INSTALL/bin:$PATH"
|
ENV PATH="$DENO_INSTALL/bin:$PATH"
|
||||||
|
|
||||||
RUN chmod +x /opt/yt_sync.py /opt/next_song.py /opt/ultrasync.sh
|
RUN chmod +x /opt/yt_sync.py /opt/ultrasync.sh /opt/je_te_met_en_pls.py
|
||||||
|
|
||||||
RUN mkdir -p /songs /jingles /air-support /var/log/icecast
|
RUN mkdir -p /songs /jingles /air-support /var/log/liquidsoap
|
||||||
|
|
||||||
RUN chown -R zambla:zambla /config
|
RUN chown -R zambla:zambla /config
|
||||||
RUN chown -R zambla:zambla /opt
|
RUN chown -R zambla:zambla /opt
|
||||||
RUN chown -R zambla:zambla /songs
|
RUN chown -R zambla:zambla /songs
|
||||||
RUN chown -R zambla:zambla /jingles
|
RUN chown -R zambla:zambla /jingles
|
||||||
RUN chown -R zambla:zambla /var/log/icecast
|
RUN chown -R zambla:zambla /var/log/liquidsoap
|
||||||
RUN chown -R zambla:zambla /usr/share/icecast2
|
|
||||||
|
|
||||||
ADD air-support /air-support
|
ADD air-support /air-support
|
||||||
|
|
||||||
|
|||||||
29
README.md
29
README.md
@@ -1,3 +1,30 @@
|
|||||||
# radio-bullshit
|
# radio-bullshit
|
||||||
|
|
||||||
Radio Bullshit
|
Radio Bullshit - Internet radio streaming powered by Liquidsoap
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Automatic song/jingle alternation (1 jingle per 4 songs)
|
||||||
|
- YouTube playlist sync with yt-dlp
|
||||||
|
- Built-in HTTP streaming server (no Icecast needed!)
|
||||||
|
- Fallback "air support" audio for resilience
|
||||||
|
- Modern, open-source stack (Liquidsoap + Python)
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build . -t radio-bullshit && \
|
||||||
|
docker run -it --rm -p 8000:8000 \
|
||||||
|
-v `pwd`/jingles:/jingles \
|
||||||
|
-v `pwd`/songs:/songs \
|
||||||
|
radio-bullshit
|
||||||
|
```
|
||||||
|
|
||||||
|
Then visit http://localhost:8000 to listen!
|
||||||
|
|
||||||
|
## Stack
|
||||||
|
|
||||||
|
- **Liquidsoap** - Audio stream generation and HTTP server
|
||||||
|
- **yt-dlp** - YouTube playlist downloading
|
||||||
|
- **FFmpeg** - Audio format conversion
|
||||||
|
- **Python 3.9** - Automation scripts
|
||||||
|
|||||||
@@ -1,23 +1,17 @@
|
|||||||
#!/bin/bash -e
|
#!/bin/bash -e
|
||||||
|
|
||||||
export hostname=${HOSTNAME:=localhost}
|
export HOSTNAME=${HOSTNAME:=localhost}
|
||||||
export port=${PORT:=8000}
|
export PORT=${PORT:=8000}
|
||||||
export max_listeners=${MAX_LISTENERS:=30}
|
export MAX_LISTENERS=${MAX_LISTENERS:=30}
|
||||||
export admin_user=${ADMIN_USER:=admin}
|
export ADMIN_USER=${ADMIN_USER:=admin}
|
||||||
export admin_password=${ADMIN_PASSWORD:=admin}
|
export ADMIN_PASSWORD=${ADMIN_PASSWORD:=admin}
|
||||||
|
|
||||||
m3u=${M3U:="http://${hostname}:${port}/radio-bullshit"}
|
runuser -l zambla 'touch /songs/playlist.pls /jingles/playlist.pls'
|
||||||
|
|
||||||
pass_gen="python3 -c 'import secrets, string; print(\"\".join((secrets.choice(string.ascii_letters + string.digits) for i in range(20))))'"
|
# Start background sync process
|
||||||
|
|
||||||
export source_username=$(eval $pass_gen)
|
|
||||||
export source_password=$(eval $pass_gen)
|
|
||||||
|
|
||||||
j2 ices.xml.jinja > ices.xml
|
|
||||||
j2 icecast.xml.jinja > icecast.xml
|
|
||||||
|
|
||||||
echo ${m3u} > /usr/share/icecast2/web/radio-bullshit.m3u
|
|
||||||
|
|
||||||
runuser -l zambla -c 'icecast2 -c /config/icecast.xml &'
|
|
||||||
/opt/ultrasync.sh &
|
/opt/ultrasync.sh &
|
||||||
runuser -l zambla -c 'ices2 /config/ices.xml'
|
|
||||||
|
# fallback
|
||||||
|
runuser -l zambla '/opt/je_te_met_en_pls.py /air-support /air-support/playlist.pls'
|
||||||
|
# Run Liquidsoap as zambla user
|
||||||
|
runuser -l zambla -c 'liquidsoap /opt/radio.liq'
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
<icecast>
|
|
||||||
<location>Earth</location>
|
|
||||||
<admin>icemaster@{{ hostname }}</admin>
|
|
||||||
|
|
||||||
<limits>
|
|
||||||
<clients>100</clients>
|
|
||||||
<sources>2</sources>
|
|
||||||
<queue-size>524288</queue-size>
|
|
||||||
<client-timeout>30</client-timeout>
|
|
||||||
<header-timeout>15</header-timeout>
|
|
||||||
<source-timeout>10</source-timeout>
|
|
||||||
<burst-on-connect>1</burst-on-connect>
|
|
||||||
<burst-size>65535</burst-size>
|
|
||||||
</limits>
|
|
||||||
|
|
||||||
<authentication>
|
|
||||||
<!-- Admin logs in with the username given below -->
|
|
||||||
<admin-user>{{ admin_user }}</admin-user>
|
|
||||||
<admin-password>{{ admin_password }}</admin-password>
|
|
||||||
</authentication>
|
|
||||||
|
|
||||||
<hostname>{{ hostname }}</hostname>
|
|
||||||
|
|
||||||
<http-headers>
|
|
||||||
<header name="Access-Control-Allow-Origin" value="*" />
|
|
||||||
<header name="Access-Control-Allow-Headers" value="*" />
|
|
||||||
<header name="Access-Control-Allow-Methods" value="POST, GET, OPTIONS" />
|
|
||||||
<header name="X-Robots-Tag" value="index, noarchive" />
|
|
||||||
</http-headers>
|
|
||||||
|
|
||||||
<listen-socket>
|
|
||||||
<port>{{ port }}</port>
|
|
||||||
</listen-socket>
|
|
||||||
|
|
||||||
<mount type="normal">
|
|
||||||
<mount-name>/radio-bullshit</mount-name>
|
|
||||||
|
|
||||||
<username>{{ source_username }}</username>
|
|
||||||
<password>{{ source_password }}</password>
|
|
||||||
|
|
||||||
<max-listeners>{{ max_listeners }}</max-listeners>
|
|
||||||
<burst-size>65536</burst-size>
|
|
||||||
<hidden>1</hidden>
|
|
||||||
<public>1</public>
|
|
||||||
</mount>
|
|
||||||
|
|
||||||
<fileserve>1</fileserve>
|
|
||||||
|
|
||||||
<paths>
|
|
||||||
<basedir>/usr/share/icecast2</basedir>
|
|
||||||
|
|
||||||
<logdir>/var/log/icecast</logdir>
|
|
||||||
<webroot>/usr/share/icecast2/web</webroot>
|
|
||||||
<adminroot>/usr/share/icecast2/admin</adminroot>
|
|
||||||
<alias source="/" destination="/index.html"/>
|
|
||||||
</paths>
|
|
||||||
|
|
||||||
<logging>
|
|
||||||
<accesslog>access.log</accesslog>
|
|
||||||
<errorlog>error.log</errorlog>
|
|
||||||
<loglevel>3</loglevel> <!-- 4 Debug, 3 Info, 2 Warn, 1 Error -->
|
|
||||||
<logsize>10000</logsize> <!-- Max size of a logfile -->
|
|
||||||
</logging>
|
|
||||||
|
|
||||||
<security>
|
|
||||||
<chroot>0</chroot>
|
|
||||||
</security>
|
|
||||||
</icecast>
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
<!-- <?xml version="1.0"?> -->
|
|
||||||
<ices>
|
|
||||||
<stream>
|
|
||||||
<metadata>
|
|
||||||
<name>Radio Bullshit</name>
|
|
||||||
<genre>Bullshit</genre>
|
|
||||||
<description>J'ai une superbe opportunité de travail</description>
|
|
||||||
<url>{{ hostname }}</url>
|
|
||||||
</metadata>
|
|
||||||
<input>
|
|
||||||
<param name="type">script</param>
|
|
||||||
<param name="program">/opt/next_song.py</param>
|
|
||||||
</input>
|
|
||||||
<instance>
|
|
||||||
<username>{{ source_username }}</username>
|
|
||||||
<password>{{ source_password }}</password>
|
|
||||||
<mount>/radio-bullshit</mount>
|
|
||||||
<port>{{ port }}</port>
|
|
||||||
</instance>
|
|
||||||
</stream>
|
|
||||||
</ices>
|
|
||||||
22
index.html
22
index.html
@@ -1,22 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Radio bullshit, la radio du paradis !</title>
|
|
||||||
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
|
|
||||||
<link rel="icon" href="/favicon.ico" type="image/x-icon">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<audio controls autoplay preload="none">
|
|
||||||
<source src="/radio-bullshit?type=.ogg/;" type="application/ogg">
|
|
||||||
</audio>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<ul>
|
|
||||||
<li><a href="/radio-bullshit">VLC Stream</a></li>
|
|
||||||
<li><a href="/radio-bullshit.m3u">M3U file</a></li>
|
|
||||||
</ul>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
70
je_te_met_en_pls.py
Executable file
70
je_te_met_en_pls.py
Executable file
@@ -0,0 +1,70 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import glob, os, sys
|
||||||
|
|
||||||
|
|
||||||
|
def generate_pls(directory: str, output_file: str = None) -> None:
|
||||||
|
"""Generate a .pls playlist file from all audio files in a directory.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
directory: Path to directory containing audio files
|
||||||
|
output_file: Path to output .pls file (default: directory/playlist.pls)
|
||||||
|
"""
|
||||||
|
if not os.path.isdir(directory):
|
||||||
|
print(f"Error: Directory '{directory}' does not exist")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Audio file extensions to search for
|
||||||
|
audio_extensions = ["*.mp3", "*.ogg", "*.flac", "*.wav", "*.m4a", "*.aac", "*.opus"]
|
||||||
|
|
||||||
|
# Collect all audio files
|
||||||
|
audio_files = []
|
||||||
|
for ext in audio_extensions:
|
||||||
|
pattern = os.path.join(directory, ext)
|
||||||
|
audio_files.extend(glob.glob(pattern))
|
||||||
|
|
||||||
|
if not audio_files:
|
||||||
|
print(f"Warning: No audio files found in '{directory}'")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Sort files alphabetically
|
||||||
|
audio_files.sort()
|
||||||
|
|
||||||
|
# Determine output file path
|
||||||
|
if output_file is None:
|
||||||
|
output_file = os.path.join(directory, "playlist.pls")
|
||||||
|
|
||||||
|
# Generate .pls content
|
||||||
|
pls_content = ["[playlist]"]
|
||||||
|
pls_content.append(f"NumberOfEntries={len(audio_files)}")
|
||||||
|
pls_content.append("")
|
||||||
|
|
||||||
|
for idx, file_path in enumerate(audio_files, start=1):
|
||||||
|
# Get absolute path
|
||||||
|
abs_path = os.path.abspath(file_path)
|
||||||
|
filename = os.path.basename(file_path)
|
||||||
|
|
||||||
|
pls_content.append(f"File{idx}={abs_path}")
|
||||||
|
pls_content.append(f"Title{idx}={filename}")
|
||||||
|
pls_content.append(f"Length{idx}=-1")
|
||||||
|
pls_content.append("")
|
||||||
|
|
||||||
|
pls_content.append("Version=2")
|
||||||
|
|
||||||
|
# Write to file
|
||||||
|
with open(output_file, "w") as f:
|
||||||
|
f.write("\n".join(pls_content))
|
||||||
|
|
||||||
|
print(f"Generated '{output_file}' with {len(audio_files)} entries")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("Usage: je_te_met_en_pls.py <directory> [output_file.pls]")
|
||||||
|
print("Example: je_te_met_en_pls.py /songs")
|
||||||
|
print("Example: je_te_met_en_pls.py /songs /tmp/my_playlist.pls")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
directory = sys.argv[1]
|
||||||
|
output_file = sys.argv[2] if len(sys.argv) > 2 else None
|
||||||
|
|
||||||
|
generate_pls(directory, output_file)
|
||||||
55
next_song.py
55
next_song.py
@@ -1,55 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
from json.decoder import JSONDecodeError
|
|
||||||
import random, glob, os, json
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
|
|
||||||
songs_folder = "/songs"
|
|
||||||
jingles_folder = "/jingles"
|
|
||||||
air_support_folder = "/air-support"
|
|
||||||
|
|
||||||
def get_air_support():
|
|
||||||
return random.choice(glob.glob(f"{air_support_folder}/*.ogg"))
|
|
||||||
|
|
||||||
def create_state_from_scratch() -> dict:
|
|
||||||
state = {"current_song_type": "jingle"}
|
|
||||||
with open(f"{songs_folder}/state.json", "w") as f:
|
|
||||||
json.dump(state, f, indent=4)
|
|
||||||
return state
|
|
||||||
|
|
||||||
# state loading
|
|
||||||
if not os.path.isfile(f"{songs_folder}/state.json"):
|
|
||||||
# create state file if it doesnt exist
|
|
||||||
with open(f"{songs_folder}/state.json", "w") as f:
|
|
||||||
state = create_state_from_scratch()
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
with open(f"{songs_folder}/state.json") as f:
|
|
||||||
state = json.load(f)
|
|
||||||
except JSONDecodeError:
|
|
||||||
# if file doesnt exist, we recreate a state on the disk
|
|
||||||
state = create_state_from_scratch()
|
|
||||||
|
|
||||||
# song choice
|
|
||||||
if state["current_song_type"] == "song":
|
|
||||||
state["current_song_type"] = "jingle"
|
|
||||||
try:
|
|
||||||
print(random.choice(glob.glob(f"{jingles_folder}/*.ogg")))
|
|
||||||
except IndexError:
|
|
||||||
print(get_air_support())
|
|
||||||
elif state["current_song_type"] == "jingle":
|
|
||||||
state["current_song_type"] = "song"
|
|
||||||
try:
|
|
||||||
print(random.choice(glob.glob(f"{songs_folder}/*.ogg")))
|
|
||||||
except IndexError:
|
|
||||||
print(get_air_support())
|
|
||||||
else:
|
|
||||||
# should not happen
|
|
||||||
# resiliency mode
|
|
||||||
print(get_air_support())
|
|
||||||
state = {"current_song_type": "jingle"}
|
|
||||||
|
|
||||||
# state saving
|
|
||||||
with open(f"{songs_folder}/state.json", "w") as f:
|
|
||||||
json.dump(state, f, indent=4)
|
|
||||||
137
radio.liq
Normal file
137
radio.liq
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
#!/usr/bin/liquidsoap
|
||||||
|
|
||||||
|
# Radio Bullshit - Liquidsoap Configuration
|
||||||
|
# Modern open-source streaming with automatic song/jingle alternation
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# CONFIGURATION
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Get environment variables with defaults
|
||||||
|
hostname = environment.get(default="localhost", "HOSTNAME")
|
||||||
|
port = int_of_string(environment.get(default="8000", "PORT"))
|
||||||
|
|
||||||
|
# Log configuration
|
||||||
|
log.file.path := "/var/log/liquidsoap/radio-bullshit.log"
|
||||||
|
log.level := 3
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# SOURCES
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Songs playlist - reload every 60 seconds to pick up new downloads
|
||||||
|
songs = playlist(
|
||||||
|
mode="randomize",
|
||||||
|
reload=60,
|
||||||
|
reload_mode="watch",
|
||||||
|
"/songs/playlist.pls"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Jingles playlist
|
||||||
|
jingles = playlist(
|
||||||
|
mode="randomize",
|
||||||
|
reload=60,
|
||||||
|
reload_mode="watch",
|
||||||
|
"/jingles/playlist.pls"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Air support (fallback audio when nothing else is available)
|
||||||
|
# air_support = playlist(
|
||||||
|
# mode="randomize",
|
||||||
|
# reload_mode="watch",
|
||||||
|
# "/air-support/playlist.pls"
|
||||||
|
# )
|
||||||
|
air_support = single("/air-support/Airplane_Sound_Effect.ogg")
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# ALTERNATING LOGIC
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Alternate between songs and jingles: play 1 jingle, then 4 songs
|
||||||
|
# This creates a pattern: jingle, song, song, song, song, jingle, ...
|
||||||
|
radio = rotate(
|
||||||
|
weights=[1, 1],
|
||||||
|
[jingles, songs]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add fallback to air support in case of errors
|
||||||
|
radio = fallback(
|
||||||
|
track_sensitive=false,
|
||||||
|
[radio, air_support]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Normalize audio levels for consistent volume
|
||||||
|
# radio = normalize(radio)
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# OUTPUTS
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Built-in HTTP server (replaces Icecast)
|
||||||
|
# Serves the stream directly via Liquidsoap's Harbor server
|
||||||
|
output.harbor(
|
||||||
|
%mp3(bitrate=128, samplerate=44100),
|
||||||
|
port=port,
|
||||||
|
mount="/radio-bullshit",
|
||||||
|
url="http://#{hostname}:#{port}",
|
||||||
|
radio
|
||||||
|
)
|
||||||
|
|
||||||
|
# Enable built-in HTTP server for stream and status
|
||||||
|
settings.harbor.bind_addrs := ["0.0.0.0"]
|
||||||
|
|
||||||
|
# Add a simple HTML page
|
||||||
|
harbor.http.register(
|
||||||
|
port=port,
|
||||||
|
method="GET",
|
||||||
|
"/",
|
||||||
|
fun (_, response) -> begin
|
||||||
|
html = "<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Radio Bullshit</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial, sans-serif; text-align: center; padding: 50px; background: #1a1a1a; color: #fff; }
|
||||||
|
h1 { color: #ff6b6b; }
|
||||||
|
audio { margin: 20px; }
|
||||||
|
a { color: #4ecdc4; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Radio Bullshit</h1>
|
||||||
|
<p>J'ai une superbe opportunité de travail</p>
|
||||||
|
<audio controls autoplay>
|
||||||
|
<source src=\"/radio-bullshit\" type=\"audio/mpeg\">
|
||||||
|
Your browser does not support the audio element.
|
||||||
|
</audio>
|
||||||
|
<p><a href=\"/radio-bullshit.m3u\">Download M3U Playlist</a></p>
|
||||||
|
</body>
|
||||||
|
</html>"
|
||||||
|
response.html(html)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
# Serve M3U playlist file
|
||||||
|
harbor.http.register(
|
||||||
|
port=port,
|
||||||
|
method="GET",
|
||||||
|
"/radio-bullshit.m3u",
|
||||||
|
fun (_, response) -> begin
|
||||||
|
m3u = "http://#{hostname}:#{port}/radio-bullshit"
|
||||||
|
response.content_type("audio/x-mpegurl")
|
||||||
|
response.data(m3u)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
# Status endpoint (JSON)
|
||||||
|
# harbor.http.register(
|
||||||
|
# port=port,
|
||||||
|
# method="GET",
|
||||||
|
# "/status.json",
|
||||||
|
# fun (_, response) -> begin
|
||||||
|
# status = '{"status":"online","stream":"Radio Bullshit","mount":"/radio-bullshit"}'
|
||||||
|
# response.json(parse_json(status))
|
||||||
|
# end
|
||||||
|
# )
|
||||||
|
|
||||||
|
log("Radio Bullshit is now streaming on http://#{hostname}:#{port}/radio-bullshit")
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
pip3 install -U yt-dlp
|
pip3 install -U yt-dlp
|
||||||
|
runuser -l zambla -c '/opt/je_te_met_en_pls.py /songs /songs/playlist.pls' || true
|
||||||
|
runuser -l zambla -c '/opt/je_te_met_en_pls.py /jingles /jingles/playlist.pls' || true
|
||||||
runuser -l zambla -c '/opt/yt_sync.py' || true
|
runuser -l zambla -c '/opt/yt_sync.py' || true
|
||||||
sleep 6h
|
sleep 6h
|
||||||
done
|
done
|
||||||
|
|||||||
Reference in New Issue
Block a user