StreamingCommunity 3.3.2__py3-none-any.whl → 3.3.3__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.

@@ -0,0 +1,133 @@
1
+ # 18.07.25
2
+
3
+ import os
4
+ import struct
5
+ import logging
6
+ from typing import Optional
7
+
8
+
9
+ # External library
10
+ from rich.console import Console
11
+
12
+
13
+ # Internal utilities
14
+ from .binary_paths import binary_paths
15
+
16
+
17
+ # Variable
18
+ console = Console()
19
+
20
+
21
+ class DeviceDownloader:
22
+ def __init__(self):
23
+ self.base_dir = binary_paths.ensure_binary_directory()
24
+
25
+ def extract_png_chunk(self, png_with_wvd: str, out_wvd_path: str) -> bool:
26
+ """Extract WVD data"""
27
+ try:
28
+ with open(png_with_wvd, "rb") as f:
29
+ data = f.read()
30
+ pos = 8
31
+
32
+ while pos < len(data):
33
+ length = struct.unpack(">I", data[pos:pos+4])[0]
34
+ chunk_type = data[pos+4:pos+8]
35
+ chunk_data = data[pos+8:pos+8+length]
36
+
37
+ if chunk_type == b"stEg":
38
+ with open(out_wvd_path, "wb") as f:
39
+ f.write(chunk_data)
40
+ return True
41
+
42
+ pos += 12 + length
43
+
44
+ return False
45
+
46
+ except Exception as e:
47
+ logging.error(f"Error extracting PNG chunk: {e}")
48
+ return False
49
+
50
+ def _check_existing_wvd(self) -> Optional[str]:
51
+ """Check for existing WVD files in binary directory."""
52
+ try:
53
+ console.print("[cyan]Checking for existing device.wvd files...")
54
+ if not os.path.exists(self.base_dir):
55
+ return None
56
+
57
+ # Look for any .wvd file first
58
+ for file in os.listdir(self.base_dir):
59
+ if file.lower().endswith('.wvd'):
60
+ wvd_path = os.path.join(self.base_dir, file)
61
+ if os.path.exists(wvd_path) and os.path.getsize(wvd_path) > 0:
62
+ logging.info(f"Found existing .wvd file: {file}")
63
+ return wvd_path
64
+
65
+ return None
66
+
67
+ except Exception as e:
68
+ logging.error(f"Error checking existing WVD files: {e}")
69
+ return None
70
+
71
+ def _find_png_recursively(self, start_dir: str = ".") -> Optional[str]:
72
+ """Find crunchyroll_etp_rt.png recursively starting from start_dir."""
73
+ target_filename = "crunchyroll_etp_rt.png"
74
+
75
+ try:
76
+ for root, dirs, files in os.walk(start_dir):
77
+ if target_filename in files:
78
+ png_path = os.path.join(root, target_filename)
79
+ logging.info(f"Found PNG file at: {png_path}")
80
+ return png_path
81
+
82
+ logging.warning(f"PNG file '{target_filename}' not found in '{start_dir}' and subdirectories")
83
+ return None
84
+
85
+ except Exception as e:
86
+ logging.error(f"Error during recursive PNG search: {e}")
87
+ return None
88
+
89
+ def download(self) -> Optional[str]:
90
+ """
91
+ Main method to extract WVD file from PNG.
92
+ """
93
+ try:
94
+ png_path = self._find_png_recursively()
95
+ if not png_path:
96
+ logging.error("PNG file not found, cannot extract device.wvd")
97
+ return None
98
+
99
+ device_wvd_path = os.path.join(self.base_dir, 'device.wvd')
100
+
101
+ if self.extract_png_chunk(png_path, device_wvd_path):
102
+ if os.path.exists(device_wvd_path) and os.path.getsize(device_wvd_path) > 0:
103
+ logging.info("Successfully extracted device.wvd from PNG")
104
+ return device_wvd_path
105
+ else:
106
+ logging.error("Extraction completed but resulting file is invalid")
107
+ return None
108
+ else:
109
+ logging.error("Failed to extract device.wvd from PNG")
110
+ return None
111
+
112
+ except Exception as e:
113
+ logging.error(f"Error during WVD extraction: {e}")
114
+ return None
115
+
116
+
117
+ def check_wvd_path() -> Optional[str]:
118
+ """
119
+ Check for device.wvd file in binary directory and extract from PNG if not found.
120
+ """
121
+ try:
122
+ downloader = DeviceDownloader()
123
+
124
+ existing_wvd = downloader._check_existing_wvd()
125
+ if existing_wvd:
126
+ return existing_wvd
127
+
128
+ logging.info("device.wvd not found, attempting extraction from PNG")
129
+ return downloader.download()
130
+
131
+ except Exception as e:
132
+ logging.error(f"Error checking for device.wvd: {e}")
133
+ return None
@@ -51,38 +51,91 @@ class FFMPEGDownloader:
51
51
 
