Przeglądaj źródła

智能解析音乐文件名的歌手和歌名(支持多种分隔符和前缀序号)

onecold 1 rok temu
rodzic
commit
63209fb1b6

+ 110 - 35
entry/src/main/ets/common/util/Utility.ets

@@ -406,8 +406,9 @@ export class Utility {
     return false;
   }
 
+
   // 获取缩略图
-  static async getFetchFrameByTime(filePath: string) {
+  static async getFetchFrameByTime(filePath: string,time?:number) {
     if(Utility.isMusicByExtension(filePath)){
       return undefined
     }
@@ -420,6 +421,10 @@ export class Utility {
       avImageGenerator.fdSrc = avFileDescriptor;
       // 初始化入参
       let timeUs = 0
+      console.info('onecold time='+time)
+      if(time){
+        timeUs = (time > 0) ? time*60 : 0
+      }
       let queryOption = media.AVImageQueryOptions.AV_IMAGE_QUERY_NEXT_SYNC
       let param: media.PixelMapParams = {
         width : 300,
@@ -858,6 +863,7 @@ export class Utility {
             if (!name) {
               name = getFileNameWithoutExtension(inputPath);
             }
+            let pixelMap:image.PixelMap|undefined|null = undefined
             // Create VideoItem
             videoItem = new VideoItem(
               name,
@@ -907,9 +913,13 @@ export class Utility {
                   }
                 videoItem.pixelMapPath  = imagePath;
               }else if(Utility.isVideoByExtension(inputPath)){
-                //提取封面
-                await getVideoFFmpegCover(inputPath, imagePath);
-                videoItem.mimeType = getFileFormatByPath(inputPath)
+                pixelMap = await Utility.getFetchFrameByTime(inputPath)
+                if(pixelMap!==undefined&&pixelMap!==null){
+                  imagePath = await ImageUtil.savePixelMap(pixelMap,context.filesDir,md5Name)
+                  imagePath = fileUri.getUriFromPath(imagePath)
+                }else{
+                  imagePath = ''
+                }
                 imagePath = fileUri.getUriFromPath(imagePath)
                 videoItem.pixelMapPath  = imagePath;
               }
@@ -1502,57 +1512,122 @@ function formatDuration(seconds: string, forceHHMMSS: boolean = false): string {
 }
 
 
-// 定义解析结果的数据结构
+
+/**
+ * 定义解析结果的数据结构
+ */
 class MusicInfo {
   artist: string = "";  // 艺术家名称
-  title: string = "";  // 歌曲名称
+  title: string = "";   // 歌曲名称
   isValid: boolean = false;  // 格式是否有效
 }
+
 /**
- * 解析音乐文件名
- * @param fileName - 待解析的文件名(需包含扩展名)
- * @returns 包含解析结果的MusicInfo对象
+ * 精简版常见中文姓氏(音乐场景优化版)
  */
+const COMMON_CHINESE_SURNAMES = [
+// 超常见姓氏(覆盖约80%人口)
+  '李','王','张','刘','陈','杨','黄','赵','吴','周',
+  '徐','孙','马','朱','胡','林','郭','何','高','罗',
+  '郑','梁','谢','宋','唐','许','韩','邓','曹','彭',
+  '曾','萧','田','董','潘','袁','于','蔡','余','杜',
+
+  // 音乐行业常见姓氏(歌手高频姓)
+  '汪','苏','薛','谭','华','金','魏','陶','姜','窦',
+  '章','毛','易','方','宋','任','沈','贾','江','孔',
+
+  // 组合/乐队常见字
+  '羽','泉','信','乐','花','飞','龙','风','云','草'
+];
+
 /**
- * 智能解析音乐文件名(支持多种分隔符和前缀序号)
+ * 歌手特征关键词(优化版)
+ */
+const ARTIST_KEYWORDS = [
+// 中文特征
+  '乐队','组合','乐团','和','&','合唱',' featuring',
+  // 英文特征
+  'feat','ft','with','vs','presents','presents',
+  // 符号特征
+  '×','X','※','◆','♫'
+];
+/**
+ * 智能解析音乐文件名(支持多种分隔符和格式)
  * @param fileName - 待解析的完整文件名
  * @returns 结构化音乐信息
  */
+/**
+ * 智能解析音乐文件名
+ */
 function parseMusicFileName(fileName: string): MusicInfo {
-  const result = new MusicInfo();
-
-  // 1. 预处理:移除首尾空格(保留中间空格)
-  const cleanName = fileName.trim();
+  const result: MusicInfo = new MusicInfo();
+  if (!fileName) return result;
 
-  // 2. 提取文件扩展名(以最后一个点分隔)
-  const lastDotIndex = cleanName.lastIndexOf('.');
-  if (lastDotIndex < 0) return result; // 无扩展名
+  // 预处理
+  const cleanName: string = fileName.trim();
+  const lastDotIndex: number = cleanName.lastIndexOf('.');
+  const baseName: string = lastDotIndex > 0 ?
+  cleanName.substring(0,  lastDotIndex).trim() :
+    cleanName;
 
-  const baseName = cleanName.substring(0,  lastDotIndex).trim();
-  const extension = cleanName.substring(lastDotIndex  + 1);
+  // 支持的分隔符(明确定义类型)
+  const separators: string[] = ['-', '-', '—', '~', '~'];
 
-  // 3. 支持多种分隔符(中英文短横线)
-  const separators = ['-', '-', '—']; // 半角/全角短横线
-  let dashIndex = -1;
+  // 寻找分隔位置
+  let bestSplitIndex: number = -1;
 
-  // 查找最后一个有效分隔符位置
   for (const sep of separators) {
-    const index = baseName.lastIndexOf(sep);
-    if (index > dashIndex) dashIndex = index;
+    const index: number = baseName.lastIndexOf(sep);
+    if (index > bestSplitIndex) {
+      bestSplitIndex = index;
+    }
   }
 
-  // 4. 核心解析逻辑
-  if (dashIndex > 0 && dashIndex < baseName.length  - 1) {
-    let artistPart = baseName.substring(0,  dashIndex).trim();
-    result.title  = baseName.substring(dashIndex  + 1).trim();
+  // 分割字符串
+  if (bestSplitIndex > 0 && bestSplitIndex < baseName.length  - 1) {
+    let part1: string = baseName.substring(0,  bestSplitIndex).trim();
+    let part2: string = baseName.substring(bestSplitIndex  + 1).trim();
+
+    // 处理前缀序号
+    part1 = part1.replace(/^\d+[\s\.\-- —~~]*/, '').trim();
+
+    // 判断歌手部分(明确定义返回类型)
+    const identifyArtist = (str: string): boolean => {
+      return COMMON_CHINESE_SURNAMES.some((surname:  string) =>
+      str.startsWith(surname)  ||
+      new RegExp(`[ ,,、&&]${surname}`).test(str)
+      ) || ARTIST_KEYWORDS.some((keyword:  string) =>
+      str.includes(keyword)
+      );
+    };
+
+    // 判断歌手位置
+    const part1IsArtist: boolean = identifyArtist(part1);
+    const part2IsArtist: boolean = identifyArtist(part2);
+
+    if (part1IsArtist && !part2IsArtist) {
+      result.artist  = part1;
+      result.title  = part2;
+    } else if (part2IsArtist && !part1IsArtist) {
+      result.artist  = part2;
+      result.title  = part1;
+    } else {
+      result.artist  = part1.length  <= part2.length  ? part1 : part2;
+      result.title  = part1.length  <= part2.length  ? part2 : part1;
+    }
 
-    // 5. 处理前缀序号(如"04 - ")
-    const numPrefixRegex = /^\d+\s*[--—]\s*/; // 匹配数字+分隔符组合
-    artistPart = artistPart.replace(numPrefixRegex,  '').trim();
+    // 后处理
+    result.title  = result.title
+      .replace(/(?:\(|()[^))]*(?:)|\))/g, '')
+      .replace(/\s*[—-]\s*(?:Live|Version|Remix|伴奏).*/i, '')
+      .trim();
 
-    // 6. 最终有效性验证
-    result.artist  = artistPart;
-    result.isValid  = (result.artist.length  > 0 && result.title.length  > 0);
+    // 有效性验证
+    result.isValid  = result.artist.length  > 0 &&
+      result.title.length  > 0;
+  } else {
+    result.title  = baseName;
+    result.isValid  = result.title.length  > 0;
   }
 
   return result;

+ 2 - 2
entry/src/main/ets/view/LocalMusic.ets

@@ -9635,7 +9635,7 @@ export struct LocalMusic {
           await this.initLyric(lyricPath);
 
 
-          setTimeout(() => { //rmvb和flac格式马上记忆播放会报错,会延迟1s在读取记忆播放。
+          setTimeout(() => {
             this.avSessionController.initAvSession(false);
             this.avSessionController.setAVMetadataMusic(this.songList[this.curIndex], this.duration, this.lyricContent);
 
@@ -9644,7 +9644,7 @@ export struct LocalMusic {
             this.updateSessionPlayState(true)
             this.setCurrentPlayMode()
           }, 500)
-
+          //rmvb和flac格式马上记忆播放会报错,会延迟1s在读取记忆播放。
 
           let timeoutPlay = 0
           if (StrUtil.isNotEmpty(this.videoUrl) &&