From 2834508509df74be3999adc002cc20439a76b5c5 Mon Sep 17 00:00:00 2001 From: ihmily <961532186@qq.com> Date: Wed, 25 Sep 2024 02:16:54 +0800 Subject: [PATCH] feat: optimize ffmpeg live record --- config/config.ini | 6 +- main.py | 139 +++++++++++++++++++--------------------------- utils.py | 9 +++ 3 files changed, 70 insertions(+), 84 deletions(-) diff --git a/config/config.ini b/config/config.ini index 4bc6961..327ca4c 100644 --- a/config/config.ini +++ b/config/config.ini @@ -12,12 +12,12 @@ 排队读取网址时间(秒) = 0 是否显示循环秒数 = 否 是否显示直播源地址 = 否 -分段录制是否开启 = 否 +分段录制是否开启 = 是 视频分段时间(秒) = 1800 -ts录制完成后自动转为mp4格式 = 否 +ts录制完成后自动转为mp4格式 = 是 ts录制完成后自动增加生成m4a格式 = 否 音频录制完成后自动转为mp3格式 = 否 -追加格式后删除原文件 = 否 +追加格式后删除原文件 = 是 生成时间文件 = 否 使用代理录制的平台(逗号分隔) = tiktok, afreecatv, pandalive, winktv, flextv, popkontv, twitch, showroom 额外使用代理录制的平台(逗号分隔) = diff --git a/main.py b/main.py index 2515e87..cd0509d 100644 --- a/main.py +++ b/main.py @@ -4,7 +4,7 @@ Author: Hmily GitHub: https://github.com/ihmily Date: 2023-07-17 23:52:05 -Update: 2024-09-24 20:49:12 +Update: 2024-09-25 02:10:00 Copyright (c) 2023-2024 by Hmily, All Rights Reserved. Function: Record live stream video. """ @@ -70,7 +70,7 @@ from spider import ( from utils import ( logger, check_md5, - trace_error_decorator + trace_error_decorator, get_file_paths ) from msg_push import dingtalk, xizhi, tg_bot, email_message, bark @@ -647,34 +647,39 @@ def push_message(content: str) -> Union[str, list]: push_pts = '、'.join(push_pts) if len(push_pts) > 0 else [] return push_pts + def clear_record_info(record_name, anchor_name, record_url): if record_name in recording: recording.remove(record_name) if anchor_name in not_recording: not_recording.add(anchor_name) if record_url in url_comments and record_url in running_list: - print(f"[{anchor_name}]从录制列表中清除") + print(f"[{record_name}]从录制列表中清除") running_list.remove(record_url) -def check_subprocess(record_name: str, anchor_name: str, record_url: str, ffmpeg_command: []): + +def check_subprocess(record_name: str, anchor_name: str, record_url: str, ffmpeg_command: list): process = subprocess.Popen( - ffmpeg_command, stderr=subprocess.STDOUT + ffmpeg_command, stderr=subprocess.STDOUT, text=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE ) + # 检查进程是否结束 - while True: + while process.poll() is None: if record_url in url_comments: - print(f"[{anchor_name}]录制时已被注释,本条线程将会退出") + print(f"[{record_name}]录制时已被注释,本条线程将会退出") clear_record_info(record_name, anchor_name, record_url) process.terminate() process.wait() return True - return_code = process.poll() - if return_code is not None: - print( - f"{anchor_name}]录制退出,返回码: {return_code}" - ) - break - time.sleep(1) # 等待一段时间后再检查 + time.sleep(1) + + return_code = process.returncode + if return_code == 0: + print(f"[{record_name}]录制成功完成") + else: + print(f"[{record_name}]录制退出,返回码: {return_code}") + return return_code == 0 + def start_record(url_data: tuple, count_variable: int = -1): global warning_count @@ -1014,7 +1019,7 @@ def start_record(url_data: tuple, count_variable: int = -1): else: logger.error(f'{record_url} 未知直播地址') return - + if anchor_name: # 第一次从config中读取,带有'主播:',去除'主播:' # 之后的线程循环,已经是处理后的结果,不需要去处理 @@ -1193,11 +1198,8 @@ def start_record(url_data: tuple, count_variable: int = -1): flv_url = port_info.get('flv_url', None) if flv_url: _filepath, _ = urllib.request.urlretrieve(real_url, - full_path + '/' + filename) + f'{full_path}/{filename}') record_finished = True - else: - raise Exception('该直播无flv直播流,请切换视频保存类型') - except Exception as e: logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") warning_count += 1 @@ -1244,14 +1246,14 @@ def start_record(url_data: tuple, count_variable: int = -1): ffmpeg_command.extend(command) comment_end = check_subprocess( - record_name, - anchor_name, - record_url, - ffmpeg_command - ) + record_name, + anchor_name, + record_url, + ffmpeg_command + ) if comment_end: - return - record_finished = True + return + except subprocess.CalledProcessError as e: logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") warning_count += 1 @@ -1274,8 +1276,10 @@ def start_record(url_data: tuple, count_variable: int = -1): "-segment_time", split_time, "-segment_format", "mp4", "-reset_timestamps", "1", + "-movflags", "+frag_keyframe+empty_moov", save_file_path, ] + else: if create_time_file: filename_group = [anchor_name, filename_short] @@ -1295,14 +1299,14 @@ def start_record(url_data: tuple, count_variable: int = -1): ffmpeg_command.extend(command) comment_end = check_subprocess( - record_name, - anchor_name, - record_url, - ffmpeg_command - ) + record_name, + anchor_name, + record_url, + ffmpeg_command + ) if comment_end: return - record_finished = True + except subprocess.CalledProcessError as e: logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") warning_count += 1 @@ -1333,12 +1337,12 @@ def start_record(url_data: tuple, count_variable: int = -1): comment_end = check_subprocess( record_name, anchor_name, - record_url, + record_url, ffmpeg_command ) if comment_end: + threading.Thread(target=converts_m4a, args=(save_path_name,)).start() return - record_finished = True else: filename = anchor_name + '_' + now + ".mkv" @@ -1363,11 +1367,8 @@ def start_record(url_data: tuple, count_variable: int = -1): ffmpeg_command, ) if comment_end: - return - record_finished = True - - if ts_to_m4a: threading.Thread(target=converts_m4a, args=(save_file_path,)).start() + return except subprocess.CalledProcessError as e: logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") @@ -1403,8 +1404,8 @@ def start_record(url_data: tuple, count_variable: int = -1): ffmpeg_command, ) if comment_end: + threading.Thread(target=converts_m4a, args=(save_path_name,)).start() return - record_finished = True else: filename = anchor_name + '_' + now + ".ts" @@ -1426,11 +1427,8 @@ def start_record(url_data: tuple, count_variable: int = -1): ffmpeg_command, ) if comment_end: - return - record_finished = True - - if ts_to_m4a: threading.Thread(target=converts_m4a, args=(save_file_path,)).start() + return except subprocess.CalledProcessError as e: logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") @@ -1444,20 +1442,14 @@ def start_record(url_data: tuple, count_variable: int = -1): print(f'{rec_info}/{filename}') try: - if ts_to_mp4: - save_path_name = f"{full_path}/{anchor_name}_{now}_%03d.mp4" - segment_format = 'mp4' - else: - save_path_name = f"{full_path}/{anchor_name}_{now}_%03d.ts" - segment_format = 'mpegts' - + save_path_name = f"{full_path}/{anchor_name}_{now}_%03d.ts" command = [ "-c:v", "copy", - "-c:a", 'copy', + "-c:a", "copy", "-map", "0", "-f", "segment", "-segment_time", split_time, - "-segment_format", segment_format, + "-segment_format", 'mpegts', "-reset_timestamps", "1", save_path_name, ] @@ -1470,8 +1462,13 @@ def start_record(url_data: tuple, count_variable: int = -1): ffmpeg_command, ) if comment_end: + if ts_to_mp4: + file_paths = get_file_paths(os.path.dirname(save_path_name)) + prefix = os.path.basename(save_path_name).rsplit('_', maxsplit=1)[0] + for path in file_paths: + if prefix in path: + threading.Thread(target=converts_mp4, args=(path,)).start() return - record_finished = True except subprocess.CalledProcessError as e: logger.error( @@ -1481,7 +1478,6 @@ def start_record(url_data: tuple, count_variable: int = -1): else: filename = anchor_name + '_' + now + ".ts" - print(f'{rec_info}/{filename}') save_file_path = full_path + '/' + filename @@ -1505,17 +1501,13 @@ def start_record(url_data: tuple, count_variable: int = -1): comment_end = check_subprocess( record_name, anchor_name, - record_url, + record_url, ffmpeg_command ) if comment_end: - return - record_finished = True - - if ts_to_mp4: threading.Thread(target=converts_mp4, args=(save_file_path,)).start() - if ts_to_m4a: - threading.Thread(target=converts_m4a, args=(save_file_path,)).start() + return + except subprocess.CalledProcessError as e: logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") warning_count += 1 @@ -1825,25 +1817,11 @@ while True: yingke_cookie = read_config_value(config, 'Cookie', 'yingke_cookie', '') zhihu_cookie = read_config_value(config, 'Cookie', 'zhihu_cookie', '') - if len(video_save_type) > 0: - if video_save_type.upper().lower() == "FLV".lower(): - video_save_type = "FLV" - elif video_save_type.upper().lower() == "MKV".lower(): - video_save_type = "MKV" - elif video_save_type.upper().lower() == "TS".lower(): - video_save_type = "TS" - elif video_save_type.upper().lower() == "MP4".lower(): - video_save_type = "MP4" - elif video_save_type.upper().lower() == "TS音频".lower(): - video_save_type = "TS音频" - elif video_save_type.upper().lower() == "MKV音频".lower(): - video_save_type = "MKV音频" - else: - video_save_type = "TS" - print("直播视频保存格式设置有问题,这次录制重置为默认的TS格式") + video_save_type_list = ["FLV", "MKV", "TS", "MP4", "TS音频", "MKV音频"] + if video_save_type and video_save_type.upper() in video_save_type_list: + video_save_type = video_save_type.upper() else: video_save_type = "TS" - print("直播视频保存为TS格式") def transform_int_to_time(seconds: int) -> str: @@ -1865,12 +1843,11 @@ while True: line = line.strip() if len(line) < 20: continue - + is_comment_line = line.startswith("#") if is_comment_line: line = line[1:] - if re.search('[,,]', line): split_line = re.split('[,,]', line) else: @@ -2015,4 +1992,4 @@ while True: first_run = False - time.sleep(3) \ No newline at end of file + time.sleep(3) diff --git a/utils.py b/utils.py index 7493c64..beb272e 100644 --- a/utils.py +++ b/utils.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- +import os import functools import hashlib import traceback @@ -98,3 +99,11 @@ def update_config(file_path, section, key, new_value): print(f"配置文件中[{section}]下的{key}的值已更新") except Exception as e: print(f"写入配置文件时出错: {e}") + + +def get_file_paths(directory): + file_paths = [] + for root, dirs, files in os.walk(directory): + for file in files: + file_paths.append(os.path.join(root, file)) + return file_paths