52
52
  def _check_existing_binaries(self) -> Tuple[Optional[str], Optional[str], Optional[str]]:
53
53
  """
54
- Check if FFmpeg binaries already exist in the base directory.
55
- Enhanced to check both the binary directory and system paths on macOS.
54
+ Check if FFmpeg binaries already exist.
55
+ Order: system PATH (where/which) -> binary directory
56
+
57
+ Returns:
58
+ Tuple[Optional[str], Optional[str], Optional[str]]: Paths to ffmpeg, ffprobe, ffplay
56
59
  """
57
- config = FFMPEG_CONFIGURATION[self.os_name]
58
- executables = config['executables']
59
- found_executables = []
60
-
61
- # For macOS, check both binary directory and system paths
62
- if self.os_name == 'darwin':
63
- potential_paths = [
64
- '/usr/local/bin',
65
- '/opt/homebrew/bin',
66
- '/usr/bin',
67
- self.base_dir
68
- ]
60
+ try:
61
+
62
+ # STEP 1: Check system PATH first
63
+ console.print("[cyan]Checking for FFmpeg in system PATH...[/]")
64
+ if self.os_name == 'windows':
65
+ try:
66
+ ffmpeg_path = subprocess.check_output(
67
+ ['where', 'ffmpeg'], stderr=subprocess.DEVNULL, text=True
68
+ ).strip().split('\n')[0]
69
+
70
+ ffprobe_path = subprocess.check_output(
71
+ ['where', 'ffprobe'], stderr=subprocess.DEVNULL, text=True
72
+ ).strip().split('\n')[0]
73
+
74
+ try:
75
+ ffplay_path = subprocess.check_output(
76
+ ['where', 'ffplay'], stderr=subprocess.DEVNULL, text=True
77
+ ).strip().split('\n')[0]
78
+ except subprocess.CalledProcessError:
79
+ ffplay_path = None
80
+
81
+ return ffmpeg_path, ffprobe_path, ffplay_path
82
+
83
+ except subprocess.CalledProcessError:
84
+ pass
69
85
 
86
+ else:
87
+ ffmpeg_path = shutil.which('ffmpeg')
88
+ ffprobe_path = shutil.which('ffprobe')
89
+ ffplay_path = shutil.which('ffplay')
90
+
91
+ if ffmpeg_path and ffprobe_path:
92
+ return ffmpeg_path, ffprobe_path, ffplay_path
93
+
94
+ # STEP 2: Check in binary directory
95
+ console.print("[cyan]Checking for FFmpeg in binary directory...[/]")
96
+ config = FFMPEG_CONFIGURATION[self.os_name]
97
+ executables = [exe.format(arch=self.arch) for exe in config['executables']]
98
+ found_executables = []
99
+
70
100
  for executable in executables:
71
- found = None
72
- for path in potential_paths:
73
- full_path = os.path.join(path, executable)
74
- if os.path.exists(full_path) and os.access(full_path, os.X_OK):
75
- found = full_path
76
- break
77
- found_executables.append(found)
78
- else:
79
-
80
- # Original behavior for other operating systems
81
- for executable in executables:
82
- exe_paths = glob.glob(os.path.join(self.base_dir, executable))
83
- found_executables.append(exe_paths[0] if exe_paths else None)
84
101
 
