kodyondemand / app.py
ServerX's picture
Update app.py
08414ba verified
import gradio as gr
import requests, zipfile, os, shutil, json, re
# -----------------------
# Free API keys (TMDb e OMDb) ottenute automaticamente
def get_free_keys():
"""
Simula il comportamento del pacchetto 'freekeys'
Restituisce le free API keys per TMDb e OMDb.
"""
return {
"tmdb_key": "e547e17d4e91f3e62a571655cd1ccaff",
"imdb_key": "966c4f4f"
}
free_keys = get_free_keys()
TMDB_API_KEY = free_keys["tmdb_key"]
# -----------------------
# FILE DI CONFIGURAZIONE E VIDEOTECA
SETTINGS_FILE = "settings.json"
LIBRARY_FILE = "videoteca.json"
DEFAULT_SETTINGS = {
"videolibrarypath": "path/to/videolibrary",
"folder_tvshows": "Serie TV",
"folder_movies": "Film",
"videolibrary_kodi": False
}
def load_settings():
"""Carica le impostazioni dal file JSON oppure restituisce quelle di default."""
if os.path.exists(SETTINGS_FILE):
try:
with open(SETTINGS_FILE, "r", encoding="utf-8") as f:
return json.load(f)
except Exception:
return DEFAULT_SETTINGS
else:
return DEFAULT_SETTINGS
def save_settings(new_settings):
"""Salva le impostazioni su file JSON."""
try:
with open(SETTINGS_FILE, "w", encoding="utf-8") as f:
json.dump(new_settings, f, indent=4, ensure_ascii=False)
return "Impostazioni salvate correttamente."
except Exception as e:
return f"Errore nel salvataggio delle impostazioni: {str(e)}"
def get_current_settings():
"""Restituisce le impostazioni correnti in formato JSON formattato."""
settings = load_settings()
return json.dumps(settings, indent=4, ensure_ascii=False)
def update_settings(videolibrarypath, folder_tvshows, folder_movies, videolibrary_kodi):
"""Aggiorna le impostazioni a partire dai valori forniti."""
new_settings = {
"videolibrarypath": videolibrarypath,
"folder_tvshows": folder_tvshows,
"folder_movies": folder_movies,
"videolibrary_kodi": videolibrary_kodi
}
return save_settings(new_settings)
def load_library():
"""Carica la videoteca dal file JSON."""
if os.path.exists(LIBRARY_FILE):
try:
with open(LIBRARY_FILE, "r", encoding="utf-8") as f:
return json.load(f)
except Exception:
return []
else:
return []
def save_library(lib):
"""Salva la videoteca nel file JSON."""
with open(LIBRARY_FILE, "w", encoding="utf-8") as f:
json.dump(lib, f, indent=4, ensure_ascii=False)
def add_to_library(item_str: str, item_type: str) -> str:
"""
Aggiunge un elemento (film o serie TV) alla videoteca.
L'elemento è una stringa formattata con [ID: ...].
"""
lib = load_library()
match = re.search(r'\[ID: (\d+)\]', item_str)
if not match:
return "Impossibile estrarre l'ID dall'elemento."
item_id = match.group(1)
# Controlla duplicati
for item in lib:
if item["id"] == item_id and item["type"] == item_type:
return "Elemento già presente in videoteca."
new_item = {"id": item_id, "title": item_str, "type": item_type}
lib.append(new_item)
save_library(lib)
return "Elemento aggiunto in videoteca."
def get_library_items(item_type: str) -> list:
"""Restituisce una lista di elementi della videoteca filtrata per tipo."""
lib = load_library()
return [item["title"] for item in lib if item["type"] == item_type]
# -----------------------
# FUNZIONALITÀ DI AGGIORNAMENTO DELL'APPLICAZIONE
def get_branches():
"""
Recupera la lista dei branch disponibili dal repository GitHub.
Ordina dando priorità a 'stable' e 'master'.
"""
url = "https://api.github.com/repos/stream4me/addon/branches"
try:
response = requests.get(url)
response.raise_for_status()
branches = [branch["name"] for branch in response.json()]
ordered = []
for b in ['stable', 'master']:
if b in branches:
ordered.append(b)
ordered.extend([b for b in branches if b not in ['stable', 'master']])
return ordered
except Exception:
return ["stable", "master"]
def update_from_zip(branch: str) -> str:
"""
Scarica il file zip del branch selezionato da GitHub,
lo estrae in una cartella target ("s4me_app") e restituisce un log.
"""
log = []
try:
url = f"https://github.com/stream4me/addon/archive/{branch}.zip"
log.append(f"Avvio download da: {url}")
response = requests.get(url, stream=True)
response.raise_for_status()
zip_filename = f"{branch}.zip"
with open(zip_filename, "wb") as f:
for chunk in response.iter_content(chunk_size=8192):
if chunk:
f.write(chunk)
log.append("Download completato.")
extract_base = f"s4me_app_{branch}"
target_dir = "s4me_app"
if os.path.isdir(target_dir):
shutil.rmtree(target_dir)
log.append(f"Vecchia cartella '{target_dir}' rimossa.")
log.append("Inizio estrazione...")
with zipfile.ZipFile(zip_filename, "r") as zip_ref:
zip_ref.extractall(extract_base)
log.append("Estrazione completata.")
extracted_folder = os.path.join(extract_base, f"addon-{branch}")
if os.path.isdir(extracted_folder):
os.rename(extracted_folder, target_dir)
log.append(f"Cartella rinominata in '{target_dir}'.")
else:
log.append("Errore: cartella estratta non trovata.")
os.remove(zip_filename)
shutil.rmtree(extract_base)
log.append("Pulizia completata. Aggiornamento eseguito con successo.")
except Exception as e:
log.append(f"Errore durante l'aggiornamento: {str(e)}")
return "\n".join(log)
def perform_update(selected_branch: str) -> str:
"""Funzione chiamata dal pulsante di aggiornamento nell’interfaccia."""
return update_from_zip(selected_branch)
# -----------------------
# FUNZIONALITÀ DI RICERCA E STREAMING PER FILM
def search_movie_list(movie_title: str):
"""
Cerca film tramite TMDb e restituisce una lista per il dropdown.
Formato: "Titolo (Data) [ID: movie_id]".
"""
if not movie_title:
return []
url = f"https://api.themoviedb.org/3/search/movie?api_key={TMDB_API_KEY}&query={movie_title}"
try:
response = requests.get(url)
response.raise_for_status()
data = response.json()
results = data.get("results", [])
choices = []
for movie in results:
title = movie.get("title", "Sconosciuto")
release_date = movie.get("release_date", "Data sconosciuta")
movie_id = movie.get("id")
choices.append(f"{title} ({release_date}) [ID: {movie_id}]")
return choices
except Exception as e:
return [f"Errore durante la ricerca: {str(e)}"]
def get_streaming_providers(movie_choice: str) -> str:
"""
Data la scelta del film (contenente l'ID),
richiama l'endpoint TMDb per ottenere i provider streaming.
"""
match = re.search(r'\[ID: (\d+)\]', movie_choice)
if not match:
return "Impossibile estrarre l'ID del film."
movie_id = match.group(1)
url = f"https://api.themoviedb.org/3/movie/{movie_id}/watch/providers?api_key={TMDB_API_KEY}"
try:
response = requests.get(url)
response.raise_for_status()
data = response.json()
results = data.get("results", {})
if "IT" in results:
providers = results["IT"].get("flatrate", [])
elif "US" in results:
providers = results["US"].get("flatrate", [])
else:
providers = []
if not providers:
return "Nessun canale streaming diretto trovato."
output = [provider.get("provider_name", "Sconosciuto") for provider in providers]
return "Canali streaming trovati:\n" + "\n".join(output)
except Exception as e:
return f"Errore durante la ricerca dei canali: {str(e)}"
# -----------------------
# FUNZIONALITÀ DI RICERCA PER SERIE TV
def search_tv_list(tv_title: str):
"""
Cerca serie TV tramite TMDb e restituisce una lista per il dropdown.
Formato: "Nome Serie (Data) [ID: tv_id]".
"""
if not tv_title:
return []
url = f"https://api.themoviedb.org/3/search/tv?api_key={TMDB_API_KEY}&query={tv_title}"
try:
response = requests.get(url)
response.raise_for_status()
data = response.json()
results = data.get("results", [])
choices = []
for tv in results:
name = tv.get("name", "Sconosciuto")
first_air_date = tv.get("first_air_date", "Data sconosciuta")
tv_id = tv.get("id")
choices.append(f"{name} ({first_air_date}) [ID: {tv_id}]")
return choices
except Exception as e:
return [f"Errore durante la ricerca: {str(e)}"]
def load_tv_episodes(tv_series_choice: str) -> str:
"""
Data la serie TV selezionata (con l'ID incluso), carica gli episodi della stagione 1.
Restituisce una lista formattata degli episodi.
"""
match = re.search(r'\[ID: (\d+)\]', tv_series_choice)
if not match:
return "Impossibile estrarre l'ID della serie TV."
tv_id = match.group(1)
url = f"https://api.themoviedb.org/3/tv/{tv_id}/season/1?api_key={TMDB_API_KEY}"
try:
response = requests.get(url)
response.raise_for_status()
data = response.json()
episodes = data.get("episodes", [])
if not episodes:
return "Nessun episodio trovato per la stagione 1."
output = []
for ep in episodes:
ep_num = ep.get("episode_number")
name = ep.get("name", "Sconosciuto")
overview = ep.get("overview", "")
output.append(f"Episodio {ep_num}: {name}\n{overview}\n")
return "\n".join(output)
except Exception as e:
return f"Errore durante il caricamento degli episodi: {str(e)}"
# -----------------------
# COSTRUZIONE DELL'INTERFACCIA CON GRADIO
def build_interface():
"""Costruisce l’interfaccia completa con più tab."""
branches = get_branches()
css_custom = """
body { font-size: 32px; }
.gradio-container { font-size: 32px; }
.gr-button { font-size: 32px; padding: 20px; }
.gr-input { font-size: 32px; padding: 10px; }
.gr-dropdown { font-size: 32px; padding: 10px; }
.gradio-container * { margin: 20px; }
"""
js_script = """
<script>
document.addEventListener('keydown', function(event) {
let focused = document.activeElement;
if (!focused) return;
let focusable = Array.from(document.querySelectorAll('button, input, select, textarea, a'));
let index = focusable.indexOf(focused);
if (event.key === 'ArrowDown') {
index = (index + 1) % focusable.length;
focusable[index].focus();
} else if (event.key === 'ArrowUp') {
index = (index - 1 + focusable.length) % focusable.length;
focusable[index].focus();
}
});
</script>
"""
with gr.Blocks(css=css_custom, title="Stream4Me per Smart TV") as demo:
gr.Markdown("# Stream4Me - Aggiornamento, Configurazione, Ricerca e Videoteca")
gr.HTML(js_script)
with gr.Tabs():
with gr.TabItem("Aggiornamento"):
gr.Markdown("## Aggiorna l'applicazione dal repository GitHub")
branch_dropdown = gr.Dropdown(choices=branches, value=branches[0], label="Seleziona Branch")
update_button = gr.Button("Avvia Aggiornamento")
update_log = gr.Textbox(label="Log di Aggiornamento", lines=10)
update_button.click(fn=perform_update, inputs=branch_dropdown, outputs=update_log)
with gr.TabItem("Impostazioni"):
gr.Markdown("## Configura le impostazioni")
settings = load_settings()
input_videolibrarypath = gr.Textbox(value=settings.get("videolibrarypath", ""), label="Percorso Video Library")
input_folder_tvshows = gr.Textbox(value=settings.get("folder_tvshows", ""), label="Cartella Serie TV")
input_folder_movies = gr.Textbox(value=settings.get("folder_movies", ""), label="Cartella Film")
input_videolibrary_kodi = gr.Checkbox(value=settings.get("videolibrary_kodi", False), label="Integrazione Video Library (Kodi)")
save_button = gr.Button("Salva Impostazioni")
settings_status = gr.Textbox(label="Stato Impostazioni", lines=2)
save_button.click(fn=update_settings,
inputs=[input_videolibrarypath, input_folder_tvshows, input_folder_movies, input_videolibrary_kodi],
outputs=settings_status)
gr.Markdown("### Impostazioni Correnti")
current_settings_box = gr.Textbox(value=get_current_settings(), label="Impostazioni Correnti", lines=8)
refresh_button = gr.Button("Ricarica Impostazioni")
refresh_button.click(fn=get_current_settings, inputs=[], outputs=current_settings_box)
with gr.TabItem("Ricerca Film & Streaming"):
gr.Markdown("## Cerca un Film")
film_input = gr.Textbox(placeholder="Inserisci il nome del film...", label="Nome del Film")
film_search_button = gr.Button("Cerca Film")
film_dropdown = gr.Dropdown(choices=[], label="Seleziona il Film")
film_search_button.click(fn=search_movie_list, inputs=film_input, outputs=film_dropdown)
gr.Markdown("### Streaming / Aggiungi in Videoteca")
film_stream_button = gr.Button("Guarda Film (Streaming)")
film_stream_results = gr.Textbox(label="Risultati Streaming", lines=10)
film_stream_button.click(fn=get_streaming_providers, inputs=film_dropdown, outputs=film_stream_results)
film_add_button = gr.Button("Aggiungi Film in Videoteca")
film_add_status = gr.Textbox(label="Stato Aggiunta", lines=2)
film_add_button.click(fn=lambda x: add_to_library(x, "film"), inputs=film_dropdown, outputs=film_add_status)
with gr.TabItem("Ricerca Serie TV & Videoteca"):
gr.Markdown("## Cerca una Serie TV")
tv_input = gr.Textbox(placeholder="Inserisci il nome della serie TV...", label="Nome Serie TV")
tv_search_button = gr.Button("Cerca Serie TV")
tv_dropdown = gr.Dropdown(choices=[], label="Seleziona la Serie TV")
tv_search_button.click(fn=search_tv_list, inputs=tv_input, outputs=tv_dropdown)
gr.Markdown("### Aggiungi Serie TV in Videoteca")
tv_add_button = gr.Button("Aggiungi Serie TV in Videoteca")
tv_add_status = gr.Textbox(label="Stato Aggiunta", lines=2)
tv_add_button.click(fn=lambda x: add_to_library(x, "tv"), inputs=tv_dropdown, outputs=tv_add_status)
with gr.TabItem("Videoteca"):
gr.Markdown("## Film in Videoteca")
film_library_dropdown = gr.Dropdown(choices=get_library_items("film"), label="Film in Videoteca")
film_library_button = gr.Button("Guarda Film (Streaming)")
film_library_results = gr.Textbox(label="Streaming Film", lines=10)
film_library_button.click(fn=get_streaming_providers, inputs=film_library_dropdown, outputs=film_library_results)
gr.Markdown("## Serie TV in Videoteca")
tv_library_dropdown = gr.Dropdown(choices=get_library_items("tv"), label="Serie TV in Videoteca")
tv_library_button = gr.Button("Carica Episodi (Stagione 1)")
tv_library_results = gr.Textbox(label="Episodi", lines=10)
tv_library_button.click(fn=load_tv_episodes, inputs=tv_library_dropdown, outputs=tv_library_results)
gr.Markdown("### Ricarica Videoteca")
refresh_library_button = gr.Button("Ricarica Lista Videoteca")
def refresh_library():
return get_library_items("film"), get_library_items("tv")
film_lib_out, tv_lib_out = gr.Dropdown(choices=[], label=""), gr.Dropdown(choices=[], label="")
refresh_library_button.click(fn=refresh_library, inputs=[], outputs=[film_library_dropdown, tv_library_dropdown])
with gr.TabItem("Informazioni"):
gr.Markdown("## Informazioni sul Progetto")
gr.Markdown("""
**Stream4Me** è un progetto stand-alone per gestire l’aggiornamento, la configurazione, la ricerca e la videoteca di film e serie TV,
completamente indipendente da Kodi e ottimizzato per Smart TV.
**Funzionalità:**
- Aggiornamento automatico dal repository GitHub.
- Configurazione personalizzata tramite interfaccia web.
- Ricerca film e serie TV tramite TMDb.
- Aggiunta in videoteca e visualizzazione dei provider streaming per i film.
- Caricamento degli episodi (es. stagione 1) per le serie TV, per poterle guardare in sequenza.
- Interfaccia responsive, con navigazione tramite telecomando.
**Repository:** [Stream4Me su GitHub](https://github.com/Stream4me/addon)
**Autore:** S4Me Team
""")
return demo
if __name__ == "__main__":
demo = build_interface()
demo.launch()