forked from 77media/video-flow
317 lines
10 KiB
TypeScript
317 lines
10 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from 'react';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Slider } from '@/components/ui/slider';
|
|
import { Progress } from '@/components/ui/progress';
|
|
import { ArrowLeft, ArrowRight, Play, Pause, Music, Upload, Wand2, Volume2 } from 'lucide-react';
|
|
|
|
interface AddMusicStepProps {
|
|
onNext: () => void;
|
|
onPrevious: () => void;
|
|
}
|
|
|
|
const mockChapters = [
|
|
{
|
|
id: 1,
|
|
title: 'Introduction',
|
|
duration: 45,
|
|
music: {
|
|
id: 1,
|
|
name: 'Uplifting Corporate',
|
|
genre: 'Corporate',
|
|
duration: 45,
|
|
volume: 30,
|
|
fadeIn: 2,
|
|
fadeOut: 3,
|
|
generated: true,
|
|
},
|
|
},
|
|
{
|
|
id: 2,
|
|
title: 'Core Concepts',
|
|
duration: 80,
|
|
music: {
|
|
id: 2,
|
|
name: 'Tech Ambient',
|
|
genre: 'Ambient',
|
|
duration: 80,
|
|
volume: 25,
|
|
fadeIn: 3,
|
|
fadeOut: 2,
|
|
generated: true,
|
|
},
|
|
},
|
|
{
|
|
id: 3,
|
|
title: 'Practical Applications',
|
|
duration: 75,
|
|
music: {
|
|
id: 3,
|
|
name: 'Modern Innovation',
|
|
genre: 'Electronic',
|
|
duration: 75,
|
|
volume: 35,
|
|
fadeIn: 2,
|
|
fadeOut: 4,
|
|
generated: true,
|
|
},
|
|
},
|
|
{
|
|
id: 4,
|
|
title: 'Future Outlook',
|
|
duration: 50,
|
|
music: {
|
|
id: 4,
|
|
name: 'Inspiring Future',
|
|
genre: 'Cinematic',
|
|
duration: 50,
|
|
volume: 40,
|
|
fadeIn: 1,
|
|
fadeOut: 5,
|
|
generated: true,
|
|
},
|
|
},
|
|
];
|
|
|
|
const musicGenres = [
|
|
'Corporate', 'Ambient', 'Electronic', 'Cinematic', 'Jazz', 'Folk', 'Rock', 'Classical'
|
|
];
|
|
|
|
export function AddMusicStep({ onNext, onPrevious }: AddMusicStepProps) {
|
|
const [chapters, setChapters] = useState(mockChapters);
|
|
const [playingChapter, setPlayingChapter] = useState<number | null>(null);
|
|
|
|
const handleVolumeChange = (chapterId: number, volume: number[]) => {
|
|
setChapters(chapters.map(ch =>
|
|
ch.id === chapterId
|
|
? { ...ch, music: { ...ch.music, volume: volume[0] } }
|
|
: ch
|
|
));
|
|
};
|
|
|
|
const handleFadeChange = (chapterId: number, type: 'fadeIn' | 'fadeOut', value: number[]) => {
|
|
setChapters(chapters.map(ch =>
|
|
ch.id === chapterId
|
|
? { ...ch, music: { ...ch.music, [type]: value[0] } }
|
|
: ch
|
|
));
|
|
};
|
|
|
|
const regenerateMusic = (chapterId: number) => {
|
|
const randomGenre = musicGenres[Math.floor(Math.random() * musicGenres.length)];
|
|
const randomName = `AI Generated ${randomGenre} ${Math.floor(Math.random() * 100)}`;
|
|
|
|
setChapters(chapters.map(ch =>
|
|
ch.id === chapterId
|
|
? {
|
|
...ch,
|
|
music: {
|
|
...ch.music,
|
|
name: randomName,
|
|
genre: randomGenre,
|
|
generated: true
|
|
}
|
|
}
|
|
: ch
|
|
));
|
|
};
|
|
|
|
const togglePlay = (chapterId: number) => {
|
|
setPlayingChapter(playingChapter === chapterId ? null : chapterId);
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center space-x-2">
|
|
<Music className="h-5 w-5" />
|
|
<span>Background Music</span>
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p className="text-muted-foreground mb-6">
|
|
AI has automatically generated background music for each chapter.
|
|
You can adjust volumes, fade effects, or replace with custom music.
|
|
</p>
|
|
|
|
<div className="space-y-6">
|
|
{chapters.map((chapter) => (
|
|
<Card key={chapter.id} className="border-l-4 border-l-blue-500/20">
|
|
<CardHeader>
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center space-x-3">
|
|
<Badge variant="outline">Chapter {chapter.id}</Badge>
|
|
<h3 className="font-semibold">{chapter.title}</h3>
|
|
<Badge variant="secondary">{chapter.duration}s</Badge>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() => togglePlay(chapter.id)}
|
|
>
|
|
{playingChapter === chapter.id ? (
|
|
<Pause className="h-4 w-4" />
|
|
) : (
|
|
<Play className="h-4 w-4" />
|
|
)}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
{/* Music Info */}
|
|
<div className="bg-muted p-4 rounded-lg">
|
|
<div className="flex items-center justify-between mb-2">
|
|
<div className="flex items-center space-x-2">
|
|
<Music className="h-4 w-4 text-blue-600" />
|
|
<span className="font-medium">{chapter.music.name}</span>
|
|
<Badge variant="outline" className="text-xs">
|
|
{chapter.music.genre}
|
|
</Badge>
|
|
{chapter.music.generated && (
|
|
<Badge variant="secondary" className="text-xs">
|
|
AI Generated
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
<span className="text-sm text-muted-foreground">
|
|
{chapter.music.duration}s
|
|
</span>
|
|
</div>
|
|
|
|
{playingChapter === chapter.id && (
|
|
<div className="space-y-2">
|
|
<Progress value={Math.random() * 100} className="h-1" />
|
|
<div className="flex justify-between text-xs text-muted-foreground">
|
|
<span>0:00</span>
|
|
<span>{Math.floor(chapter.duration / 60)}:{(chapter.duration % 60).toString().padStart(2, '0')}</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Controls */}
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
{/* Volume */}
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium flex items-center">
|
|
<Volume2 className="mr-1 h-4 w-4" />
|
|
Volume ({chapter.music.volume}%)
|
|
</label>
|
|
<Slider
|
|
value={[chapter.music.volume]}
|
|
onValueChange={(value) => handleVolumeChange(chapter.id, value)}
|
|
max={100}
|
|
step={5}
|
|
className="w-full"
|
|
/>
|
|
</div>
|
|
|
|
{/* Fade In */}
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium">
|
|
Fade In ({chapter.music.fadeIn}s)
|
|
</label>
|
|
<Slider
|
|
value={[chapter.music.fadeIn]}
|
|
onValueChange={(value) => handleFadeChange(chapter.id, 'fadeIn', value)}
|
|
max={10}
|
|
step={0.5}
|
|
className="w-full"
|
|
/>
|
|
</div>
|
|
|
|
{/* Fade Out */}
|
|
<div className="space-y-2">
|
|
<label className="text-sm font-medium">
|
|
Fade Out ({chapter.music.fadeOut}s)
|
|
</label>
|
|
<Slider
|
|
value={[chapter.music.fadeOut]}
|
|
onValueChange={(value) => handleFadeChange(chapter.id, 'fadeOut', value)}
|
|
max={10}
|
|
step={0.5}
|
|
className="w-full"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Action Buttons */}
|
|
<div className="flex space-x-2 pt-2">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => regenerateMusic(chapter.id)}
|
|
>
|
|
<Wand2 className="mr-2 h-4 w-4" />
|
|
Regenerate
|
|
</Button>
|
|
<Button variant="outline" size="sm">
|
|
<Upload className="mr-2 h-4 w-4" />
|
|
Upload Custom
|
|
</Button>
|
|
<Button variant="outline" size="sm">
|
|
Browse Library
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Global Music Settings */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Global Settings</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div className="space-y-3">
|
|
<label className="text-sm font-medium">Master Volume</label>
|
|
<Slider
|
|
defaultValue={[70]}
|
|
max={100}
|
|
step={5}
|
|
className="w-full"
|
|
/>
|
|
<p className="text-xs text-muted-foreground">
|
|
Adjust overall music volume relative to voice
|
|
</p>
|
|
</div>
|
|
<div className="space-y-3">
|
|
<label className="text-sm font-medium">Cross-fade Between Chapters</label>
|
|
<Slider
|
|
defaultValue={[2]}
|
|
max={5}
|
|
step={0.5}
|
|
className="w-full"
|
|
/>
|
|
<p className="text-xs text-muted-foreground">
|
|
Smooth transitions between chapter music
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Action Buttons */}
|
|
<div className="flex justify-between">
|
|
<Button variant="outline" onClick={onPrevious}>
|
|
<ArrowLeft className="mr-2 h-4 w-4" />
|
|
Back to Scenes
|
|
</Button>
|
|
<Button onClick={onNext}>
|
|
Final Composition
|
|
<ArrowRight className="ml-2 h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
);
|
|
} |