diff --git a/app/utils/script_generator.py b/app/utils/script_generator.py index 9005e32..6493e82 100644 --- a/app/utils/script_generator.py +++ b/app/utils/script_generator.py @@ -374,22 +374,65 @@ class ScriptProcessor: 记住:要敢于用"温和的违反"制造笑点,但要把握好尺度,让观众在轻松愉快中感受到乐趣。""" def calculate_duration_and_word_count(self, time_range: str) -> int: + """ + 计算时间范围的持续时长并估算合适的字数 + + Args: + time_range: 时间范围字符串,格式为 "HH:MM:SS,mmm-HH:MM:SS,mmm" + 例如: "00:00:50,100-00:01:21,500" + + Returns: + int: 估算的合适字数 + 基于经验公式: 每0.35秒可以说一个字 + 例如: 10秒可以说约28个字 (10/0.35≈28.57) + """ try: start_str, end_str = time_range.split('-') - - def time_to_seconds(time_str): - minutes, seconds = map(int, time_str.split(':')) - return minutes * 60 + seconds - + + def time_to_seconds(time_str: str) -> float: + """ + 将时间字符串转换为秒数(带毫秒精度) + + Args: + time_str: 时间字符串,格式为 "HH:MM:SS,mmm" + 例如: "00:00:50,100" 表示50.1秒 + + Returns: + float: 转换后的秒数(带毫秒) + """ + try: + # 处理毫秒部分 + time_part, ms_part = time_str.split(',') + hours, minutes, seconds = map(int, time_part.split(':')) + milliseconds = int(ms_part) + + # 转换为秒 + total_seconds = (hours * 3600) + (minutes * 60) + seconds + (milliseconds / 1000) + return total_seconds + + except ValueError as e: + logger.warning(f"时间格式解析错误: {time_str}, error: {e}") + return 0.0 + + # 计算开始和结束时间的秒数 start_seconds = time_to_seconds(start_str) end_seconds = time_to_seconds(end_str) + + # 计算持续时间(秒) duration = end_seconds - start_seconds - word_count = int(duration / 0.35) - + + # 根据经验公式计算字数: 每0.5秒一个字 + word_count = int(duration / 0.4) + + # 确保字数在合理范围内 + word_count = max(10, min(word_count, 500)) # 限制在10-500字之间 + + logger.debug(f"时间范围 {time_range} 的持续时间为 {duration:.3f}秒, 估算字数: {word_count}") return word_count + except Exception as e: - logger.info(f"时间格式转换错误: {traceback.format_exc()}") - return 100 + logger.warning(f"字数计算错误: {traceback.format_exc()}") + return 100 # 发生错误时返回默认字数 def process_frames(self, frame_content_list: List[Dict]) -> List[Dict]: for frame_content in frame_content_list: diff --git a/webui/components/script_settings.py b/webui/components/script_settings.py index 62c81b0..470eb94 100644 --- a/webui/components/script_settings.py +++ b/webui/components/script_settings.py @@ -20,52 +20,95 @@ from webui.utils import file_utils def get_batch_timestamps(batch_files, prev_batch_files=None): """ - 获取一批文件的时间戳范围 - 返回: (first_timestamp, last_timestamp, timestamp_range) + 解析一批文件的时间戳范围,支持毫秒级精度 - 文件名格式: keyframe_001253_000050.jpg - 其中 000050 表示 00:00:50 (50秒) - 000101 表示 00:01:01 (1分1秒) - Args: batch_files: 当前批次的文件列表 - prev_batch_files: 上一个批次的文件列表,用于处理单张图片的情况 + prev_batch_files: 上一个批次的文件列表,用于处理单张图片的情况 + + Returns: + tuple: (first_timestamp, last_timestamp, timestamp_range) + 时间戳格式: HH:MM:SS,mmm (时:分:秒,毫秒) + 例如: 00:00:50,100 表示50秒100毫秒 + + 示例文件名格式: + keyframe_001253_000050100.jpg + 其中 000050100 表示 00:00:50,100 (50秒100毫秒) """ if not batch_files: logger.warning("Empty batch files") - return "00:00", "00:00", "00:00-00:00" + return "00:00:00,000", "00:00:00,000", "00:00:00,000-00:00:00,000" + + def get_frame_files(): + """获取首帧和尾帧文件名""" + if len(batch_files) == 1 and prev_batch_files and prev_batch_files: + # 单张图片情况:使用上一批次最后一帧作为首帧 + first = os.path.basename(prev_batch_files[-1]) + last = os.path.basename(batch_files[0]) + logger.debug(f"单张图片批次,使用上一批次最后一帧作为首帧: {first}") + else: + first = os.path.basename(batch_files[0]) + last = os.path.basename(batch_files[-1]) + return first, last + + def extract_time(filename): + """从文件名提取时间信息""" + try: + # 提取类似 000050100 的时间戳部分 + time_str = filename.split('_')[2].replace('.jpg', '') + if len(time_str) < 9: # 处理旧格式 + time_str = time_str.ljust(9, '0') + return time_str + except (IndexError, AttributeError) as e: + logger.warning(f"Invalid filename format: {filename}, error: {e}") + return "000000000" + + def format_timestamp(time_str): + """ + 将时间字符串转换为 HH:MM:SS,mmm 格式 - # 如果当前批次只有一张图片,且有上一个批次的文件,则使用上一批次的最后一张作为首帧 - if len(batch_files) == 1 and prev_batch_files and len(prev_batch_files) > 0: - first_frame = os.path.basename(prev_batch_files[-1]) - last_frame = os.path.basename(batch_files[0]) - logger.debug(f"单张图片批次,使用上一批次最后一帧作为首帧: {first_frame}") - else: - # 提取首帧和尾帧的时间戳 - first_frame = os.path.basename(batch_files[0]) - last_frame = os.path.basename(batch_files[-1]) + Args: + time_str: 9位数字字符串,格式为 MMSSSMMM + 例如: 000050100 表示 00分50秒100毫秒 + + Returns: + str: HH:MM:SS,mmm 格式的时间戳 + """ + try: + if len(time_str) < 9: + logger.warning(f"Invalid timestamp format: {time_str}") + return "00:00:00,000" + + # 提取分钟、秒和毫秒 + minutes = int(time_str[-9:-6]) # 取后9位的前3位作为分钟 + seconds = int(time_str[-6:-3]) # 取中间3位作为秒数 + milliseconds = int(time_str[-3:]) # 取最后3位作为毫秒 + + # 处理进位 + if seconds >= 60: + minutes += seconds // 60 + seconds = seconds % 60 + + if minutes >= 60: + hours = minutes // 60 + minutes = minutes % 60 + else: + hours = 0 + + return f"{hours:02d}:{minutes:02d}:{seconds:02d},{milliseconds:03d}" + + except ValueError as e: + logger.warning(f"时间戳格式转换失败: {time_str}, error: {e}") + return "00:00:00,000" + + # 获取首帧和尾帧文件名 + first_frame, last_frame = get_frame_files() # 从文件名中提取时间信息 - first_time = first_frame.split('_')[2].replace('.jpg', '') # 000050 - last_time = last_frame.split('_')[2].replace('.jpg', '') # 000101 - - # 转换为分:秒格式 - def format_timestamp(time_str): - # 时间格式为 MMSS,如 0050 表示 00:50, 0101 表示 01:01 - if len(time_str) < 4: - logger.warning(f"Invalid timestamp format: {time_str}") - return "00:00" - - minutes = int(time_str[-4:-2]) # 取后4位的前2位作为分钟 - seconds = int(time_str[-2:]) # 取后2位作为秒数 - - # 处理进位 - if seconds >= 60: - minutes += seconds // 60 - seconds = seconds % 60 - - return f"{minutes:02d}:{seconds:02d}" + first_time = extract_time(first_frame) + last_time = extract_time(last_frame) + # 转换为标准时间戳格式 first_timestamp = format_timestamp(first_time) last_timestamp = format_timestamp(last_time) timestamp_range = f"{first_timestamp}-{last_timestamp}" @@ -542,7 +585,7 @@ def generate_script(tr, params): # 处理帧内容生成脚本 script_result = processor.process_frames(frame_content_list) - # ��结果转换为JSON字符串 + # 结果转换为JSON字符串 script = json.dumps(script_result, ensure_ascii=False, indent=2) except Exception as e: @@ -567,7 +610,7 @@ def generate_script(tr, params): if not api_key: raise ValueError("未配置 Narrato API Key,请在基础设置中配置") - # 准���API请求 + # 准备API请求 headers = { 'X-API-Key': api_key, 'accept': 'application/json'