file-show: read id3 tags

Massimo Melina committed Feb 7, 2024 at 22:22 UTC b8fc648926cc77d91f71879a8ebd6a84e17f4c3d
3 files changed +26 -11
frontend/src/show.ts
+15 -10
@@ -1,6 +1,7 @@
1 import { DirEntry, DirList, ext2type, state, useSnapState } from './state'
2 import { createElement as h, Fragment, useEffect, useRef, useState } from 'react'
3 -import { basename, dirname, domOn, hfsEvent, hIcon, isMac, newDialog, pathEncode, restartAnimation } from './misc'
3 +import { basename, dirname, domOn, hfsEvent, hIcon, isMac, newDialog, pathEncode, restartAnimation, getOrSet,
4 + loadJs} from './misc'
5 import { useEventListener, useWindowSize } from 'usehooks-ts'
6 import { EntryDetails, useMidnight } from './BrowseFiles'
7 import { Btn, FlexV, iconBtn, Spinner } from './components'
@@ -139,16 +140,18 @@ export function fileShow(entry: DirEntry, { startPlaying=false } = {}) {
140 setLoading(false)
141 },
142 onError: curFailed,
142 - onPlay() {
143 + async onPlay() {
144 const folder = dirname(cur.n)
145 const covers = state.list.filter(x => folder === dirname(x.n) // same folder
146 && x.name.match(/(?:folder|cover|albumart.*)\.jpe?g$/i))
147 setCover(_.maxBy(covers, 's')?.n || '')
147 - navigator.mediaSession.metadata = new MediaMetadata({
148 + const meta = navigator.mediaSession.metadata = new MediaMetadata({
149 title: cur.name,
150 album: decodeURIComponent(basename(dirname(cur.uri))),
151 artwork: covers.map(x => ({ src: x.n }))
152 })
153 + if (cur.ext === 'mp3')
154 + Object.assign(meta, await getId3Tags(location + cur.n).catch(() => {}))
155 }
156 })
157 ),
@@ -289,12 +292,14 @@ function showHelp() {
292 })
293 }
294
292 -function loadJs(url: string) {
293 - return new Promise((resolve, reject) => {
294 - const el = document.createElement('script')
295 - el.setAttribute('src', url)
296 - document.head.appendChild(el)
297 - el.addEventListener('load', resolve)
298 - el.addEventListener('error', reject)
295 +function getId3Tags(url: string) {
296 + return new Promise(async (resolve, reject) => {
297 + const tagLib = (window as any).jsmediatags
298 + || await loadJs('https://cdnjs.cloudflare.com/ajax/libs/jsmediatags/3.9.5/jsmediatags.min.js')
299 + .then(() => (window as any).jsmediatags)
300 + tagLib.read(url, {
301 + onSuccess: (res: any) => resolve(res.tags),
302 + onError: reject
303 + })
304 })
305 }
\ No newline at end of file
shared/index.ts
+10
@@ -104,6 +104,16 @@ export function disableConsoleDebug() {
104 console.debug = (...args) => (window as any).DEV && was(...args)
105 }
106
107 +export function loadJs(url: string) {
108 + return new Promise((resolve, reject) => {
109 + const el = document.createElement('script')
110 + el.setAttribute('src', url)
111 + document.head.appendChild(el)
112 + el.addEventListener('load', resolve)
113 + el.addEventListener('error', reject)
114 + })
115 +}
116 +
117 type DurationUnit = 'day' | 'hour' | 'minute' | 'second'
118 export function createDurationFormatter({ locale=undefined, unitDisplay='narrow', largest='day', smallest='second', maxTokens, skipZeroes }:
119 { skipZeroes?: boolean, largest?: DurationUnit, smallest?: DurationUnit, locale?: string, unitDisplay?: 'long' | 'short' | 'narrow', maxTokens?: 1 | 2 | 3 }={}) {
src/serveFile.ts
+1 -1
@@ -125,7 +125,7 @@ export function applyRange(ctx: Koa.Context, totalSize=ctx.response.length) {
125 return ctx.throw(HTTP_BAD_REQUEST, 'bad range')
126 const max = totalSize - 1
127 const start = bytes[0] ? Number(bytes[0]) : Math.max(0, totalSize-Number(bytes[1])) // a negative start is relative to the end
128 - const end = bytes[0] ? Number(bytes[1] || max) : max
128 + const end = (bytes[0] && bytes[1]) ? Math.min(max, Number(bytes[1])) : max
129 // we don't support last-bytes without knowing max
130 if (isNaN(end) && isNaN(max) || end > max || start > max) {
131 ctx.status = HTTP_RANGE_NOT_SATISFIABLE