diff --git a/app/ui/plugins/global.tsx b/app/ui/plugins/global.tsx index d46e80a..dd68dc0 100644 --- a/app/ui/plugins/global.tsx +++ b/app/ui/plugins/global.tsx @@ -42,7 +42,7 @@ const Global: React.FC = () => { 3. stream-gears(默认。防 FLV 流花屏)
4. sync-downloader(流式边录边传,需先为主播设定上传模板。不受 - pool2/threads/segment_time 控制,默认 3 线程上传,请确保上传带宽充足。需安装 FFmpeg) + pool2/threads/segment_time 控制,默认 3 线程上传,请确保上传带宽充足。需安装 FFmpeg)详见 Wiki 点击查看 } style={{ width: '100%' }} diff --git a/biliup/engine/download.py b/biliup/engine/download.py index 38845fb..1788451 100644 --- a/biliup/engine/download.py +++ b/biliup/engine/download.py @@ -537,7 +537,7 @@ def sync_download(stream_url, headers, segment_duration=60, max_file_size=100, o def upload(video_queue, stream_info, stop_event: threading.Event): with SessionLocal() as db: data = get_stream_info(db, f"{stream_info['name']}") - data, _ = fmt_title_and_desc({**data, "name": stream_info['name']}) + data, _ = fmt_title_and_desc({**data, "name": stream_info['name'], "title": stream_info['title']}) stream_info.update(data) logger.info(f"stream_info: {stream_info}") # 获取 BiliWebAsync.__init__ 的参数名 diff --git a/biliup/engine/sync_downloader.py b/biliup/engine/sync_downloader.py index e6bd51c..bb882d9 100644 --- a/biliup/engine/sync_downloader.py +++ b/biliup/engine/sync_downloader.py @@ -67,36 +67,49 @@ class SyncDownloader: def run_ffmpeg_with_url(self, ffmpeg_cmd, output_filename): with subprocess.Popen(ffmpeg_cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE) as ffmpeg_proc: - # print("[run] 启动 ffmpeg...") - logging.info("[run] 启动 ffmpeg...") + logger.info("[run] 启动 ffmpeg...") if output_filename == "-": data = ffmpeg_proc.stdout.read(self.read_block_size) # 读取第一个 data if not data: # 如果第一个 data 为空 - logging.info("[run] ffmpeg 没有输出数据,返回 False") + logger.info("[run] ffmpeg 没有输出数据,返回 False") + err = ffmpeg_proc.stderr.read() + if err: + logger.error("[run] ffmpeg err " + err.decode("utf-8", errors="replace")) return False self.video_queue.put(data) # 将第一个数据放入队列 while True: data = ffmpeg_proc.stdout.read(self.read_block_size) if not data: - logging.info("[run] ffmpeg stdout 已到达 EOF。结束本段写入。") + logger.info("[run] ffmpeg stdout 已到达 EOF。结束本段写入。") break self.video_queue.put(data) ffmpeg_proc.wait() + # 输出 ffmpeg 的错误信息(如果有的话) + err = ffmpeg_proc.stderr.read() + if err: + logger.error("[run] ffmpeg err " + err.decode("utf-8", errors="replace")) return True # 如果正常执行,返回 True def run_streamlink_with_ffmpeg(self, streamlink_cmd, ffmpeg_cmd, output_filename): with subprocess.Popen(streamlink_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as streamlink_proc: - logging.info("[run] 启动 streamlink...") - with subprocess.Popen(ffmpeg_cmd, stdin=streamlink_proc.stdout, stdout=subprocess.PIPE) as ffmpeg_proc: - logging.info("[run] 启动 ffmpeg...") + logger.info("[run] 启动 streamlink...") + with subprocess.Popen(ffmpeg_cmd, stdin=streamlink_proc.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as ffmpeg_proc: + logger.info("[run] 启动 ffmpeg...") if output_filename == "-": - logging.info("[run] 读取 ffmpeg stdout...") + logger.info("[run] 读取 ffmpeg stdout...") # 读取第一个数据 data = ffmpeg_proc.stdout.read(self.read_block_size) if not data: # 如果第一个数据为空 - logging.info("[run] ffmpeg 没有输出数据,返回 False") + logger.info("[run] ffmpeg 没有输出数据,返回 False") streamlink_proc.kill() # 终止 streamlink 进程 ffmpeg_proc.kill() # 终止 ffmpeg 进程 + # 打印错误输出 + ffmpeg_err = ffmpeg_proc.stderr.read() + if ffmpeg_err: + logger.error("[run] ffmpeg err " + ffmpeg_err.decode("utf-8", errors="replace")) + streamlink_err = streamlink_proc.stderr.read() + if streamlink_err: + logger.error("[run] streamlink err " + streamlink_err.decode("utf-8", errors="replace")) return False self.video_queue.put(data) # 将第一个数据放入队列 @@ -104,22 +117,26 @@ class SyncDownloader: while True: data = ffmpeg_proc.stdout.read(self.read_block_size) if not data: - logging.info("[run] ffmpeg stdout 已到达 EOF。结束本段写入。") + logger.info("[run] ffmpeg stdout 已到达 EOF。结束本段写入。") break self.video_queue.put(data) ffmpeg_proc.wait() - logging.info("[run] ffmpeg 已到达输出大小并退出。结束本段写入。") - - # streamlink_proc.kill() # 确保终止 streamlink 进程 - # streamlink_proc.wait() - + logger.info("[run] ffmpeg 已到达输出大小并退出。结束本段写入。") + # 打印 ffmpeg 子进程的错误输出 + ffmpeg_err = ffmpeg_proc.stderr.read() + if ffmpeg_err: + logger.error("[run] ffmpeg err " + ffmpeg_err.decode("utf-8", errors="replace")) + # 打印 streamlink 子进程的错误输出 + streamlink_err = streamlink_proc.stderr.read() + if streamlink_err: + logger.error("[run] streamlink err " + streamlink_err.decode("utf-8", errors="replace")) return True # 如果一切正常,返回 True def build_ffmpeg_cmd(self, input_source, output_filename, headers, segment_duration): cmd = [ "ffmpeg", - "-loglevel", "quiet", + "-loglevel", "error", # "-" # 覆盖输出文件 ] if headers: @@ -160,12 +177,12 @@ class SyncDownloader: output_filename = f"{self.output_prefix}{file_index:03d}.mkv" # print(f"\n[run] ========== 准备录制第 {file_index} 段:{output_filename} ==========") # logging.info(f"\n[run] == 当前下载流地址:{self.stream_url} ==") - logging.info(f"\n[run] == 准备录制第 {file_index} 段:{output_filename} ==") + logger.info(f"\n[run] == 准备录制第 {file_index} 段:{output_filename} ==") output_filename = "-" is_hls = '.m3u8' in urlparse(self.stream_url).path if not is_hls: # print("[run] 输入源不是 HLS 地址,将直接使用 ffmpeg 进行录制。", self.stream_url) - logging.info("[run] 输入源不是 HLS 地址,将直接使用 ffmpeg 进行录制。") + logger.info("[run] 输入源不是 HLS 地址,将直接使用 ffmpeg 进行录制。") ffmpeg_cmd = self.build_ffmpeg_cmd(self.stream_url, output_filename, self.headers, self.segment_duration) if not self.run_ffmpeg_with_url(ffmpeg_cmd, output_filename): @@ -174,7 +191,7 @@ class SyncDownloader: continue else: # print("[run] 输入源是 HLS 地址,将使用 streamlink + ffmpeg 进行录制。") - logging.info("[run] 输入源是 HLS 地址,将使用 streamlink + ffmpeg 进行录制。") + logger.info("[run] 输入源是 HLS 地址,将使用 streamlink + ffmpeg 进行录制。") streamlink_cmd = [ "streamlink", "--stream-segment-threads", "3", diff --git a/biliup/plugins/huya.py b/biliup/plugins/huya.py index f3f9f53..fb29ed0 100644 --- a/biliup/plugins/huya.py +++ b/biliup/plugins/huya.py @@ -16,6 +16,7 @@ from ..plugins import logger, match1, random_user_agent HUYA_WEB_BASE_URL = "https://www.huya.com" HUYA_MOBILE_BASE_URL = "https://m.huya.com" + @Plugin.download(regexp=r'https?://(?:(?:www|m)\.)?huya\.com') class Huya(DownloadBase): def __init__(self, fname, url, suffix='flv'): @@ -25,7 +26,7 @@ class Huya(DownloadBase): self.__room_id = url.split('huya.com/')[1].split('?')[0] self.huya_danmaku = config.get('huya_danmaku', False) self.huya_max_ratio = config.get('huya_max_ratio', 0) - self.huya_cdn = config.get('huya_cdn', "").upper() # 不填写时使用主播的CDN优先级 + self.huya_cdn = config.get('huya_cdn', "").upper() # 不填写时使用主播的CDN优先级 self.huya_protocol = 'Hls' if config.get('huya_protocol') == 'Hls' else 'Flv' self.huya_imgplus = config.get('huya_imgplus', True) self.huya_cdn_fallback = config.get('huya_cdn_fallback', False) @@ -123,12 +124,10 @@ class Huya(DownloadBase): self.raw_stream_url += f"&ratio={record_ratio}" return True - def danmaku_init(self): if self.huya_danmaku: self.danmaku = DanmakuClient(self.url, self.gen_download_filename()) - async def get_room_profile(self, use_api=False) -> dict: if use_api: params = { @@ -137,8 +136,8 @@ class Huya(DownloadBase): 'roomid': self.__room_id, 'showSecret': 1, } - resp = (await client.get(f"https://mp.huya.com/cache.php", \ - headers=self.fake_headers, params=params)).json() + resp = (await client.get(f"https://mp.huya.com/cache.php", + headers=self.fake_headers, params=params)).json() if resp['status'] != 200: raise Exception(f"{resp['message']}") return resp['data'] @@ -148,13 +147,12 @@ class Huya(DownloadBase): raise Exception(f"找不到这个主播") return json.loads(html.split('stream: ')[1].split('};')[0]) - async def get_stream_urls(self, protocol, use_api=False, allow_imgplus=True, is_xingxiu=False) -> dict: ''' 返回指定协议的所有CDN流 ''' streams = {} - weights = {} # https://cdnweb.huya.com/getUidsDomainList?anchor_uid={anchor_uid} + weights = {} # https://cdnweb.huya.com/getUidsDomainList?anchor_uid={anchor_uid} room_profile = await self.get_room_profile(use_api=use_api) if not use_api: try: @@ -170,8 +168,8 @@ class Huya(DownloadBase): if not allow_imgplus: stream_name = stream_name.replace('-imgplus', '') anti_code = anti_code + "&codec=264" \ - if is_xingxiu else \ - self.__build_query(stream_name, anti_code, _get_uid(self.fake_headers['cookie'], stream_name)) + if is_xingxiu else \ + self.__build_query(stream_name, anti_code, _get_uid(self.fake_headers.get('cookie', ''), stream_name)) for stream in stream_info: # 优先级<0代表不可用 priority = stream['iWebPriorityRate'] @@ -273,5 +271,6 @@ def _get_uid(cookie: str, stream_name: str) -> int: # } # )['data']['uid'] + def update_user_agent(headers: dict): - headers['User-Agent'] = f"HYSDK(Windows, {int(time.time())})" \ No newline at end of file + headers['User-Agent'] = f"HYSDK(Windows, {int(time.time())})"