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 = """ """ 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()