@@ -6,24 +6,41 @@ import { setAlbumArtInCache } from "../hooks/useAlbumArt";
66export async function importAudioFiles (
77 audioFiles : Array < { file : File } | File > ,
88 addSong : ( song : Song , file : File ) => Promise < void > ,
9- t : any ,
9+ t : any
1010) {
1111 if ( ! audioFiles || audioFiles . length === 0 ) return ;
1212
1313 const BATCH_SIZE = 10 ;
1414 let successCount = 0 ;
1515 let errorCount = 0 ;
16+ let skippedCount = 0 ; // Count skipped duplicates
1617 let currentBatch = 1 ;
1718 const totalBatches = Math . ceil ( audioFiles . length / BATCH_SIZE ) ;
1819
20+ // Track unique files to avoid duplicate imports in this session
21+ const processedFileIds = new Set < string > ( ) ;
22+
1923 for ( let i = 0 ; i < audioFiles . length ; i += BATCH_SIZE ) {
2024 const batch = audioFiles . slice ( i , i + BATCH_SIZE ) ;
21- toast . loading ( t ( "batch.processing" , { currentBatch, totalBatches } ) ) ;
25+ toast . loading (
26+ t ( "batch.processing" , { currentBatch, totalBatches, successCount } )
27+ ) ;
2228
2329 // Process sequentially to reduce memory pressure
2430 for ( const audioFile of batch ) {
2531 try {
2632 const file : File = ( audioFile as any ) . file || ( audioFile as File ) ;
33+
34+ // Unique ID based on file metadata (name, size, lastModified)
35+ const fileId = `${ file . name } -${ file . size } -${ file . lastModified } ` ;
36+ if ( processedFileIds . has ( fileId ) ) {
37+ // Skip duplicates detected via unique ID
38+ console . log ( `Skipping duplicate file: ${ file . name } ` ) ;
39+ skippedCount ++ ;
40+ continue ;
41+ }
42+ processedFileIds . add ( fileId ) ; // Mark the file as processed
43+
2744 const metadata = await extractAudioMetadata ( file ) ;
2845 const songId = generateUniqueId ( ) ;
2946
@@ -32,31 +49,36 @@ export async function importAudioFiles(
3249 let processedMimeType = file . type ;
3350
3451 const isFlo =
35- file . name . toLowerCase ( ) . endsWith ( ' .flo' ) ||
36- metadata . encoding ?. codec === ' flo' ;
52+ file . name . toLowerCase ( ) . endsWith ( " .flo" ) ||
53+ metadata . encoding ?. codec === " flo" ;
3754
3855 if ( isFlo ) {
3956 try {
4057 const arrayBuffer = await file . arrayBuffer ( ) ;
41- const isSafari = / ^ ( (? ! c h r o m e | a n d r o i d ) .) * s a f a r i / i. test ( navigator . userAgent ) ;
58+ const isSafari = / ^ ( (? ! c h r o m e | a n d r o i d ) .) * s a f a r i / i. test (
59+ navigator . userAgent
60+ ) ;
4261
4362 if ( isSafari ) {
4463 // Safari: Pre-decode to WAV for compatibility
4564 const { decodeFloToWav } = await import ( "./refloWavHelper" ) ;
4665 const wavBytes = await decodeFloToWav ( arrayBuffer ) ;
47- const wavBlob = new Blob ( [ wavBytes ] , { type : ' audio/wav' } ) ;
66+ const wavBlob = new Blob ( [ wavBytes ] , { type : " audio/wav" } ) ;
4867 processedFile = new File (
4968 [ wavBlob ] ,
50- file . name . replace ( / \. f l o $ / i, ' .wav' ) ,
51- { type : ' audio/wav' }
69+ file . name . replace ( / \. f l o $ / i, " .wav" ) ,
70+ { type : " audio/wav" }
5271 ) ;
53- processedMimeType = ' audio/wav' ;
72+ processedMimeType = " audio/wav" ;
5473 console . log ( `Pre-decoded flo to WAV for Safari: ${ file . name } ` ) ;
5574 } else {
5675 // Non-Safari: Pre-decode to PCM for Web Audio API
5776 const { decodeFloToAudioBuffer } = await import ( "./floProcessor" ) ;
5877 const audioContext = new AudioContext ( ) ;
59- const audioBuffer = await decodeFloToAudioBuffer ( arrayBuffer , audioContext ) ;
78+ const audioBuffer = await decodeFloToAudioBuffer (
79+ arrayBuffer ,
80+ audioContext
81+ ) ;
6082
6183 // Store as interleaved Float32Array PCM
6284 const frameCount = audioBuffer . length ;
@@ -66,25 +88,27 @@ export async function importAudioFiles(
6688 // Interleave channels
6789 for ( let i = 0 ; i < frameCount ; i ++ ) {
6890 for ( let ch = 0 ; ch < channels ; ch ++ ) {
69- pcmData [ i * channels + ch ] = audioBuffer . getChannelData ( ch ) [ i ] ;
91+ pcmData [ i * channels + ch ] = audioBuffer . getChannelData ( ch ) [
92+ i
93+ ] ;
7094 }
7195 }
7296
73- const pcmBlob = new Blob ( [ pcmData . buffer ] , { type : ' audio/pcm' } ) ;
97+ const pcmBlob = new Blob ( [ pcmData . buffer ] , { type : " audio/pcm" } ) ;
7498 processedFile = new File (
7599 [ pcmBlob ] ,
76- file . name . replace ( / \. f l o $ / i, ' .pcm' ) ,
77- { type : ' audio/pcm' }
100+ file . name . replace ( / \. f l o $ / i, " .pcm" ) ,
101+ { type : " audio/pcm" }
78102 ) ;
79- processedMimeType = ' audio/pcm' ;
103+ processedMimeType = " audio/pcm" ;
80104
81105 // Store AudioBuffer properties for reconstruction
82106 metadata . encoding = {
83107 ...metadata . encoding ,
84108 sampleRate : audioBuffer . sampleRate ,
85109 channels : audioBuffer . numberOfChannels ,
86110 bitsPerSample : 32 , // Float32
87- codec : ' pcm-float32' ,
111+ codec : " pcm-float32" ,
88112 } ;
89113
90114 // Close the temporary AudioContext
@@ -95,22 +119,27 @@ export async function importAudioFiles(
95119 } catch ( error ) {
96120 console . warn ( "Failed to pre-decode flo file, storing original:" , error ) ;
97121 // Keep original file if pre-decoding fails
98- processedMimeType = ' audio/x-flo' ;
122+ processedMimeType = " audio/x-flo" ;
99123 }
100124 }
101125
102126 // Save album art separately if present
103127 const hasAlbumArt = ! ! metadata . albumArt ;
104128 if ( hasAlbumArt && metadata . albumArt ) {
105- await musicIndexedDbHelper . saveAlbumArt ( songId , metadata . albumArt ) ;
129+ await musicIndexedDbHelper . saveAlbumArt (
130+ songId ,
131+ metadata . albumArt
132+ ) ;
106133 setAlbumArtInCache ( songId , metadata . albumArt ) ;
107134 }
108135
109136 const song : Song = {
110137 id : songId ,
111138 title : metadata . title ,
112139 artist : metadata . artist ,
113- album : metadata . album || t ( "songInfo.album" , { title : t ( "common.unknownAlbum" ) } ) ,
140+ album :
141+ metadata . album ||
142+ t ( "songInfo.album" , { title : t ( "common.unknownAlbum" ) } ) ,
114143 duration : metadata . duration ,
115144 url : "" , // Will be set by addSong
116145 albumArt : metadata . albumArt , // Keep for immediate display
@@ -142,8 +171,14 @@ export async function importAudioFiles(
142171 }
143172
144173 toast . dismiss ( ) ;
174+ // Show accurate results with skipped files count
145175 if ( successCount > 0 ) {
146- toast . success ( t ( "filePicker.successImport" , { count : successCount } ) ) ;
176+ toast . success (
177+ t ( "filePicker.successImport" , {
178+ count : successCount ,
179+ skipped : skippedCount ,
180+ } )
181+ ) ;
147182 }
148183 if ( errorCount > 0 ) {
149184 toast . error ( t ( "filePicker.failedImport" , { count : errorCount } ) ) ;
0 commit comments