mirror of
https://github.com/Arrowar/StreamingCommunity.git
synced 2025-06-05 02:55:25 +00:00
Fix build pyinstaller
This commit is contained in:
parent
077b935b75
commit
c1d3e1f809
61
.github/workflows/build.yml
vendored
61
.github/workflows/build.yml
vendored
@ -82,16 +82,26 @@ jobs:
|
||||
shell: pwsh
|
||||
run: |
|
||||
pyinstaller --onefile --hidden-import=pycryptodomex --hidden-import=ua_generator `
|
||||
--hidden-import=qbittorrentapi --hidden-import=qbittorrent `
|
||||
--hidden-import=bs4 --hidden-import=httpx --hidden-import=rich --hidden-import=tqdm `
|
||||
--hidden-import=m3u8 --hidden-import=psutil --hidden-import=unidecode `
|
||||
--hidden-import=jsbeautifier --hidden-import=six --hidden-import=pathvalidate `
|
||||
--hidden-import=Cryptodome.Cipher --hidden-import=Cryptodome.Cipher.AES `
|
||||
--hidden-import=Cryptodome.Util --hidden-import=Cryptodome.Util.Padding `
|
||||
--hidden-import=Cryptodome.Random --hidden-import=Pillow `
|
||||
--hidden-import=pyTelegramBotAPI --additional-hooks-dir=pyinstaller/hooks `
|
||||
--add-data "StreamingCommunity;StreamingCommunity" `
|
||||
--name=StreamingCommunity_win --icon=".github/media/logo.ico" test_run.py
|
||||
--hidden-import=qbittorrentapi --hidden-import=qbittorrent `
|
||||
--hidden-import=bs4 --hidden-import=httpx --hidden-import=rich --hidden-import=tqdm `
|
||||
--hidden-import=m3u8 --hidden-import=psutil --hidden-import=unidecode `
|
||||
--hidden-import=jsbeautifier --hidden-import=jsbeautifier.core `
|
||||
--hidden-import=jsbeautifier.javascript --hidden-import=jsbeautifier.javascript.beautifier `
|
||||
--hidden-import=jsbeautifier.unpackers --hidden-import=jsbeautifier.unpackers.packer `
|
||||
--hidden-import=jsbeautifier.unpackers.javascriptobfuscator `
|
||||
--hidden-import=jsbeautifier.unpackers.myobfuscate `
|
||||
--hidden-import=jsbeautifier.unpackers.urlencode `
|
||||
--hidden-import=jsbeautifier.unpackers.meshim `
|
||||
--hidden-import=editorconfig --hidden-import=editorconfig.handlers `
|
||||
--hidden-import=six --hidden-import=pathvalidate `
|
||||
--hidden-import=Cryptodome.Cipher --hidden-import=Cryptodome.Cipher.AES `
|
||||
--hidden-import=Cryptodome.Util --hidden-import=Cryptodome.Util.Padding `
|
||||
--hidden-import=Cryptodome.Random `
|
||||
--hidden-import=telebot `
|
||||
--additional-hooks-dir=pyinstaller/hooks `
|
||||
--add-data "StreamingCommunity;StreamingCommunity" `
|
||||
--name=StreamingCommunity_win --icon=".github/media/logo.ico" test_run.py
|
||||
|
||||
- name: Build executable with PyInstaller (Linux)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
@ -99,13 +109,23 @@ jobs:
|
||||
--hidden-import=qbittorrentapi --hidden-import=qbittorrent \
|
||||
--hidden-import=bs4 --hidden-import=httpx --hidden-import=rich --hidden-import=tqdm \
|
||||
--hidden-import=m3u8 --hidden-import=psutil --hidden-import=unidecode \
|
||||
--hidden-import=jsbeautifier --hidden-import=six --hidden-import=pathvalidate \
|
||||
--hidden-import=jsbeautifier --hidden-import=jsbeautifier.core \
|
||||
--hidden-import=jsbeautifier.javascript --hidden-import=jsbeautifier.javascript.beautifier \
|
||||
--hidden-import=jsbeautifier.unpackers --hidden-import=jsbeautifier.unpackers.packer \
|
||||
--hidden-import=jsbeautifier.unpackers.javascriptobfuscator \
|
||||
--hidden-import=jsbeautifier.unpackers.myobfuscate \
|
||||
--hidden-import=jsbeautifier.unpackers.urlencode \
|
||||
--hidden-import=jsbeautifier.unpackers.meshim \
|
||||
--hidden-import=editorconfig --hidden-import=editorconfig.handlers \
|
||||
--hidden-import=six --hidden-import=pathvalidate \
|
||||
--hidden-import=Cryptodome.Cipher --hidden-import=Cryptodome.Cipher.AES \
|
||||
--hidden-import=Cryptodome.Util --hidden-import=Cryptodome.Util.Padding \
|
||||
--hidden-import=Cryptodome.Random --hidden-import=Pillow \
|
||||
--hidden-import=pyTelegramBotAPI --additional-hooks-dir=pyinstaller/hooks \
|
||||
--hidden-import=Cryptodome.Random \
|
||||
--hidden-import=telebot \
|
||||
--additional-hooks-dir=pyinstaller/hooks \
|
||||
--add-data "StreamingCommunity:StreamingCommunity" \
|
||||
--name=StreamingCommunity_linux test_run.py
|
||||
|
||||
- name: Build executable with PyInstaller (macOS)
|
||||
if: matrix.os == 'macos-latest'
|
||||
run: |
|
||||
@ -113,11 +133,20 @@ jobs:
|
||||
--hidden-import=qbittorrentapi --hidden-import=qbittorrent \
|
||||
--hidden-import=bs4 --hidden-import=httpx --hidden-import=rich --hidden-import=tqdm \
|
||||
--hidden-import=m3u8 --hidden-import=psutil --hidden-import=unidecode \
|
||||
--hidden-import=jsbeautifier --hidden-import=six --hidden-import=pathvalidate \
|
||||
--hidden-import=jsbeautifier --hidden-import=jsbeautifier.core \
|
||||
--hidden-import=jsbeautifier.javascript --hidden-import=jsbeautifier.javascript.beautifier \
|
||||
--hidden-import=jsbeautifier.unpackers --hidden-import=jsbeautifier.unpackers.packer \
|
||||
--hidden-import=jsbeautifier.unpackers.javascriptobfuscator \
|
||||
--hidden-import=jsbeautifier.unpackers.myobfuscate \
|
||||
--hidden-import=jsbeautifier.unpackers.urlencode \
|
||||
--hidden-import=jsbeautifier.unpackers.meshim \
|
||||
--hidden-import=editorconfig --hidden-import=editorconfig.handlers \
|
||||
--hidden-import=six --hidden-import=pathvalidate \
|
||||
--hidden-import=Cryptodome.Cipher --hidden-import=Cryptodome.Cipher.AES \
|
||||
--hidden-import=Cryptodome.Util --hidden-import=Cryptodome.Util.Padding \
|
||||
--hidden-import=Cryptodome.Random --hidden-import=Pillow \
|
||||
--hidden-import=pyTelegramBotAPI --additional-hooks-dir=pyinstaller/hooks \
|
||||
--hidden-import=Cryptodome.Random \
|
||||
--hidden-import=telebot \
|
||||
--additional-hooks-dir=pyinstaller/hooks \
|
||||
--add-data "StreamingCommunity:StreamingCommunity" \
|
||||
--name=StreamingCommunity_mac test_run.py
|
||||
|
||||
|
@ -46,6 +46,7 @@
|
||||
- 🔍 [Parser](#m3u8_parser-settings)
|
||||
- 📝 [Command](#command)
|
||||
- 💻 [Examples of terminal](#examples-of-terminal-usage)
|
||||
- 🔧 [Manual domain configuration](#update-domains)
|
||||
- 🐳 [Docker](#docker)
|
||||
- 📝 [Telegram Usage](#telegram-usage)
|
||||
- 🎓 [Tutorial](#tutorials)
|
||||
|
@ -130,6 +130,9 @@ class VideoSource:
|
||||
logging.info(f"M3U8 URL: {self.m3u8_url}")
|
||||
break
|
||||
|
||||
else:
|
||||
logging.error("Failed to find M3U8 URL: No match found")
|
||||
|
||||
return self.m3u8_url
|
||||
|
||||
except Exception as e:
|
||||
|
@ -119,6 +119,8 @@ class VideoSource:
|
||||
|
||||
if match:
|
||||
return match.group(1)
|
||||
else:
|
||||
logging.error("Failed to find M3U8 URL: No match found")
|
||||
|
||||
else:
|
||||
|
||||
@ -151,6 +153,8 @@ class VideoSource:
|
||||
|
||||
if match:
|
||||
return match.group(1)
|
||||
else:
|
||||
logging.error("Failed to find M3U8 URL: No match found")
|
||||
|
||||
return None
|
||||
|
||||
|
@ -436,6 +436,18 @@ class HLS_Downloader:
|
||||
if TELEGRAM_BOT:
|
||||
bot.send_message(f"Contenuto già scaricato!", None)
|
||||
return response
|
||||
|
||||
if GET_ONLY_LINK:
|
||||
console.print(f"URL: {self.m3u8_url}[/bold red]")
|
||||
return {
|
||||
'path': None,
|
||||
'url': self.m3u8_url,
|
||||
'is_master': getattr(self.m3u8_manager, 'is_master', None),
|
||||
'msg': None,
|
||||
'error': error_msg,
|
||||
'stopped': True
|
||||
}
|
||||
|
||||
|
||||
self.path_manager.setup_directories()
|
||||
|
||||
@ -466,9 +478,8 @@ class HLS_Downloader:
|
||||
|
||||
final_file = self.merge_manager.merge()
|
||||
self.path_manager.move_final_file(final_file)
|
||||
self.path_manager.cleanup()
|
||||
|
||||
self._print_summary()
|
||||
self.path_manager.cleanup()
|
||||
|
||||
return {
|
||||
'path': self.path_manager.output_path,
|
||||
|
@ -74,6 +74,9 @@ class M3U8_Segments:
|
||||
|
||||
# Sync
|
||||
self.queue = PriorityQueue()
|
||||
self.buffer = {}
|
||||
self.expected_index = 0
|
||||
|
||||
self.stop_event = threading.Event()
|
||||
self.downloaded_segments = set()
|
||||
self.base_timeout = 0.5
|
||||
@ -94,6 +97,15 @@ class M3U8_Segments:
|
||||
self.active_retries_lock = threading.Lock()
|
||||
|
||||
def __get_key__(self, m3u8_parser: M3U8_Parser) -> bytes:
|
||||
"""
|
||||
Fetches the encryption key from the M3U8 playlist.
|
||||
|
||||
Args:
|
||||
m3u8_parser (M3U8_Parser): An instance of M3U8_Parser containing parsed M3U8 data.
|
||||
|
||||
Returns:
|
||||
bytes: The decryption key in byte format.
|
||||
"""
|
||||
key_uri = urljoin(self.url, m3u8_parser.keys.get('uri'))
|
||||
parsed_url = urlparse(key_uri)
|
||||
self.key_base_url = f"{parsed_url.scheme}://{parsed_url.netloc}/"
|
||||
@ -110,6 +122,12 @@ class M3U8_Segments:
|
||||
raise Exception(f"Failed to fetch key: {e}")
|
||||
|
||||
def parse_data(self, m3u8_content: str) -> None:
|
||||
"""
|
||||
Parses the M3U8 content and extracts necessary data.
|
||||
|
||||
Args:
|
||||
m3u8_content (str): The raw M3U8 playlist content.
|
||||
"""
|
||||
m3u8_parser = M3U8_Parser()
|
||||
m3u8_parser.parse_data(uri=self.url, raw_content=m3u8_content)
|
||||
|
||||
@ -131,6 +149,14 @@ class M3U8_Segments:
|
||||
self.class_ts_estimator.total_segments = len(self.segments)
|
||||
|
||||
def get_info(self) -> None:
|
||||
"""
|
||||
Retrieves M3U8 playlist information from the given URL.
|
||||
|
||||
If the URL is an index URL, this method:
|
||||
- Sends an HTTP GET request to fetch the M3U8 playlist.
|
||||
- Parses the M3U8 content using `parse_data`.
|
||||
- Saves the playlist to a temporary folder.
|
||||
"""
|
||||
if self.is_index_url:
|
||||
try:
|
||||
client_params = {'headers': {'User-Agent': get_userAgent()}, 'timeout': MAX_TIMEOOUT}
|
||||
@ -251,9 +277,6 @@ class M3U8_Segments:
|
||||
"""
|
||||
Writes segments to file with additional verification.
|
||||
"""
|
||||
buffer = {}
|
||||
expected_index = 0
|
||||
|
||||
with open(self.tmp_file_path, 'wb') as f:
|
||||
while not self.stop_event.is_set() or not self.queue.empty():
|
||||
if self.interrupt_flag.is_set():
|
||||
@ -267,28 +290,28 @@ class M3U8_Segments:
|
||||
|
||||
# Handle failed segments
|
||||
if segment_content is None:
|
||||
if index == expected_index:
|
||||
expected_index += 1
|
||||
if index == self.expected_index:
|
||||
self.expected_index += 1
|
||||
continue
|
||||
|
||||
# Write segment if it's the next expected one
|
||||
if index == expected_index:
|
||||
if index == self.expected_index:
|
||||
f.write(segment_content)
|
||||
f.flush()
|
||||
expected_index += 1
|
||||
self.expected_index += 1
|
||||
|
||||
# Write any buffered segments that are now in order
|
||||
while expected_index in buffer:
|
||||
next_segment = buffer.pop(expected_index)
|
||||
while self.expected_index in self.buffer:
|
||||
next_segment = self.buffer.pop(self.expected_index)
|
||||
|
||||
if next_segment is not None:
|
||||
f.write(next_segment)
|
||||
f.flush()
|
||||
|
||||
expected_index += 1
|
||||
self.expected_index += 1
|
||||
|
||||
else:
|
||||
buffer[index] = segment_content
|
||||
self.buffer[index] = segment_content
|
||||
|
||||
except queue.Empty:
|
||||
self.current_timeout = min(MAX_TIMEOOUT, self.current_timeout * 1.1)
|
||||
@ -440,6 +463,9 @@ class M3U8_Segments:
|
||||
if self.info_nFailed > 0:
|
||||
self._display_error_summary()
|
||||
|
||||
self.buffer = {}
|
||||
self.expected_index = 0
|
||||
|
||||
def _display_error_summary(self) -> None:
|
||||
"""Generate final error report."""
|
||||
console.print(f"\n[cyan]Retry Summary: "
|
||||
|
@ -87,7 +87,8 @@ def MP4_downloader(url: str, path: str, referer: str = None, headers_: dict = No
|
||||
return None, False
|
||||
|
||||
if GET_ONLY_LINK:
|
||||
return {'path': path, 'url': url}
|
||||
console.print(f"URL: {url}[/bold red]")
|
||||
return path, True
|
||||
|
||||
if not (url.lower().startswith('http://') or url.lower().startswith('https://')):
|
||||
logging.error(f"Invalid URL: {url}")
|
||||
|
@ -180,7 +180,7 @@ def join_audios(video_path: str, audio_tracks: List[Dict[str, str]], out_path: s
|
||||
Each dictionary should contain the 'path' key with the path to the audio file.
|
||||
- out_path (str): The path to save the output file.
|
||||
"""
|
||||
video_audio_same_duration = check_duration_v_a(video_path, audio_tracks[0].get('path'))
|
||||
video_audio_same_duration, duration_diff = check_duration_v_a(video_path, audio_tracks[0].get('path'))
|
||||
|
||||
# Start command with locate ffmpeg
|
||||
ffmpeg_cmd = [get_ffmpeg_path()]
|
||||
@ -242,7 +242,7 @@ def join_audios(video_path: str, audio_tracks: List[Dict[str, str]], out_path: s
|
||||
|
||||
# Use shortest input path for video and audios
|
||||
if not video_audio_same_duration:
|
||||
console.log("[red]Use shortest input ...")
|
||||
console.log(f"[red]Use shortest input (Duration difference: {duration_diff:.2f} seconds)...")
|
||||
ffmpeg_cmd.extend(['-shortest', '-strict', 'experimental'])
|
||||
|
||||
# Overwrite
|
||||
|
@ -57,7 +57,6 @@ def get_video_duration(file_path: str) -> float:
|
||||
Returns:
|
||||
(float): The duration of the video in seconds if successful, None if there's an error.
|
||||
"""
|
||||
|
||||
try:
|
||||
ffprobe_cmd = [get_ffprobe_path(), '-v', 'error', '-show_format', '-print_format', 'json', file_path]
|
||||
logging.info(f"FFmpeg command: {ffprobe_cmd}")
|
||||
@ -95,7 +94,6 @@ def format_duration(seconds: float) -> Tuple[int, int, int]:
|
||||
Returns:
|
||||
list[int, int, int]: List containing hours, minutes, and seconds.
|
||||
"""
|
||||
|
||||
hours, remainder = divmod(seconds, 3600)
|
||||
minutes, seconds = divmod(remainder, 60)
|
||||
|
||||
@ -157,11 +155,7 @@ def get_ffprobe_info(file_path):
|
||||
'codec_names': codec_names
|
||||
}
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
logging.error(f"ffprobe failed for file {file_path}: {e}")
|
||||
return None
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to parse JSON output from ffprobe for file {file_path}: {e}")
|
||||
return None
|
||||
|
||||
@ -198,23 +192,25 @@ def need_to_force_to_ts(file_path):
|
||||
return False
|
||||
|
||||
|
||||
def check_duration_v_a(video_path, audio_path):
|
||||
def check_duration_v_a(video_path, audio_path, tolerance=1.0):
|
||||
"""
|
||||
Check if the duration of the video and audio matches.
|
||||
|
||||
Parameters:
|
||||
- video_path (str): Path to the video file.
|
||||
- audio_path (str): Path to the audio file.
|
||||
- tolerance (float): Allowed tolerance for the duration difference (in seconds).
|
||||
|
||||
Returns:
|
||||
- bool: True if the duration of the video and audio matches, False otherwise.
|
||||
- tuple: (bool, float) -> True if the duration of the video and audio matches, False otherwise, along with the difference in duration.
|
||||
"""
|
||||
|
||||
# Ottieni la durata del video
|
||||
video_duration = get_video_duration(video_path)
|
||||
|
||||
# Ottieni la durata dell'audio
|
||||
audio_duration = get_video_duration(audio_path)
|
||||
|
||||
# Verifica se le durate corrispondono
|
||||
return video_duration == audio_duration
|
||||
duration_difference = abs(video_duration - audio_duration)
|
||||
|
||||
# Check if the duration difference is within the tolerance
|
||||
if duration_difference <= tolerance:
|
||||
return True, duration_difference
|
||||
else:
|
||||
return False, duration_difference
|
@ -52,7 +52,7 @@ class M3U8_Ts_Estimator:
|
||||
self.now_downloaded_size += size_download
|
||||
logging.debug(f"Current total downloaded size: {self.now_downloaded_size}")
|
||||
|
||||
def capture_speed(self, interval: float = 1):
|
||||
def capture_speed(self, interval: float = 1.5):
|
||||
"""Capture the internet speed periodically."""
|
||||
last_upload, last_download = 0, 0
|
||||
speed_buffer = deque(maxlen=3)
|
||||
|
Loading…
x
Reference in New Issue
Block a user