mirror of
https://github.com/Arrowar/StreamingCommunity.git
synced 2025-06-07 12:05:35 +00:00
try fix stuttering
This commit is contained in:
parent
37b2161a1a
commit
dafb0f201b
@ -44,13 +44,14 @@ from .util import (
|
||||
M3U8_Decryption,
|
||||
M3U8_Ts_Files,
|
||||
M3U8_Parser,
|
||||
M3U8_Codec,
|
||||
M3U8_UrlFix
|
||||
)
|
||||
|
||||
|
||||
# Config
|
||||
Download_audio = config_manager.get_bool('M3U8_OPTIONS', 'download_audio')
|
||||
Donwload_subtitles = config_manager.get_bool('M3U8_OPTIONS', 'download_subtitles')
|
||||
DOWNLOAD_AUDIO = config_manager.get_bool('M3U8_OPTIONS', 'download_audio')
|
||||
DOWNLOAD_SUBTITLES = config_manager.get_bool('M3U8_OPTIONS', 'download_subtitles')
|
||||
DOWNLOAD_SPECIFIC_AUDIO = config_manager.get_list('M3U8_OPTIONS', 'specific_list_audio')
|
||||
DOWNLOAD_SPECIFIC_SUBTITLE = config_manager.get_list('M3U8_OPTIONS', 'specific_list_subtitles')
|
||||
TQDM_MAX_WORKER = config_manager.get_int('M3U8', 'tdqm_workers')
|
||||
@ -394,10 +395,10 @@ class M3U8_Segments:
|
||||
# Refresh progress bar
|
||||
progress_counter.refresh()
|
||||
|
||||
def join(self, output_filename: str, video_decoding: str = None, audio_decoding: str = None):
|
||||
def join(self, output_filename: str):
|
||||
"""
|
||||
Join all segments file to a mp4 file name
|
||||
!! NOT USED
|
||||
!! NOT USED IN THIS VERSION
|
||||
|
||||
Parameters:
|
||||
- video_decoding(str): video decoding to use with ffmpeg for only video
|
||||
@ -437,9 +438,7 @@ class M3U8_Segments:
|
||||
# ADD IF
|
||||
concatenate_and_save(
|
||||
file_list_path = file_list_path,
|
||||
output_filename = output_filename,
|
||||
video_decoding = video_decoding,
|
||||
audio_decoding = audio_decoding
|
||||
output_filename = output_filename
|
||||
)
|
||||
|
||||
|
||||
@ -455,7 +454,6 @@ class Downloader():
|
||||
- key (str, optional): Hexadecimal representation of the encryption key.
|
||||
"""
|
||||
|
||||
|
||||
self.m3u8_playlist = m3u8_playlist
|
||||
self.m3u8_index = m3u8_index
|
||||
self.key = bytes.fromhex(key) if key is not None else key
|
||||
@ -471,15 +469,16 @@ class Downloader():
|
||||
if self.key != None:
|
||||
hex_data = convert_to_hex(self.key)
|
||||
console.log(f"[cyan]Key use [white]=> [red]{hex_data}")
|
||||
logging.info(f"Key use: {self.key}")
|
||||
|
||||
# Initialize temp base path
|
||||
self.base_path = os.path.join(str(self.output_filename).replace(".mp4", ""))
|
||||
self.video_segments_path = os.path.join(self.base_path, "tmp", "video")
|
||||
self.audio_segments_path = os.path.join(self.base_path, "tmp", "audio")
|
||||
self.subtitle_segments_path = os.path.join(self.base_path, "tmp", "subtitle")
|
||||
logging.info(f"Output base path: {self.base_path}")
|
||||
|
||||
# Create temp folder
|
||||
logging.info("Create temp folder")
|
||||
os.makedirs(self.video_segments_path, exist_ok=True)
|
||||
os.makedirs(self.audio_segments_path, exist_ok=True)
|
||||
os.makedirs(self.subtitle_segments_path, exist_ok=True)
|
||||
@ -489,13 +488,9 @@ class Downloader():
|
||||
self.downloaded_subtitle = []
|
||||
self.downloaded_video = []
|
||||
|
||||
# Default decoding
|
||||
self.video_decoding = "avc1.640028"
|
||||
self.audio_decoding = "mp4a.40.2"
|
||||
|
||||
def __df_make_req__(self, url: str) -> str:
|
||||
"""
|
||||
Make a request to get text from the provided URL.
|
||||
Make a request to get text from the provided URL to test if index or m3u8 work correcly.
|
||||
|
||||
Parameters:
|
||||
- url (str): The URL to make the request to.
|
||||
@ -503,23 +498,30 @@ class Downloader():
|
||||
Returns:
|
||||
- str: The text content of the response.
|
||||
"""
|
||||
|
||||
try:
|
||||
|
||||
# Send a GET request to the provided URL
|
||||
config_headers.get('index')['user-agent'] = get_headers()
|
||||
response = requests.get(url, headers=config_headers.get('index'))
|
||||
|
||||
# Check status response of request
|
||||
logging.info(f"Test url: {url}")
|
||||
response.raise_for_status()
|
||||
|
||||
if response.ok:
|
||||
return response.text
|
||||
|
||||
else:
|
||||
logging.error(f"[df_make_req] Request to {url} failed with status code: {response.status_code}")
|
||||
logging.error(f"Request to {url} failed with status code: {response.status_code}")
|
||||
return None
|
||||
|
||||
except requests.RequestException as req_err:
|
||||
logging.error(f"[df_make_req] Error occurred during request: {req_err}")
|
||||
logging.error(f"Error occurred during request: {req_err}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"[df_make_req] An unexpected error occurred: {e}")
|
||||
logging.error(f"An unexpected error occurred: {e}")
|
||||
return None
|
||||
|
||||
def manage_playlist(self, m3u8_playlist_text):
|
||||
@ -530,7 +532,7 @@ class Downloader():
|
||||
m3u8_playlist_text (str): The text content of the M3U8 playlist.
|
||||
"""
|
||||
|
||||
global Download_audio, Donwload_subtitles
|
||||
global DOWNLOAD_AUDIO, DOWNLOAD_SUBTITLES
|
||||
|
||||
# Create an instance of the M3U8_Parser class
|
||||
parse_class_m3u8 = M3U8_Parser(DOWNLOAD_SPECIFIC_SUBTITLE)
|
||||
@ -547,7 +549,7 @@ class Downloader():
|
||||
console.log(f"[cyan]Find audios language: [red]{[obj_audio.get('language') for obj_audio in self.list_available_audio]}")
|
||||
else:
|
||||
console.log("[red]Cant find a list of audios")
|
||||
Download_audio = False
|
||||
DOWNLOAD_AUDIO = False
|
||||
|
||||
# Collect available subtitles and default subtitle
|
||||
self.list_available_subtitles = parse_class_m3u8.get_subtitles()
|
||||
@ -558,7 +560,7 @@ class Downloader():
|
||||
console.log(f"[cyan]Find subtitles language: [red]{[obj_sub.get('language') for obj_sub in self.list_available_subtitles]}")
|
||||
else:
|
||||
console.log("[red]Cant find a list of audios")
|
||||
Donwload_subtitles = False
|
||||
DOWNLOAD_SUBTITLES = False
|
||||
|
||||
# Collect best quality video
|
||||
m3u8_index_obj = parse_class_m3u8.get_best_quality()
|
||||
@ -566,7 +568,6 @@ class Downloader():
|
||||
# Get URI of the best quality and codecs parameters
|
||||
console.log(f"[cyan]Select resolution: [red]{m3u8_index_obj.get('width')}")
|
||||
m3u8_index = m3u8_index_obj.get('uri')
|
||||
m3u8_index_decoding = m3u8_index_obj.get('codecs')
|
||||
|
||||
# Fix URL if it is not complete with http:\\site_name.domain\...
|
||||
if "http" not in m3u8_index:
|
||||
@ -581,13 +582,12 @@ class Downloader():
|
||||
logging.warning("[download_m3u8] Can't find a valid m3u8 index")
|
||||
sys.exit(0)
|
||||
|
||||
# Collect best index, video decoding, and audio decoding
|
||||
# Set m3u8_index
|
||||
self.m3u8_index = m3u8_index
|
||||
|
||||
# if is present in playlist
|
||||
if m3u8_index_decoding != None:
|
||||
self.video_decoding = m3u8_index_decoding.split(",")[0]
|
||||
self.audio_decoding = m3u8_index_decoding.split(",")[1]
|
||||
# Get obj codec
|
||||
self.codec: M3U8_Codec = parse_class_m3u8.codec
|
||||
logging.info(f"Get coded: {self.codec}")
|
||||
|
||||
def manage_subtitle(self):
|
||||
"""
|
||||
@ -622,8 +622,8 @@ class Downloader():
|
||||
'path': os.path.abspath(sub_full_path)
|
||||
})
|
||||
|
||||
|
||||
# If the subtitle file doesn't exist, download it
|
||||
logging.info(f"Download uri subtitles: {obj_subtitle.get('uri')} => {sub_full_path}")
|
||||
response = requests.get(obj_subtitle.get('uri'))
|
||||
open(sub_full_path, "wb").write(response.content)
|
||||
|
||||
@ -658,6 +658,7 @@ class Downloader():
|
||||
if not os.path.exists(full_path_audio):
|
||||
|
||||
# If the audio segment directory doesn't exist, download audio segments
|
||||
logging.info(f"Download uri audio: {obj_audio.get('uri')} => {full_path_audio}")
|
||||
audio_m3u8 = M3U8_Segments(obj_audio.get('uri'), full_path_audio, self.key)
|
||||
console.log(f"[purple]Download audio segments [white]=> [red]{obj_audio.get('language')}.")
|
||||
|
||||
@ -716,7 +717,6 @@ class Downloader():
|
||||
ts_files = [f for f in os.listdir(full_path) if f.endswith(".ts")]
|
||||
ts_files.sort(key=Downloader.extract_number)
|
||||
logging.info(f"Find {len(ts_files)} stream files to join")
|
||||
logging.info(f"Using parameter: \n-c:v = {self.video_decoding} -c:a = {self.audio_decoding}])")
|
||||
|
||||
# Check if there are enough .ts files to join (at least 10)
|
||||
if len(ts_files) < 10:
|
||||
@ -735,8 +735,9 @@ class Downloader():
|
||||
return concatenate_and_save(
|
||||
file_list_path=file_list_path,
|
||||
output_filename=out_file_name,
|
||||
video_decoding=self.video_decoding,
|
||||
audio_decoding=self.audio_decoding
|
||||
v_codec=self.codec.video_codec,
|
||||
a_codec=self.codec.audio_codec,
|
||||
bandwidth=self.codec.bandwidth
|
||||
)
|
||||
|
||||
def download_audios(self):
|
||||
@ -903,12 +904,12 @@ class Downloader():
|
||||
self.manage_playlist(m3u8_playlist_text)
|
||||
|
||||
# Download subtitles
|
||||
if Donwload_subtitles:
|
||||
if DOWNLOAD_SUBTITLES:
|
||||
logging.info("Download subtitles ...")
|
||||
self.manage_subtitle()
|
||||
|
||||
# Download segmenets of audio tracks
|
||||
if Download_audio:
|
||||
if DOWNLOAD_AUDIO:
|
||||
logging.info("Download audios ...")
|
||||
self.manage_audio()
|
||||
|
||||
|
@ -13,5 +13,5 @@ from .helper import (
|
||||
from .decryption import M3U8_Decryption
|
||||
from .installer import check_ffmpeg
|
||||
from .math_calc import M3U8_Ts_Files
|
||||
from .parser import M3U8_Parser
|
||||
from .parser import M3U8_Parser, M3U8_Codec
|
||||
from .url_fix import M3U8_UrlFix
|
@ -5,6 +5,7 @@ import os
|
||||
import json
|
||||
import logging
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
|
||||
# External libraries
|
||||
@ -182,17 +183,19 @@ def add_subtitle(input_video_path: str, input_subtitle_path: str, output_video_p
|
||||
return output_video_path
|
||||
|
||||
|
||||
def concatenate_and_save(file_list_path: str, output_filename: str, video_decoding: str = None, audio_decoding: str = None, prefix: str = "segments", output_directory: str = None) -> str:
|
||||
def concatenate_and_save(file_list_path: str, output_filename: str, v_codec: str = None, a_codec: str = None, bandwidth: int = None, prefix: str = "segments", output_directory: str = None) -> str:
|
||||
"""
|
||||
Concatenate input files and save the output with specified decoding parameters.
|
||||
|
||||
Parameters:
|
||||
- file_list_path (str): Path to the file list containing the segments.
|
||||
- output_filename (str): Output filename for the concatenated video.
|
||||
- video_decoding (str): Video decoding parameter (optional).
|
||||
- audio_decoding (str): Audio decoding parameter (optional).
|
||||
- v_codec (str): Video decoding parameter (optional).
|
||||
- a_codec (str): Audio decoding parameter (optional).
|
||||
- bandwidth (int): Bitrate for the output video (optional).
|
||||
- prefix (str): Prefix to add at the end of output file name (default is "segments").
|
||||
- output_directory (str): Directory to save the output file. If not provided, defaults to the current directory.
|
||||
- codecs (str): Codecs for video and audio (optional).
|
||||
|
||||
Returns:
|
||||
- output_file_path (str): Path to the saved output file.
|
||||
@ -208,9 +211,17 @@ def concatenate_and_save(file_list_path: str, output_filename: str, video_decodi
|
||||
output_args = {
|
||||
'c': 'copy',
|
||||
'loglevel': DEBUG_FFMPEG,
|
||||
'y': None
|
||||
'y': None,
|
||||
}
|
||||
|
||||
# Add BANDWIDTH and CODECS if provided
|
||||
if bandwidth is not None:
|
||||
output_args['b:v'] = str(bandwidth)
|
||||
"""if v_codec is not None:
|
||||
output_args['vcodec'] = v_codec
|
||||
if a_codec is not None:
|
||||
output_args['acodec'] = a_codec"""
|
||||
|
||||
# Set up the output file name by modifying the video file name
|
||||
output_file_name = os.path.splitext(output_filename)[0] + f"_{prefix}.mp4"
|
||||
|
||||
|
@ -12,6 +12,58 @@ import requests
|
||||
from m3u8 import M3U8
|
||||
|
||||
|
||||
|
||||
class M3U8_Codec():
|
||||
"""
|
||||
Represents codec information for an M3U8 playlist.
|
||||
|
||||
Attributes:
|
||||
- bandwidth (int): Bandwidth of the codec.
|
||||
- resolution (str): Resolution of the codec.
|
||||
- codecs (str): Codecs information in the format "avc1.xxxxxx,mp4a.xx".
|
||||
- audio_codec (str): Audio codec extracted from the codecs information.
|
||||
- video_codec (str): Video codec extracted from the codecs information.
|
||||
"""
|
||||
|
||||
def __init__(self, bandwidth, resolution, codecs):
|
||||
"""
|
||||
Initializes the M3U8Codec object with the provided parameters.
|
||||
|
||||
Parameters:
|
||||
- bandwidth (int): Bandwidth of the codec.
|
||||
- resolution (str): Resolution of the codec.
|
||||
- codecs (str): Codecs information in the format "avc1.xxxxxx,mp4a.xx".
|
||||
"""
|
||||
self.bandwidth = bandwidth
|
||||
self.resolution = resolution
|
||||
self.codecs = codecs
|
||||
self.audio_codec = None
|
||||
self.video_codec = None
|
||||
self.parse_codecs()
|
||||
|
||||
def parse_codecs(self):
|
||||
"""
|
||||
Parses the codecs information to extract audio and video codecs.
|
||||
|
||||
Extracted codecs are set as attributes: audio_codec and video_codec.
|
||||
"""
|
||||
# Split the codecs string by comma
|
||||
codecs_list = self.codecs.split(',')
|
||||
|
||||
# Separate audio and video codecs
|
||||
for codec in codecs_list:
|
||||
if codec.startswith('avc'):
|
||||
self.video_codec = codec
|
||||
elif codec.startswith('mp4a'):
|
||||
self.audio_codec = codec
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
Returns a string representation of the M3U8Codec object.
|
||||
"""
|
||||
return f"BANDWIDTH={self.bandwidth},RESOLUTION={self.resolution},CODECS=\"{self.codecs}\""
|
||||
|
||||
|
||||
class M3U8_Parser:
|
||||
def __init__(self, DOWNLOAD_SPECIFIC_SUBTITLE = None):
|
||||
"""
|
||||
@ -24,6 +76,7 @@ class M3U8_Parser:
|
||||
self.subtitle_playlist = [] # No vvt ma url a vvt
|
||||
self.subtitle = [] # Url a vvt
|
||||
self.audio_ts = []
|
||||
self.codec: M3U8_Codec = None
|
||||
self.DOWNLOAD_SPECIFIC_SUBTITLE = DOWNLOAD_SPECIFIC_SUBTITLE
|
||||
|
||||
def parse_data(self, m3u8_content: str) -> (None):
|
||||
@ -41,11 +94,19 @@ class M3U8_Parser:
|
||||
|
||||
# Collect video info with url, resolution and codecs
|
||||
for playlist in m3u8_obj.playlists:
|
||||
|
||||
self.video_playlist.append({
|
||||
"uri": playlist.uri,
|
||||
"width": playlist.stream_info.resolution,
|
||||
"codecs": playlist.stream_info.codecs
|
||||
})
|
||||
})
|
||||
|
||||
self.codec = M3U8_Codec(
|
||||
playlist.stream_info.bandwidth,
|
||||
playlist.stream_info.resolution,
|
||||
playlist.stream_info.codecs
|
||||
)
|
||||
logging.info(f"Parse: {playlist.stream_info}")
|
||||
logging.info(f"Coded test: {self.codec.bandwidth}")
|
||||
|
||||
# Collect info of encryption if present, method, uri and iv
|
||||
for key in m3u8_obj.keys:
|
||||
@ -92,7 +153,7 @@ class M3U8_Parser:
|
||||
self.subtitle.append(segment.uri)
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"[M3U8_Parser] Error parsing M3U8 content: {e}")
|
||||
logging.error(f"Error parsing M3U8 content: {e}")
|
||||
|
||||
def get_resolution(self, uri: str) -> (int):
|
||||
"""
|
||||
@ -134,8 +195,8 @@ class M3U8_Parser:
|
||||
return sorted_uris[0]
|
||||
|
||||
except:
|
||||
logging.error("[M3U8_Parser] Error: Can't find M3U8 resolution by width...")
|
||||
logging.info("[M3U8_Parser] Try searching in URI")
|
||||
logging.error("Error: Can't find M3U8 resolution by width...")
|
||||
logging.info("Try searching in URI")
|
||||
|
||||
# Sort the list of video playlist items based on the 'width' attribute if present,
|
||||
# otherwise, use the resolution obtained from the 'uri' attribute as a fallback.
|
||||
@ -146,7 +207,7 @@ class M3U8_Parser:
|
||||
return sorted_uris[0]
|
||||
else:
|
||||
|
||||
logging.info("[M3U8_Parser] No video playlists found.")
|
||||
logging.info("No video playlists found.")
|
||||
return None
|
||||
|
||||
def get_subtitles(self):
|
||||
@ -168,7 +229,7 @@ class M3U8_Parser:
|
||||
|
||||
# Get language name
|
||||
name_language = sub_info.get("language")
|
||||
logging.info(f"[M3U8_Parser] Find subtitle: {name_language}")
|
||||
logging.info(f"Find subtitle: {name_language}")
|
||||
|
||||
# Check if there is custom subtitles to download
|
||||
if len(self.DOWNLOAD_SPECIFIC_SUBTITLE) > 0:
|
||||
@ -178,7 +239,7 @@ class M3U8_Parser:
|
||||
continue
|
||||
|
||||
# Make request to m3u8 subtitle to extract vtt
|
||||
logging.info(f"[M3U8_Parser] Download subtitle: {name_language}")
|
||||
logging.info(f"Download subtitle: {name_language}")
|
||||
req_sub_content = requests.get(sub_info.get("uri"), headers={'user-agent': get_headers()})
|
||||
|
||||
try:
|
||||
@ -196,13 +257,13 @@ class M3U8_Parser:
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"[M3U8_Parser] Cant donwload: {name_language}, error: {e}")
|
||||
logging.error(f"Cant donwload: {name_language}, error: {e}")
|
||||
|
||||
# Return
|
||||
return output
|
||||
|
||||
else:
|
||||
logging.info("[M3U8_Parser] No subtitle find")
|
||||
logging.info("No subtitle find")
|
||||
return None
|
||||
|
||||
def get_track_audios(self) -> list:
|
||||
@ -213,10 +274,10 @@ class M3U8_Parser:
|
||||
list: A list of dictionaries containing language and URI information for audio tracks, or None if no audio tracks are found.
|
||||
"""
|
||||
|
||||
logging.info(f"[M3U8_Parser] Finding {len(self.audio_ts)} playlist(s) with audio.")
|
||||
logging.info(f"Finding {len(self.audio_ts)} playlist(s) with audio.")
|
||||
|
||||
if self.audio_ts:
|
||||
logging.info("[M3U8_Parser] Getting list of available audio names")
|
||||
logging.info("Getting list of available audio names")
|
||||
list_output = []
|
||||
|
||||
# For all languages present in m3u8
|
||||
@ -232,7 +293,7 @@ class M3U8_Parser:
|
||||
return list_output
|
||||
|
||||
else:
|
||||
logging.info("[M3U8_Parser] No audio tracks found")
|
||||
logging.info("No audio tracks found")
|
||||
return None
|
||||
|
||||
def get_default_subtitle(self):
|
||||
@ -290,3 +351,4 @@ class M3U8_Parser:
|
||||
|
||||
# Return the default audio track dictionary
|
||||
return dict_default_audio
|
||||
|
@ -1,6 +1,6 @@
|
||||
# 07.04.24
|
||||
|
||||
|
||||
# to do
|
||||
# run somehwere backup
|
||||
# add config to trace if ffmpeg is install, using config in local or temp
|
||||
|
||||
@ -11,6 +11,7 @@ import logging
|
||||
# Winreg only work for windows
|
||||
if platform.system() == "Windows":
|
||||
|
||||
# Winreg only work for windows
|
||||
import winreg
|
||||
|
||||
# Define Windows registry key for user environment variables
|
||||
|
Loading…
x
Reference in New Issue
Block a user