StreamingCommunity 2.5.7__py3-none-any.whl → 2.5.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of StreamingCommunity might be problematic. Click here for more details.

Files changed (65) hide show
  1. StreamingCommunity/Api/Player/ddl.py +2 -3
  2. StreamingCommunity/Api/Site/1337xx/__init__.py +5 -6
  3. StreamingCommunity/Api/Site/1337xx/site.py +7 -14
  4. StreamingCommunity/Api/Site/1337xx/title.py +3 -5
  5. StreamingCommunity/Api/Site/altadefinizionegratis/__init__.py +7 -6
  6. StreamingCommunity/Api/Site/altadefinizionegratis/film.py +14 -19
  7. StreamingCommunity/Api/Site/altadefinizionegratis/site.py +6 -14
  8. StreamingCommunity/Api/Site/animeunity/__init__.py +7 -7
  9. StreamingCommunity/Api/Site/animeunity/film_serie.py +29 -31
  10. StreamingCommunity/Api/Site/animeunity/site.py +14 -22
  11. StreamingCommunity/Api/Site/cb01new/__init__.py +5 -4
  12. StreamingCommunity/Api/Site/cb01new/film.py +2 -5
  13. StreamingCommunity/Api/Site/cb01new/site.py +5 -13
  14. StreamingCommunity/Api/Site/ddlstreamitaly/__init__.py +5 -4
  15. StreamingCommunity/Api/Site/ddlstreamitaly/series.py +12 -49
  16. StreamingCommunity/Api/Site/ddlstreamitaly/site.py +6 -16
  17. StreamingCommunity/Api/Site/ddlstreamitaly/util/ScrapeSerie.py +2 -3
  18. StreamingCommunity/Api/Site/guardaserie/__init__.py +5 -4
  19. StreamingCommunity/Api/Site/guardaserie/series.py +11 -46
  20. StreamingCommunity/Api/Site/guardaserie/site.py +5 -13
  21. StreamingCommunity/Api/Site/guardaserie/util/ScrapeSerie.py +10 -14
  22. StreamingCommunity/Api/Site/ilcorsaronero/__init__.py +5 -4
  23. StreamingCommunity/Api/Site/ilcorsaronero/site.py +5 -13
  24. StreamingCommunity/Api/Site/ilcorsaronero/title.py +3 -5
  25. StreamingCommunity/Api/Site/mostraguarda/__init__.py +2 -2
  26. StreamingCommunity/Api/Site/mostraguarda/film.py +4 -8
  27. StreamingCommunity/Api/Site/streamingcommunity/__init__.py +8 -7
  28. StreamingCommunity/Api/Site/streamingcommunity/film.py +14 -18
  29. StreamingCommunity/Api/Site/streamingcommunity/series.py +25 -76
  30. StreamingCommunity/Api/Site/streamingcommunity/site.py +11 -23
  31. StreamingCommunity/Api/Template/Util/__init__.py +8 -1
  32. StreamingCommunity/Api/Template/Util/manage_ep.py +46 -2
  33. StreamingCommunity/Api/Template/config_loader.py +71 -0
  34. StreamingCommunity/Lib/Downloader/HLS/downloader.py +60 -59
  35. StreamingCommunity/Lib/Downloader/HLS/segments.py +40 -14
  36. StreamingCommunity/Lib/Downloader/MP4/downloader.py +47 -40
  37. StreamingCommunity/Lib/FFmpeg/command.py +59 -3
  38. StreamingCommunity/Lib/M3U8/estimator.py +5 -5
  39. StreamingCommunity/Lib/M3U8/parser.py +12 -51
  40. StreamingCommunity/Lib/TMBD/tmdb.py +66 -99
  41. StreamingCommunity/TelegramHelp/telegram_bot.py +222 -68
  42. StreamingCommunity/Util/_jsonConfig.py +14 -13
  43. StreamingCommunity/Util/ffmpeg_installer.py +70 -64
  44. StreamingCommunity/Util/headers.py +11 -122
  45. StreamingCommunity/Util/os.py +64 -55
  46. StreamingCommunity/Util/table.py +62 -108
  47. StreamingCommunity/run.py +15 -10
  48. {StreamingCommunity-2.5.7.dist-info → StreamingCommunity-2.5.8.dist-info}/METADATA +56 -22
  49. StreamingCommunity-2.5.8.dist-info/RECORD +86 -0
  50. StreamingCommunity/Api/Site/1337xx/costant.py +0 -15
  51. StreamingCommunity/Api/Site/altadefinizionegratis/costant.py +0 -21
  52. StreamingCommunity/Api/Site/animeunity/costant.py +0 -21
  53. StreamingCommunity/Api/Site/cb01new/costant.py +0 -19
  54. StreamingCommunity/Api/Site/ddlstreamitaly/costant.py +0 -20
  55. StreamingCommunity/Api/Site/guardaserie/costant.py +0 -19
  56. StreamingCommunity/Api/Site/ilcorsaronero/costant.py +0 -19
  57. StreamingCommunity/Api/Site/mostraguarda/costant.py +0 -19
  58. StreamingCommunity/Api/Site/streamingcommunity/costant.py +0 -21
  59. StreamingCommunity/TelegramHelp/request_manager.py +0 -82
  60. StreamingCommunity/TelegramHelp/session.py +0 -56
  61. StreamingCommunity-2.5.7.dist-info/RECORD +0 -96
  62. {StreamingCommunity-2.5.7.dist-info → StreamingCommunity-2.5.8.dist-info}/LICENSE +0 -0
  63. {StreamingCommunity-2.5.7.dist-info → StreamingCommunity-2.5.8.dist-info}/WHEEL +0 -0
  64. {StreamingCommunity-2.5.7.dist-info → StreamingCommunity-2.5.8.dist-info}/entry_points.txt +0 -0
  65. {StreamingCommunity-2.5.7.dist-info → StreamingCommunity-2.5.8.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
1
- # 04.02.25
1
+ # 04.02.26
2
2
  # Made by: @GiuPic
3
3
 
4
4
  import os
@@ -9,16 +9,141 @@ import uuid
9
9
  import json
10
10
  import threading
11
11
  import subprocess
12
-
12
+ import threading
13
+ from typing import Optional
13
14
 
14
15
  # External libraries
15
16
  import telebot
16
17
 
18
+ session_data = {}
19
+
20
+ class TelegramSession:
21
+
22
+ def set_session(value):
23
+ session_data['script_id'] = value
24
+
25
+ def get_session():
26
+ return session_data.get('script_id', 'unknown')
27
+
28
+ def updateScriptId(screen_id, titolo):
29
+ json_file = "../../scripts.json"
30
+ try:
31
+ with open(json_file, 'r') as f:
32
+ scripts_data = json.load(f)
33
+ except FileNotFoundError:
34
+ scripts_data = []
35
+
36
+ # cerco lo script con lo screen_id
37
+ for script in scripts_data:
38
+ if script["screen_id"] == screen_id:
39
+ # se trovo il match, aggiorno il titolo
40
+ script["titolo"] = titolo
41
+
42
+ # aggiorno il file json
43
+ with open(json_file, 'w') as f:
44
+ json.dump(scripts_data, f, indent=4)
45
+
46
+ return
47
+
48
+ print(f"Screen_id {screen_id} non trovato.")
49
+
50
+ def deleteScriptId(screen_id):
51
+ json_file = "../../scripts.json"
52
+ try:
53
+ with open(json_file, 'r') as f:
54
+ scripts_data = json.load(f)
55
+ except FileNotFoundError:
56
+ scripts_data = []
57
+
58
+ for script in scripts_data:
59
+ if script["screen_id"] == screen_id:
60
+ # se trovo il match, elimino lo script
61
+ scripts_data.remove(script)
62
+
63
+ # aggiorno il file json
64
+ with open(json_file, 'w') as f:
65
+ json.dump(scripts_data, f, indent=4)
66
+
67
+ print(f"Script eliminato per screen_id {screen_id}")
68
+ return
69
+
70
+ print(f"Screen_id {screen_id} non trovato.")
71
+
72
+ class TelegramRequestManager:
73
+ _instance = None
74
+
75
+ def __new__(cls, *args, **kwargs):
76
+ if not cls._instance:
77
+ cls._instance = super().__new__(cls)
78
+ return cls._instance
79
+
80
+ def __init__(self, json_file: str = "active_requests.json"):
81
+ if not hasattr(self, 'initialized'):
82
+ self.json_file = json_file
83
+ self.initialized = True
84
+ self.on_response_callback = None
85
+
86
+ def create_request(self, type: str) -> str:
87
+ request_data = {
88
+ "type": type,
89
+ "response": None,
90
+ "timestamp": time.time()
91
+ }
92
+
93
+ with open(self.json_file, "w") as f:
94
+ json.dump(request_data, f)
95
+
96
+ return "Ok"
97
+
98
+ def save_response(self, message_text: str) -> bool:
99
+ try:
100
+ # Carica il file JSON
101
+ with open(self.json_file, "r") as f:
102
+ data = json.load(f)
103
+
104
+ # Controlla se esiste la chiave 'type' e se la risposta è presente
105
+ if "type" in data and "response" in data:
106
+ data["response"] = message_text # Aggiorna la risposta
17
107
 
18
- # Fix import
19
- sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")))
20
- from StreamingCommunity.TelegramHelp.request_manager import RequestManager
108
+ with open(self.json_file, "w") as f:
109
+ json.dump(data, f, indent=4)
21
110
 
111
+ return True
112
+ else:
113
+ return False
114
+
115
+ except (FileNotFoundError, json.JSONDecodeError) as e:
116
+ print(f"⚠️ save_response - errore: {e}")
117
+ return False
118
+
119
+ def get_response(self) -> Optional[str]:
120
+ try:
121
+ with open(self.json_file, "r") as f:
122
+ data = json.load(f)
123
+
124
+ # Verifica se esiste la chiave "response"
125
+ if "response" in data:
126
+ response = data["response"] # Ottieni la risposta direttamente
127
+
128
+ if response is not None and self.on_response_callback:
129
+ self.on_response_callback(response)
130
+
131
+ return response
132
+
133
+ except (FileNotFoundError, json.JSONDecodeError) as e:
134
+ print(f"get_response - errore: {e}")
135
+ return None
136
+
137
+ def clear_file(self) -> bool:
138
+ try:
139
+ with open(self.json_file, "w") as f:
140
+ json.dump({}, f)
141
+ print(f"File {self.json_file} è stato svuotato con successo.")
142
+ return True
143
+
144
+ except Exception as e:
145
+ print(f"⚠️ clear_file - errore: {e}")
146
+ return False
22
147
 
23
148
  # Funzione per caricare variabili da un file .env
24
149
  def load_env(file_path="../../.env"):
@@ -44,13 +169,17 @@ class TelegramBot:
44
169
  if os.path.exists(cls._config_file):
45
170
  with open(cls._config_file, "r") as f:
46
171
  config = json.load(f)
47
- cls._instance = cls.init_bot(
48
- config["token"], config["authorized_user_id"]
49
- )
172
+
173
+ # Assicura che authorized_user_id venga trattato come una lista
174
+ authorized_users = config.get('authorized_user_id', [])
175
+ if isinstance(authorized_users, str):
176
+ authorized_users = [int(uid) for uid in authorized_users.split(",") if uid.strip().isdigit()]
177
+
178
+ cls._instance = cls.init_bot(config['token'], authorized_users)
179
+ #cls._instance = cls.init_bot(config['token'], config['authorized_user_id'])
180
+
50
181
  else:
51
- raise Exception(
52
- "Bot non ancora inizializzato. Chiamare prima init_bot() con token e authorized_user_id"
53
- )
182
+ raise Exception("Bot non ancora inizializzato. Chiamare prima init_bot() con token e authorized_user_id")
54
183
  return cls._instance
55
184
 
56
185
  @classmethod
@@ -63,7 +192,8 @@ class TelegramBot:
63
192
  json.dump(config, f)
64
193
  return cls._instance
65
194
 
66
- def __init__(self, token, authorized_user_id):
195
+ def __init__(self, token, authorized_users):
196
+
67
197
  def monitor_scripts():
68
198
  while True:
69
199
  try:
@@ -132,10 +262,10 @@ class TelegramBot:
132
262
  )
