|
|
@@ -827,8 +827,8 @@ export class Utility {
|
|
|
let metaItem = await parseAudioMetadata(uri)
|
|
|
if(metaItem){
|
|
|
item.lyricContent = metaItem.lyricContent
|
|
|
- item.bit_rate = metaItem.bit_rate
|
|
|
- item.year = metaItem.year
|
|
|
+ item.bit_rate = formatBitrateToKbps(metaItem.bit_rate || "0");
|
|
|
+ item.year = metaItem.year ||'unknown'
|
|
|
item.probe_score = metaItem.probe_score
|
|
|
item.nb_streams = metaItem.nb_streams
|
|
|
item.nb_programs = metaItem.nb_programs
|
|
|
@@ -907,13 +907,13 @@ export class Utility {
|
|
|
videoItem.pyStr = pinyin4js.getShortPinyin(name)
|
|
|
videoItem.fileName = FileUtil.getFileName(inputPath);
|
|
|
if(format.duration)
|
|
|
- videoItem.duration = convertSecondsToTime(format.duration.toString())
|
|
|
+ videoItem.duration = formatDuration(format.duration.toString()||'00:00')
|
|
|
videoItem.size = fileSize;
|
|
|
- videoItem.bit_rate = format.bit_rate;
|
|
|
+ videoItem.bit_rate =formatBitrateToKbps(format.bit_rate || "0");
|
|
|
videoItem.probe_score = format.probe_score;
|
|
|
videoItem.nb_streams = format.nb_streams;
|
|
|
videoItem.nb_programs = format.nb_programs;
|
|
|
- videoItem.year = tags.TYER || tags.date || ''; // try different tag names for year
|
|
|
+ videoItem.year = tags.TYER || tags.date || 'unknown'; // try different tag names for year
|
|
|
videoItem.lyricContent = tags.LYRICS || tags.lyrics || tags.USLT || tags.UNSYNCEDLYRICS || '';
|
|
|
|
|
|
// 检查是否有封面图片流
|
|
|
@@ -924,22 +924,23 @@ export class Utility {
|
|
|
// 如果有封面图片,则提取
|
|
|
if (hasCover) {
|
|
|
|
|
|
- // try {
|
|
|
- // let md5Name = await MD5.digestSync(inputPath)
|
|
|
- // // const imageName = `${md5Name}.jpg`;
|
|
|
- // const imagePath = `${context.filesDir}${FileUtil.separator}${md5Name}`;
|
|
|
- // console.info('readMetaInfoFFmpeg asset imagePath: ', imagePath);
|
|
|
- // // 提取封面图片
|
|
|
- // await extractCoverImage(inputPath, imagePath);
|
|
|
- //
|
|
|
- // // 检查图片是否生成成功
|
|
|
- // if (fs.accessSync(imagePath)) {
|
|
|
- // videoItem.pixelMapPath = imagePath;
|
|
|
- //
|
|
|
- // }
|
|
|
- // } catch (error) {
|
|
|
- // console.warn(' 提取封面图片失败:', error.message);
|
|
|
- // }
|
|
|
+ try {
|
|
|
+ let md5Name = await MD5.digestSync(inputPath)
|
|
|
+ // const imageName = `${md5Name}.jpg`;
|
|
|
+ const imagePath = `${context.filesDir}${FileUtil.separator}${md5Name}`;
|
|
|
+ console.info('readMetaInfoFFmpeg asset imagePath: ', imagePath);
|
|
|
+ // 提取封面图片
|
|
|
+ // await extractCoverImage(inputPath, imagePath);
|
|
|
+ await FFmpegCover(inputPath, imagePath);
|
|
|
+
|
|
|
+ // 检查图片是否生成成功
|
|
|
+ if (fs.accessSync(imagePath)) {
|
|
|
+ videoItem.pixelMapPath = imagePath;
|
|
|
+
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.warn(' 提取封面图片失败:', error.message);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
console.info('Successfully parsed metadata:', videoItem);
|
|
|
@@ -1511,6 +1512,31 @@ function convertSecondsToTime(secondsStr: string): string {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * 秒数 → 智能时间格式(自动选择 MM:SS 或 HH:MM:SS)
|
|
|
+ * @param seconds 秒数字符串(如 "283.524351" 或 "3675")
|
|
|
+ * @param forceHHMMSS 强制使用 HH:MM:SS 格式(默认自动判断)
|
|
|
+ * @returns 格式化后的时间字符串
|
|
|
+ */
|
|
|
+function formatDuration(seconds: string, forceHHMMSS: boolean = false): string {
|
|
|
+ // 1. 校验输入
|
|
|
+ const secNum = parseFloat(seconds);
|
|
|
+ if (isNaN(secNum) || secNum < 0) return forceHHMMSS ? "00:00:00" : "00:00";
|
|
|
+
|
|
|
+ // 2. 计算时间分量
|
|
|
+ const totalSec = Math.floor(secNum);
|
|
|
+ const hours = Math.floor(totalSec / 3600);
|
|
|
+ const mins = Math.floor((totalSec % 3600) / 60);
|
|
|
+ const secs = totalSec % 60;
|
|
|
+
|
|
|
+ // 3. 格式化输出
|
|
|
+ const pad = (n: number) => n.toString().padStart(2, '0');
|
|
|
+
|
|
|
+ return forceHHMMSS || hours > 0
|
|
|
+ ? `${pad(hours)}:${pad(mins)}:${pad(secs)}` // HH:MM:SS
|
|
|
+ : `${pad(mins)}:${pad(secs)}`; // MM:SS
|
|
|
+}
|
|
|
+
|
|
|
|
|
|
// 定义解析结果的数据结构
|
|
|
class MusicInfo {
|
|
|
@@ -1574,6 +1600,11 @@ function getFileNameWithoutExtension(filePath: string): string {
|
|
|
return lastDotIndex > 0 ? fileName.substring(0, lastDotIndex) : fileName;
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * 从音乐文件中提取封面
|
|
|
+ * @param inputPath 音乐文件路径
|
|
|
+ * @returns Promise<void>
|
|
|
+ */
|
|
|
async function extractCoverImage(inputPath: string, outputPath: string): Promise<void> {
|
|
|
const commands = [
|
|
|
'ffmpeg',
|
|
|
@@ -1598,6 +1629,30 @@ async function extractCoverImage(inputPath: string, outputPath: string): Promise
|
|
|
});
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+/**
|
|
|
+ * 从音乐文件中提取封面
|
|
|
+ * @param inputPath 音乐文件路径
|
|
|
+ * @returns Promise<void>
|
|
|
+ */
|
|
|
+async function FFmpegCover(inputPath: string, outputPath: string) {
|
|
|
+ let commands = ["ffmpeg", "-i", inputPath, "-map", "0:v", "-c:v", "copy", outputPath];
|
|
|
+ FFmpeg.execute(commands, {
|
|
|
+ logCallback: (logLevel: number, logMessage: string) => {
|
|
|
+ console.info(`[FFmpeg LOG] [${logLevel}]${logMessage}`)
|
|
|
+ },
|
|
|
+ progressCallback: (message: string) => {
|
|
|
+ console.info(`[FFmpeg progress]${JSON.stringify(FFProgressMessageParser.parse(message))}`)
|
|
|
+ },
|
|
|
+ })
|
|
|
+ .then(() => {
|
|
|
+ console.info("FFmpeg execution succeeded.");
|
|
|
+ })
|
|
|
+ .catch((error: Error) => {
|
|
|
+ console.error(`FFmpeg execution failed with error: ${error.message}`);
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* 从音乐文件中提取歌词内容
|
|
|
* @param inputPath 音乐文件路径
|
|
|
@@ -1702,4 +1757,23 @@ async function parseAudioMetadata(inputPath: string): Promise<VideoItem> {
|
|
|
reject(new Error(`元数据解析失败: ${error instanceof Error ? error.message : String(error)}`));
|
|
|
}
|
|
|
});
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * 将比特率(bps)转换为 kbps 并格式化
|
|
|
+ * @param bitRate 比特率字符串(如 "5644802")
|
|
|
+ * @param decimalPlaces 保留小数位数(默认0)
|
|
|
+ * @returns 格式化后的 kbps 字符串(如 "5644 kbps")
|
|
|
+ */
|
|
|
+function formatBitrateToKbps(bitRate: string, decimalPlaces: number = 0): string {
|
|
|
+ // 1. 转换为数字
|
|
|
+ const bitsPerSecond = parseInt(bitRate);
|
|
|
+ if (isNaN(bitsPerSecond) || bitsPerSecond < 0) return "0 kbps";
|
|
|
+
|
|
|
+ // 2. 计算 kbps(1 kbps = 1000 bps)
|
|
|
+ const kbps = bitsPerSecond / 1000;
|
|
|
+
|
|
|
+ // 3. 格式化输出
|
|
|
+ return `${kbps.toFixed(decimalPlaces)} kbps`;
|
|
|
}
|