067cc51cf2
Build and Push Docker Image / build-and-push (push) Failing after 10m6s
- 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
154 lines
4.6 KiB
Python
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()
|