85
- return tuple(found_executables) if len(found_executables) == 3 else (None, None, None)
102
+ # Check for exact match first
103
+ exe_paths = glob.glob(os.path.join(self.base_dir, executable))
104
+ if exe_paths:
105
+ found_executables.append(exe_paths[0])
106
+
107
+ else:
108
+ # Check for standard names
109
+ if self.os_name == 'windows':
110
+ if 'ffmpeg' in executable:
111
+ standard_path = os.path.join(self.base_dir, 'ffmpeg.exe')
112
+ elif 'ffprobe' in executable:
113
+ standard_path = os.path.join(self.base_dir, 'ffprobe.exe')
114
+ else:
115
+ standard_path = None
116
+ else:
117
+ if 'ffmpeg' in executable:
118
+ standard_path = os.path.join(self.base_dir, 'ffmpeg')
119
+ elif 'ffprobe' in executable:
120
+ standard_path = os.path.join(self.base_dir, 'ffprobe')
121
+ else:
122
+ standard_path = None
123
+
124
+ if standard_path and os.path.exists(standard_path):
125
+ found_executables.append(standard_path)
126
+ else:
127
+ found_executables.append(None)
128
+
129
+ # Return found executables if we have at least ffmpeg and ffprobe
130
+ if len(found_executables) >= 2 and found_executables[0] and found_executables[1]:
131
+ ffplay_path = found_executables[2] if len(found_executables) > 2 else None
132
+ return found_executables[0], found_executables[1], ffplay_path
133
+
134
+ return (None, None, None)
135
+
136
+ except Exception as e:
137
+ logging.error(f"Error checking existing binaries: {e}")
138
+ return (None, None, None)
86
139
 
87
140
  def _get_latest_version(self, repo: str) -> Optional[str]:
88
141
  """
@@ -210,7 +263,6 @@ class FFMPEGDownloader:
210
263
  ffprobe_path = shutil.which('ffprobe')
211
264
 
212
265
  if ffmpeg_path and ffprobe_path:
213
- console.print("[bold green]FFmpeg successfully installed via apt[/]")
214
266
  return ffmpeg_path, ffprobe_path, None
215
267
  else:
216
268
  console.print("[bold yellow]Failed to install FFmpeg via apt. Proceeding with static download.[/]")
@@ -242,7 +294,6 @@ class FFMPEGDownloader:
242
294
  # Extract the file
243
295
  if self._extract_file(download_path, final_path):
244
296
  successful_extractions.append(final_path)
245
- console.print(f"[bold green]Successfully installed {executable}[/]")
246
297
  else:
247
298
  console.print(f"[bold red]Failed to extract {executable}[/]")
248
299
 
@@ -261,69 +312,23 @@ class FFMPEGDownloader:
261
312
  def check_ffmpeg() -> Tuple[Optional[str], Optional[str], Optional[str]]:
262
313
  """
263
314
  Check for FFmpeg executables in the system and download them if not found.
264
- Enhanced detection for macOS systems.
265
-
315
+ Order: system PATH (where/which) -> binary directory -> download
316
+
266
317
  Returns:
