Files
pie 067cc51cf2
Build and Push Docker Image / build-and-push (push) Failing after 10m6s
feat: resolve chromecast discovery conflict and add CI/CD workflow
- Update Zeroconf to unicast mode to resolve port 5353 conflict with avahi-daemon
- Make API and Streamlit ports configurable via environment variables (defaults: 8055, 8505)
- Add Gitea Actions workflow for automated Docker builds and registry pushes
- Refactor Chromecast discovery to use modern CastBrowser API
2026-05-03 17:19:26 +01:00

154 lines
4.6 KiB
Python

import streamlit as st
import httpx
import time
from pathlib import Path
from utils import get_logger, get_host_ip, API_PORT
logger = get_logger(__name__)
# Config - Use host IP so the browser can find the API
HOST_IP = get_host_ip()
API_URL = f"http://{HOST_IP}:{API_PORT}"
st.set_page_config(
page_title="Boys Streaming 📺",
page_icon="📺",
layout="wide"
)
# Custom CSS for big buttons and clean look
st.markdown("""
<style>
.stButton>button {
width: 100%;
height: 5em;
font-size: 20px !important;
font-weight: bold !important;
}
.thumb-container {
border: 2px solid #ddd;
border-radius: 10px;
padding: 5px;
text-align: center;
cursor: pointer;
}
.thumb-selected {
border: 5px solid #ff4b4b;
}
</style>
""", unsafe_allow_html=True)
# Session State Initialization
if 'selected_stories' not in st.session_state:
st.session_state.selected_stories = []
if 'library' not in st.session_state:
st.session_state.library = None
def fetch_library():
try:
response = httpx.get(f"{API_URL}/library")
if response.status_code == 200:
st.session_state.library = response.json()
else:
st.error("Failed to fetch library from API.")
except Exception as e:
st.error(f"Could not connect to API: {e}")
def toggle_story(story_path):
if story_path in st.session_state.selected_stories:
st.session_state.selected_stories.remove(story_path)
else:
if len(st.session_state.selected_stories) < 3:
st.session_state.selected_stories.append(story_path)
else:
st.warning("Only 3 stories allowed!")
def start_bedtime():
if len(st.session_state.selected_stories) != 3:
st.error("Please pick exactly 3 stories first!")
return
# Auto-pick one music track
if not st.session_state.library['music']:
st.error("No music tracks found in library (> 2h)!")
return
import random
music_track = random.choice(st.session_state.library['music'])
playlist = st.session_state.selected_stories + [music_track['path']]
try:
response = httpx.post(f"{API_URL}/cast", json={"video_paths": playlist})
if response.status_code == 200:
st.success("Bedtime ritual started! Casting to Chromecast...")
else:
st.error("Failed to start casting.")
except Exception as e:
st.error(f"Error starting bedtime: {e}")
# --- UI Layout ---
st.title("📺 Boys Streaming - Bedtime!")
if st.button("🔄 Refresh Library"):
fetch_library()
if not st.session_state.library:
fetch_library()
if st.session_state.library:
stories = st.session_state.library.get('stories', [])
st.subheader(f"📖 Pick 3 Stories ({len(st.session_state.selected_stories)}/3)")
# Display stories in a grid
cols_per_row = 4
for i in range(0, len(stories), cols_per_row):
cols = st.columns(cols_per_row)
for j, col in enumerate(cols):
if i + j < len(stories):
story = stories[i + j]
is_selected = story['path'] in st.session_state.selected_stories
with col:
# Use a unique key for each button
thumb_url = f"{API_URL}/thumbs/{story['thumbnail']}"
st.image(thumb_url, use_container_width=True)
label = f"{story['filename']}" if is_selected else story['filename']
if st.button(label, key=f"btn_{story['id']}"):
toggle_story(story['path'])
st.rerun()
st.divider()
# Action area
col1, col2 = st.columns(2)
with col1:
if st.button("🚀 START BEDTIME", type="primary", use_container_width=True):
start_bedtime()
with col2:
if st.button("🛑 STOP CASTING", use_container_width=True):
httpx.post(f"{API_URL}/stop")
st.warning("Casting stopped.")
# Status Monitor
st.divider()
status_placeholder = st.empty()
# Auto-refresh status
try:
status_resp = httpx.get(f"{API_URL}/status")
if status_resp.status_code == 200:
status = status_resp.json()
if status.get('is_playing'):
status_placeholder.info(f"Currently Playing item index: {status.get('current_index', 0) + 1} of {status.get('queue_len', 0)}")
else:
status_placeholder.text("Chromecast is idle.")
except:
pass
else:
st.info("Waiting for library scan...")
time.sleep(2)
st.rerun()