diff --git a/CODEBUDDY.md b/CODEBUDDY.md index 8dec568..f97f51a 100644 --- a/CODEBUDDY.md +++ b/CODEBUDDY.md @@ -17,54 +17,67 @@ - **Framework**: Next.js 15 with App Router, React 19 - **State**: Zustand for client state - **Styling**: Tailwind CSS with HSL CSS variables (dark mode by default) -- **UI Components**: Custom components built on Radix UI primitives +- **UI Components**: Custom components built on Radix UI primitives + class-variance-authority - **Animations**: Framer Motion - **Form Validation**: Zod - **Media Processing**: Sharp (images), FFmpeg (video/audio) +- **Internationalization**: i18n store + server helpers (cookie/Accept-Language detection) ### Directory Structure ``` src/ -├── app/ # Next.js App Router -│ ├── (auth)/ # Auth routes group (login, register) -│ ├── (dashboard)/ # Dashboard routes with Sidebar layout -│ │ ├── tools/ # Tool pages (image-compress, video-frames, audio-compress) -│ │ └── layout.tsx # Dashboard layout with Sidebar -│ ├── api/ # API routes -│ │ ├── upload/ # File upload endpoint -│ │ └── process/ # Processing endpoints per tool type -│ ├── globals.css # Global styles with CSS variables -│ └── layout.tsx # Root layout (Header + Footer) +├── app/ # Next.js App Router +│ ├── (auth)/ # Auth routes group +│ ├── (dashboard)/ # Dashboard routes with Sidebar layout +│ │ └── tools/ # Tool pages (image/audio/video/texture-atlas) +│ ├── api/ # API routes +│ │ ├── upload/ # File upload endpoint +│ │ └── process/ # Processing endpoints per tool type +│ ├── globals.css # Global styles with CSS variables +│ └── layout.tsx # Root layout (Header + Footer + SEO) ├── components/ -│ ├── ui/ # Base UI primitives (button, card, input, etc.) -│ ├── tools/ # Tool-specific components (FileUploader, ConfigPanel, ProgressBar, ResultPreview) -│ └── layout/ # Layout components (Header, Footer, Sidebar) -├── lib/ -│ ├── api.ts # API client functions -│ └── utils.ts # Utility functions (cn, formatFileSize, etc.) -├── store/ -│ ├── authStore.ts # Auth state -│ └── uploadStore.ts # File upload and processing state -└── types/ - └── index.ts # TypeScript types (UploadedFile, ProcessedFile, configs, etc.) +│ ├── layout/ # Header, Footer, Sidebar, LanguageSwitcher +│ ├── seo/ # StructuredData +│ ├── tools/ # Tool components (FileUploader, ConfigPanel, ResultPreview...) +│ │ └── atlas/ # Texture-atlas specific panels +│ └── ui/ # Base UI primitives (button, card, dialog, etc.) +├── hooks/ # Custom hooks (e.g., atlas worker bridge) +├── lib/ # API clients, i18n, atlas algorithms, image processing +├── locales/ # i18n resources +├── store/ # Zustand stores (upload/atlas/auth) +└── types/ # Shared TypeScript types ``` ### Key Patterns -**Route Groups**: Uses `(auth)` and `(dashboard)` route groups. Dashboard routes share a layout with `Sidebar` component. +**Route Groups**: Uses `(auth)` and `(dashboard)` route groups. Dashboard routes share a layout with `Sidebar`. -**Tool Pages Pattern**: Each tool (image-compress, video-frames, audio-compress) follows the same pattern: -1. Uses `FileUploader` for drag-drop file input -2. Uses `ConfigPanel` for tool-specific configuration options -3. Uses `ProgressBar` to show processing status -4. Uses `ResultPreview` to display processed files -5. State managed via `useUploadStore` Zustand store +**Tool Pages Pattern**: Tools (audio-compress, image-compress, video-frames) share a common UI flow: +1. `FileUploader` for drag-drop input +2. `ConfigPanel` for tool-specific settings +3. `ProgressBar` for processing status +4. `ResultPreview` for outputs +5. State managed via `useUploadStore` -**API Routes**: API routes under `app/api/` use Node.js runtime. Each processing endpoint validates input and returns JSON responses. Currently mock implementations - production would use Sharp/FFmpeg and cloud storage. +**Texture Atlas Tool**: `/tools/texture-atlas` uses a three-panel layout (file list, canvas preview, config panel) with Web Worker processing for packing and preview. -**State Management**: Zustand stores in `store/` directory. `uploadStore` manages file list, processing status and progress. `authStore` manages user authentication state. +**API Routes**: `app/api/` routes run on Node.js. Upload and processing endpoints validate input and return JSON. Production processing is intended to use Sharp/FFmpeg. -**Styling**: Uses `cn()` utility from `lib/utils.ts` for Tailwind class merging. Theme colors defined as CSS variables in `globals.css`. Component styling uses HSL color functions like `hsl(var(--primary))`. +**State Management**: Zustand stores in `store/` directory. +- `uploadStore`: file queue and processing progress +- `atlasStore`: texture-atlas state (sprites, configs, results, preview) +- `authStore`: user state placeholder -**Type Definitions**: All shared types in `types/index.ts`. Includes file types, processing configs, API responses, and user types. +**Internationalization**: `useI18nStore` + `useSafeTranslation` to avoid SSR/CSR hydration mismatch; server uses cookie/Accept-Language to set locale. + +**Styling**: Uses `cn()` from `lib/utils.ts` for Tailwind class merging. Theme colors are CSS variables in `globals.css`. + +**Type Definitions**: Shared types live in `types/index.ts` (uploads, processing configs, atlas models, API responses). + +### Service & Processing Logic +- `lib/api.ts`: API client wrappers (upload/process/download/quota) +- `lib/atlas-packer.ts` + `lib/atlas-worker.ts`: browser-side packing algorithms +- `hooks/useAtlasWorker.ts`: Web Worker bridge for atlas packing +- `lib/image-processor.ts`: Sharp-based image compression pipeline +- `lib/file-storage.ts`: temp storage, validation, cleanup, downloads diff --git a/src/app/(dashboard)/tools/video-frames/page.tsx b/src/app/(dashboard)/tools/video-frames/page.tsx index 3c33d46..d551a4a 100644 --- a/src/app/(dashboard)/tools/video-frames/page.tsx +++ b/src/app/(dashboard)/tools/video-frames/page.tsx @@ -1,260 +1,596 @@ -"use client"; +'use client'; -import { useState, useCallback, useEffect } from "react"; -import { motion } from "framer-motion"; -import { Video, Settings } from "lucide-react"; -import { FileUploader } from "@/components/tools/FileUploader"; -import { ProgressBar } from "@/components/tools/ProgressBar"; -import { ResultPreview } from "@/components/tools/ResultPreview"; -import { ConfigPanel, type ConfigOption } from "@/components/tools/ConfigPanel"; -import { Button } from "@/components/ui/button"; -import { useUploadStore } from "@/store/uploadStore"; -import { generateId } from "@/lib/utils"; -import { useTranslation, getServerTranslations } from "@/lib/i18n"; -import type { UploadedFile, ProcessedFile, VideoFramesConfig } from "@/types"; +import React, { useState, useCallback, useRef, useMemo, useEffect } from 'react'; +import { useDropzone } from 'react-dropzone'; +import JSZip from 'jszip'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Progress } from '@/components/ui/progress'; +import { Badge } from '@/components/ui/badge'; +import { + Film, + Upload, + Download, + Loader2, + CheckCircle2, + Play, + Pause, + Settings2, + Trash2, + CheckSquare, + Square, + ImageIcon, + Clock, + Maximize2, + FileVideo, + Eye, +} from 'lucide-react'; +import { FramePreviewPlayer } from '@/components/frame-preview-player'; +import { + ExtractedFrame, + ExtractionSettings, + VideoMetadata, + validateVideo, + getVideoMetadata, + extractFrames, + calculateEstimatedFrames, + formatFileSize, + formatDuration, + SUPPORTED_VIDEO_FORMATS, + MAX_DURATION, + MAX_FILE_SIZE, + MIN_FRAME_RATE, + MAX_FRAME_RATE, +} from '@/lib/frame-extractor'; -const videoAccept = { - "video/*": [".mp4", ".mov", ".avi", ".webm", ".mkv"], -}; +type ExtractionStatus = 'idle' | 'uploading' | 'extracting' | 'completed' | 'error'; -const defaultConfig: VideoFramesConfig = { - fps: 30, - format: "png", - quality: 90, - width: undefined, - height: undefined, -}; +export default function VideoToFramesPage() { + // 视频相关状态 + const [videoFile, setVideoFile] = useState(null); + const [videoUrl, setVideoUrl] = useState(''); + const [metadata, setMetadata] = useState(null); + const videoRef = useRef(null); + const [isPlaying, setIsPlaying] = useState(false); -function useConfigOptions(config: VideoFramesConfig, getT: (key: string) => string): ConfigOption[] { - return [ - { - id: "fps", - type: "slider", - label: getT("config.videoFrames.fps"), - description: getT("config.videoFrames.fpsDescription"), - value: config.fps, - min: 1, - max: 60, - step: 1, - suffix: " fps", - icon: