File size: 18,007 Bytes
f4042ee
08414ba
f4042ee
2d7a990
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
08414ba
f4042ee
08414ba
 
f4042ee
fa074f5
f4042ee
 
 
 
 
 
fa074f5
f4042ee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
08414ba
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2d7a990
 
f4042ee
 
 
fa074f5
f4042ee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fa074f5
08414ba
f4042ee
 
 
 
 
 
 
 
 
 
 
 
 
 
fa074f5
 
f4042ee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2d7a990
08414ba
 
2d7a990
08414ba
 
2d7a990
 
08414ba
2d7a990
 
 
 
 
 
08414ba
2d7a990
 
 
08414ba
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2d7a990
08414ba
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2d7a990
 
 
f4042ee
08414ba
f4042ee
 
 
 
 
 
 
 
fa074f5
f4042ee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
08414ba
 
 
f4042ee
 
fa074f5
f4042ee
 
 
 
 
 
 
 
 
 
 
fa074f5
f4042ee
 
 
 
 
 
 
 
 
 
08414ba
2d7a990
 
08414ba
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2d7a990
f4042ee
 
 
08414ba
 
f4042ee
 
fa074f5
f4042ee
08414ba
 
 
fa074f5
f4042ee
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
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()