267
- Tuple[Optional[str], Optional[str], Optional[str]]: Paths to ffmpeg, ffprobe, and ffplay executables.
318
+ Tuple[Optional[str], Optional[str], Optional[str]]: Paths to ffmpeg, ffprobe, and ffplay
268
319
  """
269
320
  try:
270
- system_platform = binary_paths.system
321
+ # Create downloader instance to use its existing check method
322
+ downloader = FFMPEGDownloader()
271
323
 
272
- # Special handling for macOS
273
- if system_platform == 'darwin':
274
-
275
- # Common installation paths on macOS
276
- potential_paths = [
277
- '/usr/local/bin', # Homebrew default
278
- '/opt/homebrew/bin', # Apple Silicon Homebrew
279
- '/usr/bin', # System default
280
- binary_paths.get_binary_directory(), # Custom installation
281
- '/Applications/binary' # Custom installation
282
- ]
283
-
284
- for path in potential_paths:
285
- ffmpeg_path = os.path.join(path, 'ffmpeg')
286
- ffprobe_path = os.path.join(path, 'ffprobe')
287
- ffplay_path = os.path.join(path, 'ffplay')
288
-
289
- if (os.path.exists(ffmpeg_path) and os.path.exists(ffprobe_path) and
290
- os.access(ffmpeg_path, os.X_OK) and os.access(ffprobe_path, os.X_OK)):
291
-
292
- # Return found executables, with ffplay being optional
293
- ffplay_path = ffplay_path if os.path.exists(ffplay_path) else None
294
- return ffmpeg_path, ffprobe_path, ffplay_path
295
-
296
- # Windows detection
297
- elif system_platform == 'windows':
298
- try:
299
- ffmpeg_path = subprocess.check_output(
300
- ['where', 'ffmpeg'], stderr=subprocess.DEVNULL, text=True
301
- ).strip().split('\n')[0]
302
-
303
- ffprobe_path = subprocess.check_output(
304
- ['where', 'ffprobe'], stderr=subprocess.DEVNULL, text=True
305
- ).strip().split('\n')[0]
306
-
307
- ffplay_path = subprocess.check_output(
308
- ['where', 'ffplay'], stderr=subprocess.DEVNULL, text=True
309
- ).strip().split('\n')[0]
310
-
311
- return ffmpeg_path, ffprobe_path, ffplay_path
312
-
313
- except subprocess.CalledProcessError:
314
- logging.warning("One or more FFmpeg binaries were not found with command where")
315
-
316
- # Linux detection
317
- else:
318
- ffmpeg_path = shutil.which('ffmpeg')
319
- ffprobe_path = shutil.which('ffprobe')
320
- ffplay_path = shutil.which('ffplay')
321
-
322
- if ffmpeg_path and ffprobe_path:
323
- return ffmpeg_path, ffprobe_path, ffplay_path
324
+ # STEP 1 & 2: Check existing binaries (system PATH + binary directory)
325
+ ffmpeg_path, ffprobe_path, ffplay_path = downloader._check_existing_binaries()
326
+
327
+ # If found, return them
328
+ if ffmpeg_path and ffprobe_path:
329
+ return ffmpeg_path, ffprobe_path, ffplay_path
324
330
 
325
- # If executables were not found, attempt to download FFmpeg
326
- downloader = FFMPEGDownloader()
331
+ # STEP 3: Download if not found
327
332
  return downloader.download()
328
333
 
329
334
  except Exception as e:
@@ -2,17 +2,13 @@
2
2
 
3
3
  import io
4
4
  import os
5
- import glob
6
- import sys
5
+ import time
7
6
  import shutil
8
- import struct
9
7
  import logging
10
8
  import socket
11
9
  import platform
12
10
  import inspect
13
- import subprocess
14
11
  import contextlib
15
- import importlib.metadata
16
12
 
17
13
 
18
14
  # External library
@@ -25,7 +21,7 @@ from pathvalidate import sanitize_filename, sanitize_filepath
25
21
  # Internal utilities
26
22
  from .installer.ffmpeg_install import check_ffmpeg
27
23
  from .installer.bento4_install import check_mp4decrypt
28
- from .installer.binary_paths import binary_paths
24
+ from .installer.device_install import check_wvd_path
29
25
 
30
26
 
31
27
  # Variable
@@ -245,7 +241,7 @@ class OsManager:
245
241
  return False
246
242
 
247
243
 
248
- class InternManager():
244
+ class InternetManager():
249
245
  def format_file_size(self, size_bytes: float) -> str:
250
246
  """
251
247
  Formats a file size from bytes into a human-readable string representation.
@@ -314,83 +310,18 @@ class OsSummary:
314
310
  self.ffprobe_path = None
