mirror of
https://codeberg.org/armin/noiz.git
synced 2026-09-01 05:50:52 +02:00
Parse embedded cover art from ID3v2 APIC frames and FLAC picture blocks
This commit is contained in:
parent
a46be8ed0a
commit
aacb427d1b
1 changed files with 44 additions and 5 deletions
49
index.html
49
index.html
|
|
@ -21,8 +21,11 @@ footer { display: flex !important; align-items: center; gap: 16px; padding: 10px
|
|||
footer .brand { margin-left: auto; }
|
||||
.content { flex: 1; overflow: hidden; display: flex; flex-direction: column; }
|
||||
.pinned { flex-shrink: 0; background: linear-gradient(to bottom, #1e2132, #161821); padding: 16px 24px; display: flex; flex-direction: row; gap: 20px; align-items: center; }
|
||||
.player { display: flex; flex-direction: row; width: 100%; align-items: center; gap: 24px; }
|
||||
.player { display: flex; flex-direction: row; width: 100%; align-items: center; }
|
||||
canvas { width: 100%; height: 100px; border-radius: 6px; display: block; }
|
||||
.cover-art { width: 120px; height: 120px; flex-shrink: 0; border-radius: 8px; overflow: hidden; background: #272c42; display: flex; align-items: center; justify-content: center; margin-right: 24px; }
|
||||
.cover-art img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
||||
.cover-art .no-cover { font-size: 32px; color: #6b7089; user-select: none; }
|
||||
.player-right { display: flex; flex-direction: column; width: 320px; flex-shrink: 0; align-items: center; }
|
||||
.audio-info { font-size: 11px; color: #6b7089; font-variant-numeric: tabular-nums; min-height: 1.4em; }
|
||||
.player .track-info { margin-bottom: 4px; }
|
||||
|
|
@ -831,6 +834,23 @@ async function readId3v2(file) {
|
|||
if (frameSize < 1 || frameSize > 10000000) { pos += 10; continue; }
|
||||
const fData = tagData.slice(pos + 10, pos + 10 + frameSize);
|
||||
if (fData.length < 2) { pos += frameSize + 10; continue; }
|
||||
if (frameId === 'APIC') {
|
||||
let off = 1;
|
||||
while (off < fData.length && fData[off] !== 0) off++;
|
||||
const mime = new TextDecoder('iso-8859-1').decode(fData.slice(1, off));
|
||||
off++;
|
||||
off++;
|
||||
if (fData[0] === 0) {
|
||||
while (off < fData.length && fData[off] !== 0) off++;
|
||||
off++;
|
||||
} else {
|
||||
while (off + 1 < fData.length && (fData[off] !== 0 || fData[off + 1] !== 0)) off += 2;
|
||||
off += 2;
|
||||
}
|
||||
tags.picture = URL.createObjectURL(new Blob([fData.slice(off)], { type: mime || 'image/jpeg' }));
|
||||
pos += frameSize + 10;
|
||||
continue;
|
||||
}
|
||||
const enc = fData[0];
|
||||
const text = decodeText(fData, 1, fData.length - 1, enc);
|
||||
if (!text) { pos += frameSize + 10; continue; }
|
||||
|
|
@ -875,25 +895,43 @@ async function readVorbisComments(file) {
|
|||
if (data[0] !== 0x66 || data[1] !== 0x4C || data[2] !== 0x61 || data[3] !== 0x43) return null;
|
||||
let pos = 4;
|
||||
let commentData = null;
|
||||
let pictureData = null;
|
||||
let pictureMime = '';
|
||||
while (pos + 4 <= data.length) {
|
||||
const isLast = data[pos] & 0x80;
|
||||
const blockType = data[pos] & 0x7f;
|
||||
const blockLen = (data[pos + 1] << 16) | (data[pos + 2] << 8) | data[pos + 3];
|
||||
pos += 4;
|
||||
if (pos + blockLen > data.length) break;
|
||||
if (blockType === 4) { commentData = data.slice(pos, pos + blockLen); break; }
|
||||
if (blockType === 4) { commentData = data.slice(pos, pos + blockLen); }
|
||||
if (blockType === 6) {
|
||||
let p = pos + 4;
|
||||
const mimeLen = (data[p] << 24) | (data[p+1] << 16) | (data[p+2] << 8) | data[p+3];
|
||||
p += 4;
|
||||
pictureMime = new TextDecoder('iso-8859-1').decode(data.slice(p, p + mimeLen));
|
||||
p += mimeLen;
|
||||
const descLen = (data[p] << 24) | (data[p+1] << 16) | (data[p+2] << 8) | data[p+3];
|
||||
p += 4 + descLen + 16;
|
||||
const picLen = (data[p] << 24) | (data[p+1] << 16) | (data[p+2] << 8) | data[p+3];
|
||||
p += 4;
|
||||
pictureData = data.slice(p, p + picLen);
|
||||
}
|
||||
pos += blockLen;
|
||||
if (isLast) break;
|
||||
}
|
||||
if (!commentData) return null;
|
||||
if (!commentData && !pictureData) return null;
|
||||
let off = 0;
|
||||
if (off + 4 > commentData.length) return null;
|
||||
const tags = {};
|
||||
if (pictureData) {
|
||||
tags.picture = URL.createObjectURL(new Blob([pictureData], { type: pictureMime || 'image/jpeg' }));
|
||||
}
|
||||
if (!commentData) return tags;
|
||||
if (off + 4 > commentData.length) return tags;
|
||||
const vendorLen = commentData[off] | (commentData[off + 1] << 8) | (commentData[off + 2] << 16) | (commentData[off + 3] << 24);
|
||||
off += 4 + vendorLen;
|
||||
if (off + 4 > commentData.length) return null;
|
||||
const numTags = commentData[off] | (commentData[off + 1] << 8) | (commentData[off + 2] << 16) | (commentData[off + 3] << 24);
|
||||
off += 4;
|
||||
const tags = {};
|
||||
for (let i = 0; i < numTags; i++) {
|
||||
if (off + 4 > commentData.length) break;
|
||||
const tagLen = commentData[off] | (commentData[off + 1] << 8) | (commentData[off + 2] << 16) | (commentData[off + 3] << 24);
|
||||
|
|
@ -1054,6 +1092,7 @@ async function processFiles(files, paths) {
|
|||
composer: tags.composer || '',
|
||||
urlTag: tags.urlTag || '',
|
||||
encodedBy: tags.encodedBy || '',
|
||||
picture: tags.picture || '',
|
||||
filename: file.name,
|
||||
url,
|
||||
file,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue