anycoder-6d5b65da / components /MediaStudio.jsx
Mehdi
Upload components/MediaStudio.jsx with huggingface_hub
01260e0 verified
Raw
History Blame Contribute Delete
15.6 kB
import { useState, useRef, useCallback } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { useDropzone } from 'react-dropzone'
import {
FaImage, FaVideo, FaMusic, FaCamera, FaUpload, FaDownload,
FaPlay, FaPause, FaStop, FaRedo, FaCut, FaPalette, FaMagic,
FaTrash, FaCopy, FaSave, FaShare, FaExpand, FaCompress, FaFilter,
FaAdjust, FaSun, FaMoon, FaEraser, FaPaintBrush, FaCrop, FaRotate
} from 'react-icons/fa'
import Webcam from 'react-webcam'
import toast from 'react-hot-toast'
export default function MediaStudio({ language, theme }) {
const [activeTab, setActiveTab] = useState('image')
const [selectedFile, setSelectedFile] = useState(null)
const [isProcessing, setIsProcessing] = useState(false)
const [filters, setFilters] = useState({
brightness: 100,
contrast: 100,
saturation: 100,
blur: 0,
hue: 0
})
const [history, setHistory] = useState([])
const [historyIndex, setHistoryIndex] = useState(-1)
const [cameraActive, setCameraActive] = useState(false)
const [recording, setRecording] = useState(false)
const webcamRef = useRef(null)
const canvasRef = useRef(null)
const tabs = [
{ id: 'image', name: language === 'fa' ? 'تصویر' : 'Image', icon: FaImage },
{ id: 'video', name: language === 'fa' ? 'ویدیو' : 'Video', icon: FaVideo },
{ id: 'audio', name: language === 'fa' ? 'صدا' : 'Audio', icon: FaMusic },
{ id: 'camera', name: language === 'fa' ? 'دوربین' : 'Camera', icon: FaCamera }
]
const onDrop = useCallback((acceptedFiles) => {
const file = acceptedFiles[0]
if (file) {
const reader = new FileReader()
reader.onload = (e) => {
setSelectedFile({
name: file.name,
type: file.type,
url: e.target.result,
size: file.size
})
saveToHistory(e.target.result)
toast.success(language === 'fa' ? 'فایل با موفقیت آپلود شد' : 'File uploaded successfully')
}
reader.readAsDataURL(file)
}
}, [])
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop,
accept: {
'image/*': ['.jpeg', '.jpg', '.png', '.gif', '.webp'],
'video/*': ['.mp4', '.avi', '.mov', '.webm'],
'audio/*': ['.mp3', '.wav', '.ogg', '.m4a']
}
})
const saveToHistory = (data) => {
const newHistory = history.slice(0, historyIndex + 1)
newHistory.push(data)
setHistory(newHistory)
setHistoryIndex(newHistory.length - 1)
}
const applyFilter = (filterType, value) => {
setFilters(prev => ({ ...prev, [filterType]: value }))
// Apply filter to canvas
if (selectedFile && canvasRef.current) {
const canvas = canvasRef.current
const ctx = canvas.getContext('2d')
const img = new Image()
img.onload = () => {
canvas.width = img.width
canvas.height = img.height
ctx.filter = `brightness(${filters.brightness}%) contrast(${filters.contrast}%) saturate(${filters.saturation}%) blur(${filters.blur}px) hue-rotate(${filters.hue}deg)`
ctx.drawImage(img, 0, 0)
}
img.src = selectedFile.url
}
}
const capturePhoto = () => {
if (webcamRef.current) {
const imageSrc = webcamRef.current.getScreenshot()
setSelectedFile({
name: `capture_${Date.now()}.jpg`,
type: 'image/jpeg',
url: imageSrc,
size: 0
})
saveToHistory(imageSrc)
toast.success(language === 'fa' ? 'عکس گرفته شد' : 'Photo captured')
}
}
const startRecording = () => {
setRecording(true)
toast.success(language === 'fa' ? 'ضبط شروع شد' : 'Recording started')
}
const stopRecording = () => {
setRecording(false)
toast.success(language === 'fa' ? 'ضبط متوقف شد' : 'Recording stopped')
}
const resetFilters = () => {
setFilters({
brightness: 100,
contrast: 100,
saturation: 100,
blur: 0,
hue: 0
})
}
const downloadFile = () => {
if (selectedFile) {
const a = document.createElement('a')
a.href = selectedFile.url
a.download = `edited_${selectedFile.name}`
a.click()
toast.success(language === 'fa' ? 'فایل دانلود شد' : 'File downloaded')
}
}
const undo = () => {
if (historyIndex > 0) {
setHistoryIndex(historyIndex - 1)
setSelectedFile(prev => ({ ...prev, url: history[historyIndex - 1] }))
}
}
const redo = () => {
if (historyIndex < history.length - 1) {
setHistoryIndex(historyIndex + 1)
setSelectedFile(prev => ({ ...prev, url: history[historyIndex + 1] }))
}
}
return (
<div className="flex flex-col h-full space-y-4">
{/* Tab Navigation */}
<div className="flex space-x-reverse space-x-1 p-2 bg-gray-800 rounded-lg">
{tabs.map(tab => {
const Icon = tab.icon
return (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`flex items-center space-x-reverse space-x-2 px-4 py-2 rounded-lg transition-all ${
activeTab === tab.id
? 'bg-primary-600 text-white'
: 'bg-gray-700 text-gray-300 hover:bg-gray-600'
}`}
>
<Icon className="w-4 h-4" />
<span className="text-sm font-medium">{tab.name}</span>
</button>
)
})}
</div>
{/* Main Content */}
<div className="flex-1 flex space-x-reverse space-x-4 min-h-0">
{/* Media Preview Area */}
<div className="flex-1 bg-gray-800 rounded-lg p-4">
{activeTab === 'camera' ? (
<div className="h-full flex flex-col items-center justify-center">
{cameraActive ? (
<div className="relative">
<Webcam
audio={false}
ref={webcamRef}
screenshotFormat="image/jpeg"
className="rounded-lg"
/>
<div className="absolute bottom-4 left-1/2 transform translate-x-1/2 flex space-x-reverse space-x-2">
<button
onClick={capturePhoto}
className="p-3 bg-primary-600 text-white rounded-full hover:bg-primary-700 transition-all"
>
<FaCamera className="w-5 h-5" />
</button>
{recording ? (
<button
onClick={stopRecording}
className="p-3 bg-red-600 text-white rounded-full hover:bg-red-700 transition-all animate-pulse"
>
<FaStop className="w-5 h-5" />
</button>
) : (
<button
onClick={startRecording}
className="p-3 bg-red-600 text-white rounded-full hover:bg-red-700 transition-all"
>
<FaPlay className="w-5 h-5" />
</button>
)}
</div>
</div>
) : (
<button
onClick={() => setCameraActive(true)}
className="flex flex-col items-center space-y-2 p-8 bg-gray-700 rounded-lg hover:bg-gray-600 transition-all"
>
<FaCamera className="w-12 h-12 text-gray-400" />
<span className="text-gray-300">
{language === 'fa' ? 'فعال کردن دوربین' : 'Enable Camera'}
</span>
</button>
)}
</div>
) : selectedFile ? (
<div className="h-full flex flex-col">
<div className="flex-1 flex items-center justify-center">
{selectedFile.type.startsWith('image/') ? (
<img
src={selectedFile.url}
alt={selectedFile.name}
className="max-w-full max-h-full object-contain rounded-lg"
/>
) : selectedFile.type.startsWith('video/') ? (
<video
src={selectedFile.url}
controls
className="max-w-full max-h-full rounded-lg"
/>
) : (
<audio
src={selectedFile.url}
controls
className="w-full"
/>
)}
</div>
<canvas ref={canvasRef} className="hidden" />
</div>
) : (
<div
{...getRootProps()}
className="h-full flex flex-col items-center justify-center border-2 border-dashed border-gray-600 rounded-lg cursor-pointer hover:border-primary-500 transition-all"
>
<input {...getInputProps()} />
<FaUpload className="w-12 h-12 text-gray-400 mb-4" />
<p className="text-gray-300 text-center">
{isDragActive
? (language === 'fa' ? 'فایل را رها کنید' : 'Drop the file here')
: (language === 'fa' ? 'فایل را بکشید یا کلیک کنید' : 'Drag & drop or click to upload')
}
</p>
</div>
)}
</div>
{/* Tools Panel */}
<div className="w-80 bg-gray-800 rounded-lg p-4 space-y-4 overflow-y-auto">
{/* File Info */}
{selectedFile && (
<div className="p-3 bg-gray-700 rounded-lg">
<h3 className="text-sm font-medium mb-2">
{language === 'fa' ? 'اطلاعات فایل' : 'File Info'}
</h3>
<div className="space-y-1 text-xs text-gray-300">
<p><span className="font-medium">{language === 'fa' ? 'نام:' : 'Name:'}</span> {selectedFile.name}</p>
<p><span className="font-medium">{language === 'fa' ? 'نوع:' : 'Type:'}</span> {selectedFile.type}</p>
<p><span className="font-medium">{language === 'fa' ? 'حجم:' : 'Size:'}</span> {(selectedFile.size / 1024 / 1024).toFixed(2)} MB</p>
</div>
</div>
)}
{/* Actions */}
<div className="space-y-2">
<h3 className="text-sm font-medium">
{language === 'fa' ? 'عملیات' : 'Actions'}
</h3>
<div className="grid grid-cols-2 gap-2">
<button
onClick={downloadFile}
disabled={!selectedFile}
className="flex items-center justify-center space-x-reverse space-x-1 px-3 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 transition-all"
>
<FaDownload className="w-3 h-3" />
<span className="text-xs">{language === 'fa' ? 'دانلود' : 'Download'}</span>
</button>
<button
onClick={undo}
disabled={historyIndex <= 0}
className="flex items-center justify-center space-x-reverse space-x-1 px-3 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 disabled:opacity-50 transition-all"
>
<FaRedo className="w-3 h-3" />
<span className="text-xs">{language === 'fa' ? 'بازگشت' : 'Undo'}</span>
</button>
<button
onClick={redo}
disabled={historyIndex >= history.length - 1}
className="flex items-center justify-center space-x-reverse space-x-1 px-3 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 disabled:opacity-50 transition-all"
>
<FaRedo className="w-3 h-3" />
<span className="text-xs">{language === 'fa' ? 'انجام مجدد' : 'Redo'}</span>
</button>
<button
onClick={resetFilters}
disabled={!selectedFile}
className="flex items-center justify-center space-x-reverse space-x-1 px-3 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:opacity-50 transition-all"
>
<FaEraser className="w-3 h-3" />
<span className="text-xs">{language === 'fa' ? 'بازنشانی' : 'Reset'}</span>
</button>
</div>
</div>
{/* Filters (for images) */}
{selectedFile && selectedFile.type.startsWith('image/') && (
<div className="space-y-3">
<h3 className="text-sm font-medium">
{language === 'fa' ? 'فیلترها' : 'Filters'}
</h3>
<div className="space-y-3">
<div>
<label className="text-xs text-gray-400 flex items-center justify-between mb-1">
<span>{language === 'fa' ? 'روشنایی' : 'Brightness'}</span>
<span>{filters.brightness}%</span>
</label>
<input
type="range"
min="0"
max="200"
value={filters.brightness}
onChange={(e) => applyFilter('brightness', e.target.value)}
className="w-full"
/>
</div>
<div>
<label className="text-xs text-gray-400 flex items-center justify-between mb-1">
<span>{language === 'fa' ? 'کنتراست' : 'Contrast'}</span>
<span>{filters.contrast}%</span>
</label>
<input
type="range"
min="0"
max="200"
value={filters.contrast}
onChange={(e) => applyFilter('contrast', e.target.value)}
className="w-full"
/>
</div>
<div>
<label className="text-xs text-gray-400 flex items-center justify-between mb-1">
<span>{language === 'fa' ? 'اشباع' : 'Saturation'}</span>
<span>{filters.saturation}%</span>
</label>
<input
type="range"
min="0"
max="200"
value={filters.saturation}
onChange={(e) => applyFilter('saturation', e.target.value)}
className="w-full"
/>
</div>
<div>
<label className="text-xs text-gray-400 flex items-center justify-between mb-1">
<span>{language === 'fa' ? 'تاری' : 'Blur'}</span>
<span>{filters.blur}px</span>
</label>
<input
type="range"
min="0"
max="10"
value={filters.blur}
onChange={(e) => applyFilter('blur', e.target.value)}
className="w-full"
/>
</div>
<div>
<label className="text-xs text-gray-400 flex items-center justify-between mb-1">
<span>{language === 'fa'