import { common, ConfigurationConstant } from '@kit.AbilityKit'; import { CommonConstants } from '../common/constants/CommonConstants'; import { VideoItem } from '../viewmodel/VideoItem'; import { LazyDataSource } from '../common/util/LazyDataSource'; import { AppUtil, LogUtil, PreferencesUtil, StrUtil, ToastUtil } from '@pura/harmony-utils'; import fs from '@ohos.file.fs'; import { changeMusicCover, findLocalCoverImage, getApiLyric, repairAudioMetadata, searchCover, syncLyricToDB} from '../common/util/MusicTagUtils'; import { util } from '@kit.ArkTS'; import { extractHwMediaMetadata, FFMpegTags, Utility } from '../common/util/Utility'; import { DialogHelper } from '@pura/harmony-dialog'; import MediaTable from '../common/util/MediaTable'; // 批量乱码修正,元数据接口用华为的接口获取 @Component export struct FixMessyView { private listScroller: ListScroller = new ListScroller() onFixResult = (_result: boolean) => { } @Link isFixMessy: boolean; @State bundleName: string = '' @State dataSource: LazyDataSource = new LazyDataSource([]) @Prop selectedFiles: Array @StorageProp('topRectHeight') topRectHeight: number = 0; @State isDarkMode: boolean = false @StorageProp('currentColorMode') @Watch('onColorModeChange') currentMode: number = ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT; @StorageProp('themeColor') themeColor: string = CommonConstants.DEFAULT_THEME_COLOR; context = this.getUIContext().getHostContext() as common.UIAbilityContext // 新增状态用于进度显示 @State currentProgress: number = 0; @State totalFiles: number = 0; @State currentFileIndex: number = 0; @State currentFileName: string = ''; @State isEmbedding: boolean = false; @State embedSuccessCount: number = 0; @State embedFailedCount: number = 0; private table: MediaTable = new MediaTable(this.context); // 新增状态用于跟踪每个文件的嵌入状态 @State embedStatusMap: Map = new Map(); onColorModeChange() { this.isDarkMode = this.currentMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK } async aboutToAppear() { this.bundleName = AppUtil.getBundleName() // 确保 selectedFiles 有默认值后再初始化 dataSource if (this.selectedFiles && this.selectedFiles.length > 0) { this.dataSource = new LazyDataSource(this.selectedFiles) } else { this.dataSource = new LazyDataSource([]) } await new Promise((resolve, reject) => { this.table.getRdbStore(this.context, (err:Error) => { err ? reject(err) : resolve(); }); }); } build() { Column(){ this.topTitleBar() this.startButton() this.listView() } .height('100%') .width('100%') .backgroundColor($r('app.color.index_background')) } @Builder listView() { Column(){ List({ scroller: this.listScroller }) { ListItemGroup({ header: this.listHeader() }) { LazyForEach(this.dataSource, (item: VideoItem, index: number) => { ListItem() { Column() { this.MusicItem(item, index) } } .transition(TransitionEffect.asymmetric( TransitionEffect.scale({ x: 1.2, y: 1.2 }).animation({ duration: 500 }), TransitionEffect.scale({ x: 0, y: 0 }) )) .clickEffect({ level: ClickEffectLevel.LIGHT }) }, (item: VideoItem) => item.filePath) } } .cachedCount(2) // .layoutWeight(1) // .height('100%') .transition(TransitionEffect.asymmetric( TransitionEffect.scale({ x: 1.2, y: 1.2 }).animation({ duration: 500 }), TransitionEffect.scale({ x: 0, y: 0 }) )) } .borderRadius(20) .padding({top:10,bottom:6}) .height('auto') .backgroundColor($r('app.color.start_window_background')) .margin({top:20,right:20,left:20}) .layoutWeight(1) .height('100%') } @Builder listHeader() { Stack({alignContent:Alignment.Start}){ Text(`共${this.selectedFiles.length}首歌`) .fontSize(14) .maxLines(1) .textAlign(TextAlign.Start) .padding({ left: 25 }) .fontColor($r('app.color.text_color')) .margin({bottom:6,top:4,left: 6 }) } .height('auto').width('100%') } @Builder startButton(){ Stack(){ Progress({ value: this.currentProgress, total: this.totalFiles, type: ProgressType.Capsule }).height(55) .margin({top:20,right:20,left:20}) .backgroundColor($r('app.color.index_background')) Button({ type: ButtonType.Capsule, stateEffect: true }) { Row() { // 菜单图标 SymbolGlyph($r('sys.symbol.star_trophy'))// .size({ width: 22, height: 22 }) .fontSize(22) .fontColor([this.themeColor]) .alignSelf(ItemAlign.Center) .margin({ left: 25 }) // 菜单标题 Text('开始乱码修正') .margin({ left: 10, right: 20 }) .fontSize(15) .fontWeight(480) Blank() // 右侧箭头 Image($r('app.media.arrow_right')) .width(22) .height(22) .margin({ left: 0, right: 20 }) .align(Alignment.Center) } .width('100%') } .margin({top:20,right:20,left:20}) .height(55) .enabled(!this.isEmbedding) .backgroundColor($r('app.color.start_window_background')) .clickEffect({ level: ClickEffectLevel.MIDDLE,scale:0.6 }) .onClick(()=>{ this.startEmbed() }) } } /** * 开始修复 */ async startEmbed() { if (this.isEmbedding || !this.selectedFiles || this.selectedFiles.length === 0) { return; } this.isEmbedding = true; this.currentProgress = 0; this.currentFileIndex = 0; this.embedSuccessCount = 0; this.embedFailedCount = 0; this.totalFiles = this.selectedFiles.length; // 清空之前的嵌入状态 this.embedStatusMap = new Map(); // 创建异步任务数组 const embedTasks = this.selectedFiles.map(async (item: VideoItem, index: number) => { try { this.currentFileIndex = index + 1; this.currentFileName = item.name; let success = true; let hwTags = await extractHwMediaMetadata(item.filePath) let artist = hwTags?.artist|| ''; let title = hwTags?.title|| ''; let album = hwTags?.album|| ''; console.info('onecold 乱码修正 artist='+artist); console.info('onecold 乱码修正 title='+title); console.info('onecold 乱码修正 album='+album); const metadata:FFMpegTags = { title: title, artist: artist, album: album, }; //内嵌下标签的值,设置overwrite为true会直接修改原文件 const result:boolean = await repairAudioMetadata( item.filePath, item.lyricContent||'', metadata, true ); //入库 if (result) { this.table.updateMediaInfo(item.filePath,title, artist, album, item.lyricContent||'','','','','','','','','', (success: boolean, error?: string) => { if (success) { console.info('onecold 乱码修正 更新库成功') }else{ console.info('onecold 乱码修正 更新库失败') } }); } // 更新嵌入状态 if (item.filePath) { this.embedStatusMap.set(item.filePath, success); } if (success) { this.embedSuccessCount++; } else { this.embedFailedCount++; } // 滚动到当前处理的项目位置 // this.listScroller.scrollToIndex(index); this.currentProgress = Math.floor(((index + 1) / this.totalFiles) * 100); return success; } catch (error) { console.error(`处理文件 ${item.name} 失败: ${JSON.stringify(error)}`); this.embedFailedCount++; return false; } }); try { // 并发执行所有嵌入任务 await Promise.all(embedTasks); } catch (error) { this.onFixResult(false) console.error(`批量修复过程出错: ${JSON.stringify(error)}`); } finally { ToastUtil.showToast(`修复完成: 成功 ${this.embedSuccessCount}, 失败 ${this.embedFailedCount}`) this.isEmbedding = false; this.currentProgress = 0; this.totalFiles = this.selectedFiles.length; this.onFixResult(true) // 可以在这里添加完成后的提示或回调 console.info(`修复完成: 成功 ${this.embedSuccessCount}, 失败 ${this.embedFailedCount}`); } } @Builder topTitleBar() { // 顶部安全区和自定义标题栏 Column() { // 顶部安全区 Blank() .height(this.topRectHeight) .backgroundColor(this.isDarkMode ? $r('app.color.title_bar_bg') : this.themeColor) .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP]) // 自定义标题栏(Stack实现绝对居中) Stack() { // 居中标题 Text('批量乱码修正') .fontSize(18) .fontColor(Color.White) .align(Alignment.Center) // 左右按钮 Row() { Image($r('app.media.left_back_white')) .width(26) .height(26) .margin({ left: 12, right: 8 }) .onClick(() => { this.getUIContext().animateTo({ duration: 666 }, () => { // 动画闭包内控制Image组件的出现和消失 this.isFixMessy = !this.isFixMessy }) }) Blank().flexGrow(1) Blank().width(32) } .height(48) .width('100%') .alignItems(VerticalAlign.Center) } .height(48) .width('100%') .backgroundColor(this.isDarkMode ? $r('app.color.title_bar_bg') : this.themeColor) } } @Builder private MusicItem(item: VideoItem, index?: number) { Button({ type: ButtonType.Normal, stateEffect: true }) { Column() { Row() { SymbolGlyph($r('sys.symbol.music')) .fontSize(20) .fontColor([this.themeColor]) .alignSelf(ItemAlign.Center) .padding({ left: 25 }) Text(item.name) .fontSize(14) .maxLines(1) .padding({ left: 5 }) .textOverflow({ overflow: TextOverflow.MARQUEE }) .animation({ duration: 555, curve: 'Linear', }) .fontColor(Color.Gray) .margin({ left: 8 }) Blank() // 根据嵌入状态显示勾选图标 SymbolGlyph($r('sys.symbol.checkmark_circle')) .fontSize(20) .fontColor([this.themeColor]) .alignSelf(ItemAlign.Center) .padding({ right: 25 }) .animation({ duration: 666, curve: 'ease-in-out' // 可选动画曲线 }) .visibility(this.embedStatusMap.get(item.filePath)?Visibility.Visible:Visibility.None) } .layoutWeight(1) .height('100%') .width('100%') } } .backgroundColor(Color.Transparent) .width('100%') .height(40) } }