133
263
 
134
264
  self.token = token
135
- self.authorized_user_id = authorized_user_id
136
- self.chat_id = authorized_user_id
265
+ self.authorized_users = authorized_users
266
+ self.chat_id = authorized_users
137
267
  self.bot = telebot.TeleBot(token)
138
- self.request_manager = RequestManager()
268
+ self.request_manager = TelegramRequestManager()
139
269
 
140
270
  # Registra gli handler
141
271
  self.register_handlers()
@@ -171,7 +301,7 @@ class TelegramBot:
171
301
  self.handle_response(message)
172
302
 
173
303
  def is_authorized(self, user_id):
174
- return user_id == self.authorized_user_id
304
+ return user_id in self.authorized_users
175
305
 
176
306
  def handle_get_id(self, message):
177
307
  if not self.is_authorized(message.from_user.id):
@@ -194,7 +324,6 @@ class TelegramBot:
194
324
 
195
325
  screen_id = str(uuid.uuid4())[:8]
196
326
  debug_mode = os.getenv("DEBUG")
197
- verbose = debug_mode
198
327
 
199
328
  if debug_mode == "True":
200
329
  subprocess.Popen(["python3", "../../test_run.py", screen_id])
@@ -407,6 +536,13 @@ class TelegramBot:
407
536
  temp_file = f"/tmp/screen_output_{screen_id}.txt"
