import React, { useState, useRef, useEffect } from 'react'; import { Upload, Scissors, Play, Pause, Download, Clock, FileVideo, AlertCircle, Terminal, CheckCircle2 } from 'lucide-react'; const formatTime = (seconds) => { const date = new Date(0); date.setSeconds(seconds); return date.toISOString().substr(11, 8); }; export default function App() { const [videoFile, setVideoFile] = useState(null); const [videoUrl, setVideoUrl] = useState(null); const [duration, setDuration] = useState(0); const [splitDuration, setSplitDuration] = useState(1); // em minutos const [segments, setSegments] = useState([]); const [isPlaying, setIsPlaying] = useState(false); const [currentTime, setCurrentTime] = useState(0); const [activeSegmentIndex, setActiveSegmentIndex] = useState(null); const [processingSegment, setProcessingSegment] = useState(null); const [showTerminal, setShowTerminal] = useState(false); const videoRef = useRef(null); const mediaRecorderRef = useRef(null); // Manipular upload de arquivo const handleFileChange = (event) => { const file = event.target.files[0]; if (file) { const url = URL.createObjectURL(file); setVideoFile(file); setVideoUrl(url); setSegments([]); setActiveSegmentIndex(null); } }; // Carregar metadados do vídeo const handleLoadedMetadata = () => { if (videoRef.current) { setDuration(videoRef.current.duration); calculateSegments(videoRef.current.duration, splitDuration); } }; // Calcular segmentos quando a duração ou o tempo de corte muda const calculateSegments = (totalDuration, minutes) => { if (!totalDuration || !minutes) return; const secondsPerSegment = minutes * 60; const numberOfSegments = Math.ceil(totalDuration / secondsPerSegment); const newSegments = []; for (let i = 0; i < numberOfSegments; i++) { const start = i * secondsPerSegment; const end = Math.min((i + 1) * secondsPerSegment, totalDuration); newSegments.push({ id: i, label: `Parte ${i + 1}`, start, end, duration: end - start }); } setSegments(newSegments); }; useEffect(() => { calculateSegments(duration, splitDuration); }, [splitDuration, duration]); // Atualizar tempo atual const handleTimeUpdate = () => { if (videoRef.current) { const current = videoRef.current.currentTime; setCurrentTime(current); // Lógica para parar ao fim do segmento se estivermos reproduzindo apenas um segmento if (activeSegmentIndex !== null) { const segment = segments[activeSegmentIndex]; if (current >= segment.end) { videoRef.current.pause(); setIsPlaying(false); // Opcional: resetar para o início do segmento ou sair do modo segmento } } } }; // Reproduzir um segmento específico const playSegment = (index) => { if (videoRef.current) { const segment = segments[index]; videoRef.current.currentTime = segment.start; setActiveSegmentIndex(index); videoRef.current.play(); setIsPlaying(true); } }; // Função para exportar (gravar) um segmento const exportSegment = async (segment) => { if (!videoRef.current) return; setProcessingSegment(segment.id); const video = videoRef.current; // Setup para gravação video.currentTime = segment.start; // Esperar o seek await new Promise(r => setTimeout(r, 500)); try { // Tenta capturar o stream do elemento de vídeo // Nota: captureStream é experimental em alguns navegadores, mas funciona bem no Chrome/FF const stream = video.captureStream ? video.captureStream() : video.mozCaptureStream(); const mimeType = MediaRecorder.isTypeSupported('video/webm;codecs=vp9') ? 'video/webm;codecs=vp9' : 'video/webm'; const mediaRecorder = new MediaRecorder(stream, { mimeType }); const chunks = []; mediaRecorder.ondataavailable = (e) => { if (e.data.size > 0) chunks.push(e.data); }; mediaRecorder.onstop = () => { const blob = new Blob(chunks, { type: mimeType }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${videoFile.name.split('.')[0]}_parte_${segment.id + 1}.webm`; a.click(); URL.revokeObjectURL(url); setProcessingSegment(null); setActiveSegmentIndex(null); }; mediaRecorder.start(); video.play(); // Monitorar o tempo para parar a gravação const checkTime = () => { if (video.currentTime >= segment.end) { mediaRecorder.stop(); video.pause(); video.removeEventListener('timeupdate', checkTime); } }; video.addEventListener('timeupdate', checkTime); } catch (err) { console.error("Erro ao gravar:", err); alert("Seu navegador não suporta a captura direta de vídeo (MediaRecorder API)."); setProcessingSegment(null); } }; // Gerar comando FFmpeg const generateFFmpegCommand = () => { if (!videoFile) return ""; let cmd = `# Execute isso no seu terminal para cortar sem re-codificar (super rápido):\n\n`; segments.forEach(seg => { const start = new Date(seg.start * 1000).toISOString().substr(11, 8); const duration = seg.duration.toFixed(2); cmd += `ffmpeg -i "${videoFile.name}" -ss ${start} -t ${duration} -c copy "${videoFile.name.split('.')[0]}_part${seg.id + 1}.mp4"\n`; }); return cmd; }; return (
{/* Header */}

SplitMaster

Processamento 100% local e privado
{/* Coluna da Esquerda: Upload e Player */}
{/* Área de Upload ou Player */} {!videoUrl ? (

Arraste seu vídeo aqui

MP4, MOV, MKV ou WebM

) : (
{/* Botão de Trocar Vídeo */}

{videoFile.name}

{formatTime(duration)} de duração

)}
{/* Coluna da Direita: Configuração e Lista */}
{/* Configuração de Divisão */}

Configuração

setSplitDuration(parseFloat(e.target.value))} className="flex-1 h-2 bg-slate-700 rounded-lg appearance-none cursor-pointer accent-indigo-500" /> {splitDuration}m

O vídeo será dividido logicamente em {segments.length} partes.

{/* Lista de Segmentos */}

Segmentos

{segments.length === 0 ? (
Carregue um vídeo para ver os segmentos.
) : ( segments.map((segment) => (
{segment.id + 1}

{segment.label}

{formatTime(segment.start)} - {formatTime(segment.end)}

)) )}
{/* Área Avançada: Comandos FFmpeg */}
{showTerminal && segments.length > 0 && (
{generateFFmpegCommand()}

Copie e cole este código no seu terminal para cortar o vídeo original instantaneamente sem re-codificar.

)}
); }