Compare commits

...

6 Commits

3 changed files with 70 additions and 11 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ FROM python:3.11-slim
WORKDIR /app
RUN apt-get update && apt-get install -y tzdata && \
RUN apt-get update && apt-get install -y tzdata ffmpeg && \
ln -snf /usr/share/zoneinfo/Europe/Warsaw /etc/localtime && echo Europe/Warsaw > /etc/timezone && \
apt-get clean
+56 -7
View File
@@ -612,15 +612,42 @@ def podcast_rss(station: str, slug_with_ext: str, request: Request, db: Session
return Response(status_code=404)
return Response(content=xml, media_type="application/xml")
def iter_stream(target_url):
import requests
with requests.get(target_url, stream=True, timeout=10) as r:
def iter_stream_transcode(target_url):
import subprocess
cmd = [
"ffmpeg",
"-reconnect", "1",
"-reconnect_streamed", "1",
"-reconnect_delay_max", "5",
"-loglevel", "error",
"-i", target_url,
"-c:a", "libmp3lame",
"-b:a", "320k",
"-f", "mp3",
"-"
]
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
try:
while True:
chunk = process.stdout.read(8192)
if not chunk:
break
yield chunk
finally:
process.kill()
def iter_stream(r):
try:
for chunk in r.iter_content(chunk_size=8192):
if chunk:
yield chunk
finally:
r.close()
from fastapi import Request
@app.get("/live/{station}")
def live_stream_redirect(station: str, db: Session = Depends(db_session)):
def live_stream_redirect(station: str, request: Request, db: Session = Depends(db_session)):
# Usuwamy sztuczne rozszerzenia dodane dla Mopidy
if station.endswith(".aac"):
station = station[:-4]
@@ -634,13 +661,35 @@ def live_stream_redirect(station: str, db: Session = Depends(db_session)):
if station == "radio357":
config_url = db.query(Config).filter_by(key="r357_live_url").first()
target_url = config_url.value if config_url else "https://stream.rcs.revma.com/ye5kghkgcm0uv"
# Proxy strumienia! Zwracamy jako audio/mpeg żeby ominąć unwrapper
return StreamingResponse(iter_stream(target_url), media_type="audio/mpeg")
# Statyczne metadane nagłówkowe dla Mopidy (ponieważ FFmpeg gubi te dynamiczne wbudowane w strumień)
resp_headers = {
"icy-name": "Radio 357",
"icy-description": "RadioSync Proxy (AAC to MP3 Transcode)"
}
# Proxy + Transkodowanie AAC -> MP3 w locie!
return StreamingResponse(iter_stream_transcode(target_url), media_type="audio/mpeg", headers=resp_headers)
elif station == "rns":
config_url = db.query(Config).filter_by(key="rns_live_url").first()
target_url = config_url.value if config_url else "https://stream.rcs.revma.com/4md4m0a0fs8uv"
return StreamingResponse(iter_stream(target_url), media_type="audio/mpeg")
# Przekazujemy żądanie o metadane z odtwarzacza (Mopidy) do serwera (Revma)
req_headers = {}
if "icy-metadata" in request.headers:
req_headers["Icy-MetaData"] = request.headers["icy-metadata"]
import requests
r = requests.get(target_url, stream=True, timeout=10, headers=req_headers)
# Odbieramy nagłówki metadanych od Revmy i przekazujemy z powrotem do odtwarzacza
resp_headers = {}
for k, v in r.headers.items():
if k.lower().startswith("icy-"):
resp_headers[k] = v
return StreamingResponse(iter_stream(r), media_type="audio/mpeg", headers=resp_headers)
else:
raise HTTPException(status_code=404, detail="Brak streamu dla podanej stacji")
+13 -3
View File
@@ -93,9 +93,9 @@
<li class="nav-item"><a class="nav-link" href="/programs/jazz">Jazz FM</a></li>
</ul>
<div class="d-flex gap-2">
<a href="/live/radio357.m3u" class="btn btn-outline-info btn-sm" target="_blank" title="Bezpośredni stream premium Radia 357 (wyższa jakość, brak reklam)">Stream 357</a>
<a href="/live/rns.m3u" class="btn btn-outline-info btn-sm" target="_blank" title="Bezpośredni stream premium RNŚ (wyższa jakość, brak reklam)">Stream RNŚ</a>
<a href="/feeds/Podcasts.opml" class="btn btn-outline-warning btn-sm" target="_blank">Master OPML</a>
<button onclick="copyToClipboard('/live/radio357', this)" class="btn btn-outline-info btn-sm" title="Kopiuj bezpośredni stream premium Radia 357">Stream 357</button>
<button onclick="copyToClipboard('/live/rns', this)" class="btn btn-outline-info btn-sm" title="Kopiuj bezpośredni stream premium RNŚ">Stream RNŚ</button>
<button onclick="copyToClipboard('/feeds/Podcasts.opml', this)" class="btn btn-outline-warning btn-sm">Master OPML</button>
</div>
</div>
</div>
@@ -104,5 +104,15 @@
{% block content %}{% endblock %}
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
function copyToClipboard(path, btn) {
const fullUrl = window.location.origin + path;
navigator.clipboard.writeText(fullUrl).then(() => {
const originalText = btn.innerText;
btn.innerText = 'Skopiowano!';
setTimeout(() => { btn.innerText = originalText; }, 2000);
});
}
</script>
</body>
</html>