315
311
  self.ffplay_path = None
316
312
  self.mp4decrypt_path = None
317
-
318
- def check_ffmpeg_location(self, command: list) -> str:
319
- """
320
- Check if a specific executable (ffmpeg or ffprobe) is located using the given command.
321
- Returns the path of the executable or None if not found.
322
- """
323
- try:
324
- result = subprocess.check_output(command, text=True).strip()
325
- return result.split('\n')[0] if result else None
326
-
327
- except subprocess.CalledProcessError:
328
- return None
329
-
330
- def get_library_version(self, lib_name: str):
331
- """
332
- Retrieve the version of a Python library.
333
-
334
- Args:
335
- lib_name (str): The name of the Python library.
336
-
337
- Returns:
338
- str: The library name followed by its version, or `-not installed` if not found.
339
- """
340
- try:
341
- version = importlib.metadata.version(lib_name)
342
- return f"{lib_name}-{version}"
343
-
344
- except importlib.metadata.PackageNotFoundError:
345
- return f"{lib_name}-not installed"
346
-
347
- def install_library(self, lib_name: str):
348
- """
349
- Install a Python library using pip.
350
-
351
- Args:
352
- lib_name (str): The name of the library to install.
353
- """
354
- try:
355
- console.print(f"Installing {lib_name}...", style="bold yellow")
356
- subprocess.check_call([sys.executable, "-m", "pip", "install", lib_name])
357
- console.print(f"{lib_name} installed successfully!", style="bold green")
358
-
359
- except subprocess.CalledProcessError as e:
360
- console.print(f"Failed to install {lib_name}: {e}", style="bold red")
361
- sys.exit(1)
313
+ self.wvd_path = None
314
+ self.init()
362
315
 
363
316
  def init(self):
364
317
 
365
- # Initialize binary paths and check for existing binaries
366
- binary_dir = binary_paths.get_binary_directory()
367
- arch = binary_paths.arch
368
-
369
- # Check for existing FFmpeg binaries in binary directory
370
- if os.path.exists(binary_dir):
371
- ffmpeg_files = glob.glob(os.path.join(binary_dir, f'*ffmpeg*{arch}*'))
372
- ffprobe_files = glob.glob(os.path.join(binary_dir, f'*ffprobe*{arch}*'))
373
-
374
- if ffmpeg_files and ffprobe_files:
375
- self.ffmpeg_path = ffmpeg_files[0]
376
- self.ffprobe_path = ffprobe_files[0]
377
- else:
378
- self.ffmpeg_path, self.ffprobe_path, self.ffplay_path = check_ffmpeg()
379
- else:
380
- self.ffmpeg_path, self.ffprobe_path, self.ffplay_path = check_ffmpeg()
381
-
382
- # Check mp4decrypt
318
+ # Check for binaries
319
+ self.ffmpeg_path, self.ffprobe_path, _ = check_ffmpeg()
383
320
  self.mp4decrypt_path = check_mp4decrypt()
384
-
385
- # Validate required binaries
386
- if not self.ffmpeg_path or not self.ffprobe_path:
387
- console.log("[red]Can't locate ffmpeg or ffprobe")
388
- sys.exit(0)
389
-
390
- if not self.mp4decrypt_path:
391
- console.log("[yellow]Warning: mp4decrypt not found")
321
+ self.wvd_path = check_wvd_path()
392
322
 
393
323
  self._display_binary_paths()
324
+ time.sleep(0.3)
394
325
 
395
326
  def _display_binary_paths(self):
396
327
  """Display the paths of all detected binaries."""
@@ -398,7 +329,7 @@ class OsSummary:
398
329
  'ffmpeg': self.ffmpeg_path,
399
330
  'ffprobe': self.ffprobe_path,
400
331
  'mp4decrypt': self.mp4decrypt_path,
401
- 'wvd': get_wvd_path()
332
+ 'wvd': self.wvd_path
402
333
  }
403
334
 
404
335
  path_strings = []
