feat: add direct downloader

This commit is contained in:
ihmily 2025-09-01 16:10:16 +08:00
parent 525b720627
commit 93a12ab41d
2 changed files with 167 additions and 19 deletions

173
main.py
View File

@ -22,11 +22,11 @@ import shutil
import random
import uuid
from pathlib import Path
import urllib.parse
import urllib.request
from urllib.error import URLError, HTTPError
from typing import Any
import configparser
import httpx
from src import spider, stream
from src.proxy import ProxyDetector
from src.utils import logger
@ -382,6 +382,35 @@ def clear_record_info(record_name: str, record_url: str) -> None:
color_obj.print_colored(f"[{record_name}]已经从录制列表中移除\n", color_obj.YELLOW)
def direct_download_stream(source_url: str, save_path: str, record_name: str, live_url: str) -> bool:
try:
with open(save_path, 'wb') as f:
client = httpx.Client(timeout=None)
with client.stream('GET', source_url, headers={}, follow_redirects=True) as response:
if response.status_code != 200:
logger.error(f"请求直播流失败,状态码: {response.status_code}")
return False
downloaded = 0
chunk_size = 1024 * 16
for chunk in response.iter_bytes(chunk_size):
if live_url in url_comments or exit_recording:
color_obj.print_colored(f"[{record_name}]录制时已被注释或请求停止,下载中断", color_obj.YELLOW)
clear_record_info(record_name, live_url)
return False
if chunk:
f.write(chunk)
downloaded += len(chunk)
print()
return True
except Exception as e:
logger.error(f"FLV下载错误: {e} 发生错误的行数: {e.__traceback__.tb_lineno}")
return False
def check_subprocess(record_name: str, record_url: str, ffmpeg_command: list, save_type: str,
script_command: str | None = None) -> bool:
save_file_path = ffmpeg_command[-1]
@ -476,6 +505,21 @@ def get_quality_code(qn):
return QUALITY_MAPPING.get(qn)
def is_flv_preferred_platform(link):
return any(i in link for i in ["douyin", "tiktok"])
def select_source_url(link, stream_info):
if is_flv_preferred_platform(link):
codec = utils.get_query_params(stream_info.get('flv_url'), "codec")
if codec and codec[0] == 'h265':
logger.warning("FLV is not supported for h265 codec, use HLS source instead")
else:
return stream_info.get('flv_url')
return stream_info.get('record_url')
def start_record(url_data: tuple, count_variable: int = -1) -> None:
global error_count
@ -1045,7 +1089,7 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
time.sleep(push_check_seconds)
continue
real_url = port_info.get('record_url')
real_url = select_source_url(record_url, port_info)
full_path = f'{default_path}/{platform}'
if real_url:
now = datetime.datetime.today().strftime("%Y-%m-%d_%H-%M-%S")
@ -1081,7 +1125,7 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
if enable_https_recording and real_url.startswith("http://"):
real_url = real_url.replace("http://", "https://")
http_record_list = ['shopee']
http_record_list = ['shopee', "migu"]
if platform in http_record_list:
real_url = real_url.replace("https://", "http://")
@ -1169,10 +1213,18 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
if platform in only_audio_platform_list:
only_audio_record = True
if only_audio_record or any(i in video_save_type for i in ['MP3', 'M4A']):
record_save_type = video_save_type
if is_flv_preferred_platform(record_url) and port_info.get('flv_url'):
codec = utils.get_query_params(port_info['flv_url'], "codec")
if codec and codec[0] == 'h265':
logger.warning("FLV is not supported for h265 codec, use TS format instead")
record_save_type = "TS"
if only_audio_record or any(i in record_save_type for i in ['MP3', 'M4A']):
try:
now = time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime())
extension = "mp3" if "m4a" not in video_save_type.lower() else "m4a"
extension = "mp3" if "m4a" not in record_save_type.lower() else "m4a"
name_format = "_%03d" if split_video_by_time else ""
save_file_path = (f"{full_path}/{anchor_name}_{title_in_name}{now}"
f"{name_format}.{extension}")
@ -1180,7 +1232,7 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
if split_video_by_time:
print(f'\r{anchor_name} 准备开始录制音频: {save_file_path}')
if "MP3" in video_save_type:
if "MP3" in record_save_type:
command = [
"-map", "0:a",
"-c:a", "libmp3lame",
@ -1204,7 +1256,7 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
]
else:
if "MP3" in video_save_type:
if "MP3" in record_save_type:
command = [
"-map", "0:a",
"-c:a", "libmp3lame",
@ -1227,7 +1279,7 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
record_name,
record_url,
ffmpeg_command,
video_save_type,
record_save_type,
custom_script
)
if comment_end:
@ -1239,7 +1291,8 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
error_count += 1
error_window.append(1)
if video_save_type == "FLV" or only_flv_record:
if only_flv_record:
logger.info(f"Use Direct Downloader to Download FLV Stream: {record_url}")
filename = anchor_name + f'_{title_in_name}' + now + '.flv'
save_file_path = f'{full_path}/{filename}'
print(f'{rec_info}/{filename}')
@ -1256,11 +1309,19 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
try:
flv_url = port_info.get('flv_url')
if flv_url:
_filepath, _ = urllib.request.urlretrieve(flv_url, save_file_path)
record_finished = True
recording.add(record_name)
start_record_time = datetime.datetime.now()
recording_time_list[record_name] = [start_record_time, record_quality_zh]
download_success = direct_download_stream(
flv_url, save_file_path, record_name, record_url
)
if download_success:
record_finished = True
print(f"\n{anchor_name} {time.strftime('%Y-%m-%d %H:%M:%S')} 直播录制完成\n")
recording.discard(record_name)
print(
f"\n{anchor_name} {time.strftime('%Y-%m-%d %H:%M:%S')} 直播录制完成\n")
else:
logger.debug("未找到FLV直播流跳过录制")
except Exception as e:
@ -1299,7 +1360,81 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
except Exception as e:
logger.error(f"转码失败: {e} ")
elif video_save_type == "MKV":
elif record_save_type == "FLV":
filename = anchor_name + f'_{title_in_name}' + now + ".flv"
print(f'{rec_info}/{filename}')
save_file_path = full_path + '/' + filename
try:
if split_video_by_time:
now = time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime())
save_file_path = f"{full_path}/{anchor_name}_{title_in_name}{now}_%03d.flv"
command = [
"-map", "0",
"-c:v", "copy",
"-c:a", "copy",
"-bsf:a", "aac_adtstoasc",
"-f", "segment",
"-segment_time", split_time,
"-segment_format", "flv",
"-reset_timestamps", "1",
save_file_path
]
else:
command = [
"-map", "0",
"-c:v", "copy",
"-c:a", "copy",
"-bsf:a", "aac_adtstoasc",
"-f", "flv",
"{path}".format(path=save_file_path),
]
ffmpeg_command.extend(command)
comment_end = check_subprocess(
record_name,
record_url,
ffmpeg_command,
record_save_type,
custom_script
)
if comment_end:
return
except subprocess.CalledProcessError as e:
logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}")
with max_request_lock:
error_count += 1
error_window.append(1)
try:
if converts_to_mp4:
seg_file_path = f"{full_path}/{anchor_name}_{title_in_name}{now}_%03d.mp4"
if split_video_by_time:
segment_video(
save_file_path, seg_file_path,
segment_format='mp4', segment_time=split_time,
is_original_delete=delete_origin_file
)
else:
threading.Thread(
target=converts_mp4,
args=(save_file_path, delete_origin_file)
).start()
else:
seg_file_path = f"{full_path}/{anchor_name}_{title_in_name}{now}_%03d.flv"
if split_video_by_time:
segment_video(
save_file_path, seg_file_path,
segment_format='flv', segment_time=split_time,
is_original_delete=delete_origin_file
)
except Exception as e:
logger.error(f"转码失败: {e} ")
elif record_save_type == "MKV":
filename = anchor_name + f'_{title_in_name}' + now + ".mkv"
print(f'{rec_info}/{filename}')
save_file_path = full_path + '/' + filename
@ -1335,7 +1470,7 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
record_name,
record_url,
ffmpeg_command,
video_save_type,
record_save_type,
custom_script
)
if comment_end:
@ -1347,7 +1482,7 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
error_count += 1
error_window.append(1)
elif video_save_type == "MP4":
elif record_save_type == "MP4":
filename = anchor_name + f'_{title_in_name}' + now + ".mp4"
print(f'{rec_info}/{filename}')
save_file_path = full_path + '/' + filename
@ -1382,7 +1517,7 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
record_name,
record_url,
ffmpeg_command,
video_save_type,
record_save_type,
custom_script
)
if comment_end:
@ -1418,7 +1553,7 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
record_name,
record_url,
ffmpeg_command,
video_save_type,
record_save_type,
custom_script
)
if comment_end:
@ -1462,7 +1597,7 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
record_name,
record_url,
ffmpeg_command,
video_save_type,
record_save_type,
custom_script
)
if comment_end:

View File

@ -10,6 +10,7 @@ import hashlib
import re
import traceback
from typing import Any
from urllib.parse import parse_qs, urlparse
from collections import OrderedDict
import execjs
from .logger import logger
@ -191,3 +192,15 @@ def replace_url(file_path: str | Path, old: str, new: str) -> None:
if old in content:
with open(file_path, 'w', encoding='utf-8-sig') as f:
f.write(content.replace(old, new))
def get_query_params(url: str, param_name: OptionalStr) -> dict | list[str]:
parsed_url = urlparse(url)
query_params = parse_qs(parsed_url.query)
if param_name is None:
return query_params
else:
values = query_params.get(param_name, [])
return values