mirror of
https://github.com/ihmily/DouyinLiveRecorder.git
synced 2025-12-26 05:48:32 +08:00
feat: add direct downloader
This commit is contained in:
parent
525b720627
commit
93a12ab41d
173
main.py
173
main.py
@ -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:
|
||||
|
||||
13
src/utils.py
13
src/utils.py
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user