@@ -409,8 +340,9 @@ class OsSummary:
409
340
  console.print(f"[cyan]Path: {', [white]'.join(path_strings)}")
410
341
 
411
342
 
343
+ # Initialize the os_summary, internet_manager, and os_manager when the module is imported
412
344
  os_manager = OsManager()
413
- internet_manager = InternManager()
345
+ internet_manager = InternetManager()
414
346
  os_summary = OsSummary()
415
347
 
416
348
 
@@ -419,29 +351,6 @@ def suppress_output():
419
351
  with contextlib.redirect_stdout(io.StringIO()):
420
352
  yield
421
353
 
422
- def extract_png_chunk(png_with_wvd, out_wvd_path):
423
- with open(png_with_wvd, "rb") as f:
424
- data = f.read()
425
- pos = 8
426
-
427
- while pos < len(data):
428
- length = struct.unpack(">I", data[pos:pos+4])[0]
429
- chunk_type = data[pos+4:pos+8]
430
- chunk_data = data[pos+8:pos+8+length]
431
-
432
- if chunk_type == b"stEg":
433
- with open(out_wvd_path, "wb") as f:
434
- f.write(chunk_data)
435
- return
436
-
437
- pos += 12 + length
438
-
439
-
440
- def _g(_=None):
441
- a = [100,101,118,105,99,101,46,119,118,100]
442
- return ''.join(map(chr, a))
443
-
444
-
445
354
  def get_call_stack():
446
355
  """Retrieves the current call stack with details about each call."""
447
356
  stack = inspect.stack()
@@ -478,29 +387,5 @@ def get_mp4decrypt_path():
478
387
  return os_summary.mp4decrypt_path
479
388
 
480
389
  def get_wvd_path():
481
- """
482
- Searches the system's binary folder and returns the path of the first file ending with 'wvd'.
483
- Returns None if not found.
484
- """
485
- binary_dir = binary_paths.get_binary_directory()
486
-
487
- if not os.path.exists(binary_dir):
488
- return None
489
-
490
- for file in os.listdir(binary_dir):
491
- if file.lower().endswith('wvd'):
492
- return os.path.join(binary_dir, file)
493
-
494
- png_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), ".github", ".site", "img", "crunchyroll_etp_rt.png")
495
- out_wvd_path = os.path.join(binary_dir, _g())
496
-
497
- if os.path.exists(png_path):
498
- try:
499
- extract_png_chunk(png_path, out_wvd_path)
500
- if os.path.exists(out_wvd_path):
501
- return out_wvd_path
502
-
503
- except Exception:
504
- pass
505
-
506
- return None
390
+ """Returns the path of wvd."""
391
+ return os_summary.wvd_path
StreamingCommunity/run.py CHANGED
@@ -24,7 +24,7 @@ from rich.prompt import Prompt
24
24
  from .global_search import global_search
25
25
  from StreamingCommunity.Util.message import start_message
26
26
  from StreamingCommunity.Util.config_json import config_manager
27
- from StreamingCommunity.Util.os import os_summary, internet_manager, os_manager
27
+ from StreamingCommunity.Util.os import internet_manager, os_manager
28
28
  from StreamingCommunity.Util.logger import Logger
29
29
  from StreamingCommunity.Lib.TMBD import tmdb
30
30
  from StreamingCommunity.Upload.update import update as git_update
@@ -97,7 +97,6 @@ def load_search_functions() -> Dict[str, Tuple]:
97
97
  def initialize():
98
98
  """Initialize the application with system checks and setup."""
99
99
  start_message()
100
- os_summary.init()
101
100
 
102
101
  # Windows 7 terminal size fix
103
102
  if platform.system() == "Windows" and "7" in platform.version():
@@ -107,7 +106,7 @@ def initialize():
107
106
  if sys.version_info < (3, 7):
108
107
  console.log("[red]Install python version > 3.7.16")
109
108
  sys.exit(0)
110
-
109
+
111
110
  # Show trending content
112
111
  if SHOW_TRENDING:
113
112
  print()