style: auto-fix linting issues
This commit is contained in:
parent
24f5dedcfe
commit
2c0ca538d1
4 changed files with 100 additions and 54 deletions
38
index.html
38
index.html
|
|
@ -658,21 +658,45 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="search-tab-content" id="library-tab-local">
|
<div class="search-tab-content" id="library-tab-local">
|
||||||
<div class="track-list" id="library-local-container">
|
<div class="track-list" id="library-local-container">
|
||||||
<div id="local-files-intro" style="padding: 20px; text-align: center;">
|
<div id="local-files-intro" style="padding: 20px; text-align: center">
|
||||||
<button id="select-local-folder-btn" class="btn-secondary">
|
<button id="select-local-folder-btn" class="btn-secondary">
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 8px;">
|
<svg
|
||||||
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
style="margin-right: 8px"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"
|
||||||
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
<span id="select-local-folder-text">Select Music Folder</span>
|
<span id="select-local-folder-text">Select Music Folder</span>
|
||||||
</button>
|
</button>
|
||||||
<p style="margin-top: 10px; font-size: 0.9rem; color: var(--muted-foreground);">
|
<p style="margin-top: 10px; font-size: 0.9rem; color: var(--muted-foreground)">
|
||||||
Select a folder on your device to play local files. <br>
|
Select a folder on your device to play local files. <br />
|
||||||
Note: Metadata reading is basic (FLAC/MP3 tags).
|
Note: Metadata reading is basic (FLAC/MP3 tags).
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div id="local-files-header" style="display: none; justify-content: space-between; align-items: center; padding: 10px 20px;">
|
<div
|
||||||
|
id="local-files-header"
|
||||||
|
style="
|
||||||
|
display: none;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px 20px;
|
||||||
|
"
|
||||||
|
>
|
||||||
<h3>Local Files</h3>
|
<h3>Local Files</h3>
|
||||||
<button id="change-local-folder-btn" class="btn-secondary" style="font-size: 0.8rem; padding: 4px 8px;">Change Folder</button>
|
<button
|
||||||
|
id="change-local-folder-btn"
|
||||||
|
class="btn-secondary"
|
||||||
|
style="font-size: 0.8rem; padding: 4px 8px"
|
||||||
|
>
|
||||||
|
Change Folder
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="local-files-list"></div>
|
<div id="local-files-list"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
17
js/app.js
17
js/app.js
|
|
@ -824,7 +824,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
try {
|
try {
|
||||||
const handle = await window.showDirectoryPicker({
|
const handle = await window.showDirectoryPicker({
|
||||||
id: 'music-folder',
|
id: 'music-folder',
|
||||||
mode: 'read'
|
mode: 'read',
|
||||||
});
|
});
|
||||||
|
|
||||||
await db.saveSetting('local_folder_handle', handle);
|
await db.saveSetting('local_folder_handle', handle);
|
||||||
|
|
@ -832,7 +832,8 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
const btn = document.getElementById('select-local-folder-btn');
|
const btn = document.getElementById('select-local-folder-btn');
|
||||||
const btnText = document.getElementById('select-local-folder-text');
|
const btnText = document.getElementById('select-local-folder-text');
|
||||||
if (btn) {
|
if (btn) {
|
||||||
if (btnText) btnText.textContent = 'Scanning...'; else btn.textContent = 'Scanning...';
|
if (btnText) btnText.textContent = 'Scanning...';
|
||||||
|
else btn.textContent = 'Scanning...';
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -843,7 +844,13 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
for await (const entry of dirHandle.values()) {
|
for await (const entry of dirHandle.values()) {
|
||||||
if (entry.kind === 'file') {
|
if (entry.kind === 'file') {
|
||||||
const name = entry.name.toLowerCase();
|
const name = entry.name.toLowerCase();
|
||||||
if (name.endsWith('.flac') || name.endsWith('.mp3') || name.endsWith('.m4a') || name.endsWith('.wav') || name.endsWith('.ogg')) {
|
if (
|
||||||
|
name.endsWith('.flac') ||
|
||||||
|
name.endsWith('.mp3') ||
|
||||||
|
name.endsWith('.m4a') ||
|
||||||
|
name.endsWith('.wav') ||
|
||||||
|
name.endsWith('.ogg')
|
||||||
|
) {
|
||||||
const file = await entry.getFile();
|
const file = await entry.getFile();
|
||||||
const metadata = await readTrackMetadata(file);
|
const metadata = await readTrackMetadata(file);
|
||||||
metadata.id = `local-${idCounter++}-${file.name}`;
|
metadata.id = `local-${idCounter++}-${file.name}`;
|
||||||
|
|
@ -865,7 +872,6 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
|
||||||
window.localFilesCache = tracks;
|
window.localFilesCache = tracks;
|
||||||
ui.renderLibraryPage();
|
ui.renderLibraryPage();
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.name !== 'AbortError') {
|
if (err.name !== 'AbortError') {
|
||||||
console.error('Error selecting folder:', err);
|
console.error('Error selecting folder:', err);
|
||||||
|
|
@ -874,7 +880,8 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
const btn = document.getElementById('select-local-folder-btn');
|
const btn = document.getElementById('select-local-folder-btn');
|
||||||
const btnText = document.getElementById('select-local-folder-text');
|
const btnText = document.getElementById('select-local-folder-text');
|
||||||
if (btn) {
|
if (btn) {
|
||||||
if (btnText) btnText.textContent = 'Select Music Folder'; else btn.textContent = 'Select Music Folder';
|
if (btnText) btnText.textContent = 'Select Music Folder';
|
||||||
|
else btn.textContent = 'Select Music Folder';
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,14 +33,14 @@ export async function addMetadataToAudio(audioBlob, track, api, quality) {
|
||||||
*/
|
*/
|
||||||
export async function readTrackMetadata(file) {
|
export async function readTrackMetadata(file) {
|
||||||
const metadata = {
|
const metadata = {
|
||||||
title: file.name.replace(/\.[^/.]+$/, ""),
|
title: file.name.replace(/\.[^/.]+$/, ''),
|
||||||
artists: [],
|
artists: [],
|
||||||
artist: { name: 'Unknown Artist' }, // For fallback/compatibility
|
artist: { name: 'Unknown Artist' }, // For fallback/compatibility
|
||||||
album: { title: 'Unknown Album', cover: 'assets/appicon.png', releaseDate: null },
|
album: { title: 'Unknown Album', cover: 'assets/appicon.png', releaseDate: null },
|
||||||
duration: 0,
|
duration: 0,
|
||||||
isLocal: true,
|
isLocal: true,
|
||||||
file: file,
|
file: file,
|
||||||
id: `local-${file.name}-${file.lastModified}`
|
id: `local-${file.name}-${file.lastModified}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -69,7 +69,7 @@ async function readFlacMetadata(file, metadata) {
|
||||||
if (!isFlacFile(dataView)) return;
|
if (!isFlacFile(dataView)) return;
|
||||||
|
|
||||||
const blocks = parseFlacBlocks(dataView);
|
const blocks = parseFlacBlocks(dataView);
|
||||||
const vorbisBlock = blocks.find(b => b.type === 4);
|
const vorbisBlock = blocks.find((b) => b.type === 4);
|
||||||
|
|
||||||
const artists = [];
|
const artists = [];
|
||||||
if (vorbisBlock) {
|
if (vorbisBlock) {
|
||||||
|
|
@ -100,7 +100,7 @@ async function readFlacMetadata(file, metadata) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (artists.length > 0) {
|
if (artists.length > 0) {
|
||||||
metadata.artists = artists.flatMap(a => a.split(/; |\/|\\/)).map(name => ({ name: name.trim() }));
|
metadata.artists = artists.flatMap((a) => a.split(/; |\/|\\/)).map((name) => ({ name: name.trim() }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,8 +112,7 @@ async function readM4aMetadata(file, metadata) {
|
||||||
|
|
||||||
const atoms = parseMp4Atoms(view);
|
const atoms = parseMp4Atoms(view);
|
||||||
|
|
||||||
|
const moov = atoms.find((a) => a.type === 'moov');
|
||||||
const moov = atoms.find(a => a.type === 'moov');
|
|
||||||
if (!moov) return;
|
if (!moov) return;
|
||||||
|
|
||||||
const moovStart = moov.offset + 8;
|
const moovStart = moov.offset + 8;
|
||||||
|
|
@ -121,7 +120,7 @@ async function readM4aMetadata(file, metadata) {
|
||||||
const moovData = new DataView(view.buffer, moovStart, moovLen);
|
const moovData = new DataView(view.buffer, moovStart, moovLen);
|
||||||
const moovAtoms = parseMp4Atoms(moovData);
|
const moovAtoms = parseMp4Atoms(moovData);
|
||||||
|
|
||||||
const udta = moovAtoms.find(a => a.type === 'udta');
|
const udta = moovAtoms.find((a) => a.type === 'udta');
|
||||||
if (!udta) return;
|
if (!udta) return;
|
||||||
|
|
||||||
const udtaStart = moovStart + udta.offset + 8;
|
const udtaStart = moovStart + udta.offset + 8;
|
||||||
|
|
@ -129,7 +128,7 @@ async function readM4aMetadata(file, metadata) {
|
||||||
const udtaData = new DataView(view.buffer, udtaStart, udtaLen);
|
const udtaData = new DataView(view.buffer, udtaStart, udtaLen);
|
||||||
const udtaAtoms = parseMp4Atoms(udtaData);
|
const udtaAtoms = parseMp4Atoms(udtaData);
|
||||||
|
|
||||||
const meta = udtaAtoms.find(a => a.type === 'meta');
|
const meta = udtaAtoms.find((a) => a.type === 'meta');
|
||||||
if (!meta) return;
|
if (!meta) return;
|
||||||
|
|
||||||
const metaStart = udtaStart + meta.offset + 12;
|
const metaStart = udtaStart + meta.offset + 12;
|
||||||
|
|
@ -137,7 +136,7 @@ async function readM4aMetadata(file, metadata) {
|
||||||
const metaData = new DataView(view.buffer, metaStart, metaLen);
|
const metaData = new DataView(view.buffer, metaStart, metaLen);
|
||||||
const metaAtoms = parseMp4Atoms(metaData);
|
const metaAtoms = parseMp4Atoms(metaData);
|
||||||
|
|
||||||
const ilst = metaAtoms.find(a => a.type === 'ilst');
|
const ilst = metaAtoms.find((a) => a.type === 'ilst');
|
||||||
if (!ilst) return;
|
if (!ilst) return;
|
||||||
|
|
||||||
const ilstStart = metaStart + ilst.offset + 8;
|
const ilstStart = metaStart + ilst.offset + 8;
|
||||||
|
|
@ -151,7 +150,7 @@ async function readM4aMetadata(file, metadata) {
|
||||||
const itemStart = ilstStart + item.offset + 8;
|
const itemStart = ilstStart + item.offset + 8;
|
||||||
const itemLen = item.size - 8;
|
const itemLen = item.size - 8;
|
||||||
const itemData = new DataView(view.buffer, itemStart, itemLen);
|
const itemData = new DataView(view.buffer, itemStart, itemLen);
|
||||||
const dataAtom = parseMp4Atoms(itemData).find(a => a.type === 'data');
|
const dataAtom = parseMp4Atoms(itemData).find((a) => a.type === 'data');
|
||||||
if (dataAtom) {
|
if (dataAtom) {
|
||||||
const contentLen = dataAtom.size - 16;
|
const contentLen = dataAtom.size - 16;
|
||||||
const contentOffset = itemStart + dataAtom.offset + 16;
|
const contentOffset = itemStart + dataAtom.offset + 16;
|
||||||
|
|
@ -161,15 +160,16 @@ async function readM4aMetadata(file, metadata) {
|
||||||
} else if (item.type === '©ART') {
|
} else if (item.type === '©ART') {
|
||||||
artistStr = new TextDecoder().decode(new Uint8Array(view.buffer, contentOffset, contentLen));
|
artistStr = new TextDecoder().decode(new Uint8Array(view.buffer, contentOffset, contentLen));
|
||||||
} else if (item.type === '©alb') {
|
} else if (item.type === '©alb') {
|
||||||
metadata.album.title = new TextDecoder().decode(new Uint8Array(view.buffer, contentOffset, contentLen));
|
metadata.album.title = new TextDecoder().decode(
|
||||||
|
new Uint8Array(view.buffer, contentOffset, contentLen)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (artistStr) {
|
if (artistStr) {
|
||||||
metadata.artists = artistStr.split(/; |\/|\\/).map(name => ({ name: name.trim() }));
|
metadata.artists = artistStr.split(/; |\/|\\/).map((name) => ({ name: name.trim() }));
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('Error parsing M4A:', e);
|
console.warn('Error parsing M4A:', e);
|
||||||
}
|
}
|
||||||
|
|
@ -228,7 +228,7 @@ async function readMp3Metadata(file, metadata) {
|
||||||
|
|
||||||
const artistStr = tpe1 || tpe2;
|
const artistStr = tpe1 || tpe2;
|
||||||
if (artistStr) {
|
if (artistStr) {
|
||||||
metadata.artists = artistStr.split('/').map(name => ({ name: name.trim() }));
|
metadata.artists = artistStr.split('/').map((name) => ({ name: name.trim() }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -237,9 +237,18 @@ async function readMp3Metadata(file, metadata) {
|
||||||
const tailView = new DataView(tailBuffer);
|
const tailView = new DataView(tailBuffer);
|
||||||
const tag = new TextDecoder().decode(new Uint8Array(tailBuffer, 0, 3));
|
const tag = new TextDecoder().decode(new Uint8Array(tailBuffer, 0, 3));
|
||||||
if (tag === 'TAG') {
|
if (tag === 'TAG') {
|
||||||
const title = new TextDecoder().decode(new Uint8Array(tailBuffer, 3, 30)).replace(/\0/g, '').trim();
|
const title = new TextDecoder()
|
||||||
const artist = new TextDecoder().decode(new Uint8Array(tailBuffer, 33, 30)).replace(/\0/g, '').trim();
|
.decode(new Uint8Array(tailBuffer, 3, 30))
|
||||||
const album = new TextDecoder().decode(new Uint8Array(tailBuffer, 63, 30)).replace(/\0/g, '').trim();
|
.replace(/\0/g, '')
|
||||||
|
.trim();
|
||||||
|
const artist = new TextDecoder()
|
||||||
|
.decode(new Uint8Array(tailBuffer, 33, 30))
|
||||||
|
.replace(/\0/g, '')
|
||||||
|
.trim();
|
||||||
|
const album = new TextDecoder()
|
||||||
|
.decode(new Uint8Array(tailBuffer, 63, 30))
|
||||||
|
.replace(/\0/g, '')
|
||||||
|
.trim();
|
||||||
if (title) metadata.title = title;
|
if (title) metadata.title = title;
|
||||||
if (artist && metadata.artists.length === 0) {
|
if (artist && metadata.artists.length === 0) {
|
||||||
metadata.artists = [{ name: artist }];
|
metadata.artists = [{ name: artist }];
|
||||||
|
|
@ -276,14 +285,20 @@ function readID3Picture(view) {
|
||||||
|
|
||||||
let mimeEnd = offset;
|
let mimeEnd = offset;
|
||||||
while (view.getUint8(mimeEnd) !== 0) mimeEnd++;
|
while (view.getUint8(mimeEnd) !== 0) mimeEnd++;
|
||||||
const mime = new TextDecoder('iso-8859-1').decode(view.buffer.slice(view.byteOffset + offset, view.byteOffset + mimeEnd));
|
const mime = new TextDecoder('iso-8859-1').decode(
|
||||||
|
view.buffer.slice(view.byteOffset + offset, view.byteOffset + mimeEnd)
|
||||||
|
);
|
||||||
offset = mimeEnd + 1;
|
offset = mimeEnd + 1;
|
||||||
|
|
||||||
const picType = view.getUint8(offset);
|
const picType = view.getUint8(offset);
|
||||||
offset++;
|
offset++;
|
||||||
|
|
||||||
let descEnd = offset;
|
let descEnd = offset;
|
||||||
while (view.getUint8(descEnd) !== 0 || (encoding === 1 || encoding === 2 ? view.getUint8(descEnd+1) !== 0 : false)) descEnd++;
|
while (
|
||||||
|
view.getUint8(descEnd) !== 0 ||
|
||||||
|
(encoding === 1 || encoding === 2 ? view.getUint8(descEnd + 1) !== 0 : false)
|
||||||
|
)
|
||||||
|
descEnd++;
|
||||||
offset = descEnd + (encoding === 1 || encoding === 2 ? 2 : 1);
|
offset = descEnd + (encoding === 1 || encoding === 2 ? 2 : 1);
|
||||||
|
|
||||||
const imgData = view.buffer.slice(view.byteOffset + offset, view.byteOffset + view.byteLength);
|
const imgData = view.buffer.slice(view.byteOffset + offset, view.byteOffset + view.byteLength);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue