StreamingCommunity 1.7.6__py3-none-any.whl → 1.8.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.
- StreamingCommunity/Src/Api/Player/Helper/Vixcloud/js_parser.py +4 -1
- StreamingCommunity/Src/Api/Player/Helper/Vixcloud/util.py +166 -166
- StreamingCommunity/Src/Api/Player/ddl.py +89 -89
- StreamingCommunity/Src/Api/Player/maxstream.py +151 -151
- StreamingCommunity/Src/Api/Player/supervideo.py +193 -193
- StreamingCommunity/Src/Api/Player/vixcloud.py +224 -212
- StreamingCommunity/Src/Api/Site/1337xx/__init__.py +50 -50
- StreamingCommunity/Src/Api/Site/1337xx/costant.py +14 -14
- StreamingCommunity/Src/Api/Site/1337xx/site.py +83 -83
- StreamingCommunity/Src/Api/Site/1337xx/title.py +66 -66
- StreamingCommunity/Src/Api/Site/altadefinizione/__init__.py +50 -50
- StreamingCommunity/Src/Api/Site/altadefinizione/costant.py +14 -14
- StreamingCommunity/Src/Api/Site/altadefinizione/film.py +69 -69
- StreamingCommunity/Src/Api/Site/altadefinizione/site.py +86 -86
- StreamingCommunity/Src/Api/Site/animeunity/__init__.py +50 -50
- StreamingCommunity/Src/Api/Site/animeunity/costant.py +15 -15
- StreamingCommunity/Src/Api/Site/animeunity/film_serie.py +131 -131
- StreamingCommunity/Src/Api/Site/animeunity/site.py +164 -164
- StreamingCommunity/Src/Api/Site/bitsearch/__init__.py +51 -51
- StreamingCommunity/Src/Api/Site/bitsearch/costant.py +15 -15
- StreamingCommunity/Src/Api/Site/bitsearch/site.py +84 -84
- StreamingCommunity/Src/Api/Site/bitsearch/title.py +47 -47
- StreamingCommunity/Src/Api/Site/cb01new/__init__.py +51 -51
- StreamingCommunity/Src/Api/Site/cb01new/costant.py +15 -15
- StreamingCommunity/Src/Api/Site/cb01new/film.py +69 -69
- StreamingCommunity/Src/Api/Site/cb01new/site.py +74 -74
- StreamingCommunity/Src/Api/Site/ddlstreamitaly/__init__.py +57 -57
- StreamingCommunity/Src/Api/Site/ddlstreamitaly/costant.py +16 -16
- StreamingCommunity/Src/Api/Site/ddlstreamitaly/series.py +142 -142
- StreamingCommunity/Src/Api/Site/ddlstreamitaly/site.py +92 -92
- StreamingCommunity/Src/Api/Site/ddlstreamitaly/util/ScrapeSerie.py +82 -82
- StreamingCommunity/Src/Api/Site/guardaserie/__init__.py +52 -52
- StreamingCommunity/Src/Api/Site/guardaserie/costant.py +15 -15
- StreamingCommunity/Src/Api/Site/guardaserie/series.py +195 -195
- StreamingCommunity/Src/Api/Site/guardaserie/site.py +84 -84
- StreamingCommunity/Src/Api/Site/guardaserie/util/ScrapeSerie.py +110 -110
- StreamingCommunity/Src/Api/Site/mostraguarda/__init__.py +48 -48
- StreamingCommunity/Src/Api/Site/mostraguarda/costant.py +14 -14
- StreamingCommunity/Src/Api/Site/mostraguarda/film.py +94 -94
- StreamingCommunity/Src/Api/Site/piratebays/__init__.py +50 -50
- StreamingCommunity/Src/Api/Site/piratebays/costant.py +14 -14
- StreamingCommunity/Src/Api/Site/piratebays/site.py +88 -88
- StreamingCommunity/Src/Api/Site/piratebays/title.py +45 -45
- StreamingCommunity/Src/Api/Site/streamingcommunity/__init__.py +55 -55
- StreamingCommunity/Src/Api/Site/streamingcommunity/costant.py +14 -14
- StreamingCommunity/Src/Api/Site/streamingcommunity/film.py +70 -70
- StreamingCommunity/Src/Api/Site/streamingcommunity/series.py +203 -203
- StreamingCommunity/Src/Api/Site/streamingcommunity/site.py +125 -125
- StreamingCommunity/Src/Api/Template/Class/SearchType.py +101 -101
- StreamingCommunity/Src/Api/Template/Util/__init__.py +4 -4
- StreamingCommunity/Src/Api/Template/Util/get_domain.py +137 -137
- StreamingCommunity/Src/Api/Template/Util/manage_ep.py +153 -153
- StreamingCommunity/Src/Api/Template/Util/recall_search.py +37 -37
- StreamingCommunity/Src/Api/Template/__init__.py +2 -2
- StreamingCommunity/Src/Api/Template/site.py +87 -87
- StreamingCommunity/Src/Lib/Downloader/HLS/downloader.py +968 -968
- StreamingCommunity/Src/Lib/Downloader/HLS/proxyes.py +110 -110
- StreamingCommunity/Src/Lib/Downloader/HLS/segments.py +540 -540
- StreamingCommunity/Src/Lib/Downloader/MP4/downloader.py +156 -156
- StreamingCommunity/Src/Lib/Downloader/TOR/downloader.py +222 -222
- StreamingCommunity/Src/Lib/Downloader/__init__.py +4 -4
- StreamingCommunity/Src/Lib/Driver/driver_1.py +76 -76
- StreamingCommunity/Src/Lib/FFmpeg/__init__.py +4 -4
- StreamingCommunity/Src/Lib/FFmpeg/capture.py +170 -170
- StreamingCommunity/Src/Lib/FFmpeg/command.py +292 -292
- StreamingCommunity/Src/Lib/FFmpeg/util.py +241 -241
- StreamingCommunity/Src/Lib/M3U8/__init__.py +5 -5
- StreamingCommunity/Src/Lib/M3U8/decryptor.py +128 -128
- StreamingCommunity/Src/Lib/M3U8/estimator.py +172 -172
- StreamingCommunity/Src/Lib/M3U8/parser.py +666 -666
- StreamingCommunity/Src/Lib/M3U8/url_fixer.py +51 -51
- StreamingCommunity/Src/Lib/TMBD/__init__.py +1 -1
- StreamingCommunity/Src/Lib/TMBD/obj_tmbd.py +39 -39
- StreamingCommunity/Src/Lib/TMBD/tmdb.py +345 -345
- StreamingCommunity/Src/Upload/update.py +64 -64
- StreamingCommunity/Src/Upload/version.py +5 -5
- StreamingCommunity/Src/Util/_jsonConfig.py +204 -204
- StreamingCommunity/Src/Util/call_stack.py +42 -42
- StreamingCommunity/Src/Util/color.py +20 -20
- StreamingCommunity/Src/Util/console.py +12 -12
- StreamingCommunity/Src/Util/headers.py +147 -147
- StreamingCommunity/Src/Util/logger.py +53 -53
- StreamingCommunity/Src/Util/message.py +46 -46
- StreamingCommunity/Src/Util/os.py +417 -417
- StreamingCommunity/Src/Util/table.py +163 -163
- StreamingCommunity/run.py +196 -196
- {StreamingCommunity-1.7.6.dist-info → StreamingCommunity-1.8.0.dist-info}/METADATA +1 -1
- StreamingCommunity-1.8.0.dist-info/RECORD +97 -0
- StreamingCommunity-1.7.6.dist-info/RECORD +0 -97
- {StreamingCommunity-1.7.6.dist-info → StreamingCommunity-1.8.0.dist-info}/LICENSE +0 -0
- {StreamingCommunity-1.7.6.dist-info → StreamingCommunity-1.8.0.dist-info}/WHEEL +0 -0
- {StreamingCommunity-1.7.6.dist-info → StreamingCommunity-1.8.0.dist-info}/entry_points.txt +0 -0
- {StreamingCommunity-1.7.6.dist-info → StreamingCommunity-1.8.0.dist-info}/top_level.txt +0 -0
|
@@ -1,292 +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)
|
|
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)
|