Support audio files in gallery and viewer, include Musik .txt in ZIP, exclude .txt from website
This commit is contained in:
1 parent
77e8876a07
commit
25e5f3d0a6
12 files changed
+245
-40
No files matched your search
@@ -59,8 +59,8 @@ export async function generateMetadata({
|
|||||||
where: { relativePath: mediaPath },
|
where: { relativePath: mediaPath },
|
||||||
});
|
});
|
||||||
|
|
||||||
// Don't leak metadata for unpublished items
|
// Don't leak metadata for unpublished items or text files
|
||||||
if (!item?.published) {
|
if (!item?.published || item.filename.toLowerCase().endsWith('.txt') || item.mediaType === 'OTHER') {
|
||||||
return { title: 'Nele & Till - Hochzeit' };
|
return { title: 'Nele & Till - Hochzeit' };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ export default async function MediaDirectRoute({
|
|||||||
where: { relativePath: mediaPath },
|
where: { relativePath: mediaPath },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!item) {
|
if (!item || item.filename.toLowerCase().endsWith('.txt') || item.mediaType === 'OTHER') {
|
||||||
notFound();
|
notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,10 @@ export default function AdminLayoutEditor() {
|
|||||||
const res = await fetch('/api/media?admin=true&limit=1000');
|
const res = await fetch('/api/media?admin=true&limit=1000');
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
setItems(data.items);
|
const sortedItems = (data.items || []).slice().sort((a: MediaItem, b: MediaItem) =>
|
||||||
|
a.relativePath.localeCompare(b.relativePath, undefined, { numeric: true, sensitivity: 'base' })
|
||||||
|
);
|
||||||
|
setItems(sortedItems);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error fetching admin data:', err);
|
console.error('Error fetching admin data:', err);
|
||||||
|
|||||||
+39
-12
@@ -54,11 +54,12 @@ export default function AdminDashboard() {
|
|||||||
const res = await fetch('/api/media?admin=true&limit=5000');
|
const res = await fetch('/api/media?admin=true&limit=5000');
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
setItems(data.items);
|
const mediaOnly = data.items.filter((i: MediaItem) => !i.filename.toLowerCase().endsWith('.txt') && i.mediaType !== 'OTHER');
|
||||||
|
setItems(mediaOnly);
|
||||||
setStats({
|
setStats({
|
||||||
total: data.items.length,
|
total: mediaOnly.length,
|
||||||
published: data.items.filter((i: MediaItem) => i.published).length,
|
published: mediaOnly.filter((i: MediaItem) => i.published).length,
|
||||||
unpublished: data.items.filter((i: MediaItem) => !i.published).length,
|
unpublished: mediaOnly.filter((i: MediaItem) => !i.published).length,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -186,7 +187,22 @@ export default function AdminDashboard() {
|
|||||||
|
|
||||||
if (!authChecked) return <div style={{ padding: '20px' }}>Prüfe Berechtigung...</div>;
|
if (!authChecked) return <div style={{ padding: '20px' }}>Prüfe Berechtigung...</div>;
|
||||||
|
|
||||||
const displayedItems = selectedFolder ? items.filter(i => i.directory === selectedFolder) : items;
|
const displayedItems = (selectedFolder ? items.filter(i => i.directory === selectedFolder) : items)
|
||||||
|
.slice()
|
||||||
|
.sort((a, b) => {
|
||||||
|
if (!selectedFolder && a.directory !== b.directory) {
|
||||||
|
const indexA = orderedFolders.indexOf(a.directory);
|
||||||
|
const indexB = orderedFolders.indexOf(b.directory);
|
||||||
|
if (indexA !== -1 && indexB !== -1) return indexA - indexB;
|
||||||
|
if (indexA !== -1) return -1;
|
||||||
|
if (indexB !== -1) return 1;
|
||||||
|
const dirCompare = (a.directory || '').localeCompare(b.directory || '', undefined, { numeric: true, sensitivity: 'base' });
|
||||||
|
if (dirCompare !== 0) return dirCompare;
|
||||||
|
}
|
||||||
|
const fileCompare = a.filename.localeCompare(b.filename, undefined, { numeric: true, sensitivity: 'base' });
|
||||||
|
if (fileCompare !== 0) return fileCompare;
|
||||||
|
return a.relativePath.localeCompare(b.relativePath, undefined, { numeric: true, sensitivity: 'base' });
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="admin-content" style={{ padding: '20px', maxWidth: '1200px', margin: '0 auto' }}>
|
<div className="admin-content" style={{ padding: '20px', maxWidth: '1200px', margin: '0 auto' }}>
|
||||||
@@ -318,13 +334,24 @@ export default function AdminDashboard() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ position: 'relative', aspectRatio: '1/1', background: '#eee' }}>
|
<div style={{ position: 'relative', aspectRatio: '1/1', background: '#eee' }}>
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
{item.mediaType === 'AUDIO' ? (
|
||||||
<img
|
<div style={{ width: '100%', height: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', background: '#f8f4ee', color: 'var(--color-primary)' }}>
|
||||||
src={`/api/media/${item.relativePath}?type=thumb`}
|
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
alt={item.filename}
|
<path d="M9 18V5l12-2v13"></path>
|
||||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
<circle cx="6" cy="18" r="3"></circle>
|
||||||
loading="lazy"
|
<circle cx="18" cy="16" r="3"></circle>
|
||||||
/>
|
</svg>
|
||||||
|
<span style={{ fontSize: '0.75rem', marginTop: '6px', fontWeight: 600 }}>Audio (MP3)</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
/* eslint-disable-next-line @next/next/no-img-element */
|
||||||
|
<img
|
||||||
|
src={`/api/media/${item.relativePath}?type=thumb`}
|
||||||
|
alt={item.filename}
|
||||||
|
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{item.mediaType === 'VIDEO' && (
|
{item.mediaType === 'VIDEO' && (
|
||||||
<div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'rgba(0,0,0,0.3)' }}>
|
<div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'rgba(0,0,0,0.3)' }}>
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="white" stroke="none"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="white" stroke="none"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>
|
||||||
|
|||||||
@@ -9,12 +9,19 @@ export async function GET(request: NextRequest) {
|
|||||||
try {
|
try {
|
||||||
const items = await prisma.mediaItem.findMany({
|
const items = await prisma.mediaItem.findMany({
|
||||||
orderBy: [
|
orderBy: [
|
||||||
{ featured: 'desc' },
|
{ directory: 'asc' },
|
||||||
{ placementMode: 'asc' },
|
{ filename: 'asc' },
|
||||||
{ sortOrder: 'asc' },
|
{ id: 'asc' }
|
||||||
{ createdAt: 'desc' }
|
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
items.sort((a, b) => {
|
||||||
|
if (a.directory !== b.directory) {
|
||||||
|
return (a.directory || '').localeCompare(b.directory || '', undefined, { numeric: true, sensitivity: 'base' });
|
||||||
|
}
|
||||||
|
const fileCompare = a.filename.localeCompare(b.filename, undefined, { numeric: true, sensitivity: 'base' });
|
||||||
|
if (fileCompare !== 0) return fileCompare;
|
||||||
|
return a.relativePath.localeCompare(b.relativePath, undefined, { numeric: true, sensitivity: 'base' });
|
||||||
|
});
|
||||||
return NextResponse.json({ items });
|
return NextResponse.json({ items });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching layout items:', error);
|
console.error('Error fetching layout items:', error);
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ export async function GET(request: NextRequest) {
|
|||||||
const where: Record<string, unknown> = {};
|
const where: Record<string, unknown> = {};
|
||||||
if (!isAdminRequest) {
|
if (!isAdminRequest) {
|
||||||
where.published = true;
|
where.published = true;
|
||||||
|
where.mediaType = { not: 'OTHER' };
|
||||||
|
where.NOT = { filename: { endsWith: '.txt' } };
|
||||||
}
|
}
|
||||||
if (directoryParam) {
|
if (directoryParam) {
|
||||||
const dirs = directoryParam.split(',');
|
const dirs = directoryParam.split(',');
|
||||||
@@ -52,9 +54,9 @@ export async function GET(request: NextRequest) {
|
|||||||
take: limit + 1,
|
take: limit + 1,
|
||||||
...(cursor ? { skip: 1, cursor: { id: cursor } } : {}),
|
...(cursor ? { skip: 1, cursor: { id: cursor } } : {}),
|
||||||
orderBy: [
|
orderBy: [
|
||||||
{ featured: 'desc' },
|
{ directory: 'asc' },
|
||||||
{ sortOrder: 'asc' },
|
{ filename: 'asc' },
|
||||||
{ createdAt: 'desc' },
|
{ id: 'asc' },
|
||||||
],
|
],
|
||||||
include: { _count: { select: { likes: true } } },
|
include: { _count: { select: { likes: true } } },
|
||||||
});
|
});
|
||||||
@@ -63,6 +65,15 @@ export async function GET(request: NextRequest) {
|
|||||||
const nextItem = items.pop();
|
const nextItem = items.pop();
|
||||||
nextCursor = nextItem!.id;
|
nextCursor = nextItem!.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
items.sort((a, b) => {
|
||||||
|
if (a.directory !== b.directory) {
|
||||||
|
return (a.directory || '').localeCompare(b.directory || '', undefined, { numeric: true, sensitivity: 'base' });
|
||||||
|
}
|
||||||
|
const fileCompare = a.filename.localeCompare(b.filename, undefined, { numeric: true, sensitivity: 'base' });
|
||||||
|
if (fileCompare !== 0) return fileCompare;
|
||||||
|
return a.relativePath.localeCompare(b.relativePath, undefined, { numeric: true, sensitivity: 'base' });
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// Custom pagination with randomized chunk sorting
|
// Custom pagination with randomized chunk sorting
|
||||||
let seed = Math.floor(Math.random() * 1000000);
|
let seed = Math.floor(Math.random() * 1000000);
|
||||||
|
|||||||
+10
-2
@@ -9,7 +9,11 @@ async function getInitialMedia() {
|
|||||||
try {
|
try {
|
||||||
// 1. Fetch metadata for all published items to sort them properly
|
// 1. Fetch metadata for all published items to sort them properly
|
||||||
const allMetadata = await prisma.mediaItem.findMany({
|
const allMetadata = await prisma.mediaItem.findMany({
|
||||||
where: { published: true },
|
where: {
|
||||||
|
published: true,
|
||||||
|
mediaType: { not: 'OTHER' },
|
||||||
|
NOT: { filename: { endsWith: '.txt' } },
|
||||||
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
featured: true,
|
featured: true,
|
||||||
@@ -48,7 +52,11 @@ async function getInitialMedia() {
|
|||||||
|
|
||||||
// Fetch available directories for filtering
|
// Fetch available directories for filtering
|
||||||
const dirs = await prisma.mediaItem.findMany({
|
const dirs = await prisma.mediaItem.findMany({
|
||||||
where: { published: true },
|
where: {
|
||||||
|
published: true,
|
||||||
|
mediaType: { not: 'OTHER' },
|
||||||
|
NOT: { filename: { endsWith: '.txt' } },
|
||||||
|
},
|
||||||
select: { directory: true },
|
select: { directory: true },
|
||||||
distinct: ['directory'],
|
distinct: ['directory'],
|
||||||
orderBy: { directory: 'asc' }
|
orderBy: { directory: 'asc' }
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export default function FullscreenViewer({
|
|||||||
votingOpen,
|
votingOpen,
|
||||||
}: ViewerProps) {
|
}: ViewerProps) {
|
||||||
const isVideo = item.mediaType === 'VIDEO';
|
const isVideo = item.mediaType === 'VIDEO';
|
||||||
|
const isAudio = item.mediaType === 'AUDIO';
|
||||||
const touchStartX = useRef<number | null>(null);
|
const touchStartX = useRef<number | null>(null);
|
||||||
|
|
||||||
// Update URL to reflect current media
|
// Update URL to reflect current media
|
||||||
@@ -118,6 +119,58 @@ export default function FullscreenViewer({
|
|||||||
playsInline
|
playsInline
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
/>
|
/>
|
||||||
|
) : isAudio ? (
|
||||||
|
<div
|
||||||
|
className="viewer-content"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
background: 'rgba(30, 36, 28, 0.95)',
|
||||||
|
border: '1px solid rgba(124, 154, 110, 0.4)',
|
||||||
|
borderRadius: '16px',
|
||||||
|
padding: '40px 30px',
|
||||||
|
maxWidth: '460px',
|
||||||
|
width: '90%',
|
||||||
|
boxShadow: '0 20px 50px rgba(0,0,0,0.6)',
|
||||||
|
color: '#fff',
|
||||||
|
textAlign: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '100px',
|
||||||
|
height: '100px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: 'linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-dark) 100%)',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginBottom: '20px',
|
||||||
|
boxShadow: '0 8px 24px rgba(0,0,0,0.3)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M9 18V5l12-2v13"></path>
|
||||||
|
<circle cx="6" cy="18" r="3"></circle>
|
||||||
|
<circle cx="18" cy="16" r="3"></circle>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h2 style={{ fontFamily: 'var(--font-display)', fontSize: '1.5rem', marginBottom: '8px', color: '#fff' }}>
|
||||||
|
{item.filename.replace(/\.[^/.]+$/, '')}
|
||||||
|
</h2>
|
||||||
|
<p style={{ color: 'rgba(255,255,255,0.7)', fontSize: '0.9rem', marginBottom: '24px' }}>
|
||||||
|
Nele & Till • Hochzeit
|
||||||
|
</p>
|
||||||
|
<audio
|
||||||
|
src={`/api/media/${item.relativePath}`}
|
||||||
|
controls
|
||||||
|
autoPlay
|
||||||
|
style={{ width: '100%', outline: 'none', borderRadius: '8px' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
// eslint-disable-next-line @next/next/no-img-element
|
||||||
<img
|
<img
|
||||||
|
|||||||
@@ -59,7 +59,9 @@ export default function MediaTile({ item, index, onClick, votingOpen }: MediaTil
|
|||||||
};
|
};
|
||||||
|
|
||||||
const isVideo = item.mediaType === 'VIDEO';
|
const isVideo = item.mediaType === 'VIDEO';
|
||||||
|
const isAudio = item.mediaType === 'AUDIO';
|
||||||
const likesCount = item._count?.likes ?? 0;
|
const likesCount = item._count?.likes ?? 0;
|
||||||
|
const songTitle = item.filename.replace(/\.[^/.]+$/, '');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -77,17 +79,75 @@ export default function MediaTile({ item, index, onClick, votingOpen }: MediaTil
|
|||||||
<div style={{ display: 'block', paddingTop: `${(1 / displayRatio) * 100}%` }} />
|
<div style={{ display: 'block', paddingTop: `${(1 / displayRatio) * 100}%` }} />
|
||||||
{isVisible && (
|
{isVisible && (
|
||||||
<>
|
<>
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
{isAudio ? (
|
||||||
<img
|
<div
|
||||||
src={isVideo ? `/api/media/${item.relativePath}?type=poster` : `/api/media/${item.relativePath}?type=thumb`}
|
style={{
|
||||||
alt={item.filename}
|
position: 'absolute',
|
||||||
loading="lazy"
|
inset: 0,
|
||||||
onLoad={(e) => {
|
display: 'flex',
|
||||||
const target = e.target as HTMLImageElement;
|
flexDirection: 'column',
|
||||||
target.parentElement?.classList.remove('loading-skeleton');
|
alignItems: 'center',
|
||||||
}}
|
justifyContent: 'center',
|
||||||
/>
|
background: 'linear-gradient(135deg, #4A5D40 0%, #2F3E28 100%)',
|
||||||
{isVideo && (
|
color: '#fff',
|
||||||
|
padding: '16px',
|
||||||
|
textAlign: 'center',
|
||||||
|
}}
|
||||||
|
ref={(el) => {
|
||||||
|
if (el) el.parentElement?.classList.remove('loading-skeleton');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{
|
||||||
|
width: '56px',
|
||||||
|
height: '56px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: 'rgba(255, 255, 255, 0.15)',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginBottom: '10px',
|
||||||
|
backdropFilter: 'blur(4px)',
|
||||||
|
boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
|
||||||
|
}}>
|
||||||
|
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M9 18V5l12-2v13"></path>
|
||||||
|
<circle cx="6" cy="18" r="3"></circle>
|
||||||
|
<circle cx="18" cy="16" r="3"></circle>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
fontFamily: 'var(--font-display)',
|
||||||
|
fontSize: '1.1rem',
|
||||||
|
fontWeight: 600,
|
||||||
|
letterSpacing: '0.02em',
|
||||||
|
marginBottom: '4px',
|
||||||
|
lineHeight: 1.2,
|
||||||
|
textShadow: '0 2px 4px rgba(0,0,0,0.3)',
|
||||||
|
}}>
|
||||||
|
{songTitle}
|
||||||
|
</div>
|
||||||
|
<span style={{
|
||||||
|
fontSize: '0.75rem',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '0.1em',
|
||||||
|
opacity: 0.8,
|
||||||
|
}}>
|
||||||
|
Hochzeitslied
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
/* eslint-disable-next-line @next/next/no-img-element */
|
||||||
|
<img
|
||||||
|
src={isVideo ? `/api/media/${item.relativePath}?type=poster` : `/api/media/${item.relativePath}?type=thumb`}
|
||||||
|
alt={item.filename}
|
||||||
|
loading="lazy"
|
||||||
|
onLoad={(e) => {
|
||||||
|
const target = e.target as HTMLImageElement;
|
||||||
|
target.parentElement?.classList.remove('loading-skeleton');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{(isVideo || isAudio) && (
|
||||||
<div className="play-icon">
|
<div className="play-icon">
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" stroke="none">
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" stroke="none">
|
||||||
<polygon points="5 3 19 12 5 21 5 3"></polygon>
|
<polygon points="5 3 19 12 5 21 5 3"></polygon>
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ const EXCLUDED_SUFFIXES = [
|
|||||||
'.partial',
|
'.partial',
|
||||||
'.crdownload',
|
'.crdownload',
|
||||||
'.part',
|
'.part',
|
||||||
|
'.txt',
|
||||||
];
|
];
|
||||||
|
|
||||||
const EXCLUDED_DIRECTORIES = new Set([
|
const EXCLUDED_DIRECTORIES = new Set([
|
||||||
|
|||||||
@@ -61,19 +61,53 @@ export async function buildZip(): Promise<boolean> {
|
|||||||
|
|
||||||
// 5. Add each published file
|
// 5. Add each published file
|
||||||
let addedCount = 0;
|
let addedCount = 0;
|
||||||
|
const addedPaths = new Set<string>();
|
||||||
|
|
||||||
for (const item of publishedItems) {
|
for (const item of publishedItems) {
|
||||||
|
if (item.relativePath.toLowerCase().endsWith('.txt')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const absolutePath = path.join(MEDIA_ROOT, item.relativePath);
|
const absolutePath = path.join(MEDIA_ROOT, item.relativePath);
|
||||||
|
|
||||||
// Verify file still exists
|
// Verify file still exists
|
||||||
try {
|
try {
|
||||||
await fs.promises.access(absolutePath, fs.constants.R_OK);
|
await fs.promises.access(absolutePath, fs.constants.R_OK);
|
||||||
archive.file(absolutePath, { name: item.relativePath });
|
archive.file(absolutePath, { name: item.relativePath });
|
||||||
|
addedPaths.add(item.relativePath);
|
||||||
addedCount++;
|
addedCount++;
|
||||||
} catch {
|
} catch {
|
||||||
console.warn(`[ZIP] File missing, skipping: ${item.relativePath}`);
|
console.warn(`[ZIP] File missing, skipping: ${item.relativePath}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always include .txt files from the Musik directory (e.g. song lyrics)
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(MEDIA_ROOT)) {
|
||||||
|
const rootEntries = await fs.promises.readdir(MEDIA_ROOT, { withFileTypes: true });
|
||||||
|
const musicDirs = rootEntries.filter(
|
||||||
|
(e) => e.isDirectory() && e.name.toLowerCase() === 'musik'
|
||||||
|
);
|
||||||
|
for (const mDir of musicDirs) {
|
||||||
|
const musicDirPath = path.join(MEDIA_ROOT, mDir.name);
|
||||||
|
const musicFiles = await fs.promises.readdir(musicDirPath, { withFileTypes: true });
|
||||||
|
for (const file of musicFiles) {
|
||||||
|
if (file.isFile() && file.name.toLowerCase().endsWith('.txt')) {
|
||||||
|
const relPath = path.posix.join(mDir.name, file.name);
|
||||||
|
const absPath = path.join(musicDirPath, file.name);
|
||||||
|
if (!addedPaths.has(relPath)) {
|
||||||
|
archive.file(absPath, { name: relPath });
|
||||||
|
addedPaths.add(relPath);
|
||||||
|
addedCount++;
|
||||||
|
console.log(`[ZIP] Included text file in zip: ${relPath}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('[ZIP] Error reading Musik directory for .txt files:', err);
|
||||||
|
}
|
||||||
|
|
||||||
// 6. Finalize archive
|
// 6. Finalize archive
|
||||||
await archive.finalize();
|
await archive.finalize();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/**
|
||||||
* Filesystem scanner — recursively scans media directory,
|
* Filesystem scanner — recursively scans media directory,
|
||||||
* detects new/changed/removed files, and syncs with the database.
|
* detects new/changed/removed files, and syncs with the database.
|
||||||
*/
|
*/
|
||||||
@@ -61,8 +61,9 @@ export async function runFullScan(): Promise<{
|
|||||||
} else {
|
} else {
|
||||||
unchanged++;
|
unchanged++;
|
||||||
dbPaths.delete(relPath);
|
dbPaths.delete(relPath);
|
||||||
if (existing.thumbnailReady === false) {
|
const mediaType = getMediaType(file.ext);
|
||||||
enqueueProcessing(relPath, getMediaType(file.ext)).catch(console.error);
|
if (existing.thumbnailReady === false && (mediaType === 'IMAGE' || mediaType === 'VIDEO')) {
|
||||||
|
enqueueProcessing(relPath, mediaType).catch(console.error);
|
||||||
console.log('Re-queued missing thumbnail:', relPath);
|
console.log('Re-queued missing thumbnail:', relPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"program":{"fileNames":["./node_modules/typescript/lib/lib.es5.d.ts","./node_modules/typescript/lib/lib.es2015.d.ts","./node_modules/typescript/lib/lib.es2016.d.ts","./node_modules/typescript/lib/lib.es2017.d.ts","./node_modules/typescript/lib/lib.es2018.d.ts","./node_modules/typescript/lib/lib.es2019.d.ts","./node_modules/typescript/lib/lib.es2020.d.ts","./node_modules/typescript/lib/lib.es2021.d.ts","./node_modules/typescript/lib/lib.es2022.d.ts","./node_modules/typescript/lib/lib.es2023.d.ts","./node_modules/typescript/lib/lib.esnext.d.ts","./node_modules/typescript/lib/lib.dom.d.ts","./node_modules/typescript/lib/lib.dom.iterable.d.ts","./node_modules/typescript/lib/lib.es2015.core.d.ts","./node_modules/typescript/lib/lib.es2015.collection.d.ts","./node_modules/typescript/lib/lib.es2015.generator.d.ts","./node_modules/typescript/lib/lib.es2015.iterable.d.ts","./node_modules/typescript/lib/lib.es2015.promise.d.ts","./node_modules/typescript/lib/lib.es2015.proxy.d.ts","./node_modules/typescript/lib/lib.es2015.reflect.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2016.array.include.d.ts","./node_modules/typescript/lib/lib.es2016.intl.d.ts","./node_modules/typescript/lib/lib.es2017.date.d.ts","./node_modules/typescript/lib/lib.es2017.object.d.ts","./node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2017.string.d.ts","./node_modules/typescript/lib/lib.es2017.intl.d.ts","./node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","./node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","./node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","./node_modules/typescript/lib/lib.es2018.intl.d.ts","./node_modules/typescript/lib/lib.es2018.promise.d.ts","./node_modules/typescript/lib/lib.es2018.regexp.d.ts","./node_modules/typescript/lib/lib.es2019.array.d.ts","./node_modules/typescript/lib/lib.es2019.object.d.ts","./node_modules/typescript/lib/lib.es2019.string.d.ts","./node_modules/typescript/lib/lib.es2019.symbol.d.ts","./node_modules/typescript/lib/lib.es2019.intl.d.ts","./node_modules/typescript/lib/lib.es2020.bigint.d.ts","./node_modules/typescript/lib/lib.es2020.date.d.ts","./node_modules/typescript/lib/lib.es2020.promise.d.ts","./node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2020.string.d.ts","./node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2020.intl.d.ts","./node_modules/typescript/lib/lib.es2020.number.d.ts","./node_modules/typescript/lib/lib.es2021.promise.d.ts","./node_modules/typescript/lib/lib.es2021.string.d.ts","./node_modules/typescript/lib/lib.es2021.weakref.d.ts","./node_modules/typescript/lib/lib.es2021.intl.d.ts","./node_modules/typescript/lib/lib.es2022.array.d.ts","./node_modules/typescript/lib/lib.es2022.error.d.ts","./node_modules/typescript/lib/lib.es2022.intl.d.ts","./node_modules/typescript/lib/lib.es2022.object.d.ts","./node_modules/typescript/lib/lib.es2022.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2022.string.d.ts","./node_modules/typescript/lib/lib.es2022.regexp.d.ts","./node_modules/typescript/lib/lib.es2023.array.d.ts","./node_modules/typescript/lib/lib.es2023.collection.d.ts","./node_modules/typescript/lib/lib.es2023.intl.d.ts","./node_modules/typescript/lib/lib.esnext.array.d.ts","./node_modules/typescript/lib/lib.esnext.collection.d.ts","./node_modules/typescript/lib/lib.esnext.intl.d.ts","./node_modules/typescript/lib/lib.esnext.disposable.d.ts","./node_modules/typescript/lib/lib.esnext.string.d.ts","./node_modules/typescript/lib/lib.esnext.promise.d.ts","./node_modules/typescript/lib/lib.esnext.decorators.d.ts","./node_modules/typescript/lib/lib.esnext.object.d.ts","./node_modules/typescript/lib/lib.esnext.regexp.d.ts","./node_modules/typescript/lib/lib.decorators.d.ts","./node_modules/typescript/lib/lib.decorators.legacy.d.ts","./node_modules/next/dist/styled-jsx/types/css.d.ts","./node_modules/@types/react/global.d.ts","./node_modules/csstype/index.d.ts","./node_modules/@types/prop-types/index.d.ts","./node_modules/@types/react/index.d.ts","./node_modules/next/dist/styled-jsx/types/index.d.ts","./node_modules/next/dist/styled-jsx/types/macro.d.ts","./node_modules/next/dist/styled-jsx/types/style.d.ts","./node_modules/next/dist/styled-jsx/types/global.d.ts","./node_modules/next/dist/shared/lib/amp.d.ts","./node_modules/next/amp.d.ts","./node_modules/@types/node/ts5.6/globals.typedarray.d.ts","./node_modules/@types/node/ts5.6/buffer.buffer.d.ts","./node_modules/@types/node/assert.d.ts","./node_modules/@types/node/assert/strict.d.ts","./node_modules/buffer/index.d.ts","./node_modules/undici-types/header.d.ts","./node_modules/undici-types/readable.d.ts","./node_modules/undici-types/file.d.ts","./node_modules/undici-types/fetch.d.ts","./node_modules/undici-types/formdata.d.ts","./node_modules/uLine truncated
|
{"program":{"fileNames":["./node_modules/typescript/lib/lib.es5.d.ts","./node_modules/typescript/lib/lib.es2015.d.ts","./node_modules/typescript/lib/lib.es2016.d.ts","./node_modules/typescript/lib/lib.es2017.d.ts","./node_modules/typescript/lib/lib.es2018.d.ts","./node_modules/typescript/lib/lib.es2019.d.ts","./node_modules/typescript/lib/lib.es2020.d.ts","./node_modules/typescript/lib/lib.es2021.d.ts","./node_modules/typescript/lib/lib.es2022.d.ts","./node_modules/typescript/lib/lib.es2023.d.ts","./node_modules/typescript/lib/lib.esnext.d.ts","./node_modules/typescript/lib/lib.dom.d.ts","./node_modules/typescript/lib/lib.dom.iterable.d.ts","./node_modules/typescript/lib/lib.es2015.core.d.ts","./node_modules/typescript/lib/lib.es2015.collection.d.ts","./node_modules/typescript/lib/lib.es2015.generator.d.ts","./node_modules/typescript/lib/lib.es2015.iterable.d.ts","./node_modules/typescript/lib/lib.es2015.promise.d.ts","./node_modules/typescript/lib/lib.es2015.proxy.d.ts","./node_modules/typescript/lib/lib.es2015.reflect.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2016.array.include.d.ts","./node_modules/typescript/lib/lib.es2016.intl.d.ts","./node_modules/typescript/lib/lib.es2017.date.d.ts","./node_modules/typescript/lib/lib.es2017.object.d.ts","./node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2017.string.d.ts","./node_modules/typescript/lib/lib.es2017.intl.d.ts","./node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","./node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","./node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","./node_modules/typescript/lib/lib.es2018.intl.d.ts","./node_modules/typescript/lib/lib.es2018.promise.d.ts","./node_modules/typescript/lib/lib.es2018.regexp.d.ts","./node_modules/typescript/lib/lib.es2019.array.d.ts","./node_modules/typescript/lib/lib.es2019.object.d.ts","./node_modules/typescript/lib/lib.es2019.string.d.ts","./node_modules/typescript/lib/lib.es2019.symbol.d.ts","./node_modules/typescript/lib/lib.es2019.intl.d.ts","./node_modules/typescript/lib/lib.es2020.bigint.d.ts","./node_modules/typescript/lib/lib.es2020.date.d.ts","./node_modules/typescript/lib/lib.es2020.promise.d.ts","./node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2020.string.d.ts","./node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2020.intl.d.ts","./node_modules/typescript/lib/lib.es2020.number.d.ts","./node_modules/typescript/lib/lib.es2021.promise.d.ts","./node_modules/typescript/lib/lib.es2021.string.d.ts","./node_modules/typescript/lib/lib.es2021.weakref.d.ts","./node_modules/typescript/lib/lib.es2021.intl.d.ts","./node_modules/typescript/lib/lib.es2022.array.d.ts","./node_modules/typescript/lib/lib.es2022.error.d.ts","./node_modules/typescript/lib/lib.es2022.intl.d.ts","./node_modules/typescript/lib/lib.es2022.object.d.ts","./node_modules/typescript/lib/lib.es2022.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2022.string.d.ts","./node_modules/typescript/lib/lib.es2022.regexp.d.ts","./node_modules/typescript/lib/lib.es2023.array.d.ts","./node_modules/typescript/lib/lib.es2023.collection.d.ts","./node_modules/typescript/lib/lib.es2023.intl.d.ts","./node_modules/typescript/lib/lib.esnext.array.d.ts","./node_modules/typescript/lib/lib.esnext.collection.d.ts","./node_modules/typescript/lib/lib.esnext.intl.d.ts","./node_modules/typescript/lib/lib.esnext.disposable.d.ts","./node_modules/typescript/lib/lib.esnext.string.d.ts","./node_modules/typescript/lib/lib.esnext.promise.d.ts","./node_modules/typescript/lib/lib.esnext.decorators.d.ts","./node_modules/typescript/lib/lib.esnext.object.d.ts","./node_modules/typescript/lib/lib.esnext.regexp.d.ts","./node_modules/typescript/lib/lib.decorators.d.ts","./node_modules/typescript/lib/lib.decorators.legacy.d.ts","./node_modules/next/dist/styled-jsx/types/css.d.ts","./node_modules/@types/react/global.d.ts","./node_modules/csstype/index.d.ts","./node_modules/@types/prop-types/index.d.ts","./node_modules/@types/react/index.d.ts","./node_modules/next/dist/styled-jsx/types/index.d.ts","./node_modules/next/dist/styled-jsx/types/macro.d.ts","./node_modules/next/dist/styled-jsx/types/style.d.ts","./node_modules/next/dist/styled-jsx/types/global.d.ts","./node_modules/next/dist/shared/lib/amp.d.ts","./node_modules/next/amp.d.ts","./node_modules/@types/node/ts5.6/globals.typedarray.d.ts","./node_modules/@types/node/ts5.6/buffer.buffer.d.ts","./node_modules/@types/node/assert.d.ts","./node_modules/@types/node/assert/strict.d.ts","./node_modules/buffer/index.d.ts","./node_modules/undici-types/header.d.ts","./node_modules/undici-types/readable.d.ts","./node_modules/undici-types/file.d.ts","./node_modules/undici-types/fetch.d.ts","./node_modules/undici-types/formdata.d.ts","./node_modules/uLine truncated
|
||||||
Reference in new issue
Block a user