diff --git a/components/files/files-list.js b/components/files/files-list.js
index 18fc0a1..26af1b7 100644
--- a/components/files/files-list.js
+++ b/components/files/files-list.js
@@ -1,8 +1,10 @@
+import {useState, useEffect} from 'react'
import PropTypes from 'prop-types'
import List from '@mui/material/List'
import ListSubheader from '@mui/material/ListSubheader'
import Typography from '@mui/material/Typography'
import Box from '@mui/material/Box'
+import Skeleton from '@mui/material/Skeleton'
import {useTheme, useColorScheme, styled} from '@mui/material/styles'
import Table from '@mui/material/Table'
import TableHead from '@mui/material/TableHead'
@@ -49,6 +51,7 @@ function formatSize(size) {
export default function FilesList({files}) {
const theme = useTheme()
const {mode} = useColorScheme()
+ const [audioMeta, setAudioMeta] = useState({})
const musicFiles = files.filter(file => file.mime.startsWith('audio'))
const pdfFiles = files.filter(file => file.mime === 'application/pdf')
@@ -63,86 +66,81 @@ export default function FilesList({files}) {
return extensionOrder[a.ext.toLowerCase()] - extensionOrder[b.ext.toLowerCase()]
})
+ useEffect(() => {
+ const audioFiles = files.filter(f => f.mime.startsWith('audio'))
+ if (audioFiles.length === 0) return
+
+ let cancelled = false
+
+ async function fetchAllMeta() {
+ const mm = await import('music-metadata-browser')
+ const results = {}
+ await Promise.all(
+ audioFiles.map(async file => {
+ try {
+ const meta = await mm.fetchFromUrl(`${apiUrl}${file.url}`, {skipCovers: true})
+ results[file.id] = meta.format
+ } catch {
+ results[file.id] = null
+ }
+ })
+ )
+ if (!cancelled) setAudioMeta(results)
+ }
+
+ fetchAllMeta()
+ return () => {
+ cancelled = true
+ }
+ }, [files])
+
+ const losslessFormats = new Set(['.flac', '.wav', '.aiff', '.alac'])
+
+ const qualityBadge = ext => {
+ const isHaute = losslessFormats.has(ext.toLowerCase())
+ return (
+ {isHaute ? 'Haute' : 'Faible'}
+ )
+ }
+
+ const renderMeta = file => {
+ const format = file.ext.replace('.', '').toUpperCase()
+
+ if (!(file.id in audioMeta)) {
+ return
+ }
+
+ const meta = audioMeta[file.id]
+ const sampleRate = meta?.sampleRate ? `${meta.sampleRate / 1000} kHz` : null
+ const bitDepth = meta?.bitsPerSample ? `${meta.bitsPerSample} bits` : null
+ const bitrate = meta?.bitrate ? `${Math.round(meta.bitrate / 1000)} kbps` : null
+ const details = [sampleRate, bitDepth ?? bitrate].filter(Boolean).join(' · ')
+
+ return (
+
+
+ {qualityBadge(file.ext)}
+ {format}
+
+ {details && {details}}
+
+ )
+ }
+
const handleClick = (e, url, fileName) => {
e.stopPropagation()
FileSaver.saveAs(url, fileName)
}
- const getQuality = (extension, caption) => {
- switch (extension) {
- case '.ogg':
- case '.aac':
- case '.mp3':
- return (
- Faible
- )
- case '.flac':
- if (caption === 'MAX') {
- return (
- {caption}
- )
- }
-
- if (caption === 'HAUTE') {
- return (
- {caption}
- )
- }
-
- return (
- Haute
- )
-
- default:
- return
- }
- }
-
return (
<>
{musicFiles.length > 0 && (
@@ -159,10 +157,9 @@ export default function FilesList({files}) {
- MAX : Jusqu’à 24-bit, 96 kHz, ≃ 3000 kbps (flac)
HAUTE : 16 bits, ≃ 900 kbps (flac)
FAIBLE : 320 kbps (ogg / aac / mp3)
- QUALITÉ
+ FORMAT
@@ -170,7 +167,7 @@ export default function FilesList({files}) {
{sortedMusicFiles.map(file => (
- {getQuality(file.ext.toLowerCase(), file?.caption?.toUpperCase())}
+ {renderMeta(file)}