408
537
 
409
538
  try:
539
+ # Verifica se lo screen con l'ID specificato esiste
540
+ existing_screens = subprocess.check_output(["screen", "-list"]).decode('utf-8')
541
+ if screen_id not in existing_screens:
542
+ print(f"⚠️ La sessione screen con ID {screen_id} non esiste.")
543
+ self.bot.send_message(message.chat.id, f"⚠️ La sessione screen con ID {screen_id} non esiste.")
544
+ return
545
+
410
546
  # Cattura l'output della screen
411
547
  subprocess.run(
412
548
  ["screen", "-X", "-S", screen_id, "hardcopy", "-h", temp_file],
@@ -440,51 +576,22 @@ class TelegramBot:
440
576
  "\n\n", "\n"
441
577
  ) # Rimuovi newline multipli
442
578
 
443
- # Estrarre tutte le parti da "Download:" fino a "Video" o "Subtitle", senza includerli
444
- download_matches = re.findall(
445
- r"Download: (.*?)(?:Video|Subtitle)", cleaned_output
446
- )
447
- if download_matches:
448
- # Serie TV e Film StreamingCommunity
449
-
450
- proc_matches = re.findall(r"Proc: ([\d\.]+%)", cleaned_output)
451
-
452
- # Creare una stringa unica con tutti i risultati
453
- result_string = "\n".join(
454
- [
455
- f"Download: {download_matches[i].strip()}\nDownload al {proc_matches[i]}"
456
- for i in range(len(download_matches))
457
- if i < len(proc_matches)
458
- ]
459
- )
460
-
461
- if result_string != "":
462
- cleaned_output = result_string
463
- else:
464
- print(f"❌ La parola 'Download:' non è stata trovata nella stringa.")
465
- else:
466
-
467
- download_list = []
468
-
469
- # Estrai tutte le righe che iniziano con "Download:" fino al prossimo "Download" o alla fine della riga
470
- matches = re.findall(r"Download:\s*(.*?)(?=Download|$)", cleaned_output)
579
+ # Dentro cleaned_output c'è una stringa recupero quello che si trova tra ## ##
580
+ download_section = re.search(r"##(.*?)##", cleaned_output, re.DOTALL)
581
+ if download_section:
582
+ cleaned_output_0 = "Download: " + download_section.group(1).strip()
471
583
 
