Mehdi commited on
Commit
01260e0
·
verified ·
1 Parent(s): 0758862

Upload components/MediaStudio.jsx with huggingface_hub

Browse files
Files changed (1) hide show
  1. components/MediaStudio.jsx +389 -0
components/MediaStudio.jsx ADDED
@@ -0,0 +1,389 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useRef, useCallback } from 'react'
2
+ import { motion, AnimatePresence } from 'framer-motion'
3
+ import { useDropzone } from 'react-dropzone'
4
+ import {
5
+ FaImage, FaVideo, FaMusic, FaCamera, FaUpload, FaDownload,
6
+ FaPlay, FaPause, FaStop, FaRedo, FaCut, FaPalette, FaMagic,
7
+ FaTrash, FaCopy, FaSave, FaShare, FaExpand, FaCompress, FaFilter,
8
+ FaAdjust, FaSun, FaMoon, FaEraser, FaPaintBrush, FaCrop, FaRotate
9
+ } from 'react-icons/fa'
10
+ import Webcam from 'react-webcam'
11
+ import toast from 'react-hot-toast'
12
+
13
+ export default function MediaStudio({ language, theme }) {
14
+ const [activeTab, setActiveTab] = useState('image')
15
+ const [selectedFile, setSelectedFile] = useState(null)
16
+ const [isProcessing, setIsProcessing] = useState(false)
17
+ const [filters, setFilters] = useState({
18
+ brightness: 100,
19
+ contrast: 100,
20
+ saturation: 100,
21
+ blur: 0,
22
+ hue: 0
23
+ })
24
+ const [history, setHistory] = useState([])
25
+ const [historyIndex, setHistoryIndex] = useState(-1)
26
+ const [cameraActive, setCameraActive] = useState(false)
27
+ const [recording, setRecording] = useState(false)
28
+ const webcamRef = useRef(null)
29
+ const canvasRef = useRef(null)
30
+
31
+ const tabs = [
32
+ { id: 'image', name: language === 'fa' ? 'تصویر' : 'Image', icon: FaImage },
33
+ { id: 'video', name: language === 'fa' ? 'ویدیو' : 'Video', icon: FaVideo },
34
+ { id: 'audio', name: language === 'fa' ? 'صدا' : 'Audio', icon: FaMusic },
35
+ { id: 'camera', name: language === 'fa' ? 'دوربین' : 'Camera', icon: FaCamera }
36
+ ]
37
+
38
+ const onDrop = useCallback((acceptedFiles) => {
39
+ const file = acceptedFiles[0]
40
+ if (file) {
41
+ const reader = new FileReader()
42
+ reader.onload = (e) => {
43
+ setSelectedFile({
44
+ name: file.name,
45
+ type: file.type,
46
+ url: e.target.result,
47
+ size: file.size
48
+ })
49
+ saveToHistory(e.target.result)
50
+ toast.success(language === 'fa' ? 'فایل با موفقیت آپلود شد' : 'File uploaded successfully')
51
+ }
52
+ reader.readAsDataURL(file)
53
+ }
54
+ }, [])
55
+
56
+ const { getRootProps, getInputProps, isDragActive } = useDropzone({
57
+ onDrop,
58
+ accept: {
59
+ 'image/*': ['.jpeg', '.jpg', '.png', '.gif', '.webp'],
60
+ 'video/*': ['.mp4', '.avi', '.mov', '.webm'],
61
+ 'audio/*': ['.mp3', '.wav', '.ogg', '.m4a']
62
+ }
63
+ })
64
+
65
+ const saveToHistory = (data) => {
66
+ const newHistory = history.slice(0, historyIndex + 1)
67
+ newHistory.push(data)
68
+ setHistory(newHistory)
69
+ setHistoryIndex(newHistory.length - 1)
70
+ }
71
+
72
+ const applyFilter = (filterType, value) => {
73
+ setFilters(prev => ({ ...prev, [filterType]: value }))
74
+ // Apply filter to canvas
75
+ if (selectedFile && canvasRef.current) {
76
+ const canvas = canvasRef.current
77
+ const ctx = canvas.getContext('2d')
78
+ const img = new Image()
79
+ img.onload = () => {
80
+ canvas.width = img.width
81
+ canvas.height = img.height
82
+ ctx.filter = `brightness(${filters.brightness}%) contrast(${filters.contrast}%) saturate(${filters.saturation}%) blur(${filters.blur}px) hue-rotate(${filters.hue}deg)`
83
+ ctx.drawImage(img, 0, 0)
84
+ }
85
+ img.src = selectedFile.url
86
+ }
87
+ }
88
+
89
+ const capturePhoto = () => {
90
+ if (webcamRef.current) {
91
+ const imageSrc = webcamRef.current.getScreenshot()
92
+ setSelectedFile({
93
+ name: `capture_${Date.now()}.jpg`,
94
+ type: 'image/jpeg',
95
+ url: imageSrc,
96
+ size: 0
97
+ })
98
+ saveToHistory(imageSrc)
99
+ toast.success(language === 'fa' ? 'عکس گرفته شد' : 'Photo captured')
100
+ }
101
+ }
102
+
103
+ const startRecording = () => {
104
+ setRecording(true)
105
+ toast.success(language === 'fa' ? 'ضبط شروع شد' : 'Recording started')
106
+ }
107
+
108
+ const stopRecording = () => {
109
+ setRecording(false)
110
+ toast.success(language === 'fa' ? 'ضبط متوقف شد' : 'Recording stopped')
111
+ }
112
+
113
+ const resetFilters = () => {
114
+ setFilters({
115
+ brightness: 100,
116
+ contrast: 100,
117
+ saturation: 100,
118
+ blur: 0,
119
+ hue: 0
120
+ })
121
+ }
122
+
123
+ const downloadFile = () => {
124
+ if (selectedFile) {
125
+ const a = document.createElement('a')
126
+ a.href = selectedFile.url
127
+ a.download = `edited_${selectedFile.name}`
128
+ a.click()
129
+ toast.success(language === 'fa' ? 'فایل دانلود شد' : 'File downloaded')
130
+ }
131
+ }
132
+
133
+ const undo = () => {
134
+ if (historyIndex > 0) {
135
+ setHistoryIndex(historyIndex - 1)
136
+ setSelectedFile(prev => ({ ...prev, url: history[historyIndex - 1] }))
137
+ }
138
+ }
139
+
140
+ const redo = () => {
141
+ if (historyIndex < history.length - 1) {
142
+ setHistoryIndex(historyIndex + 1)
143
+ setSelectedFile(prev => ({ ...prev, url: history[historyIndex + 1] }))
144
+ }
145
+ }
146
+
147
+ return (
148
+ <div className="flex flex-col h-full space-y-4">
149
+ {/* Tab Navigation */}
150
+ <div className="flex space-x-reverse space-x-1 p-2 bg-gray-800 rounded-lg">
151
+ {tabs.map(tab => {
152
+ const Icon = tab.icon
153
+ return (
154
+ <button
155
+ key={tab.id}
156
+ onClick={() => setActiveTab(tab.id)}
157
+ className={`flex items-center space-x-reverse space-x-2 px-4 py-2 rounded-lg transition-all ${
158
+ activeTab === tab.id
159
+ ? 'bg-primary-600 text-white'
160
+ : 'bg-gray-700 text-gray-300 hover:bg-gray-600'
161
+ }`}
162
+ >
163
+ <Icon className="w-4 h-4" />
164
+ <span className="text-sm font-medium">{tab.name}</span>
165
+ </button>
166
+ )
167
+ })}
168
+ </div>
169
+
170
+ {/* Main Content */}
171
+ <div className="flex-1 flex space-x-reverse space-x-4 min-h-0">
172
+ {/* Media Preview Area */}
173
+ <div className="flex-1 bg-gray-800 rounded-lg p-4">
174
+ {activeTab === 'camera' ? (
175
+ <div className="h-full flex flex-col items-center justify-center">
176
+ {cameraActive ? (
177
+ <div className="relative">
178
+ <Webcam
179
+ audio={false}
180
+ ref={webcamRef}
181
+ screenshotFormat="image/jpeg"
182
+ className="rounded-lg"
183
+ />
184
+ <div className="absolute bottom-4 left-1/2 transform translate-x-1/2 flex space-x-reverse space-x-2">
185
+ <button
186
+ onClick={capturePhoto}
187
+ className="p-3 bg-primary-600 text-white rounded-full hover:bg-primary-700 transition-all"
188
+ >
189
+ <FaCamera className="w-5 h-5" />
190
+ </button>
191
+ {recording ? (
192
+ <button
193
+ onClick={stopRecording}
194
+ className="p-3 bg-red-600 text-white rounded-full hover:bg-red-700 transition-all animate-pulse"
195
+ >
196
+ <FaStop className="w-5 h-5" />
197
+ </button>
198
+ ) : (
199
+ <button
200
+ onClick={startRecording}
201
+ className="p-3 bg-red-600 text-white rounded-full hover:bg-red-700 transition-all"
202
+ >
203
+ <FaPlay className="w-5 h-5" />
204
+ </button>
205
+ )}
206
+ </div>
207
+ </div>
208
+ ) : (
209
+ <button
210
+ onClick={() => setCameraActive(true)}
211
+ className="flex flex-col items-center space-y-2 p-8 bg-gray-700 rounded-lg hover:bg-gray-600 transition-all"
212
+ >
213
+ <FaCamera className="w-12 h-12 text-gray-400" />
214
+ <span className="text-gray-300">
215
+ {language === 'fa' ? 'فعال کردن دوربین' : 'Enable Camera'}
216
+ </span>
217
+ </button>
218
+ )}
219
+ </div>
220
+ ) : selectedFile ? (
221
+ <div className="h-full flex flex-col">
222
+ <div className="flex-1 flex items-center justify-center">
223
+ {selectedFile.type.startsWith('image/') ? (
224
+ <img
225
+ src={selectedFile.url}
226
+ alt={selectedFile.name}
227
+ className="max-w-full max-h-full object-contain rounded-lg"
228
+ />
229
+ ) : selectedFile.type.startsWith('video/') ? (
230
+ <video
231
+ src={selectedFile.url}
232
+ controls
233
+ className="max-w-full max-h-full rounded-lg"
234
+ />
235
+ ) : (
236
+ <audio
237
+ src={selectedFile.url}
238
+ controls
239
+ className="w-full"
240
+ />
241
+ )}
242
+ </div>
243
+ <canvas ref={canvasRef} className="hidden" />
244
+ </div>
245
+ ) : (
246
+ <div
247
+ {...getRootProps()}
248
+ 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"
249
+ >
250
+ <input {...getInputProps()} />
251
+ <FaUpload className="w-12 h-12 text-gray-400 mb-4" />
252
+ <p className="text-gray-300 text-center">
253
+ {isDragActive
254
+ ? (language === 'fa' ? 'فایل را رها کنید' : 'Drop the file here')
255
+ : (language === 'fa' ? 'فایل را بکشید یا کلیک کنید' : 'Drag & drop or click to upload')
256
+ }
257
+ </p>
258
+ </div>
259
+ )}
260
+ </div>
261
+
262
+ {/* Tools Panel */}
263
+ <div className="w-80 bg-gray-800 rounded-lg p-4 space-y-4 overflow-y-auto">
264
+ {/* File Info */}
265
+ {selectedFile && (
266
+ <div className="p-3 bg-gray-700 rounded-lg">
267
+ <h3 className="text-sm font-medium mb-2">
268
+ {language === 'fa' ? 'اطلاعات فایل' : 'File Info'}
269
+ </h3>
270
+ <div className="space-y-1 text-xs text-gray-300">
271
+ <p><span className="font-medium">{language === 'fa' ? 'نام:' : 'Name:'}</span> {selectedFile.name}</p>
272
+ <p><span className="font-medium">{language === 'fa' ? 'نوع:' : 'Type:'}</span> {selectedFile.type}</p>
273
+ <p><span className="font-medium">{language === 'fa' ? 'حجم:' : 'Size:'}</span> {(selectedFile.size / 1024 / 1024).toFixed(2)} MB</p>
274
+ </div>
275
+ </div>
276
+ )}
277
+
278
+ {/* Actions */}
279
+ <div className="space-y-2">
280
+ <h3 className="text-sm font-medium">
281
+ {language === 'fa' ? 'عملیات' : 'Actions'}
282
+ </h3>
283
+ <div className="grid grid-cols-2 gap-2">
284
+ <button
285
+ onClick={downloadFile}
286
+ disabled={!selectedFile}
287
+ 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"
288
+ >
289
+ <FaDownload className="w-3 h-3" />
290
+ <span className="text-xs">{language === 'fa' ? 'دانلود' : 'Download'}</span>
291
+ </button>
292
+ <button
293
+ onClick={undo}
294
+ disabled={historyIndex <= 0}
295
+ 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"
296
+ >
297
+ <FaRedo className="w-3 h-3" />
298
+ <span className="text-xs">{language === 'fa' ? 'بازگشت' : 'Undo'}</span>
299
+ </button>
300
+ <button
301
+ onClick={redo}
302
+ disabled={historyIndex >= history.length - 1}
303
+ 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"
304
+ >
305
+ <FaRedo className="w-3 h-3" />
306
+ <span className="text-xs">{language === 'fa' ? 'انجام مجدد' : 'Redo'}</span>
307
+ </button>
308
+ <button
309
+ onClick={resetFilters}
310
+ disabled={!selectedFile}
311
+ 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"
312
+ >
313
+ <FaEraser className="w-3 h-3" />
314
+ <span className="text-xs">{language === 'fa' ? 'بازنشانی' : 'Reset'}</span>
315
+ </button>
316
+ </div>
317
+ </div>
318
+
319
+ {/* Filters (for images) */}
320
+ {selectedFile && selectedFile.type.startsWith('image/') && (
321
+ <div className="space-y-3">
322
+ <h3 className="text-sm font-medium">
323
+ {language === 'fa' ? 'فیلترها' : 'Filters'}
324
+ </h3>
325
+
326
+ <div className="space-y-3">
327
+ <div>
328
+ <label className="text-xs text-gray-400 flex items-center justify-between mb-1">
329
+ <span>{language === 'fa' ? 'روشنایی' : 'Brightness'}</span>
330
+ <span>{filters.brightness}%</span>
331
+ </label>
332
+ <input
333
+ type="range"
334
+ min="0"
335
+ max="200"
336
+ value={filters.brightness}
337
+ onChange={(e) => applyFilter('brightness', e.target.value)}
338
+ className="w-full"
339
+ />
340
+ </div>
341
+
342
+ <div>
343
+ <label className="text-xs text-gray-400 flex items-center justify-between mb-1">
344
+ <span>{language === 'fa' ? 'کنتراست' : 'Contrast'}</span>
345
+ <span>{filters.contrast}%</span>
346
+ </label>
347
+ <input
348
+ type="range"
349
+ min="0"
350
+ max="200"
351
+ value={filters.contrast}
352
+ onChange={(e) => applyFilter('contrast', e.target.value)}
353
+ className="w-full"
354
+ />
355
+ </div>
356
+
357
+ <div>
358
+ <label className="text-xs text-gray-400 flex items-center justify-between mb-1">
359
+ <span>{language === 'fa' ? 'اشباع' : 'Saturation'}</span>
360
+ <span>{filters.saturation}%</span>
361
+ </label>
362
+ <input
363
+ type="range"
364
+ min="0"
365
+ max="200"
366
+ value={filters.saturation}
367
+ onChange={(e) => applyFilter('saturation', e.target.value)}
368
+ className="w-full"
369
+ />
370
+ </div>
371
+
372
+ <div>
373
+ <label className="text-xs text-gray-400 flex items-center justify-between mb-1">
374
+ <span>{language === 'fa' ? 'تاری' : 'Blur'}</span>
375
+ <span>{filters.blur}px</span>
376
+ </label>
377
+ <input
378
+ type="range"
379
+ min="0"
380
+ max="10"
381
+ value={filters.blur}
382
+ onChange={(e) => applyFilter('blur', e.target.value)}
383
+ className="w-full"
384
+ />
385
+ </div>
386
+
387
+ <div>
388
+ <label className="text-xs text-gray-400 flex items-center justify-between mb-1">
389
+ <span>{language === 'fa'