Spaces:
Sleeping
Sleeping
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() | |