{"version":3,"file":"lrcSubtitleBuilder.cjs","sources":["../../../src/utils/lrcSubtitleBuilder.js"],"sourcesContent":["import { Lrc } from 'lrc-kit';\r\nimport fs from 'fs-extra';\r\n\r\n/**\r\n * LRC 字幕构建器\r\n * 使用 lrc-kit 库从 LRC 文件创建字幕元素\r\n */\r\nexport class LRCSubtitleBuilder {\r\n  /**\r\n   * 从 LRC 文件加载字幕（同步方式）\r\n   * @param {string} lrcPath - LRC 文件路径\r\n   * @param {Object} options - 字幕样式选项\r\n   * @returns {Array} 字幕元素配置数组\r\n   */\r\n  static loadFromFile(lrcPath, options = {}) {\r\n    if (!fs.existsSync(lrcPath)) {\r\n      throw new Error(`LRC 文件不存在: ${lrcPath}`);\r\n    }\r\n\r\n    const content = fs.readFileSync(lrcPath, 'utf-8');\r\n    return this.loadFromContent(content, options);\r\n  }\r\n\r\n  /**\r\n   * 从 LRC 内容字符串加载字幕\r\n   * @param {string} lrcContent - LRC 文件内容\r\n   * @param {Object} options - 字幕样式选项\r\n   * @returns {Array} 字幕元素配置数组\r\n   */\r\n  static loadFromContent(lrcContent, options = {}) {\r\n    // 先手动解析多时间标签格式（支持 [03:06.90][02:33.64][01:06.36]文本 这种格式）\r\n    const lyricsWithMultipleTimes = this.parseMultipleTimeTags(lrcContent);\r\n    \r\n    // 如果有多个时间标签的歌词，使用手动解析的结果\r\n    if (lyricsWithMultipleTimes.length > 0) {\r\n      return this.toSubtitleElements(lyricsWithMultipleTimes, options);\r\n    }\r\n    \r\n    // 否则使用 lrc-kit 解析标准 LRC 内容\r\n    const parsedLrc = Lrc.parse(lrcContent);\r\n    \r\n    // 转换为字幕元素配置\r\n    return this.toSubtitleElements(parsedLrc.lyrics, options);\r\n  }\r\n\r\n  /**\r\n   * 解析多时间标签格式的 LRC 内容\r\n   * 支持格式：[03:06.90][02:33.64][01:06.36]文本内容\r\n   * @param {string} lrcContent - LRC 文件内容\r\n   * @returns {Array} 歌词数组，格式: [{timestamp: 秒数, content: '歌词内容'}, ...]\r\n   */\r\n  static parseMultipleTimeTags(lrcContent) {\r\n    const lines = lrcContent.split(/\\r?\\n/);\r\n    const lyrics = [];\r\n    \r\n    // 匹配时间标签的正则表达式：[mm:ss.xx] 或 [m:ss.xx]\r\n    const timeTagRegex = /\\[(\\d{1,2}):(\\d{2})\\.(\\d{2})\\]/g;\r\n    \r\n    for (const line of lines) {\r\n      const trimmedLine = line.trim();\r\n      \r\n      // 跳过空行\r\n      if (!trimmedLine) {\r\n        continue;\r\n      }\r\n      \r\n      // 重置正则表达式\r\n      timeTagRegex.lastIndex = 0;\r\n      \r\n      // 检查是否是元数据行（如 [ar:艺术家]），元数据行不包含时间标签格式\r\n      // 先检查是否有时间标签格式\r\n      const hasTimeTag = timeTagRegex.test(trimmedLine);\r\n      timeTagRegex.lastIndex = 0; // 重置正则表达式\r\n      \r\n      // 如果是元数据行（以 [ 开头但不是时间标签），跳过\r\n      if (trimmedLine.startsWith('[') && !hasTimeTag) {\r\n        continue;\r\n      }\r\n      \r\n      // 提取所有时间标签\r\n      const timeTags = [];\r\n      let match;\r\n      while ((match = timeTagRegex.exec(trimmedLine)) !== null) {\r\n        const minutes = parseInt(match[1], 10);\r\n        const seconds = parseInt(match[2], 10);\r\n        const centiseconds = parseInt(match[3], 10);\r\n        const timestamp = minutes * 60 + seconds + centiseconds / 100;\r\n        timeTags.push(timestamp);\r\n      }\r\n      \r\n      // 如果没有找到时间标签，跳过这一行\r\n      if (timeTags.length === 0) {\r\n        continue;\r\n      }\r\n      \r\n      // 提取文本内容（移除所有时间标签后剩余的部分）\r\n      const text = trimmedLine.replace(timeTagRegex, '').trim();\r\n      \r\n      // 如果文本为空，跳过\r\n      if (!text) {\r\n        continue;\r\n      }\r\n      \r\n      // 为每个时间标签创建一个歌词条目\r\n      for (const timestamp of timeTags) {\r\n        lyrics.push({\r\n          timestamp: timestamp,\r\n          content: text\r\n        });\r\n      }\r\n    }\r\n    \r\n    // 按时间戳排序\r\n    lyrics.sort((a, b) => a.timestamp - b.timestamp);\r\n    \r\n    return lyrics;\r\n  }\r\n\r\n  /**\r\n   * 将 lrc-kit 解析的歌词数组转换为字幕元素配置数组\r\n   * @param {Array} lyrics - lrc-kit 解析的歌词数组，格式: [{timestamp: 秒数, content: '歌词内容'}, ...]\r\n   * @param {Object} options - 配置选项\r\n   * @returns {Array} 字幕元素配置数组\r\n   */\r\n  static toSubtitleElements(lyrics, options = {}) {\r\n    const {\r\n      textColor = '#ffffff',\r\n      fontSize = 32,\r\n      x = '50%',\r\n      y = '80%',\r\n      textAlign = 'center',\r\n      minDuration = 1, // 最小显示时长\r\n      maxDuration = 5.0,  // 最大显示时长\r\n      sceneDuration = null, // 场景或轨道时长，用于计算最后一句歌词的显示时长\r\n      lastLineDuration = null, // 最后一句歌词的显示时长（如果指定，优先使用）\r\n      ...otherOptions // 其他选项（如 animations, split, fontPath 等）\r\n    } = options;\r\n\r\n    const elements = [];\r\n\r\n    for (let i = 0; i < lyrics.length; i++) {\r\n      const lyric = lyrics[i];\r\n      const nextLyric = lyrics[i + 1];\r\n\r\n      // 计算显示时长\r\n      let duration;\r\n      if (nextLyric) {\r\n        // 下一句歌词开始前结束，留一点间隔\r\n        duration = Math.max(\r\n          minDuration,\r\n          Math.min(\r\n            maxDuration,\r\n            nextLyric.timestamp - lyric.timestamp - 0.1 // 留 0.1 秒间隔\r\n          )\r\n        );\r\n      } else {\r\n        // 最后一句歌词的时长计算\r\n        if (lastLineDuration !== null && lastLineDuration !== undefined) {\r\n          // 如果明确指定了最后一句的时长，使用指定值\r\n          duration = Math.max(minDuration, lastLineDuration);\r\n        } else if (sceneDuration !== null && sceneDuration !== undefined && sceneDuration > 0) {\r\n          // 如果提供了场景/轨道时长，最后一句显示到场景/轨道结束\r\n          // 计算从最后一句开始到场景结束的时长\r\n          const timeToEnd = sceneDuration - lyric.timestamp;\r\n          duration = Math.max(minDuration, timeToEnd);\r\n        } else {\r\n          // 否则使用默认时长（3秒或最小时长）\r\n          duration = Math.max(minDuration, 3);\r\n        }\r\n      }\r\n      \r\n      elements.push({\r\n        ...otherOptions, // 传递其他选项（animations, split, fontPath 等）\r\n        type: 'subtitle',  // 使用 subtitle 类型\r\n        text: lyric.content,  // lrc-kit 使用 content 字段\r\n        color: textColor,\r\n        textColor: textColor,\r\n        fontSize: fontSize,\r\n        x: x,\r\n        y: y,\r\n        textAlign: textAlign,\r\n        startTime: lyric.timestamp,  // lrc-kit 使用 timestamp 字段（单位：秒）\r\n        duration: duration\r\n      });\r\n    }\r\n\r\n    return elements;\r\n  }\r\n\r\n  /**\r\n   * 将字幕元素添加到场景或轨道（同步方式）\r\n   * @param {Object} sceneOrTrack - 场景或轨道对象\r\n   * @param {string} lrcPath - LRC 文件路径\r\n   * @param {Object} options - 字幕样式选项\r\n   * @returns {Object} 返回场景或轨道对象（支持链式调用）\r\n   */\r\n  static addSubtitlesFromLRC(sceneOrTrack, lrcPath, options = {}) {\r\n    if (!fs.existsSync(lrcPath)) {\r\n      throw new Error(`LRC 文件不存在: ${lrcPath}`);\r\n    }\r\n\r\n    // 获取场景或轨道的时长，用于计算最后一句歌词的显示时长\r\n    const sceneOrTrackDuration = sceneOrTrack.duration;\r\n    \r\n    // 将场景/轨道时长传递给 options，用于计算最后一句歌词的时长\r\n    const subtitleElements = this.loadFromFile(lrcPath, {\r\n      ...options,\r\n      sceneDuration: sceneOrTrackDuration // 传递场景/轨道时长\r\n    });\r\n    \r\n    // 将字幕元素添加到场景或轨道\r\n    for (const subtitleConfig of subtitleElements) {\r\n      // 使用 addSubtitle 方法添加字幕\r\n      // startTime 是 LRC 中解析的时间戳，相对于场景开始时间（场景的 startTime 通常是 0）\r\n      sceneOrTrack.addSubtitle(subtitleConfig);\r\n    }\r\n\r\n    return sceneOrTrack;\r\n  }\r\n}\r\n\r\n\r\n\r\n"],"names":["Lrc"],"mappings":"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"}