Skip to content

Instantly share code, notes, and snippets.

@ohaiibuzzle
Last active December 3, 2022 06:07
Show Gist options
  • Save ohaiibuzzle/19e382cb668263048719d29c99a649e4 to your computer and use it in GitHub Desktop.
Save ohaiibuzzle/19e382cb668263048719d29c99a649e4 to your computer and use it in GitHub Desktop.
"""
A simple and silly HTTP server that plays audio from internet sources
"""
from http import server
import urllib
import subprocess
ffplay = None
player = None
url = None
class Handler(server.BaseHTTPRequestHandler):
CSS_CODE = """
<style>
body {
background-color: #000000;
color: #ffffff;
font-family: Arial, Helvetica, sans-serif;
display:flex; flex-direction:column; justify-content:center;
min-height:100vh;
align-items:center;
text-align:center;
}
input {
padding: 12px 20px;
margin: 8px 0;
box-sizing: border-box;
border: 2px solid #000000;
border-radius: 4px;
background-color: #ffffff;
color: #000000;
text-align: center;
width: 100%;
height: 100%;
}
input[type=submit] {
background-color: #000000;
color: #ffffff;
border: 2px solid #000000;
border-radius: 4px;
padding: 12px 20px;
cursor: pointer;
width: 100%;
}
input[type=submit]:hover {
background-color: #ffffff;
color: #000000;
border: 2px solid #000000;
border-radius: 4px;
padding: 12px 20px;
cursor: pointer;
width: 100%;
}
.container {
width: 80%;
margin: auto;
max-width: 800px;
}
#playback-form {
display: flex;
flex-direction: row;
justify-content: center;
}
.control-button {
display: flex;
flex-direction: row;
justify-content: center;
width: 80%;
margin: auto;
max-width: 800px;
}
</style>
"""
HOME_PAGE = """
<html>
<head>
<title>Wireless Radio</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{css}
</head>
<body>
<div class="container">
<h1 style="text-align: center; width: 100%;">Wireless Radio</h1>
<div class="container">
<p style="text-align: center;">Enter a YouTube URL to play</p>
<form method="POST" action="/play" autocomplete="off" id="playback-form">
<input type=url name="url" placeholder="https://www.youtube.com/watch?v=..." style="width: 70%; margin: auto;" required>
<input type=text name="time" style="width: 30%; margin: auto;" value="00:00:00" autocomplete="off" pattern=[0-9]{{2,}}:[0-9]{{2}}:[0-9]{{2}}>
</form>
</div>
<div class="control-button">
<input type="submit" value="Play" form="playback-form" style="width: 50%">
<form method="POST" action="/stop" style="width: 50%; height: 100%"><input type=submit value="Stop"></form>
</div>
<p style="text-align: center;">Currently playing:</p>
{url}
</div>
</body>
</html>
"""
def do_GET(self):
global url
global ffplay
now_playing = (
"Nothing" if (ffplay is None or ffplay.poll() is not None) else url
)
if now_playing.startswith(
"https://www.youtube.com/watch?v="
) or now_playing.startswith("https://youtu.be/"):
now_playing = now_playing.replace(
"https://www.youtube.com/watch?v=", "https://www.youtube.com/embed/"
)
now_playing = now_playing.replace(
"https://youtu.be/", "https://www.youtube.com/embed/"
)
now_playing = now_playing + "?autoplay=1"
now_playing = f"""
<iframe src="{now_playing}" frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen style="display:block;margin: 0 auto; width: 100%; max-width: 800px; height: 450px;"></iframe>
</iframe>
"""
else:
now_playing = f"""<b style="text-align: center;">{now_playing}</b>"""
# Construct a simple HTTP resp with a text box and submit as a form
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(
self.HOME_PAGE.format(css=self.CSS_CODE, url=now_playing).encode("utf-8")
)
def do_POST(self):
routing_table: dict = {
"/play": self.play,
"/stop": self.stop,
}
if self.path in routing_table:
routing_table[self.path]()
else:
self.send_response(302)
self.send_header("Location", "/")
def play(self):
global ffplay
global player
global url
# Check if the radio is playing
if ffplay is not None:
print("Killing ffplay")
ffplay.terminate()
ffplay = None
if player is not None:
print("Killing player")
player.terminate()
player = None
# Read the form data
length = int(self.headers["Content-Length"])
post_data = self.rfile.read(length).decode("utf-8")
# Parse the form data
post_data = urllib.parse.parse_qs(post_data)
# Get the URL
url = post_data["url"][0]
# Get the time offset
time = post_data["time"][0]
print(f"Playing {url} at {time}")
# Start the player
# yt-dlp -f bestaudio <url> -o - | ffplay -nodisp -autoexit -ss <time> -
player = subprocess.Popen(
["yt-dlp", "-f", "bestaudio", url, "-o", "-"],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
)
ffplay = subprocess.Popen(
["ffplay", "-hide_banner", "-nodisp", "-autoexit", "-ss", time, "-"],
stdin=player.stdout,
)
# Redirect to the home page
self.send_response(302)
self.send_header("Location", "/")
self.end_headers()
def stop(self):
global player
global ffplay
# Check if the radio is playing
if ffplay is not None:
print("Killing ffplay")
ffplay.terminate()
ffplay = None
if player is not None:
print("Killing player")
# If it is, kill the process
player.terminate()
player = None
self.send_response(302)
self.send_header("Location", "/")
self.end_headers()
if __name__ == "__main__":
server_address = ("", 8000)
httpd = server.HTTPServer(server_address, Handler)
print("Starting server at http://localhost:8000")
httpd.serve_forever()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment