StreamingCommunity 2.2.0__py3-none-any.whl → 2.4.0__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 (56) hide show
  1. StreamingCommunity/Api/Player/Helper/Vixcloud/util.py +15 -24
  2. StreamingCommunity/Api/Site/1337xx/site.py +9 -6
  3. StreamingCommunity/Api/Site/1337xx/title.py +2 -2
  4. StreamingCommunity/Api/Site/altadefinizionegratis/costant.py +19 -0
  5. StreamingCommunity/Api/Site/{altadefinizione → altadefinizionegratis}/film.py +2 -2
  6. StreamingCommunity/Api/Site/{altadefinizione → altadefinizionegratis}/site.py +28 -22
  7. StreamingCommunity/Api/Site/animeunity/__init__.py +1 -1
  8. StreamingCommunity/Api/Site/animeunity/costant.py +6 -2
  9. StreamingCommunity/Api/Site/animeunity/film_serie.py +3 -3
  10. StreamingCommunity/Api/Site/animeunity/site.py +29 -21
  11. StreamingCommunity/Api/Site/cb01new/costant.py +6 -2
  12. StreamingCommunity/Api/Site/cb01new/film.py +2 -2
  13. StreamingCommunity/Api/Site/cb01new/site.py +20 -13
  14. StreamingCommunity/Api/Site/ddlstreamitaly/costant.py +6 -2
  15. StreamingCommunity/Api/Site/ddlstreamitaly/series.py +2 -2
  16. StreamingCommunity/Api/Site/ddlstreamitaly/site.py +9 -5
  17. StreamingCommunity/Api/Site/guardaserie/costant.py +6 -2
  18. StreamingCommunity/Api/Site/guardaserie/series.py +2 -3
  19. StreamingCommunity/Api/Site/guardaserie/site.py +10 -6
  20. StreamingCommunity/Api/Site/ilcorsaronero/costant.py +6 -2
  21. StreamingCommunity/Api/Site/ilcorsaronero/site.py +22 -13
  22. StreamingCommunity/Api/Site/ilcorsaronero/title.py +3 -3
  23. StreamingCommunity/Api/Site/ilcorsaronero/util/ilCorsarScraper.py +1 -1
  24. StreamingCommunity/Api/Site/mostraguarda/costant.py +6 -2
  25. StreamingCommunity/Api/Site/mostraguarda/film.py +2 -2
  26. StreamingCommunity/Api/Site/streamingcommunity/costant.py +7 -3
  27. StreamingCommunity/Api/Site/streamingcommunity/film.py +3 -3
  28. StreamingCommunity/Api/Site/streamingcommunity/series.py +3 -3
  29. StreamingCommunity/Api/Site/streamingcommunity/site.py +30 -26
  30. StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py +24 -24
  31. StreamingCommunity/Api/Template/Util/get_domain.py +171 -142
  32. StreamingCommunity/Api/Template/site.py +1 -1
  33. StreamingCommunity/Lib/Downloader/HLS/downloader.py +14 -3
  34. StreamingCommunity/Lib/Downloader/HLS/segments.py +36 -22
  35. StreamingCommunity/Lib/Downloader/TOR/downloader.py +3 -3
  36. StreamingCommunity/Lib/M3U8/decryptor.py +1 -0
  37. StreamingCommunity/Lib/M3U8/estimator.py +2 -2
  38. StreamingCommunity/Lib/M3U8/url_fixer.py +6 -0
  39. StreamingCommunity/Lib/TMBD/tmdb.py +1 -1
  40. StreamingCommunity/Upload/version.py +1 -1
  41. StreamingCommunity/Util/_jsonConfig.py +43 -19
  42. StreamingCommunity/Util/ffmpeg_installer.py +31 -14
  43. StreamingCommunity/Util/headers.py +15 -2
  44. StreamingCommunity/Util/logger.py +9 -0
  45. StreamingCommunity/Util/os.py +100 -138
  46. StreamingCommunity/Util/table.py +6 -6
  47. StreamingCommunity/run.py +61 -7
  48. {StreamingCommunity-2.2.0.dist-info → StreamingCommunity-2.4.0.dist-info}/METADATA +116 -35
  49. StreamingCommunity-2.4.0.dist-info/RECORD +92 -0
  50. StreamingCommunity/Api/Site/altadefinizione/costant.py +0 -15
  51. StreamingCommunity-2.2.0.dist-info/RECORD +0 -92
  52. /StreamingCommunity/Api/Site/{altadefinizione → altadefinizionegratis}/__init__.py +0 -0
  53. {StreamingCommunity-2.2.0.dist-info → StreamingCommunity-2.4.0.dist-info}/LICENSE +0 -0
  54. {StreamingCommunity-2.2.0.dist-info → StreamingCommunity-2.4.0.dist-info}/WHEEL +0 -0
  55. {StreamingCommunity-2.2.0.dist-info → StreamingCommunity-2.4.0.dist-info}/entry_points.txt +0 -0
  56. {StreamingCommunity-2.2.0.dist-info → StreamingCommunity-2.4.0.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,7 @@
1
1
  # 29.01.24
2
2
 
3
3
  import os
4
+ import sys
4
5
  import json
5
6
  import httpx
6
7
  import logging
@@ -22,34 +23,57 @@ class ConfigManager:
22
23
  """Read the configuration file."""
23
24
  try:
24
25
  logging.info(f"Reading file: {self.file_path}")
25
-
26
- # Check if file exist
26
+
27
+ # Check if file exists
27
28
  if os.path.exists(self.file_path):
28
29
  with open(self.file_path, 'r') as f:
29
30
  self.config = json.load(f)
30
31
  logging.info("Configuration file loaded successfully.")
31
-
32
- # Download config.json
32
+
33
+ # Download config.json if it doesn't exist locally
33
34
  else:
34
35
  logging.info("Configuration file does not exist. Downloading...")
35
- url = "https://raw.githubusercontent.com/Lovi-0/StreamingCommunity/refs/heads/main/config.json"
36
-
37
- with httpx.Client() as client:
38
- response = client.get(url)
39
-
40
- if response.status_code == 200:
41
- with open(self.file_path, 'w') as f:
42
- f.write(response.text)
43
-
44
- self.config = json.loads(response.text)
45
- logging.info("Configuration file downloaded and saved.")
46
-
47
- else:
48
- logging.error(f"Failed to download configuration file. Status code: {response.status_code}")
49
-
36
+ self.download_requirements(
37
+ 'https://raw.githubusercontent.com/Lovi-0/StreamingCommunity/refs/heads/main/config.json',
38
+ self.file_path
39
+ )
40
+
41
+ # Load the downloaded config.json into the config attribute
42
+ with open(self.file_path, 'r') as f:
43
+ self.config = json.load(f)
44
+ logging.info("Configuration file downloaded and saved.")
45
+
46
+ logging.info("Configuration file processed successfully.")
47
+
50
48
  except Exception as e:
51
49
  logging.error(f"Error reading configuration file: {e}")
52
50
 
51
+ def download_requirements(self, url: str, filename: str):
52
+ """
53
+ Download the requirements.txt file from the specified URL if not found locally using requests.
54
+
55
+ Args:
56
+ url (str): The URL to download the requirements file from.
57
+ filename (str): The local filename to save the requirements file as.
58
+ """
59
+ try:
60
+ import requests
61
+
62
+ logging.info(f"{filename} not found locally. Downloading from {url}...")
63
+ response = requests.get(url)
64
+
65
+ if response.status_code == 200:
66
+ with open(filename, 'wb') as f:
67
+ f.write(response.content)
68
+
69
+ else:
70
+ logging.error(f"Failed to download {filename}. HTTP Status code: {response.status_code}")
71
+ sys.exit(0)
72
+
73
+ except Exception as e:
74
+ logging.error(f"Failed to download {filename}: {e}")
75
+ sys.exit(0)
76
+
53
77
  def read_key(self, section: str, key: str, data_type: type = str) -> Any:
54
78
  """Read a key from the configuration file.
55
79
 
@@ -19,6 +19,8 @@ from rich.progress import Progress, SpinnerColumn, BarColumn, TextColumn, TimeRe
19
19
 
20
20
  # Variable
21
21
  console = Console()
22
+
23
+ # https://github.com/eugeneware/ffmpeg-static/releases
22
24
  FFMPEG_CONFIGURATION = {
23
25
  'windows': {
24
26
  'base_dir': lambda home: os.path.join(os.path.splitdrive(home)[0] + os.path.sep, 'binary'),
@@ -28,13 +30,13 @@ FFMPEG_CONFIGURATION = {
28
30
  },
29
31
  'darwin': {
30
32
  'base_dir': lambda home: os.path.join(home, 'Applications', 'binary'),
31
- 'download_url': 'https://evermeet.cx/ffmpeg/ffmpeg-{version}.zip',
33
+ 'download_url': 'https://github.com/eugeneware/ffmpeg-static/releases/download/b{version}/ffmpeg-macOS-{arch}.zip',
32
34
  'file_extension': '.zip',
33
35
  'executables': ['ffmpeg', 'ffprobe', 'ffplay']
34
36
  },
35
37
  'linux': {
36
38
  'base_dir': lambda home: os.path.join(home, '.local', 'bin', 'binary'),
37
- 'download_url': 'https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-{arch}-static.tar.xz',
39
+ 'download_url': 'https://github.com/eugeneware/ffmpeg-static/releases/download/b{version}/ffmpeg-linux-{arch}.tar.xz',
38
40
  'file_extension': '.tar.xz',
39
41
  'executables': ['ffmpeg', 'ffprobe', 'ffplay']
40
42
  }
@@ -150,19 +152,26 @@ class FFMPEGDownloader:
150
152
 
151
153
  def _get_latest_version(self) -> Optional[str]:
152
154
  """
153
- Get the latest FFmpeg version from the official website.
155
+ Get the latest FFmpeg version from the GitHub releases page.
154
156
 
155
157
  Returns:
156
- Optional[str]: The latest version string, or None if retrieval fails
158
+ Optional[str]: The latest version string, or None if retrieval fails.
157
159
 
158
160
  Raises:
159
- requests.exceptions.RequestException: If there are network-related errors
161
+ requests.exceptions.RequestException: If there are network-related errors.
160
162
  """
161
163
  try:
162
- version_url = 'https://www.gyan.dev/ffmpeg/builds/release-version'
163
- return requests.get(version_url).text.strip()
164
+ # Use GitHub API to fetch the latest release
165
+ response = requests.get(
166
+ 'https://api.github.com/repos/eugeneware/ffmpeg-static/releases/latest'
167
+ )
168
+ response.raise_for_status()
169
+ latest_release = response.json()
170
+
171
+ # Extract the tag name or version from the release
172
+ return latest_release.get('tag_name')
164
173
  except Exception as e:
165
- logging.error(f"Unable to get version: {e}")
174
+ logging.error(f"Unable to get version from GitHub: {e}")
166
175
  return None
167
176
 
168
177
  def _download_file(self, url: str, destination: str) -> bool:
@@ -324,14 +333,22 @@ def check_ffmpeg() -> Tuple[Optional[str], Optional[str], Optional[str]]:
324
333
  # Windows detection
325
334
  elif system_platform == 'windows':
326
335
  try:
327
- ffmpeg_path = subprocess.check_output(['where', 'ffmpeg'], text=True).strip().split('\n')[0]
328
- ffprobe_path = subprocess.check_output(['where', 'ffprobe'], text=True).strip().split('\n')[0]
329
- ffplay_path = subprocess.check_output(['where', 'ffplay'], text=True).strip().split('\n')[0]
336
+ ffmpeg_path = subprocess.check_output(
337
+ ['where', 'ffmpeg'], stderr=subprocess.DEVNULL, text=True
338
+ ).strip().split('\n')[0]
330
339
 
331
- if ffmpeg_path and ffprobe_path:
332
- return ffmpeg_path, ffprobe_path, ffplay_path
340
+ ffprobe_path = subprocess.check_output(
341
+ ['where', 'ffprobe'], stderr=subprocess.DEVNULL, text=True
342
+ ).strip().split('\n')[0]
343
+
344
+ ffplay_path = subprocess.check_output(
345
+ ['where', 'ffplay'], stderr=subprocess.DEVNULL, text=True
346
+ ).strip().split('\n')[0]
347
+
348
+ return ffmpeg_path, ffprobe_path, ffplay_path
349
+
333
350
  except subprocess.CalledProcessError:
334
- pass
351
+ logging.warning("One or more FFmpeg binaries were not found with command where")
335
352
 
336
353
  # Linux detection
337
354
  else:
@@ -1,7 +1,9 @@
1
1
  # 4.04.24
2
2
 
3
3
  import re
4
+ import sys
4
5
  import random
6
+ from importlib.metadata import version, PackageNotFoundError
5
7
 
6
8
 
7
9
  # External library
@@ -9,7 +11,18 @@ from fake_useragent import UserAgent
9
11
 
10
12
 
11
13
  # Variable
12
- ua = UserAgent(use_external_data=True)
14
+ try:
15
+ ua_version = version('fake-useragent')
16
+ except PackageNotFoundError:
17
+ ua_version = None
18
+
19
+ if not getattr(sys, 'frozen', False):
20
+ if ua_version == '1.1.3':
21
+ ua = UserAgent(use_external_data=True)
22
+ else:
23
+ ua = UserAgent()
24
+ else:
25
+ ua = UserAgent()
13
26
 
14
27
 
15
28
  def extract_versions(user_agent):
@@ -144,4 +157,4 @@ def get_headers() -> str:
144
157
  """
145
158
 
146
159
  # Get a random user agent string from the user agent rotator
147
- return str(ua.chrome)
160
+ return str(ua.chrome)
@@ -1,5 +1,6 @@
1
1
  # 26.03.24
2
2
 
3
+ import os
3
4
  import logging
4
5
  from logging.handlers import RotatingFileHandler
5
6
 
@@ -26,6 +27,7 @@ class Logger:
26
27
 
27
28
  # Configure file logging if debug mode and logging to file are both enabled
28
29
  if self.log_to_file:
30
+ self.remove_existing_log_file()
29
31
  self.configure_file_logging()
30
32
  else:
31
33
 
@@ -51,3 +53,10 @@ class Logger:
51
53
  formatter = logging.Formatter('[%(filename)s:%(lineno)s - %(funcName)20s() ] %(asctime)s - %(levelname)s - %(message)s')
52
54
  file_handler.setFormatter(formatter)
53
55
  logging.getLogger('').addHandler(file_handler)
56
+
57
+ def remove_existing_log_file(self):
58
+ """
59
+ Remove the log file if it already exists.
60
+ """
61
+ if os.path.exists(self.log_file):
62
+ os.remove(self.log_file)
@@ -8,16 +8,16 @@ import shutil
8
8
  import hashlib
9
9
  import logging
10
10
  import platform
11
- import unidecode
12
11
  import subprocess
13
12
  import contextlib
14
- import pathvalidate
15
13
  import urllib.request
16
14
  import importlib.metadata
17
15
 
18
16
 
19
17
  # External library
20
18
  import httpx
19
+ from unidecode import unidecode
20
+ from pathvalidate import sanitize_filename, sanitize_filepath
21
21
 
22
22
 
23
23
  # Internal utilities
@@ -25,172 +25,133 @@ from .ffmpeg_installer import check_ffmpeg
25
25
  from StreamingCommunity.Util.console import console, msg
26
26
 
27
27
 
28
- # Variable
29
- OS_CONFIGURATIONS = {
30
- 'windows': {
31
- 'max_length': 255,
32
- 'invalid_chars': '<>:"/\\|?*',
33
- 'reserved_names': [
34
- "CON", "PRN", "AUX", "NUL",
35
- "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
36
- "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"
37
- ],
38
- 'max_path': 255
39
- },
40
- 'darwin': {
41
- 'max_length': 4096,
42
- 'invalid_chars': '/:',
43
- 'reserved_names': [],
44
- 'hidden_file_restriction': True
45
- },
46
- 'linux': {
47
- 'max_length': 4096,
48
- 'invalid_chars': '/\0',
49
- 'reserved_names': []
50
- }
51
- }
52
-
53
-
54
28
 
55
29
  class OsManager:
56
30
  def __init__(self):
57
31
  self.system = self._detect_system()
58
- self.config = OS_CONFIGURATIONS.get(self.system, {})
32
+ self.max_length = self._get_max_length()
59
33
 
60
34
  def _detect_system(self) -> str:
61
35
  """Detect and normalize operating system name."""
62
36
  system = platform.system().lower()
37
+ if system not in ['windows', 'darwin', 'linux']:
38
+ raise ValueError(f"Unsupported operating system: {system}")
39
+ return system
63
40
 
64
- if system in OS_CONFIGURATIONS:
65
- return system
66
-
67
- raise ValueError(f"Unsupported operating system: {system}")
41
+ def _get_max_length(self) -> int:
42
+ """Get max filename length based on OS."""
43
+ return 255 if self.system == 'windows' else 4096
68
44
 
69
45
  def _normalize_windows_path(self, path: str) -> str:
70
- """
71
- Normalize Windows paths to handle drive letters correctly.
72
-
73
- Args:
74
- path (str): Original path that might contain a drive letter.
75
-
76
- Returns:
77
- str: Properly normalized absolute path.
78
- """
79
- if self.system != 'windows':
46
+ """Normalize Windows paths."""
47
+ if not path or self.system != 'windows':
80
48
  return path
81
-
82
- # Check if path starts with a drive letter
49
+
50
+ # Preserve network paths (UNC and IP-based)
51
+ if path.startswith('\\\\') or path.startswith('//'):
52
+ return path.replace('/', '\\')
53
+
54
+ # Handle drive letters
83
55
  if len(path) >= 2 and path[1] == ':':
84
56
  drive = path[0:2]
85
- rest = path[2:].lstrip(os.sep)
86
- # Ensure proper absolute path format
87
- return os.path.join(drive + os.sep, rest)
88
- return path
89
-
90
- def _process_filename(self, filename: str) -> str:
91
- """
92
- Comprehensively process filename with cross-platform considerations.
93
-
94
- Args:
95
- filename (str): Original filename.
96
-
97
- Returns:
98
- str: Processed filename.
99
- """
100
- name, ext = os.path.splitext(filename)
101
-
102
- # Handle length restrictions
103
- if len(name) > self.config['max_length']:
104
- name = self._truncate_filename(name)
105
-
106
- # Reconstruct filename
107
- processed_filename = name + ext
108
-
109
- return processed_filename
57
+ rest = path[2:].replace('/', '\\').lstrip('\\')
58
+ return f"{drive}\\{rest}"
110
59
 
111
- def _truncate_filename(self, name: str) -> str:
112
- """
113
- Truncate filename based on OS-specific rules.
114
-
115
- Args:
116
- name (str): Original filename.
60
+ return path.replace('/', '\\')
61
+
62
+ def _normalize_mac_path(self, path: str) -> str:
63
+ """Normalize macOS paths."""
64
+ if not path or self.system != 'darwin':
65
+ return path
66
+
67
+ # Convert Windows separators to Unix
68
+ normalized = path.replace('\\', '/')
117
69
 
118
- Returns:
119
- str: Truncated filename.
120
- """
121
- logging.info("_truncate_filename: ", name)
70
+ # Ensure absolute paths start with /
71
+ if normalized.startswith('/'):
72
+ return os.path.normpath(normalized)
122
73
 
123
- if self.system == 'windows':
124
- return name[:self.config['max_length'] - 3] + '___'
125
- elif self.system == 'darwin':
126
- return name[:self.config['max_length']]
127
- elif self.system == 'linux':
128
- return name[:self.config['max_length'] - 2] + '___'
74
+ return normalized
129
75
 
130
76
  def get_sanitize_file(self, filename: str) -> str:
131
- """
132
- Sanitize filename using pathvalidate with unidecode.
133
-
134
- Args:
135
- filename (str): Original filename.
136
-
137
- Returns:
138
- str: Sanitized filename.
139
- """
77
+ """Sanitize filename."""
78
+ if not filename:
79
+ return filename
140
80
 
141
- # Decode unicode characters and sanitize
142
- decoded_filename = unidecode.unidecode(filename)
143
- sanitized_filename = pathvalidate.sanitize_filename(decoded_filename)
81
+ # Decode and sanitize
82
+ decoded = unidecode(filename)
83
+ sanitized = sanitize_filename(decoded)
144
84
 
145
- # Truncate if necessary based on OS configuration
146
- name, ext = os.path.splitext(sanitized_filename)
147
- if len(name) > self.config['max_length']:
148
- name = self._truncate_filename(name)
85
+ # Split name and extension
86
+ name, ext = os.path.splitext(sanitized)
149
87
 
150
- result = name + ext
151
- return result
152
-
153
- def get_sanitize_path(self, path: str) -> str:
154
- """
155
- Sanitize folder path using pathvalidate with unidecode.
88
+ # Calculate available length for name considering the '...' and extension
89
+ max_name_length = self.max_length - len('...') - len(ext)
156
90
 
157
- Args:
158
- path (str): Original folder path.
91
+ # Truncate name if it exceeds the max name length
92
+ if len(name) > max_name_length:
93
+ name = name[:max_name_length] + '...'
159
94
 
160
- Returns:
161
- str: Sanitized folder path.
162
- """
95
+ # Ensure the final file name includes the extension
96
+ return name + ext
163
97
 
164
- # Normalize path for Windows drive letters first
165
- path = self._normalize_windows_path(path)
98
+ def get_sanitize_path(self, path: str) -> str:
99
+ """Sanitize complete path."""
100
+ if not path:
101
+ return path
166
102
 
167
- # Decode unicode characters and sanitize
168
- decoded_path = unidecode.unidecode(path)
169
- sanitized_path = pathvalidate.sanitize_filepath(decoded_path)
170
-
171
- # Split path and process each component
172
- path_components = os.path.normpath(sanitized_path).split(os.sep)
103
+ # Decode unicode characters
104
+ decoded = unidecode(path)
173
105
 
174
- # Handle Windows drive letter specially
175
- if self.system == 'windows' and len(path_components[0]) == 2 and path_components[0][1] == ':':
176
- drive = path_components.pop(0)
177
- processed_components = [drive + os.sep]
106
+ # Basic path sanitization
107
+ sanitized = sanitize_filepath(decoded)
178
108
 
109
+ if self.system == 'windows':
110
+ # Handle network paths (UNC or IP-based)
111
+ if path.startswith('\\\\') or path.startswith('//'):
112
+ parts = path.replace('/', '\\').split('\\')
113
+ # Keep server/IP and share name as is
114
+ sanitized_parts = parts[:4]
115
+ # Sanitize remaining parts
116
+ if len(parts) > 4:
117
+ sanitized_parts.extend([
118
+ self.get_sanitize_file(part)
119
+ for part in parts[4:]
120
+ if part
121
+ ])
122
+ return '\\'.join(sanitized_parts)
123
+
124
+ # Handle drive letters
125
+ elif len(path) >= 2 and path[1] == ':':
126
+ drive = path[:2]
127
+ rest = path[2:].lstrip('\\').lstrip('/')
128
+ path_parts = [drive] + [
129
+ self.get_sanitize_file(part)
130
+ for part in rest.replace('/', '\\').split('\\')
131
+ if part
132
+ ]
133
+ return '\\'.join(path_parts)
134
+
135
+ # Regular path
136
+ else:
137
+ parts = path.replace('/', '\\').split('\\')
138
+ return '\\'.join(p for p in parts if p)
179
139
  else:
180
- processed_components = []
140
+ # Handle Unix-like paths (Linux and macOS)
141
+ is_absolute = path.startswith('/')
142
+ parts = path.replace('\\', '/').split('/')
143
+ sanitized_parts = [
144
+ self.get_sanitize_file(part)
145
+ for part in parts
146
+ if part
147
+ ]
148
+
149
+ result = '/'.join(sanitized_parts)
150
+ if is_absolute:
151
+ result = '/' + result
152
+
153
+ return result
181
154
 
182
- # Process remaining components
183
- for component in path_components:
184
- if component: # Skip empty components
185
- if len(component) > self.config['max_length']:
186
- component = self._truncate_filename(component)
187
-
188
- processed_components.append(component)
189
-
190
- # Join with proper separator and normalize
191
- result = os.path.normpath(os.path.join(*processed_components))
192
- return result
193
-
194
155
  def create_path(self, path: str, mode: int = 0o755) -> bool:
195
156
  """
196
157
  Create directory path with specified permissions.
@@ -272,6 +233,7 @@ class OsManager:
272
233
  logging.error(f"An error occurred while checking file existence: {e}")
273
234
  return False
274
235
 
236
+
275
237
  class InternManager():
276
238
 
277
239
  def format_file_size(self, size_bytes: float) -> str:
@@ -174,12 +174,12 @@ class TVShowManager:
174
174
 
175
175
  else:
176
176
  choices = [str(i) for i in range(0, max_int_input)]
177
- choices.extend(["q", "", "back"])
177
+ choices.extend(["q", "quit", "b", "back"])
178
178
 
179
179
  key = Prompt.ask("[cyan]Insert media [red]index", choices=choices, show_choices=False)
180
180
  last_command = key
181
181
 
182
- if key.lower() == "q":
182
+ if key.lower() == "q" or key.lower() == "quit":
183
183
  break
184
184
 
185
185
  elif key == "":
@@ -188,7 +188,7 @@ class TVShowManager:
188
188
  if self.slice_end > total_items:
189
189
  self.slice_end = total_items
190
190
 
191
- elif key.lower() == "back" and research_func:
191
+ elif (key.lower() == "b" or key.lower() == "back") and research_func:
192
192
  self.run_back_command(research_func)
193
193
 
194
194
  else:
@@ -205,19 +205,19 @@ class TVShowManager:
205
205
 
206
206
  else:
207
207
  choices = [str(i) for i in range(0, max_int_input)]
208
- choices.extend(["q", "", "back"])
208
+ choices.extend(["q", "quit", "b", "back"])
209
209
 
210
210
  key = Prompt.ask("[cyan]Insert media [red]index", choices=choices, show_choices=False)
211
211
  last_command = key
212
212
 
213
- if key.lower() == "q":
213
+ if key.lower() == "q" or key.lower() == "quit":
214
214
  break
215
215
 
216
216
  elif key == "":
217
217
  self.slice_start = 0
218
218
  self.slice_end = self.step
219
219
 
220
- elif key.lower() == "back" and research_func:
220
+ elif (key.lower() == "b" or key.lower() == "back") and research_func:
221
221
  self.run_back_command(research_func)
222
222
 
223
223
  else:
StreamingCommunity/run.py CHANGED
@@ -125,7 +125,6 @@ def initialize():
125
125
 
126
126
 
127
127
  def main():
128
-
129
128
  start = time.time()
130
129
 
131
130
  # Create logger
@@ -136,9 +135,39 @@ def main():
136
135
  search_functions = load_search_functions()
137
136
  logging.info(f"Load module in: {time.time() - start} s")
138
137
 
139
- # Create dynamic argument parser
140
- parser = argparse.ArgumentParser(description='Script to download film and series from the internet.')
141
-
138
+ # Create argument parser
139
+ parser = argparse.ArgumentParser(
140
+ description='Script to download movies and series from the internet. Use these commands to configure the script and control its behavior.'
141
+ )
142
+
143
+ # Add arguments for the main configuration parameters
144
+ parser.add_argument(
145
+ '--add_siteName', type=bool, help='Enable or disable adding the site name to the file name (e.g., true/false).'
146
+ )
147
+ parser.add_argument(
148
+ '--disable_searchDomain', type=bool, help='Enable or disable searching in configured domains (e.g., true/false).'
149
+ )
150
+ parser.add_argument(
151
+ '--not_close', type=bool, help='If set to true, the script will not close the console after execution (e.g., true/false).'
152
+ )
153
+
154
+ # Add arguments for M3U8 configuration
155
+ parser.add_argument(
156
+ '--default_video_worker', type=int, help='Number of workers for video during M3U8 download (default: 12).'
157
+ )
158
+ parser.add_argument(
159
+ '--default_audio_worker', type=int, help='Number of workers for audio during M3U8 download (default: 12).'
160
+ )
161
+
162
+ # Add options for audio and subtitles
163
+ parser.add_argument(
164
+ '--specific_list_audio', type=str, help='Comma-separated list of specific audio languages to download (e.g., ita,eng).'
165
+ )
166
+ parser.add_argument(
167
+ '--specific_list_subtitles', type=str, help='Comma-separated list of specific subtitle languages to download (e.g., eng,spa).'
168
+ )
169
+
170
+ # Add arguments for search functions
142
171
  color_map = {
143
172
  "anime": "red",
144
173
  "film_serie": "yellow",
@@ -153,10 +182,35 @@ def main():
153
182
  long_option = alias
154
183
  parser.add_argument(f'-{short_option}', f'--{long_option}', action='store_true', help=f'Search for {alias.split("_")[0]} on streaming platforms.')
155
184
 
156
- # Parse command line arguments
185
+ # Parse command-line arguments
157
186
  args = parser.parse_args()
158
187
 
159
- # Mapping command-line arguments to functions
188
+ # Map command-line arguments to the config values
189
+ config_updates = {}
190
+
191
+ if args.add_siteName is not None:
192
+ config_updates['DEFAULT.add_siteName'] = args.add_siteName
193
+ if args.disable_searchDomain is not None:
194
+ config_updates['DEFAULT.disable_searchDomain'] = args.disable_searchDomain
195
+ if args.not_close is not None:
196
+ config_updates['DEFAULT.not_close'] = args.not_close
197
+ if args.default_video_worker is not None:
198
+ config_updates['M3U8_DOWNLOAD.default_video_worker'] = args.default_video_worker
199
+ if args.default_audio_worker is not None:
200
+ config_updates['M3U8_DOWNLOAD.default_audio_worker'] = args.default_audio_worker
201
+ if args.specific_list_audio is not None:
202
+ config_updates['M3U8_DOWNLOAD.specific_list_audio'] = args.specific_list_audio.split(',')
203
+ if args.specific_list_subtitles is not None:
204
+ config_updates['M3U8_DOWNLOAD.specific_list_subtitles'] = args.specific_list_subtitles.split(',')
205
+
206
+ # Apply the updates to the config file
207
+ for key, value in config_updates.items():
208
+ section, option = key.split('.')
209
+ config_manager.set_key(section, option, value)
210
+
211
+ config_manager.write_config()
212
+
213
+ # Map command-line arguments to functions
160
214
  arg_to_function = {alias: func for alias, (func, _) in search_functions.items()}
161
215
 
162
216
  # Check which argument is provided and run the corresponding function
@@ -188,4 +242,4 @@ def main():
188
242
  run_function(input_to_function[category])
189
243
  else:
190
244
  console.print("[red]Invalid category.")
191
- sys.exit(0)
245
+ sys.exit(0)