472
- # Se sono stati trovati download, stampali
473
- if matches:
474
- for i, match in enumerate(matches, 1):
475
- # rimuovo solo la parte "downloader.py:57Result:400" se esiste
476
- match = re.sub(r"downloader.py:\d+Result:400", "", match)
477
- match = match.strip() # Rimuovo gli spazi bianchi in eccesso
478
- if match: # Assicurati che la stringa non sia vuota
479
- print(f"Download {i}: {match}")
584
+ # Recupero tutto quello che viene dopo con ####
585
+ download_section_bottom = re.search(r"####(.*)", cleaned_output, re.DOTALL)
586
+ if download_section_bottom:
587
+ cleaned_output_1 = download_section_bottom.group(1).strip()
480
588
 
481
- # Aggiungi il risultato modificato alla lista
482
- download_list.append(f"Download {i}: {match}")
483
-
484
- # Creare una stringa unica con tutti i risultati
485
- cleaned_output = "\n".join(download_list)
486
- else:
487
- print("❌ Nessun download trovato")
589
+ # Unico i due risultati se esistono
590
+ if cleaned_output_0 and cleaned_output_1:
591
+ cleaned_output = f"{cleaned_output_0}\n{cleaned_output_1}"
592
+ # Rimuovo 'segments.py:302' e 'downloader.py:385' se presente
593
+ cleaned_output = re.sub(r'downloader\.py:\d+', '', cleaned_output)
594
+ cleaned_output = re.sub(r'segments\.py:\d+', '', cleaned_output)
488
595
 
