mirror of
https://github.com/Arrowar/StreamingCommunity.git
synced 2025-06-07 12:05:35 +00:00
v1.9.2
This commit is contained in:
parent
0bedb1bf05
commit
eb95a4e5ac
@ -120,8 +120,7 @@ class VideoSource:
|
|||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("\n")
|
logging.error(f"Failed to get vixcloud contente with error: {e}")
|
||||||
console.print(Panel("[red bold]Coming soon", title="Notification", title_align="left", border_style="yellow"))
|
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
# Parse response with BeautifulSoup to get content
|
# Parse response with BeautifulSoup to get content
|
||||||
|
@ -117,16 +117,29 @@ def validate_selection(list_season_select: List[int], seasons_count: int) -> Lis
|
|||||||
Returns:
|
Returns:
|
||||||
- List[int]: Adjusted list of valid season numbers.
|
- List[int]: Adjusted list of valid season numbers.
|
||||||
"""
|
"""
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
|
||||||
# Remove any seasons greater than the available seasons
|
# Remove any seasons greater than the available seasons
|
||||||
valid_seasons = [season for season in list_season_select if 1 <= season <= seasons_count]
|
valid_seasons = [season for season in list_season_select if 1 <= season <= seasons_count]
|
||||||
|
|
||||||
# If the list is empty, the input was completely invalid
|
# If the list is empty, the input was completely invalid
|
||||||
if not valid_seasons:
|
if not valid_seasons:
|
||||||
print()
|
logging.error(f"Invalid selection: The selected seasons are outside the available range (1-{seasons_count}). Please try again.")
|
||||||
raise ValueError(f"Invalid selection: The selected seasons are outside the available range (1-{seasons_count}).")
|
|
||||||
|
|
||||||
return valid_seasons
|
# Re-prompt for valid input
|
||||||
|
input_seasons = input(f"Enter valid season numbers (1-{seasons_count}): ")
|
||||||
|
list_season_select = list(map(int, input_seasons.split(',')))
|
||||||
|
continue # Re-prompt the user if the selection is invalid
|
||||||
|
|
||||||
|
return valid_seasons # Return the valid seasons if the input is correct
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
logging.error("Error: Please enter valid integers separated by commas.")
|
||||||
|
|
||||||
|
# Prompt the user for valid input again
|
||||||
|
input_seasons = input(f"Enter valid season numbers (1-{seasons_count}): ")
|
||||||
|
list_season_select = list(map(int, input_seasons.split(',')))
|
||||||
|
|
||||||
|
|
||||||
# --> for episode
|
# --> for episode
|
||||||
@ -141,13 +154,26 @@ def validate_episode_selection(list_episode_select: List[int], episodes_count: i
|
|||||||
Returns:
|
Returns:
|
||||||
- List[int]: Adjusted list of valid episode numbers.
|
- List[int]: Adjusted list of valid episode numbers.
|
||||||
"""
|
"""
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
|
||||||
# Remove any episodes greater than the available episodes
|
# Remove any episodes greater than the available episodes
|
||||||
valid_episodes = [episode for episode in list_episode_select if 1 <= episode <= episodes_count]
|
valid_episodes = [episode for episode in list_episode_select if 1 <= episode <= episodes_count]
|
||||||
|
|
||||||
# If the list is empty, the input was completely invalid
|
# If the list is empty, the input was completely invalid
|
||||||
if not valid_episodes:
|
if not valid_episodes:
|
||||||
print()
|
logging.error(f"Invalid selection: The selected episodes are outside the available range (1-{episodes_count}). Please try again.")
|
||||||
raise ValueError(f"Invalid selection: The selected episodes are outside the available range (1-{episodes_count}).")
|
|
||||||
|
|
||||||
return valid_episodes
|
# Re-prompt for valid input
|
||||||
|
input_episodes = input(f"Enter valid episode numbers (1-{episodes_count}): ")
|
||||||
|
list_episode_select = list(map(int, input_episodes.split(',')))
|
||||||
|
continue # Re-prompt the user if the selection is invalid
|
||||||
|
|
||||||
|
return valid_episodes
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
logging.error("Error: Please enter valid integers separated by commas.")
|
||||||
|
|
||||||
|
# Prompt the user for valid input again
|
||||||
|
input_episodes = input(f"Enter valid episode numbers (1-{episodes_count}): ")
|
||||||
|
list_episode_select = list(map(int, input_episodes.split(',')))
|
@ -22,7 +22,6 @@ from StreamingCommunity.Util.os import (
|
|||||||
# Logic class
|
# Logic class
|
||||||
from ...FFmpeg import (
|
from ...FFmpeg import (
|
||||||
print_duration_table,
|
print_duration_table,
|
||||||
get_video_duration_s,
|
|
||||||
join_video,
|
join_video,
|
||||||
join_audios,
|
join_audios,
|
||||||
join_subtitle
|
join_subtitle
|
||||||
@ -52,6 +51,7 @@ GET_ONLY_LINK = config_manager.get_bool('M3U8_PARSER', 'get_only_link')
|
|||||||
max_timeout = config_manager.get_int("REQUESTS", "timeout")
|
max_timeout = config_manager.get_int("REQUESTS", "timeout")
|
||||||
headers_index = config_manager.get_dict('REQUESTS', 'user-agent')
|
headers_index = config_manager.get_dict('REQUESTS', 'user-agent')
|
||||||
m3u8_url_fixer = M3U8_UrlFix()
|
m3u8_url_fixer = M3U8_UrlFix()
|
||||||
|
list_MissingTs = []
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -397,7 +397,8 @@ class ContentDownloader:
|
|||||||
self.expected_real_time = video_m3u8.expected_real_time
|
self.expected_real_time = video_m3u8.expected_real_time
|
||||||
|
|
||||||
# Download the video streams and print status
|
# Download the video streams and print status
|
||||||
video_m3u8.download_streams(f"{Colors.MAGENTA}video", "video")
|
info_dw = video_m3u8.download_streams(f"{Colors.MAGENTA}video", "video")
|
||||||
|
list_MissingTs.append(info_dw)
|
||||||
|
|
||||||
# Print duration information of the downloaded video
|
# Print duration information of the downloaded video
|
||||||
#print_duration_table(downloaded_video[0].get('path'))
|
#print_duration_table(downloaded_video[0].get('path'))
|
||||||
@ -427,7 +428,8 @@ class ContentDownloader:
|
|||||||
audio_m3u8.get_info()
|
audio_m3u8.get_info()
|
||||||
|
|
||||||
# Download the audio segments and print status
|
# Download the audio segments and print status
|
||||||
audio_m3u8.download_streams(f"{Colors.MAGENTA}audio {Colors.RED}{obj_audio.get('language')}", "audio")
|
info_dw = audio_m3u8.download_streams(f"{Colors.MAGENTA}audio {Colors.RED}{obj_audio.get('language')}", f"audio_{obj_audio.get('language')}")
|
||||||
|
list_MissingTs.append(info_dw)
|
||||||
|
|
||||||
# Print duration information of the downloaded audio
|
# Print duration information of the downloaded audio
|
||||||
#print_duration_table(obj_audio.get('path'))
|
#print_duration_table(obj_audio.get('path'))
|
||||||
@ -838,40 +840,33 @@ class HLS_Downloader:
|
|||||||
|
|
||||||
# Rename the output file to the desired output filename if it does not already exist
|
# Rename the output file to the desired output filename if it does not already exist
|
||||||
if not os.path.exists(self.output_filename):
|
if not os.path.exists(self.output_filename):
|
||||||
|
missing_ts = False
|
||||||
|
missing_info = ""
|
||||||
|
|
||||||
# Rename the converted file to the specified output filename
|
# Rename the converted file to the specified output filename
|
||||||
os.rename(out_path, self.output_filename)
|
os.rename(out_path, self.output_filename)
|
||||||
|
|
||||||
# Get duration information for the output file
|
|
||||||
end_output_time = print_duration_table(self.output_filename, description=False, return_string=False)
|
|
||||||
|
|
||||||
# Calculate file size and duration for reporting
|
# Calculate file size and duration for reporting
|
||||||
formatted_size = internet_manager.format_file_size(os.path.getsize(self.output_filename))
|
formatted_size = internet_manager.format_file_size(os.path.getsize(self.output_filename))
|
||||||
formatted_duration = print_duration_table(self.output_filename, description=False, return_string=True)
|
formatted_duration = print_duration_table(self.output_filename, description=False, return_string=True)
|
||||||
|
|
||||||
expected_real_seconds = dict_to_seconds(self.content_downloader.expected_real_time)
|
# Collect info about type missing
|
||||||
end_output_seconds = dict_to_seconds(end_output_time)
|
for item in list_MissingTs:
|
||||||
|
if int(item['nFailed']) >= 1:
|
||||||
# Check if the downloaded content is complete based on expected duration
|
|
||||||
if expected_real_seconds is not None:
|
|
||||||
missing_ts = not (expected_real_seconds - 3 <= end_output_seconds <= expected_real_seconds + 3)
|
|
||||||
else:
|
|
||||||
missing_ts = "Undefined"
|
|
||||||
|
|
||||||
# Second check for missing segments
|
|
||||||
if not missing_ts:
|
|
||||||
if get_video_duration_s(self.output_filename) < int(expected_real_seconds) - 5:
|
|
||||||
missing_ts = True
|
missing_ts = True
|
||||||
|
missing_info += f"[red]TS Failed: {item['nFailed']} {item['type']} tracks[/red]\n"
|
||||||
|
|
||||||
# Prepare the report panel content
|
# Prepare the report panel content
|
||||||
print("")
|
print("")
|
||||||
panel_content = (
|
panel_content = (
|
||||||
f"[bold green]Download completed![/bold green]\n"
|
f"[bold green]Download completed![/bold green]\n"
|
||||||
f"[cyan]File size: [bold red]{formatted_size}[/bold red]\n"
|
f"[cyan]File size: [bold red]{formatted_size}[/bold red]\n"
|
||||||
f"[cyan]Duration: [bold]{formatted_duration}[/bold]\n"
|
f"[cyan]Duration: [bold]{formatted_duration}[/bold]"
|
||||||
f"[cyan]Missing TS: [bold red]{missing_ts}[/bold red]"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if missing_ts:
|
||||||
|
panel_content += f"\n{missing_info}"
|
||||||
|
|
||||||
# Display the download completion message
|
# Display the download completion message
|
||||||
console.print(Panel(
|
console.print(Panel(
|
||||||
panel_content,
|
panel_content,
|
||||||
|
@ -35,7 +35,7 @@ from ...M3U8 import (
|
|||||||
M3U8_Parser,
|
M3U8_Parser,
|
||||||
M3U8_UrlFix
|
M3U8_UrlFix
|
||||||
)
|
)
|
||||||
from ...FFmpeg.util import print_duration_table
|
from ...FFmpeg.util import print_duration_table, format_duration
|
||||||
from .proxyes import main_test_proxy
|
from .proxyes import main_test_proxy
|
||||||
|
|
||||||
# Config
|
# Config
|
||||||
@ -98,6 +98,7 @@ class M3U8_Segments:
|
|||||||
# OTHER INFO
|
# OTHER INFO
|
||||||
self.info_maxRetry = 0
|
self.info_maxRetry = 0
|
||||||
self.info_nRetry = 0
|
self.info_nRetry = 0
|
||||||
|
self.info_nFailed = 0
|
||||||
|
|
||||||
def __get_key__(self, m3u8_parser: M3U8_Parser) -> bytes:
|
def __get_key__(self, m3u8_parser: M3U8_Parser) -> bytes:
|
||||||
"""
|
"""
|
||||||
@ -133,6 +134,7 @@ class M3U8_Segments:
|
|||||||
hex_content = binascii.hexlify(response.content).decode('utf-8')
|
hex_content = binascii.hexlify(response.content).decode('utf-8')
|
||||||
byte_content = bytes.fromhex(hex_content)
|
byte_content = bytes.fromhex(hex_content)
|
||||||
|
|
||||||
|
#console.print(f"[cyan]Find key: [red]{hex_content}")
|
||||||
return byte_content
|
return byte_content
|
||||||
|
|
||||||
def parse_data(self, m3u8_content: str) -> None:
|
def parse_data(self, m3u8_content: str) -> None:
|
||||||
@ -332,6 +334,8 @@ class M3U8_Segments:
|
|||||||
console.log(f"[red]Final retry failed for segment: {index}")
|
console.log(f"[red]Final retry failed for segment: {index}")
|
||||||
self.queue.put((index, None)) # Marker for failed segment
|
self.queue.put((index, None)) # Marker for failed segment
|
||||||
progress_bar.update(1)
|
progress_bar.update(1)
|
||||||
|
self.info_nFailed += 1
|
||||||
|
|
||||||
#break
|
#break
|
||||||
|
|
||||||
sleep_time = backoff_factor * (2 ** attempt)
|
sleep_time = backoff_factor * (2 ** attempt)
|
||||||
@ -426,10 +430,10 @@ class M3U8_Segments:
|
|||||||
AUDIO_WORKERS = DEFAULT_AUDIO_WORKERS
|
AUDIO_WORKERS = DEFAULT_AUDIO_WORKERS
|
||||||
|
|
||||||
# Differnt workers for audio and video
|
# Differnt workers for audio and video
|
||||||
if "video" == str(type):
|
if "video" in str(type):
|
||||||
TQDM_MAX_WORKER = VIDEO_WORKERS
|
TQDM_MAX_WORKER = VIDEO_WORKERS
|
||||||
|
|
||||||
if "audio" == str(type):
|
if "audio" in str(type):
|
||||||
TQDM_MAX_WORKER = AUDIO_WORKERS
|
TQDM_MAX_WORKER = AUDIO_WORKERS
|
||||||
|
|
||||||
console.print(f"[cyan]Video workers[white]: [green]{VIDEO_WORKERS} [white]| [cyan]Audio workers[white]: [green]{AUDIO_WORKERS}")
|
console.print(f"[cyan]Video workers[white]: [green]{VIDEO_WORKERS} [white]| [cyan]Audio workers[white]: [green]{AUDIO_WORKERS}")
|
||||||
@ -526,11 +530,6 @@ class M3U8_Segments:
|
|||||||
if self.download_interrupted:
|
if self.download_interrupted:
|
||||||
console.log("[red] Download was manually stopped.")
|
console.log("[red] Download was manually stopped.")
|
||||||
|
|
||||||
# Optional: Delete partial download
|
|
||||||
if os.path.exists(self.tmp_file_path):
|
|
||||||
os.remove(self.tmp_file_path)
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
# Clean up
|
# Clean up
|
||||||
self.stop_event.set()
|
self.stop_event.set()
|
||||||
writer_thread.join(timeout=30)
|
writer_thread.join(timeout=30)
|
||||||
@ -550,7 +549,10 @@ class M3U8_Segments:
|
|||||||
if file_size == 0:
|
if file_size == 0:
|
||||||
raise Exception("Output file is empty")
|
raise Exception("Output file is empty")
|
||||||
|
|
||||||
console.print(f"[cyan]Max retry per URL[white]: [green]{self.info_maxRetry}[green] [white]| [cyan]Total retry done[white]: [green]{self.info_nRetry}[green] [white]| [cyan]Duration: {print_duration_table(self.tmp_file_path, None, True)} \n")
|
# Get expected time
|
||||||
|
ex_hours, ex_minutes, ex_seconds = format_duration(self.expected_real_time_s)
|
||||||
|
ex_formatted_duration = f"[yellow]{int(ex_hours)}[red]h [yellow]{int(ex_minutes)}[red]m [yellow]{int(ex_seconds)}[red]s"
|
||||||
|
console.print(f"[cyan]Max retry per URL[white]: [green]{self.info_maxRetry}[green] [white]| [cyan]Total retry done[white]: [green]{self.info_nRetry}[green] [white]| [cyan]Missing TS: [red]{self.info_nFailed} [white]| [cyan]Duration: {print_duration_table(self.tmp_file_path, None, True)} [white]| [cyan]Expected duation: {ex_formatted_duration} \n")
|
||||||
|
|
||||||
if self.info_nRetry >= len(self.segments) * (1/3.33):
|
if self.info_nRetry >= len(self.segments) * (1/3.33):
|
||||||
console.print(
|
console.print(
|
||||||
@ -558,3 +560,6 @@ class M3U8_Segments:
|
|||||||
"Consider reducing the number of [cyan]workers[/cyan] in the [magenta]config.json[/magenta] file. "
|
"Consider reducing the number of [cyan]workers[/cyan] in the [magenta]config.json[/magenta] file. "
|
||||||
"This will impact [bold]performance[/bold]."
|
"This will impact [bold]performance[/bold]."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Info to return
|
||||||
|
return {'type': type, 'nFailed': self.info_nFailed}
|
@ -1,4 +1,4 @@
|
|||||||
# 18.04.24
|
# 18.04.24
|
||||||
|
|
||||||
from .command import join_video, join_audios, join_subtitle
|
from .command import join_video, join_audios, join_subtitle
|
||||||
from .util import print_duration_table, get_video_duration_s
|
from .util import print_duration_table, get_video_duration
|
||||||
|
@ -79,7 +79,7 @@ def get_video_duration(file_path: str) -> float:
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error get video duration: {e}")
|
logging.error(f"Get video duration error: {e}")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
@ -144,7 +144,6 @@ def print_duration_table(file_path: str, description: str = "Duration", return_s
|
|||||||
- str: The formatted duration string if return_string is True.
|
- str: The formatted duration string if return_string is True.
|
||||||
- dict: A dictionary with keys 'h', 'm', 's' representing hours, minutes, and seconds if return_string is False.
|
- dict: A dictionary with keys 'h', 'm', 's' representing hours, minutes, and seconds if return_string is False.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
video_duration = get_video_duration(file_path)
|
video_duration = get_video_duration(file_path)
|
||||||
|
|
||||||
if video_duration is not None:
|
if video_duration is not None:
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
__title__ = 'StreamingCommunity'
|
__title__ = 'StreamingCommunity'
|
||||||
__version__ = '1.9.0'
|
__version__ = '1.9.2'
|
||||||
__author__ = 'Lovi-0'
|
__author__ = 'Lovi-0'
|
||||||
__description__ = 'A command-line program to download film'
|
__description__ = 'A command-line program to download film'
|
||||||
__copyright__ = 'Copyright 2024'
|
__copyright__ = 'Copyright 2024'
|
||||||
|
@ -309,9 +309,8 @@ class InternManager():
|
|||||||
class OsSummary:
|
class OsSummary:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
ffmpeg_path, ffprobe_path = check_ffmpeg()
|
self.ffmpeg_path = None
|
||||||
self.ffmpeg_path = ffmpeg_path
|
self.ffprobe_path = None
|
||||||
self.ffprobe_path = ffprobe_path
|
|
||||||
|
|
||||||
def get_executable_version(self, command: list):
|
def get_executable_version(self, command: list):
|
||||||
"""
|
"""
|
||||||
@ -454,13 +453,17 @@ class OsSummary:
|
|||||||
else:
|
else:
|
||||||
command = 'which'
|
command = 'which'
|
||||||
|
|
||||||
# Locate ffmpeg and ffprobe
|
# Locate ffmpeg and ffprobe from path enviroment
|
||||||
if self.ffmpeg_path != None and "binary" not in self.ffmpeg_path:
|
if self.ffmpeg_path != None and "binary" not in self.ffmpeg_path:
|
||||||
self.ffmpeg_path = self.check_ffmpeg_location([command, 'ffmpeg'])
|
self.ffmpeg_path = self.check_ffmpeg_location([command, 'ffmpeg'])
|
||||||
|
|
||||||
if self.ffprobe_path != None and "binary" not in self.ffprobe_path:
|
if self.ffprobe_path != None and "binary" not in self.ffprobe_path:
|
||||||
self.ffprobe_path = self.check_ffmpeg_location([command, 'ffprobe'])
|
self.ffprobe_path = self.check_ffmpeg_location([command, 'ffprobe'])
|
||||||
|
|
||||||
|
# Locate ffmpeg from bin installation
|
||||||
|
if self.ffmpeg_path is None or self.ffprobe_path is None:
|
||||||
|
self.ffmpeg_path, self.ffprobe_path = check_ffmpeg()
|
||||||
|
|
||||||
if self.ffmpeg_path is None or self.ffprobe_path is None:
|
if self.ffmpeg_path is None or self.ffprobe_path is None:
|
||||||
console.log("[red]Cant locate ffmpeg or ffprobe")
|
console.log("[red]Cant locate ffmpeg or ffprobe")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
@ -501,7 +504,6 @@ class OsSummary:
|
|||||||
logging.info(f"Libraries: {', '.join([self.get_library_version(lib) for lib in optional_libraries])}")
|
logging.info(f"Libraries: {', '.join([self.get_library_version(lib) for lib in optional_libraries])}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# OTHER
|
# OTHER
|
||||||
os_manager = OsManager()
|
os_manager = OsManager()
|
||||||
internet_manager = InternManager()
|
internet_manager = InternManager()
|
||||||
|
@ -16,7 +16,7 @@ from StreamingCommunity.Util.message import start_message
|
|||||||
from StreamingCommunity.Util.console import console, msg
|
from StreamingCommunity.Util.console import console, msg
|
||||||
from StreamingCommunity.Util._jsonConfig import config_manager
|
from StreamingCommunity.Util._jsonConfig import config_manager
|
||||||
from StreamingCommunity.Upload.update import update as git_update
|
from StreamingCommunity.Upload.update import update as git_update
|
||||||
from StreamingCommunity.Util.os import OsSummary
|
from StreamingCommunity.Util.os import os_summary
|
||||||
from StreamingCommunity.Lib.TMBD import tmdb
|
from StreamingCommunity.Lib.TMBD import tmdb
|
||||||
from StreamingCommunity.Util.logger import Logger
|
from StreamingCommunity.Util.logger import Logger
|
||||||
|
|
||||||
@ -108,7 +108,6 @@ def initialize():
|
|||||||
start_message()
|
start_message()
|
||||||
|
|
||||||
# Get system info
|
# Get system info
|
||||||
os_summary = OsSummary()
|
|
||||||
os_summary.get_system_summary()
|
os_summary.get_system_summary()
|
||||||
|
|
||||||
# Set terminal size for win 7
|
# Set terminal size for win 7
|
||||||
|
2
setup.py
2
setup.py
@ -11,7 +11,7 @@ with open("requirements.txt", "r", encoding="utf-8-sig") as f:
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="StreamingCommunity",
|
name="StreamingCommunity",
|
||||||
version="1.9.1",
|
version="1.9.2",
|
||||||
long_description=read_readme(),
|
long_description=read_readme(),
|
||||||
long_description_content_type="text/markdown",
|
long_description_content_type="text/markdown",
|
||||||
author="Lovi-0",
|
author="Lovi-0",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user