StreamingCommunity 1.7.6__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 (97) hide show
  1. StreamingCommunity/Src/Api/Player/Helper/Vixcloud/js_parser.py +140 -0
  2. StreamingCommunity/Src/Api/Player/Helper/Vixcloud/util.py +166 -0
  3. StreamingCommunity/Src/Api/Player/ddl.py +89 -0
  4. StreamingCommunity/Src/Api/Player/maxstream.py +151 -0
  5. StreamingCommunity/Src/Api/Player/supervideo.py +194 -0
  6. StreamingCommunity/Src/Api/Player/vixcloud.py +212 -0
  7. StreamingCommunity/Src/Api/Site/1337xx/__init__.py +50 -0
  8. StreamingCommunity/Src/Api/Site/1337xx/costant.py +15 -0
  9. StreamingCommunity/Src/Api/Site/1337xx/site.py +84 -0
  10. StreamingCommunity/Src/Api/Site/1337xx/title.py +66 -0
  11. StreamingCommunity/Src/Api/Site/altadefinizione/__init__.py +50 -0
  12. StreamingCommunity/Src/Api/Site/altadefinizione/costant.py +15 -0
  13. StreamingCommunity/Src/Api/Site/altadefinizione/film.py +69 -0
  14. StreamingCommunity/Src/Api/Site/altadefinizione/site.py +86 -0
  15. StreamingCommunity/Src/Api/Site/animeunity/__init__.py +50 -0
  16. StreamingCommunity/Src/Api/Site/animeunity/anime.py +126 -0
  17. StreamingCommunity/Src/Api/Site/animeunity/costant.py +15 -0
  18. StreamingCommunity/Src/Api/Site/animeunity/film_serie.py +131 -0
  19. StreamingCommunity/Src/Api/Site/animeunity/site.py +165 -0
  20. StreamingCommunity/Src/Api/Site/animeunity/util/ScrapeSerie.py +97 -0
  21. StreamingCommunity/Src/Api/Site/bitsearch/__init__.py +51 -0
  22. StreamingCommunity/Src/Api/Site/bitsearch/costant.py +15 -0
  23. StreamingCommunity/Src/Api/Site/bitsearch/site.py +84 -0
  24. StreamingCommunity/Src/Api/Site/bitsearch/title.py +47 -0
  25. StreamingCommunity/Src/Api/Site/cb01new/__init__.py +51 -0
  26. StreamingCommunity/Src/Api/Site/cb01new/costant.py +15 -0
  27. StreamingCommunity/Src/Api/Site/cb01new/film.py +69 -0
  28. StreamingCommunity/Src/Api/Site/cb01new/site.py +74 -0
  29. StreamingCommunity/Src/Api/Site/ddlstreamitaly/Player/ScrapeSerie.py +83 -0
  30. StreamingCommunity/Src/Api/Site/ddlstreamitaly/__init__.py +57 -0
  31. StreamingCommunity/Src/Api/Site/ddlstreamitaly/costant.py +16 -0
  32. StreamingCommunity/Src/Api/Site/ddlstreamitaly/series.py +142 -0
  33. StreamingCommunity/Src/Api/Site/ddlstreamitaly/site.py +93 -0
  34. StreamingCommunity/Src/Api/Site/ddlstreamitaly/util/ScrapeSerie.py +83 -0
  35. StreamingCommunity/Src/Api/Site/guardaserie/Player/ScrapeSerie.py +110 -0
  36. StreamingCommunity/Src/Api/Site/guardaserie/__init__.py +52 -0
  37. StreamingCommunity/Src/Api/Site/guardaserie/costant.py +15 -0
  38. StreamingCommunity/Src/Api/Site/guardaserie/series.py +195 -0
  39. StreamingCommunity/Src/Api/Site/guardaserie/site.py +84 -0
  40. StreamingCommunity/Src/Api/Site/guardaserie/util/ScrapeSerie.py +110 -0
  41. StreamingCommunity/Src/Api/Site/mostraguarda/__init__.py +48 -0
  42. StreamingCommunity/Src/Api/Site/mostraguarda/costant.py +15 -0
  43. StreamingCommunity/Src/Api/Site/mostraguarda/film.py +94 -0
  44. StreamingCommunity/Src/Api/Site/piratebays/__init__.py +50 -0
  45. StreamingCommunity/Src/Api/Site/piratebays/costant.py +15 -0
  46. StreamingCommunity/Src/Api/Site/piratebays/site.py +89 -0
  47. StreamingCommunity/Src/Api/Site/piratebays/title.py +45 -0
  48. StreamingCommunity/Src/Api/Site/streamingcommunity/__init__.py +55 -0
  49. StreamingCommunity/Src/Api/Site/streamingcommunity/costant.py +15 -0
  50. StreamingCommunity/Src/Api/Site/streamingcommunity/film.py +70 -0
  51. StreamingCommunity/Src/Api/Site/streamingcommunity/series.py +203 -0
  52. StreamingCommunity/Src/Api/Site/streamingcommunity/site.py +126 -0
  53. StreamingCommunity/Src/Api/Site/streamingcommunity/util/ScrapeSerie.py +113 -0
  54. StreamingCommunity/Src/Api/Template/Class/SearchType.py +101 -0
  55. StreamingCommunity/Src/Api/Template/Util/__init__.py +5 -0
  56. StreamingCommunity/Src/Api/Template/Util/get_domain.py +137 -0
  57. StreamingCommunity/Src/Api/Template/Util/manage_ep.py +153 -0
  58. StreamingCommunity/Src/Api/Template/Util/recall_search.py +37 -0
  59. StreamingCommunity/Src/Api/Template/__init__.py +3 -0
  60. StreamingCommunity/Src/Api/Template/site.py +87 -0
  61. StreamingCommunity/Src/Lib/Downloader/HLS/downloader.py +968 -0
  62. StreamingCommunity/Src/Lib/Downloader/HLS/proxyes.py +110 -0
  63. StreamingCommunity/Src/Lib/Downloader/HLS/segments.py +540 -0
  64. StreamingCommunity/Src/Lib/Downloader/MP4/downloader.py +156 -0
  65. StreamingCommunity/Src/Lib/Downloader/TOR/downloader.py +222 -0
  66. StreamingCommunity/Src/Lib/Downloader/__init__.py +5 -0
  67. StreamingCommunity/Src/Lib/Driver/driver_1.py +76 -0
  68. StreamingCommunity/Src/Lib/FFmpeg/__init__.py +4 -0
  69. StreamingCommunity/Src/Lib/FFmpeg/capture.py +170 -0
  70. StreamingCommunity/Src/Lib/FFmpeg/command.py +292 -0
  71. StreamingCommunity/Src/Lib/FFmpeg/util.py +242 -0
  72. StreamingCommunity/Src/Lib/M3U8/__init__.py +6 -0
  73. StreamingCommunity/Src/Lib/M3U8/decryptor.py +129 -0
  74. StreamingCommunity/Src/Lib/M3U8/estimator.py +173 -0
  75. StreamingCommunity/Src/Lib/M3U8/parser.py +666 -0
  76. StreamingCommunity/Src/Lib/M3U8/url_fixer.py +52 -0
  77. StreamingCommunity/Src/Lib/TMBD/__init__.py +2 -0
  78. StreamingCommunity/Src/Lib/TMBD/obj_tmbd.py +39 -0
  79. StreamingCommunity/Src/Lib/TMBD/tmdb.py +346 -0
  80. StreamingCommunity/Src/Upload/update.py +64 -0
  81. StreamingCommunity/Src/Upload/version.py +5 -0
  82. StreamingCommunity/Src/Util/_jsonConfig.py +204 -0
  83. StreamingCommunity/Src/Util/call_stack.py +42 -0
  84. StreamingCommunity/Src/Util/color.py +20 -0
  85. StreamingCommunity/Src/Util/console.py +12 -0
  86. StreamingCommunity/Src/Util/headers.py +147 -0
  87. StreamingCommunity/Src/Util/logger.py +53 -0
  88. StreamingCommunity/Src/Util/message.py +46 -0
  89. StreamingCommunity/Src/Util/os.py +417 -0
  90. StreamingCommunity/Src/Util/table.py +163 -0
  91. StreamingCommunity/run.py +196 -0
  92. StreamingCommunity-1.7.6.dist-info/LICENSE +674 -0
  93. StreamingCommunity-1.7.6.dist-info/METADATA +348 -0
  94. StreamingCommunity-1.7.6.dist-info/RECORD +97 -0
  95. StreamingCommunity-1.7.6.dist-info/WHEEL +5 -0
  96. StreamingCommunity-1.7.6.dist-info/entry_points.txt +2 -0
  97. StreamingCommunity-1.7.6.dist-info/top_level.txt +1 -0