489
596
  # Invia l'output pulito
490
597
  print(f"📄 Output della screen {screen_id}:\n{cleaned_output}")
@@ -505,7 +612,16 @@ class TelegramBot:
505
612
  os.remove(temp_file)
506
613
 
507
614
  def send_message(self, message, choices):
508
- if choices is None:
615
+
616
+ formatted_message = message
617
+ if choices:
618
+ formatted_choices = "\n".join(choices)
619
+ formatted_message = f"{message}\n\n{formatted_choices}"
620
+
621
+ for chat_id in self.authorized_users:
622
+ self.bot.send_message(chat_id, formatted_message)
623
+
624
+ """ if choices is None:
509
625
  if self.chat_id:
510
626
  print(f"{message}")
511
627
  self.bot.send_message(self.chat_id, message)
@@ -514,7 +630,7 @@ class TelegramBot:
514
630
  message = f"{message}\n\n{formatted_choices}"
515
631
  if self.chat_id:
516
632
  print(f"{message}")
517
- self.bot.send_message(self.chat_id, message)
633
+ self.bot.send_message(self.chat_id, message) """
518
634
 
519
635
  def _send_long_message(self, chat_id, text, chunk_size=4096):
520
636
  """Suddivide e invia un messaggio troppo lungo in più parti."""
@@ -527,16 +643,20 @@ class TelegramBot:
527
643
 
528
644
  if choices is None:
529
645
  print(f"{prompt_message}")
