diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..1cd3ffa
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,25 @@
+Project Description:
+This is a nextjs project, basically a youtube like video sites.
+
+Feature requirement:
+1. Has a youtube like UI
+2. has a concept of media library. each library is a path that mounted in the /mnt
+3. has the ability to scan the media libaries
+4. the quntitiy of media files can be vast, tens of thousands videos
+5. the media can work with photos and videos
+6. user can manage(add/remove) the media libraries.
+7. there should be a database(sqlite) to save the media informations
+8. there should be at least two tables in the database, one to keep the video informations, such as path, size, thumbnails, title, etc(not limit to these); one to keep the media libraries. videos should be linked to the library
+9. thumbnail generate feature
+
+
+UI:
+1. two main areas, left is the left sidebar, right is the main video area
+2. side bar can be expanded and collapsed
+3. sidebar should have these sections: Settings, Videos, Photos, Folder Viewer
+4. Settings section: open manage page in the main area, allow user to add media library path; delete library.
+5. Videos section: open video cards page in the main area, each card has thumbnail pic displayed. the card lower part display video path, size.
+6. photo section: TBD
+7. folder viewer: list libraries in the side bar folder viewer section. once one of the folder is selected, display the folder sturcture in the main area. the vdieo or photo display thunbnail and information as the video card would do. for the folder, display the folder icon, which can be entered in
+
+
diff --git a/media.db b/media.db
index f6177db..2b38a25 100644
Binary files a/media.db and b/media.db differ
diff --git a/screenshot/image copy.png b/screenshot/image copy.png
deleted file mode 100644
index 011dca8..0000000
Binary files a/screenshot/image copy.png and /dev/null differ
diff --git a/src/app/api/files/route.ts b/src/app/api/files/route.ts
index ea53142..19da4f3 100644
--- a/src/app/api/files/route.ts
+++ b/src/app/api/files/route.ts
@@ -2,6 +2,10 @@
import { NextResponse } from "next/server";
import fs from "fs";
import path from "path";
+import db from '@/db';
+
+const VIDEO_EXTENSIONS = ["mp4", "mkv", "avi", "mov", "wmv", "flv", "webm", "m4v"];
+const PHOTO_EXTENSIONS = ["jpg", "jpeg", "png", "gif", "bmp", "webp", "tiff", "svg"];
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
@@ -13,16 +17,39 @@ export async function GET(request: Request) {
try {
const files = fs.readdirSync(dirPath);
+
+ // Get media files from database for this path
+ const mediaFiles = db.prepare(`
+ SELECT path, type, thumbnail
+ FROM media
+ WHERE path LIKE ?
+ `).all(`${dirPath}%`) as Array<{path: string, type: string, thumbnail: string | null}>;
+
const result = files.map((file) => {
const filePath = path.join(dirPath, file);
const stats = fs.statSync(filePath);
+ const ext = path.extname(file).toLowerCase();
+
+ let type = 'file';
+ if (VIDEO_EXTENSIONS.some(v => ext.includes(v))) {
+ type = 'video';
+ } else if (PHOTO_EXTENSIONS.some(p => ext.includes(p))) {
+ type = 'photo';
+ }
+
+ // Find matching media file in database
+ const mediaFile = mediaFiles.find((m: any) => m.path === filePath);
+
return {
name: file,
path: filePath,
isDirectory: stats.isDirectory(),
size: stats.size,
+ type: mediaFile?.type || type,
+ thumbnail: mediaFile?.thumbnail,
};
});
+
return NextResponse.json(result);
} catch (error: any) {
return NextResponse.json({ error: error.message }, { status: 500 });
diff --git a/src/app/api/photos/route.ts b/src/app/api/photos/route.ts
new file mode 100644
index 0000000..d1019a9
--- /dev/null
+++ b/src/app/api/photos/route.ts
@@ -0,0 +1,7 @@
+import { NextResponse } from 'next/server';
+import db from '@/db';
+
+export async function GET() {
+ const photos = db.prepare('SELECT * FROM media WHERE type = ?').all('photo');
+ return NextResponse.json(photos);
+}
\ No newline at end of file
diff --git a/src/app/folder-viewer/page.tsx b/src/app/folder-viewer/page.tsx
index 0397071..1fc8a62 100644
--- a/src/app/folder-viewer/page.tsx
+++ b/src/app/folder-viewer/page.tsx
@@ -3,15 +3,17 @@
import { useState, useEffect, Suspense } from "react";
import { useSearchParams } from "next/navigation";
-import { Folder, File } from "lucide-react";
+import { Folder, File, Image, Film } from "lucide-react";
import Link from "next/link";
-import { Card, CardContent } from "@/components/ui/card";
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
interface FileSystemItem {
name: string;
path: string;
isDirectory: boolean;
size: number;
+ thumbnail?: string;
+ type?: string;
}
const FolderViewerPage = () => {
@@ -31,32 +33,88 @@ const FolderViewerPage = () => {
setItems(data);
};
+ const formatFileSize = (bytes: number) => {
+ if (bytes === 0) return '0 Bytes';
+ const k = 1024;
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
+ };
+
+ const getFileIcon = (item: FileSystemItem) => {
+ if (item.isDirectory) return ;
+ if (item.type === 'photo') return ;
+ if (item.type === 'video') return ;
+ return ;
+ };
+
+ const isMediaFile = (item: FileSystemItem) => {
+ return item.type === 'video' || item.type === 'photo';
+ };
+
if (!path) {
- return
Select a library from the sidebar
;
+ return (
+
+
+
Select a library from the sidebar
+
Choose a media library above to browse its contents