@@ -0,0 +1,292 @@
1
+ # 31.01.24
2
+
3
+ import sys
4
+ import time
5
+ import logging
6
+ import subprocess
7
+ from typing import List, Dict
8
+
9
+
10
+ # Internal utilities
11
+ from StreamingCommunity.Src.Util._jsonConfig import config_manager
12
+ from StreamingCommunity.Src.Util.os import os_manager, suppress_output
13
+ from StreamingCommunity.Src.Util.console import console
14
+ from .util import need_to_force_to_ts, check_duration_v_a
15
+ from .capture import capture_ffmpeg_real_time
16
+ from ..M3U8 import M3U8_Codec
17
+
18
+
19
+ # Config
20
+ DEBUG_MODE = config_manager.get_bool("DEFAULT", "debug")
21
+ DEBUG_FFMPEG = "debug" if DEBUG_MODE else "error"
22
+ USE_CODEC = config_manager.get_bool("M3U8_CONVERSION", "use_codec")
23
+ USE_VCODEC = config_manager.get_bool("M3U8_CONVERSION", "use_vcodec")
24
+ USE_ACODEC = config_manager.get_bool("M3U8_CONVERSION", "use_acodec")
25
+ USE_BITRATE = config_manager.get_bool("M3U8_CONVERSION", "use_bitrate")
26
+ USE_GPU = config_manager.get_bool("M3U8_CONVERSION", "use_gpu")
27
+ FFMPEG_DEFAULT_PRESET = config_manager.get("M3U8_CONVERSION", "default_preset")
28
+
29
+
30
+ # Variable
31
+ TQDM_USE_LARGE_BAR = config_manager.get_int('M3U8_DOWNLOAD', 'tqdm_use_large_bar')
32
+
33
+
34
+ def join_video(video_path: str, out_path: str, codec: M3U8_Codec = None):
35
+
36
+ """
37
+ Joins single ts video file to mp4
38
+
39
+ Parameters:
40
+ - video_path (str): The path to the video file.
41
+ - out_path (str): The path to save the output file.
42
+ - vcodec (str): The video codec to use. Defaults to 'copy'.
43
+ - acodec (str): The audio codec to use. Defaults to 'aac'.
44
+ - bitrate (str): The bitrate for the audio stream. Defaults to '192k'.
45
+ - force_ts (bool): Force video path to be mpegts as input.
46
+ """
47
+
48
+ if not os_manager.check_file(video_path):
49
+ logging.error("Missing input video for ffmpeg conversion.")
50
+ sys.exit(0)
51
+
52
+ # Start command
53
+ ffmpeg_cmd = ['ffmpeg']
54
+
55
+ # Enabled the use of gpu
56
+ if USE_GPU:
57
+ ffmpeg_cmd.extend(['-hwaccel', 'cuda'])
58
+
59
+ # Add mpegts to force to detect input file as ts file
60
+ if need_to_force_to_ts(video_path):
61
+ console.log("[red]Force input file to 'mpegts'.")
62
+ ffmpeg_cmd.extend(['-f', 'mpegts'])
63
+ vcodec = "libx264"
64
+
65
+ # Insert input video path
66
+ ffmpeg_cmd.extend(['-i', video_path])
67
+
68
+ # Add output Parameters
69
+ if USE_CODEC and codec != None:
70
+ if USE_VCODEC:
71
+ if codec.video_codec_name:
72
+ if not USE_GPU:
73
+ ffmpeg_cmd.extend(['-c:v', codec.video_codec_name])
74
+ else:
75
+ ffmpeg_cmd.extend(['-c:v', 'h264_nvenc'])
76
+ else:
77
+ console.log("[red]Cant find vcodec for 'join_audios'")
78
+ else:
79
+ if USE_GPU:
80
+ ffmpeg_cmd.extend(['-c:v', 'h264_nvenc'])
81
+
82
+
83
+ if USE_ACODEC:
84
+ if codec.audio_codec_name:
85
+ ffmpeg_cmd.extend(['-c:a', codec.audio_codec_name])
86
+ else:
87
+ console.log("[red]Cant find acodec for 'join_audios'")
88
+
89
+ if USE_BITRATE:
90
+ ffmpeg_cmd.extend(['-b:v', f'{codec.video_bitrate // 1000}k'])
91
+ ffmpeg_cmd.extend(['-b:a', f'{codec.audio_bitrate // 1000}k'])
92
+
93
+ else:
94
+ ffmpeg_cmd.extend(['-c', 'copy'])
95
+
96
+ # Ultrafast preset always or fast for gpu
97
+ if not USE_GPU:
98
+ ffmpeg_cmd.extend(['-preset', FFMPEG_DEFAULT_PRESET])
99
+ else:
100
+ ffmpeg_cmd.extend(['-preset', 'fast'])
101
+
102
+ # Overwrite
103
+ ffmpeg_cmd += [out_path, "-y"]
104
+
105
+ # Run join
106
+ if DEBUG_MODE:
107
+ subprocess.run(ffmpeg_cmd, check=True)
108
+ else:
109
+
110
+ if TQDM_USE_LARGE_BAR:
111
+ capture_ffmpeg_real_time(ffmpeg_cmd, "[cyan]Join video")
112
+ print()
113
+
114
+ else:
115
+ console.log(f"[purple]FFmpeg [white][[cyan]Join video[white]] ...")
116
+ with suppress_output():
117
+ capture_ffmpeg_real_time(ffmpeg_cmd, "[cyan]Join video")
118
+ print()
119
+
120
+ time.sleep(0.5)
121
+ if not os_manager.check_file(out_path):
122
+ logging.error("Missing output video for ffmpeg conversion video.")
123
+ sys.exit(0)
124
+
125
+
126
+ def join_audios(video_path: str, audio_tracks: List[Dict[str, str]], out_path: str, codec: M3U8_Codec = None):
127
+ """
128
+ Joins audio tracks with a video file using FFmpeg.
129
+
130
+ Parameters:
131
+ - video_path (str): The path to the video file.
132
+ - audio_tracks (list[dict[str, str]]): A list of dictionaries containing information about audio tracks.
133
+ Each dictionary should contain the 'path' key with the path to the audio file.
134
+ - out_path (str): The path to save the output file.
135
+ """
136
+
137
+ if not os_manager.check_file(video_path):
138
+ logging.error("Missing input video for ffmpeg conversion.")
139
+ sys.exit(0)
140
+
141
+ video_audio_same_duration = check_duration_v_a(video_path, audio_tracks[0].get('path'))
142
+
143
+ # Start command
144
+ ffmpeg_cmd = ['ffmpeg']
145
+
146
+ # Enabled the use of gpu
147
+ if USE_GPU:
148
+ ffmpeg_cmd.extend(['-hwaccel', 'cuda'])
149
+
150
+ # Insert input video path
151
+ ffmpeg_cmd.extend(['-i', video_path])
152
+
153
+ # Add audio tracks as input
154
+ for i, audio_track in enumerate(audio_tracks):
155
+ if os_manager.check_file(audio_track.get('path')):
156
+ ffmpeg_cmd.extend(['-i', audio_track.get('path')])
157
+ else:
158
+ logging.error(f"Skip audio join: {audio_track.get('path')} dont exist")
159
+
160
+ # Map the video and audio streams
161
+ ffmpeg_cmd.append('-map')
162
+ ffmpeg_cmd.append('0:v') # Map video stream from the first input (video_path)
163
+
164
+ for i in range(1, len(audio_tracks) + 1):
165
+ ffmpeg_cmd.append('-map')
166
+ ffmpeg_cmd.append(f'{i}:a') # Map audio streams from subsequent inputs
167
+
168
+ # Add output Parameters
169
+ if USE_CODEC:
170
+ if USE_VCODEC:
171
+ if codec.video_codec_name:
172
+ if not USE_GPU:
173
+ ffmpeg_cmd.extend(['-c:v', codec.video_codec_name])
174
+ else:
175
+ ffmpeg_cmd.extend(['-c:v', 'h264_nvenc'])
176
+ else:
177
+ console.log("[red]Cant find vcodec for 'join_audios'")
178
+ else:
179
+ if USE_GPU:
180
+ ffmpeg_cmd.extend(['-c:v', 'h264_nvenc'])
181
+
182
+ if USE_ACODEC:
183
+ if codec.audio_codec_name:
184
+ ffmpeg_cmd.extend(['-c:a', codec.audio_codec_name])
185
+ else:
186
+ console.log("[red]Cant find acodec for 'join_audios'")
187
+
188
+ if USE_BITRATE:
189
+ ffmpeg_cmd.extend(['-b:v', f'{codec.video_bitrate // 1000}k'])
190
+ ffmpeg_cmd.extend(['-b:a', f'{codec.audio_bitrate // 1000}k'])
191
+
192
+ else:
193
+ ffmpeg_cmd.extend(['-c', 'copy'])
194
+
195
+ # Ultrafast preset always or fast for gpu
196
+ if not USE_GPU:
197
+ ffmpeg_cmd.extend(['-preset', FFMPEG_DEFAULT_PRESET])
198
+ else:
199
+ ffmpeg_cmd.extend(['-preset', 'fast'])
200
+
201
+ # Use shortest input path for video and audios
202
+ if not video_audio_same_duration:
203
+ logging.info("[red]Use shortest input.")
204
+ ffmpeg_cmd.extend(['-shortest', '-strict', 'experimental'])
205
+
206
+ # Overwrite
207
+ ffmpeg_cmd += [out_path, "-y"]
208
+
209
+ # Run join
210
+ if DEBUG_MODE:
211
+ subprocess.run(ffmpeg_cmd, check=True)
212
+ else:
213
+
214
+ if TQDM_USE_LARGE_BAR:
215
+ capture_ffmpeg_real_time(ffmpeg_cmd, "[cyan]Join audio")
216
+ print()
217
+
218
+ else:
219
+ console.log(f"[purple]FFmpeg [white][[cyan]Join audio[white]] ...")
220
+ with suppress_output():
221
+ capture_ffmpeg_real_time(ffmpeg_cmd, "[cyan]Join audio")
222
+ print()
223
+
224
+ time.sleep(0.5)
225
+ if not os_manager.check_file(out_path):
226
+ logging.error("Missing output video for ffmpeg conversion audio.")
227
+ sys.exit(0)
228
+
229
+
230
+ def join_subtitle(video_path: str, subtitles_list: List[Dict[str, str]], out_path: str):
231
+ """
232
+ Joins subtitles with a video file using FFmpeg.
233
+
234
+ Parameters:
235
+ - video (str): The path to the video file.
236
+ - subtitles_list (list[dict[str, str]]): A list of dictionaries containing information about subtitles.
237
+ Each dictionary should contain the 'path' key with the path to the subtitle file and the 'name' key with the name of the subtitle.
238
+ - out_path (str): The path to save the output file.
239
+ """
240
+
241
+ if not os_manager.check_file(video_path):
242
+ logging.error("Missing input video for ffmpeg conversion.")
243
+ sys.exit(0)
244
+
245
+ # Start command
246
+ ffmpeg_cmd = ["ffmpeg", "-i", video_path]
247
+
248
+ # Add subtitle input files first
249
+ for subtitle in subtitles_list:
250
+ if os_manager.check_file(subtitle.get('path')):
251
+ ffmpeg_cmd += ["-i", subtitle['path']]
252
+ else:
253
+ logging.error(f"Skip subtitle join: {subtitle.get('path')} doesn't exist")
254
+
255
+ # Add maps for video and audio streams
256
+ ffmpeg_cmd += ["-map", "0:v", "-map", "0:a"]
257
+
258
+ # Add subtitle maps and metadata
259
+ for idx, subtitle in enumerate(subtitles_list):
260
+ ffmpeg_cmd += ["-map", f"{idx + 1}:s"]
261
+ ffmpeg_cmd += ["-metadata:s:s:{}".format(idx), "title={}".format(subtitle['language'])]
262
+
263
+ # Add output Parameters
264
+ if USE_CODEC:
265
+ ffmpeg_cmd.extend(['-c:v', 'copy', '-c:a', 'copy', '-c:s', 'mov_text'])
266
+ else:
267
+ ffmpeg_cmd.extend(['-c', 'copy', '-c:s', 'mov_text'])
268
+
269
+ # Overwrite
270
+ ffmpeg_cmd += [out_path, "-y"]
271
+ logging.info(f"FFmpeg command: {ffmpeg_cmd}")
272
+
273
+
274
+ # Run join
275
+ if DEBUG_MODE:
276
+ subprocess.run(ffmpeg_cmd, check=True)
277
+ else:
278
+
279
+ if TQDM_USE_LARGE_BAR:
280
+ capture_ffmpeg_real_time(ffmpeg_cmd, "[cyan]Join subtitle")
281
+ print()
282
+
283
+ else:
284
+ console.log(f"[purple]FFmpeg [white][[cyan]Join subtitle[white]] ...")
285
+ with suppress_output():
286
+ capture_ffmpeg_real_time(ffmpeg_cmd, "[cyan]Join subtitle")
287
+ print()
288
+
289
+ time.sleep(0.5)
290
+ if not os_manager.check_file(out_path):
291
+ logging.error("Missing output video for ffmpeg conversion subtitle.")
292
+ sys.exit(0)
@@ -0,0 +1,242 @@
1
+ # 16.04.24
2
+
3
+ import os
4
+ import sys
5
+ import json
6
+ import subprocess
7
+ import logging
8
+ from typing import Tuple
9
+
10
+
11
+ # Internal utilities
12
+ from StreamingCommunity.Src.Util.console import console
13
+
14
+
15
+ def has_audio_stream(video_path: str) -> bool:
16
+ """
17
+ Check if the input video has an audio stream.
18
+
19
+ Parameters:
20
+ - video_path (str): Path to the input video file.
21
+
22
+ Returns:
23
+ has_audio (bool): True if the input video has an audio stream, False otherwise.
24
+ """
25
+ try:
26
+ ffprobe_cmd = ['ffprobe', '-v', 'error', '-print_format', 'json', '-select_streams', 'a', '-show_streams', video_path]
27
+ logging.info(f"FFmpeg command: {ffprobe_cmd}")
28
+
29
+ with subprocess.Popen(ffprobe_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) as proc:
30
+ stdout, stderr = proc.communicate()
31
+ if stderr:
32
+ logging.error(f"Error: {stderr}")
33
+ else:
34
+ probe_result = json.loads(stdout)
35
+ return bool(probe_result.get('streams', []))
36
+
37
+ except Exception as e:
38
+ logging.error(f"Error: {e}")
39
+ return False
40
+
41
+
42
+ def get_video_duration(file_path: str) -> float:
43
+ """
44
+ Get the duration of a video file.
45
+
46
+ Parameters:
47
+ - file_path (str): The path to the video file.
48
+
49
+ Returns:
50
+ (float): The duration of the video in seconds if successful,
51
+ None if there's an error.
52
+ """
53
+
54
+ try:
55
+ ffprobe_cmd = ['ffprobe', '-v', 'error', '-show_format', '-print_format', 'json', file_path]
56
+ logging.info(f"FFmpeg command: {ffprobe_cmd}")
57
+
58
+ # Use a with statement to ensure the subprocess is cleaned up properly
59
+ with subprocess.Popen(ffprobe_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) as proc:
60
+ stdout, stderr = proc.communicate()
61
+
62
+ if proc.returncode != 0:
63
+ logging.error(f"Error: {stderr}")
64
+ return None
65
+
66
+ # Parse JSON output
67
+ probe_result = json.loads(stdout)
68
+
69
+ # Extract duration from the video information
70
+ try:
71
+ return float(probe_result['format']['duration'])
72
+ except:
73
+ logging.error("Cant get duration.")
74
+ return 1
75
+
76
+ except Exception as e:
77
+ logging.error(f"Error get video duration: {e}")
78
+ sys.exit(0)
79
+
80
+ def get_video_duration_s(filename):
81
+ """
82
+ Get the duration of a video file using ffprobe.
83
+
84
+ Parameters:
85
+ - filename (str): Path to the video file (e.g., 'sim.mp4')
86
+
87
+ Returns:
88
+ - duration (float): Duration of the video in seconds, or None if an error occurs.
89
+ """
90
+ ffprobe_cmd = ['ffprobe', '-v', 'error', '-show_entries', 'format=duration', '-of', 'default=noprint_wrappers=1:nokey=1', filename]
91
+
92
+ try:
93
+
94
+ # Run ffprobe command and capture output
95
+ result = subprocess.run(ffprobe_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, text=True)
96
+
97
+ # Extract duration from the output
98
+ duration_str = result.stdout.strip()
99
+ duration = float(duration_str) # Convert duration to float
100
+
101
+ return int(duration)
102
+
103
+ except subprocess.CalledProcessError as e:
104
+ print(f"Error running ffprobe: {e}")
105
+ return None
106
+ except ValueError as e:
107
+ print(f"Error converting duration to float: {e}")
108
+ return None
109
+
110
+
111
+ def format_duration(seconds: float) -> Tuple[int, int, int]:
112
+ """
113
+ Format duration in seconds into hours, minutes, and seconds.
114
+
115
+ Parameters:
116
+ - seconds (float): Duration in seconds.
117
+
118
+ Returns:
119
+ list[int, int, int]: List containing hours, minutes, and seconds.
120
+ """
121
+
122
+ hours, remainder = divmod(seconds, 3600)
123
+ minutes, seconds = divmod(remainder, 60)
124
+
125
+ return int(hours), int(minutes), int(seconds)
126
+
127
+
128
+ def print_duration_table(file_path: str, description: str = "Duration", return_string: bool = False):
129
+ """
130
+ Print the duration of a video file in hours, minutes, and seconds, or return it as a formatted string.
131
+
132
+ Parameters:
133
+ - file_path (str): The path to the video file.
134
+ - description (str): Optional description to be included in the output. Defaults to "Duration". If not provided, the duration will not be printed.
135
+ - return_string (bool): If True, returns the formatted duration string. If False, returns a dictionary with hours, minutes, and seconds.
136
+
137
+ Returns:
138
+ - str: The formatted duration string if return_string is True.
139
+ - dict: A dictionary with keys 'h', 'm', 's' representing hours, minutes, and seconds if return_string is False.
140
+ """
141
+
142
+ video_duration = get_video_duration(file_path)
143
+
144
+ if video_duration is not None:
145
+ hours, minutes, seconds = format_duration(video_duration)
146
+ formatted_duration = f"[yellow]{int(hours)}[red]h [yellow]{int(minutes)}[red]m [yellow]{int(seconds)}[red]s"
147
+ duration_dict = {'h': hours, 'm': minutes, 's': seconds}
148
+
149
+ if description:
150
+ console.print(f"[cyan]{description} for [white]([green]{os.path.basename(file_path)}[white]): {formatted_duration}")
151
+ else:
152
+ if return_string:
153
+ return formatted_duration
154
+ else:
155
+ return duration_dict
156
+
157
+
158
+ def get_ffprobe_info(file_path):
159
+ """
160
+ Get format and codec information for a media file using ffprobe.
161
+
162
+ Parameters:
163
+ file_path (str): Path to the media file.
164
+
165
+ Returns:
166
+ dict: A dictionary containing the format name and a list of codec names.
167
+ """
168
+ try:
169
+ result = subprocess.run(
170
+ ['ffprobe', '-v', 'error', '-show_format', '-show_streams', '-print_format', 'json', file_path],
171
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True
172
+ )
173
+ output = result.stdout
174
+ info = json.loads(output)
175
+
176
+ format_name = info['format']['format_name'] if 'format' in info else None
177
+ codec_names = [stream['codec_name'] for stream in info['streams']] if 'streams' in info else []
178
+
179
+ return {
180
+ 'format_name': format_name,
181
+ 'codec_names': codec_names
182
+ }
183
+
184
+ except subprocess.CalledProcessError as e:
185
+ logging.error(f"ffprobe failed for file {file_path}: {e}")
186
+ return None
187
+
188
+ except json.JSONDecodeError as e:
189
+ logging.error(f"Failed to parse JSON output from ffprobe for file {file_path}: {e}")
190
+ return None
191
+
192
+
193
+ def is_png_format_or_codec(file_info):
194
+ """
195
+ Check if the format is 'png_pipe' or if any codec is 'png'.
196
+
197
+ Parameters:
198
+ file_info (dict): The dictionary containing file information.
199
+
200
+ Returns:
201
+ bool: True if the format is 'png_pipe' or any codec is 'png', otherwise False.
202
+ """
203
+ if not file_info:
204
+ return False
205
+ return file_info['format_name'] == 'png_pipe' or 'png' in file_info['codec_names']
206
+
207
+
208
+ def need_to_force_to_ts(file_path):
209
+ """
210
+ Get if a file to TS format if it is in PNG format or contains a PNG codec.
211
+
212
+ Parameters:
213
+ file_path (str): Path to the input media file.
214
+ """
215
+ logging.info(f"Processing file: {file_path}")
216
+ file_info = get_ffprobe_info(file_path)
217
+
218
+ if is_png_format_or_codec(file_info):
219
+ return True
220
+ return False
221
+
222
+
223
+ def check_duration_v_a(video_path, audio_path):
224
+ """
225
+ Check if the duration of the video and audio matches.
226
+
227
+ Parameters:
228
+ - video_path (str): Path to the video file.
229
+ - audio_path (str): Path to the audio file.
230
+
231
+ Returns:
232
+ - bool: True if the duration of the video and audio matches, False otherwise.
233
+ """
234
+
235
+ # Ottieni la durata del video
236
+ video_duration = get_video_duration(video_path)
237
+
238
+ # Ottieni la durata dell'audio
239
+ audio_duration = get_video_duration(audio_path)
240
+
241
+ # Verifica se le durate corrispondono
242
+ return video_duration == audio_duration
@@ -0,0 +1,6 @@
1
+ # 02.04.24
2
+
3
+ from .decryptor import M3U8_Decryption
4
+ from .estimator import M3U8_Ts_Estimator
5
+ from .parser import M3U8_Parser, M3U8_Codec
6
+ from .url_fixer import M3U8_UrlFix
@@ -0,0 +1,129 @@
1
+ # 03.04.24
2
+
3
+ import sys
4
+ import logging
5
+ import subprocess
6
+ import importlib.util
7
+
8
+
9
+ # Internal utilities
10
+ from StreamingCommunity.Src.Util.console import console
11
+
12
+
13
+ # Check if Crypto module is installed
14
+ crypto_spec = importlib.util.find_spec("Crypto")
15
+ crypto_installed = crypto_spec is not None
16
+
17
+
18
+ if crypto_installed:
19
+ logging.info("Decrypy use: Crypto")
20
+ from Crypto.Cipher import AES # type: ignore
21
+ from Crypto.Util.Padding import unpad # type: ignore
22
+
23
+ class M3U8_Decryption:
24
+ """
25
+ Class for decrypting M3U8 playlist content using AES encryption when the Crypto module is available.
26
+ """
27
+ def __init__(self, key: bytes, iv: bytes, method: str) -> None:
28
+ """
29
+ Initialize the M3U8_Decryption object.
30
+
31
+ Parameters:
32
+ - key (bytes): The encryption key.
33
+ - iv (bytes): The initialization vector (IV).
34
+ - method (str): The encryption method.
35
+ """
36
+ self.key = key
37
+ if "0x" in str(iv):
38
+ self.iv = bytes.fromhex(iv.replace("0x", ""))
39
+ else:
40
+ self.iv = iv
41
+ self.method = method
42
+ logging.info(f"Decrypt add: ('key': {self.key}, 'iv': {self.iv}, 'method': {self.method})")
43
+
44
+ def decrypt(self, ciphertext: bytes) -> bytes:
45
+ """
46
+ Decrypt the ciphertext using the specified encryption method.
47
+
48
+ Parameters:
49
+ - ciphertext (bytes): The encrypted content to decrypt.
50
+
51
+ Returns:
52
+ bytes: The decrypted content.
53
+ """
54
+ if self.method == "AES":
55
+ cipher = AES.new(self.key, AES.MODE_ECB)
56
+ decrypted_data = cipher.decrypt(ciphertext)
57
+ return unpad(decrypted_data, AES.block_size)
58
+
59
+ elif self.method == "AES-128":
60
+ cipher = AES.new(self.key[:16], AES.MODE_CBC, iv=self.iv)
61
+ decrypted_data = cipher.decrypt(ciphertext)
62
+ return unpad(decrypted_data, AES.block_size)
63
+
64
+ elif self.method == "AES-128-CTR":
65
+ cipher = AES.new(self.key[:16], AES.MODE_CTR, nonce=self.iv)
66
+ return cipher.decrypt(ciphertext)
67
+
68
+ else:
69
+ raise ValueError("Invalid or unsupported method")
70
+
71
+ else:
72
+
73
+ # Check if openssl command is available
74
+ try:
75
+ openssl_available = subprocess.run(["openssl", "version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode == 0
76
+ logging.info("Decrypy use: OPENSSL")
77
+ except:
78
+ openssl_available = False
79
+
80
+ if not openssl_available:
81
+ console.log("[red]Neither python library: pycryptodome nor openssl software is installed. Please install either one of them. Read readme.md [Requirement].")
82
+ sys.exit(0)
83
+
84
+ class M3U8_Decryption:
85
+ """
86
+ Class for decrypting M3U8 playlist content using OpenSSL when the Crypto module is not available.
87
+ """
88
+ def __init__(self, key: bytes, iv: bytes, method: str) -> None:
89
+ """
90
+ Initialize the M3U8_Decryption object.
91
+
92
+ Parameters:
93
+ - key (bytes): The encryption key.
94
+ - iv (bytes): The initialization vector (IV).
95
+ - method (str): The encryption method.
96
+ """
97
+ self.key = key
98
+ if "0x" in str(iv):
99
+ self.iv = bytes.fromhex(iv.replace("0x", ""))
100
+ else:
101
+ self.iv = iv
102
+ self.method = method
103
+ logging.info(f"Decrypt add: ('key': {self.key}, 'iv': {self.iv}, 'method': {self.method})")
104
+
105
+ def decrypt(self, ciphertext: bytes) -> bytes:
106
+ """
107
+ Decrypt the ciphertext using the specified encryption method.
108
+
109
+ Parameters:
110
+ - ciphertext (bytes): The encrypted content to decrypt.
111
+
112
+ Returns:
113
+ bytes: The decrypted content.
114
+ """
115
+ if self.method == "AES":
116
+ openssl_cmd = f'openssl enc -d -aes-256-ecb -K {self.key.hex()} -nosalt'
117
+ elif self.method == "AES-128":
118
+ openssl_cmd = f'openssl enc -d -aes-128-cbc -K {self.key[:16].hex()} -iv {self.iv.hex()}'
119
+ elif self.method == "AES-128-CTR":
120
+ openssl_cmd = f'openssl enc -d -aes-128-ctr -K {self.key[:16].hex()} -iv {self.iv.hex()}'
121
+ else:
122
+ raise ValueError("Invalid or unsupported method")
123
+
124
+ try:
125
+ decrypted_data = subprocess.check_output(openssl_cmd.split(), input=ciphertext, stderr=subprocess.STDOUT)
126
+ except subprocess.CalledProcessError as e:
127
+ raise ValueError(f"Decryption failed: {e.output.decode()}")
128
+
129
+ return decrypted_data