530
- self.bot.send_message(
646
+ """ self.bot.send_message(
531
647
  self.chat_id,
532
648
  f"{prompt_message}",
533
- )
649
+ ) """
650
+ for chat_id in self.authorized_users: # Manda a tutti gli ID autorizzati
651
+ self.bot.send_message(chat_id, f"{prompt_message}")
534
652
  else:
535
653
  print(f"{prompt_message}\n\nOpzioni: {', '.join(choices)}")
536
- self.bot.send_message(
654
+ """ self.bot.send_message(
537
655
  self.chat_id,
538
656
  f"{prompt_message}\n\nOpzioni: {', '.join(choices)}",
539
- )
657
+ ) """
658
+ for chat_id in self.authorized_users: # Manda a tutti gli ID autorizzati
659
+ self.bot.send_message(chat_id, f"{prompt_message}\n\nOpzioni: {', '.join(choices)}")
540
660
 
541
661
  start_time = time.time()
542
662
  while time.time() - start_time < timeout:
@@ -546,7 +666,8 @@ class TelegramBot:
546
666
  time.sleep(1)
547
667
 
548
668
  print(f"⚠️ Timeout: nessuna risposta ricevuta.")
549
- self.bot.send_message(self.chat_id, "⚠️ Timeout: nessuna risposta ricevuta.")
669
+ for chat_id in self.authorized_users: # Manda a tutti gli ID autorizzati
670
+ self.bot.send_message(chat_id, "⚠️ Timeout: nessuna risposta ricevuta.")
550
671
  self.request_manager.clear_file()
551
672
  return None
552
673
 
@@ -558,4 +679,37 @@ class TelegramBot:
558
679
 
559
680
 
560
681
  def get_bot_instance():
561
- return TelegramBot.get_instance()
682
+ return TelegramBot.get_instance()
683
+
684
+ # Esempio di utilizzo
685
+ if __name__ == "__main__":
686
+
687
+ # Usa le variabili
688
+ token = os.getenv("TOKEN_TELEGRAM")
689
+ authorized_users = os.getenv("AUTHORIZED_USER_ID")
690
+
691
+ # Controlla se le variabili sono presenti
692
+ if not token:
693
+ print("Errore: TOKEN_TELEGRAM non è definito nel file .env.")
694
+ sys.exit(1)
695
+
696
+ if not authorized_users:
697
+ print("Errore: AUTHORIZED_USER_ID non è definito nel file .env.")
698
+ sys.exit(1)
699
+
700
+ try:
701
+ TOKEN = token # Inserisci il token del tuo bot Telegram sul file .env
702
+ AUTHORIZED_USER_ID = list(map(int, authorized_users.split(","))) # Inserisci il tuo ID utente Telegram sul file .env
703
+ except ValueError as e:
704
+ print(f"Errore nella conversione degli ID autorizzati: {e}. Controlla il file .env e assicurati che gli ID siano numeri interi separati da virgole.")
705
+ sys.exit(1)
706
+
707
+ # Inizializza il bot
708
+ bot = TelegramBot.init_bot(TOKEN, AUTHORIZED_USER_ID)
709
+ bot.run()
710
+
711
+ """
712
+ start - Avvia lo script
713
+ list - Lista script attivi
714
+ get - Mostra ID utente Telegram
715
+ """
@@ -4,17 +4,18 @@ import os
4
4
  import sys
5
5
  import json
6
6
  import logging
7
+ from pathlib import Path
7
8
  from typing import Any, List
8
9
 
9
10
 
10
11
  class ConfigManager:
11
- def __init__(self, file_path: str = 'config.json') -> None:
12
+ def __init__(self, file_name: str = 'config.json') -> None:
12
13
  """Initialize the ConfigManager.
13
14
 
14
15
  Parameters:
15
16
  - file_path (str, optional): The path to the configuration file. Default is 'config.json'.
16
17
  """
17
- self.file_path = file_path
18
+ self.file_path = Path(__file__).parent.parent.parent / file_name
18
19
  self.config = {}
19
20
  self.cache = {}
20
21
 
@@ -50,17 +51,17 @@ class ConfigManager:
50
51
  def download_requirements(self, url: str, filename: str):
51
52
  """
52
53
  Download the requirements.txt file from the specified URL if not found locally using requests.
53
-
54
+
54
55
  Args:
55
56
  url (str): The URL to download the requirements file from.
56
57
  filename (str): The local filename to save the requirements file as.
57
58
  """
58
59
  try:
59
60
  import requests
60
-
61
+
61
62
  logging.info(f"{filename} not found locally. Downloading from {url}...")
62
63
  response = requests.get(url)
63
-
64
+
64
65
  if response.status_code == 200:
65
66
  with open(filename, 'wb') as f:
66
67
  f.write(response.content)
@@ -68,7 +69,7 @@ class ConfigManager:
68
69
  else:
69
70
  logging.error(f"Failed to download {filename}. HTTP Status code: {response.status_code}")
70
71
  sys.exit(0)
71
-
72
+
72
73
  except Exception as e:
73
74
  logging.error(f"Failed to download {filename}: {e}")
74
75
  sys.exit(0)
@@ -89,15 +90,15 @@ class ConfigManager:
89
90
 
90
91
  if cache_key in self.cache:
91
92
  return self.cache[cache_key]
92
-
93
+
93
94
  if section in self.config and key in self.config[section]:
94
95
  value = self.config[section][key]
95
96
  else:
96
97
  raise ValueError(f"Key '{key}' not found in section '{section}'")
97
-
98
+
98
99
  value = self._convert_to_data_type(value, data_type)
99
100
  self.cache[cache_key] = value
100
-
101
+
101
102
  return value
102
103
 
103
104
  def _convert_to_data_type(self, value: str, data_type: type) -> Any:
@@ -120,7 +121,7 @@ class ConfigManager:
120
121
  return None
121
122
  else:
122
123
  return value
123
-
124
+
124
125
  def get(self, section: str, key: str) -> Any:
125
126
  """Read a value from the configuration file.
126
127
 
@@ -144,7 +145,7 @@ class ConfigManager:
144
145
  int: The integer value.
145
146
  """
146
147
  return self.read_key(section, key, int)
147
-
148
+
148
149
  def get_float(self, section: str, key: str) -> int:
149
150
  """Read an float value from the configuration file.
150
151
 
@@ -180,7 +181,7 @@ class ConfigManager:
180
181
  list: The list value.
181
182
  """
182
183
  return self.read_key(section, key, list)
183
-
184
+
184
185
  def get_dict(self, section: str, key: str) -> dict:
185
186
  """Read a dictionary value from the configuration file.
186
187
 
@@ -220,7 +221,7 @@ class ConfigManager:
220
221
  json.dump(self.config, f, indent=4)
221
222
  except Exception as e:
222
223
  print(f"Error writing configuration file: {e}")
223
-
224
+
224
225
 
225
226
  # Initialize
226
227
  config_manager = ConfigManager()
@@ -4,8 +4,6 @@ import os
4
4
  import glob
5
5
  import gzip
6
6
  import shutil
7
- import tarfile
8
- import zipfile
9
7
  import logging
10
8
  import platform
11
9
  import subprocess
@@ -179,64 +177,59 @@ class FFMPEGDownloader:
179
177
  size = file.write(chunk)
180
178
  progress.update(download_task, advance=size)
181
179
  return True
180
+
182
181
  except Exception as e:
183
182
  logging.error(f"Download error: {e}")
184
183
  return False
185
184
 
186
- def _extract_and_copy_binaries(self, archive_path: str) -> Tuple[Optional[str], Optional[str], Optional[str]]:
185
+ def _extract_file(self, gz_path: str, final_path: str) -> bool:
187
186
  """
188
- Extract FFmpeg binaries from the downloaded archive and copy them to the base directory.
187
+ Extract a gzipped file and set proper permissions.
189
188
 
190
189
  Parameters:
191
- archive_path (str): Path to the downloaded archive file
190
+ gz_path (str): Path to the gzipped file
191
+ final_path (str): Path where the extracted file should be saved
192
192
 
193
193
  Returns:
194
- Tuple[Optional[str], Optional[str], Optional[str]]: Paths to the extracted ffmpeg,
195
- ffprobe, and ffplay executables. Returns None for each executable that couldn't be extracted.
194
+ bool: True if extraction was successful, False otherwise
196
195
  """
197
196
  try:
198
- extraction_path = os.path.join(self.base_dir, 'temp_extract')
199
- os.makedirs(extraction_path, exist_ok=True)
200
-
201
- if archive_path.endswith('.zip'):
202
- with zipfile.ZipFile(archive_path, 'r') as zip_ref:
203
- zip_ref.extractall(extraction_path)
204
- elif archive_path.endswith('.tar.xz'):
205
- with tarfile.open(archive_path) as tar_ref:
206
- tar_ref.extractall(extraction_path)
207
- elif archive_path.endswith('.gz'):
208
- file_name = os.path.basename(archive_path)[:-3] # Remove extension .gz
209
- output_path = os.path.join(extraction_path, file_name)
210
- with gzip.open(archive_path, 'rb') as f_in, open(output_path, 'wb') as f_out:
197
+ logging.info(f"Attempting to extract {gz_path} to {final_path}")
198
+
199
+ # Check if source file exists and is readable
200
+ if not os.path.exists(gz_path):
201
+ logging.error(f"Source file {gz_path} does not exist")
202
+ return False
203
+
204
+ if not os.access(gz_path, os.R_OK):
205
+ logging.error(f"Source file {gz_path} is not readable")
206
+ return False
207
+
208
+ # Extract the file
209
+ with gzip.open(gz_path, 'rb') as f_in:
210
+ # Test if the gzip file is valid
211
+ try:
212
+ f_in.read(1)
213
+ f_in.seek(0)
214
+ except Exception as e:
215
+ logging.error(f"Invalid gzip file {gz_path}: {e}")
216
+ return False
217
+
218
+ # Extract the file
219
+ with open(final_path, 'wb') as f_out:
211
220
  shutil.copyfileobj(f_in, f_out)
212
- else:
213
- raise ValueError("Unsupported archive format")
214
-
215
- config = FFMPEG_CONFIGURATION[self.os_name]
216
- executables = config['executables']
217
- found_paths = []
218
-
219
- for executable in executables:
220
- exe_paths = glob.glob(os.path.join(extraction_path, '**', executable), recursive=True)
221
- if exe_paths:
222
- dest_path = os.path.join(self.base_dir, executable)
223
- shutil.copy2(exe_paths[0], dest_path)
224
-
225
- if self.os_name != 'windows':
226
- os.chmod(dest_path, 0o755)
227
-
228
- found_paths.append(dest_path)
229
- else:
230
- found_paths.append(None)
231
221
 
232
- shutil.rmtree(extraction_path, ignore_errors=True)
233
- os.remove(archive_path)
234
-
235
- return tuple(found_paths) if len(found_paths) == 3 else (None, None, None)
222
+ # Set executable permissions
223
+ os.chmod(final_path, 0o755)
224
+ logging.info(f"Successfully extracted {gz_path} to {final_path}")
225
+
226
+ # Remove the gzip file
227
+ os.remove(gz_path)
228
+ return True
236
229
 
237
230
  except Exception as e:
238
- logging.error(f"Extraction/copy error: {e}")
239
- return None, None, None
231
+ logging.error(f"Extraction error for {gz_path}: {e}")
232
+ return False
240
233
 
241
234
  def download(self) -> Tuple[Optional[str], Optional[str], Optional[str]]:
242
235
  """
@@ -244,30 +237,43 @@ class FFMPEGDownloader:
244
237
 
245
238
  Returns:
246
239
  Tuple[Optional[str], Optional[str], Optional[str]]: Paths to ffmpeg, ffprobe, and ffplay executables.
247
- Returns None for each executable that couldn't be downloaded or set up.
248
240
  """
249
241
  config = FFMPEG_CONFIGURATION[self.os_name]
250
242
  executables = [exe.format(arch=self.arch) for exe in config['executables']]
251
-
243
+ successful_extractions = []
244
+
252
245
  for executable in executables:
253
- download_url = f"https://github.com/eugeneware/ffmpeg-static/releases/latest/download/{executable}.gz"
254
- download_path = os.path.join(self.base_dir, f"{executable}.gz")
255
- final_path = os.path.join(self.base_dir, executable)
256
-
257
- console.print(f"[bold blue]Downloading {executable} from GitHub[/]")
258
- if not self._download_file(download_url, download_path):
259
- return None, None, None
260
-
261
- with gzip.open(download_path, 'rb') as f_in, open(final_path, 'wb') as f_out:
262
- shutil.copyfileobj(f_in, f_out)
263
-
264
- os.chmod(final_path, 0o755)
265
- os.remove(download_path)
266
-
246
+ try:
247
+ download_url = f"https://github.com/eugeneware/ffmpeg-static/releases/latest/download/{executable}.gz"
248
+ download_path = os.path.join(self.base_dir, f"{executable}.gz")
249
+ final_path = os.path.join(self.base_dir, executable)
250
+
251
+ # Log the current operation
252
+ logging.info(f"Processing {executable}")
253
+ console.print(f"[bold blue]Downloading {executable} from GitHub[/]")
254
+
255
+ # Download the file
256
+ if not self._download_file(download_url, download_path):
257
+ console.print(f"[bold red]Failed to download {executable}[/]")
258
+ continue
259
+
260
+ # Extract the file
261
+ if self._extract_file(download_path, final_path):
262
+ successful_extractions.append(final_path)
263
+ console.print(f"[bold green]Successfully installed {executable}[/]")
264
+ else:
265
+ console.print(f"[bold red]Failed to extract {executable}[/]")
266
+
267
+ except Exception as e:
268
+ logging.error(f"Error processing {executable}: {e}")
269
+ console.print(f"[bold red]Error processing {executable}: {str(e)}[/]")
270
+ continue
271
+
272
+ # Return the results based on successful extractions
267
273
  return (
268
- os.path.join(self.base_dir, executables[0]),
269
- os.path.join(self.base_dir, executables[1]),
270
- None
274
+ successful_extractions[0] if len(successful_extractions) > 0 else None,
275
+ successful_extractions[1] if len(successful_extractions) > 1 else None,
276
+ None # ffplay is not included in the current implementation
271
277
  )
272
278
 
273
279
  def check_ffmpeg() -> Tuple[Optional[str], Optional[str], Optional[str]]: