Fix build pyinstaller

This commit is contained in:
Lovi 2025-03-08 20:26:36 +01:00
parent 077b935b75
commit c1d3e1f809
10 changed files with 119 additions and 48 deletions

View File

@ -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

View File

@ -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)

View File

@ -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:

View File

@ -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

View File

@ -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,

View File

@ -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: "

View File

@ -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}")

View File

@ -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

View File

@ -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

View File

@ -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)