<!DOCTYPE html>

<html lang="id">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>Video Looper dengan Efek Smooth</title>

    <style>

        * {

            box-sizing: border-box;

            margin: 0;

            padding: 0;

        }

        

        body {

            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;

            line-height: 1.6;

            color: #333;

            background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);

            padding: 20px;

            min-height: 100vh;

        }

        

        .container {

            max-width: 1000px;

            margin: 0 auto;

            background: white;

            border-radius: 15px;

            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);

            overflow: hidden;

        }

        

        header {

            background: linear-gradient(90deg, #3498db, #2c3e50);

            color: white;

            text-align: center;

            padding: 25px 20px;

        }

        

        h1 {

            font-size: 2.2rem;

            margin-bottom: 10px;

        }

        

        .subtitle {

            font-size: 1.1rem;

            opacity: 0.9;

        }

        

        .main-content {

            padding: 25px;

        }

        

        .form-section {

            background: #f8f9fa;

            padding: 20px;

            border-radius: 10px;

            margin-bottom: 25px;

            display: grid;

            grid-template-columns: 1fr 1fr;

            gap: 20px;

        }

        

        .form-group {

            margin-bottom: 20px;

        }

        

        .full-width {

            grid-column: 1 / -1;

        }

        

        label {

            display: block;

            margin-bottom: 8px;

            font-weight: 600;

            color: #2c3e50;

        }

        

        input, select {

            width: 100%;

            padding: 12px 15px;

            border: 2px solid #ddd;

            border-radius: 8px;

            font-size: 16px;

            transition: border-color 0.3s;

        }

        

        input:focus, select:focus {

            border-color: #3498db;

            outline: none;

        }

        

        .btn {

            padding: 14px 20px;

            background: linear-gradient(90deg, #3498db, #2980b9);

            color: white;

            border: none;

            border-radius: 8px;

            font-size: 18px;

            font-weight: 600;

            cursor: pointer;

            transition: all 0.3s;

            display: inline-flex;

            align-items: center;

            justify-content: center;

        }

        

        .btn:hover {

            background: linear-gradient(90deg, #2980b9, #2573a7);

            transform: translateY(-2px);

            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);

        }

        

        .btn:disabled {

            background: #95a5a6;

            cursor: not-allowed;

            transform: none;

            box-shadow: none;

        }

        

        .btn i {

            margin-right: 8px;

        }

        

        .button-group {

            display: flex;

            gap: 15px;

            margin-top: 20px;

        }

        

        .btn-play {

            background: linear-gradient(90deg, #2ecc71, #27ae60);

        }

        

        .btn-download {

            background: linear-gradient(90deg, #e74c3c, #c0392b);

        }

        

        .progress-section {

            display: none;

            margin: 25px 0;

        }

        

        .progress-container {

            height: 20px;

            background: #ecf0f1;

            border-radius: 10px;

            overflow: hidden;

            margin-bottom: 10px;

        }

        

        .progress-bar {

            height: 100%;

            background: linear-gradient(90deg, #2ecc71, #27ae60);

            width: 0%;

            transition: width 0.3s;

        }

        

        .status {

            text-align: center;

            font-weight: 500;

            color: #2c3e50;

        }

        

        .video-section {

            display: none;

            text-align: center;

            margin: 25px 0;

        }

        

        .video-container {

            background: #000;

            border-radius: 10px;

            overflow: hidden;

            margin: 20px 0;

            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);

            position: relative;

            max-width: 100%;

            margin-left: auto;

            margin-right: auto;

        }

        

        video {

            max-width: 100%;

            display: block;

        }

        

        .download-section {

            display: none;

            margin: 25px 0;

        }

        

        .download-options {

            display: grid;

            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));

            gap: 20px;

            margin-top: 20px;

        }

        

        .download-option {

            background: white;

            border-radius: 10px;

            padding: 20px;

            text-align: center;

            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);

            transition: transform 0.3s;

        }

        

        .download-option:hover {

            transform: translateY(-5px);

        }

        

        .download-option h3 {

            color: #2c3e50;

            margin-bottom: 15px;

        }

        

        .info-box {

            background: #e3f2fd;

            border-left: 4px solid #2196f3;

            padding: 15px;

            border-radius: 4px;

            margin: 20px 0;

            text-align: left;

        }

        

        .info-title {

            font-weight: 600;

            margin-bottom: 5px;

            color: #2196f3;

        }

        

        .looping-info {

            display: flex;

            justify-content: space-around;

            margin: 20px 0;

            flex-wrap: wrap;

        }

        

        .info-card {

            background: white;

            border-radius: 8px;

            padding: 15px;

            margin: 10px;

            flex: 1;

            min-width: 200px;

            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);

            text-align: center;

        }

        

        .info-card h3 {

            color: #2c3e50;

            margin-bottom: 10px;

        }

        

        .info-card p {

            font-size: 1.5rem;

            font-weight: bold;

            color: #3498db;

        }

        

        .effect-options {

            display: grid;

            grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));

            gap: 15px;

            margin-top: 15px;

        }

        

        .effect-option {

            padding: 12px;

            border: 2px solid #ddd;

            border-radius: 8px;

            text-align: center;

            cursor: pointer;

            transition: all 0.3s;

        }

        

        .effect-option:hover {

            border-color: #3498db;

        }

        

        .effect-option.selected {

            border-color: #3498db;

            background-color: #e3f2fd;

        }

        

        footer {

            text-align: center;

            padding: 20px;

            color: #7f8c8d;

            font-size: 0.9rem;

            border-top: 1px solid #eee;

        }

        

        @media (max-width: 768px) {

            .form-section {

                grid-template-columns: 1fr;

            }

            

            .container {

                border-radius: 10px;

            }

            

            h1 {

                font-size: 1.8rem;

            }

            

            .main-content {

                padding: 15px;

            }

            

            .looping-info {

                flex-direction: column;

            }

            

            .button-group {

                flex-direction: column;

            }

        }

    </style>

    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">

</head>

<body>

    <div class="container">

        <header>

            <h1>Video Looper dengan Efek Smooth</h1>

            <p class="subtitle">Gabungkan video dengan efek transisi dan download hasilnya</p>

        </header>

        

        <div class="main-content">

            <div class="form-section">

                <div class="form-group full-width">

                    <label for="videoFile">Pilih Video (MP4):</label>

                    <input type="file" id="videoFile" accept="video/mp4">

                </div>

                

                <div class="form-group">

                    <label for="targetDuration">Durasi Looping (menit):</label>

                    <input type="number" id="targetDuration" min="1" value="60">

                </div>

                

                <div class="form-group">

                    <label for="transitionEffect">Efek Transisi:</label>

                    <select id="transitionEffect">

                        <option value="fade">Fade</option>

                        <option value="crossfade" selected>Crossfade</option>

                        <option value="blur">Blur</option>

                        <option value="slide">Slide</option>

                        <option value="none">Tidak Ada</option>

                    </select>

                </div>

                

                <div class="form-group full-width">

                    <label>Efek Tambahan:</label>

                    <div class="effect-options">

                        <div class="effect-option selected" data-effect="shadow">

                            <i class="fas fa-border-all"></i>

                            <p>Shadow</p>

                        </div>

                        <div class="effect-option" data-effect="glow">

                            <i class="fas fa-sun"></i>

                            <p>Glow</p>

                        </div>

                        <div class="effect-option" data-effect="vignette">

                            <i class="fas fa-circle"></i>

                            <p>Vignette</p>

                        </div>

                    </div>

                </div>

            </div>

            

            <div class="button-group">

                <button id="previewBtn" class="btn btn-play" disabled>

                    <i class="fas fa-play"></i> Preview Looping

                </button>

                <button id="processBtn" class="btn">

                    <i class="fas fa-cog"></i> Proses & Download

                </button>

            </div>

            

            <div class="progress-section" id="progressSection">

                <div class="progress-container">

                    <div class="progress-bar" id="progressBar"></div>

                </div>

                <div class="status" id="statusText">Mempersiapkan proses looping...</div>

            </div>

            

            <div class="looping-info" id="loopingInfo">

                <div class="info-card">

                    <h3>Durasi Asli Video</h3>

                    <p id="originalDurationText">00:00</p>

                </div>

                <div class="info-card">

                    <h3>Jumlah Loop</h3>

                    <p id="loopCount">0</p>

                </div>

                <div class="info-card">

                    <h3>Total Durasi</h3>

                    <p id="totalDuration">00:00</p>

                </div>

            </div>

            

            <div class="video-section" id="videoSection">

                <h2>Preview Video Looping</h2>

                <div class="video-container">

                    <video id="outputVideo" controls></video>

                </div>

            </div>

            

            <div class="download-section" id="downloadSection">

                <h2>Download Video Looping</h2>

                <p>Pilih kualitas untuk download:</p>

                

                <div class="download-options">

                    <div class="download-option">

                        <h3>Kualitas Rendah</h3>

                        <p>(Lebih Cepat)</p>

                        <button class="btn btn-download download-res" data-quality="low">

                            <i class="fas fa-download"></i> Download

                        </button>

                    </div>

                    <div class="download-option">

                        <h3>Kualitas Sedang</h3>

                        <p>(Rekomendasi)</p>

                        <button class="btn btn-download download-res" data-quality="medium">

                            <i class="fas fa-download"></i> Download

                        </button>

                    </div>

                    <div class="download-option">

                        <h3>Kualitas Tinggi</h3>

                        <p>(Lebih Lama)</p>

                        <button class="btn btn-download download-res" data-quality="high">

                            <i class="fas fa-download"></i> Download

                        </button>

                    </div>

                </div>

            </div>

            

            <div class="info-box">

                <div class="info-title">Cara Kerja Video Looper:</div>

                <p>• Video akan digabungkan dengan efek transisi smooth di antara loop</p>

                <p>• Layout video asli akan dipertahankan tanpa perubahan aspect ratio</p>

                <p>• Proses lebih cepat dengan teknik pemrosesan yang dioptimasi</p>

                <p>• Hasil akhir dapat didownload dalam berbagai kualitas</p>

            </div>

        </div>

        

        <footer>

            <p>Video Looper dengan Efek Smooth &copy; 2023</p>

        </footer>

    </div>


    <script>

        document.addEventListener('DOMContentLoaded', function() {

            const videoFileInput = document.getElementById('videoFile');

            const targetDurationInput = document.getElementById('targetDuration');

            const transitionEffectSelect = document.getElementById('transitionEffect');

            const previewBtn = document.getElementById('previewBtn');

            const processBtn = document.getElementById('processBtn');

            const progressSection = document.getElementById('progressSection');

            const progressBar = document.getElementById('progressBar');

            const statusText = document.getElementById('statusText');

            const videoSection = document.getElementById('videoSection');

            const outputVideo = document.getElementById('outputVideo');

            const downloadSection = document.getElementById('downloadSection');

            const originalDurationText = document.getElementById('originalDurationText');

            const loopCountElem = document.getElementById('loopCount');

            const totalDurationElem = document.getElementById('totalDuration');

            const loopingInfo = document.getElementById('loopingInfo');

            const effectOptions = document.querySelectorAll('.effect-option');

            const downloadButtons = document.querySelectorAll('.download-res');

            

            let originalVideo = null;

            let originalVideoDuration = 0;

            let originalVideoWidth = 0;

            let originalVideoHeight = 0;

            let processedVideoBlob = null;

            let selectedEffect = 'shadow';

            

            // Pilih efek

            effectOptions.forEach(option => {

                option.addEventListener('click', function() {

                    effectOptions.forEach(opt => opt.classList.remove('selected'));

                    this.classList.add('selected');

                    selectedEffect = this.dataset.effect;

                });

            });

            

            // Format waktu dari detik ke menit:detik

            function formatTime(seconds) {

                const mins = Math.floor(seconds / 60);

                const secs = Math.floor(seconds % 60);

                return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;

            }

            

            // Validasi input

            function validateInputs() {

                const isVideoSelected = videoFileInput.files.length > 0;

                previewBtn.disabled = !isVideoSelected;

            }

            

            videoFileInput.addEventListener('change', function() {

                validateInputs();

                

                if (videoFileInput.files.length > 0) {

                    const file = videoFileInput.files[0];

                    const videoUrl = URL.createObjectURL(file);

                    

                    // Buat video element untuk membaca metadata

                    const video = document.createElement('video');

                    video.src = videoUrl;

                    

                    video.onloadedmetadata = function() {

                        originalVideoDuration = video.duration;

                        originalVideoWidth = video.videoWidth;

                        originalVideoHeight = video.videoHeight;

                        originalDurationText.textContent = formatTime(originalVideoDuration);

                        loopingInfo.style.display = 'flex';

                        

                        // Hitung looping

                        calculateLooping();

                    };

                }

            });

            

            targetDurationInput.addEventListener('input', calculateLooping);

            

            function calculateLooping() {

                if (originalVideoDuration > 0) {

                    const targetDuration = parseInt(targetDurationInput.value) * 60; // konversi ke detik

                    const loopCount = Math.ceil(targetDuration / originalVideoDuration);

                    const totalDuration = loopCount * originalVideoDuration;

                    

                    loopCountElem.textContent = loopCount;

                    totalDurationElem.textContent = formatTime(totalDuration);

                }

            }

            

            // Preview video looping

            previewBtn.addEventListener('click', function() {

                if (videoFileInput.files.length === 0) return;

                

                const file = videoFileInput.files[0];

                const videoUrl = URL.createObjectURL(file);

                

                outputVideo.src = videoUrl;

                videoSection.style.display = 'block';

                

                // Atur event listener untuk looping

                outputVideo.onended = function() {

                    // Tambahkan efek transisi

                    const transitionEffect = transitionEffectSelect.value;

                    applyTransitionEffect(outputVideo, transitionEffect, function() {

                        outputVideo.currentTime = 0;

                        outputVideo.play();

                    });

                };

                

                outputVideo.play();

            });

            

            // Fungsi untuk menerapkan efek transisi

            function applyTransitionEffect(videoElement, effect, callback) {

                switch(effect) {

                    case 'fade':

                        videoElement.style.transition = 'opacity 0.5s ease-in-out';

                        videoElement.style.opacity = '0';

                        setTimeout(() => {

                            callback();

                            setTimeout(() => {

                                videoElement.style.opacity = '1';

                            }, 50);

                        }, 500);

                        break;

                    case 'crossfade':

                        videoElement.style.transition = 'opacity 0.8s ease-in-out';

                        videoElement.style.opacity = '0.5';

                        setTimeout(() => {

                            callback();

                            setTimeout(() => {

                                videoElement.style.opacity = '1';

                            }, 50);

                        }, 800);

                        break;

                    case 'blur':

                        videoElement.style.transition = 'filter 0.6s ease-in-out';

                        videoElement.style.filter = 'blur(5px)';

                        setTimeout(() => {

                            callback();

                            setTimeout(() => {

                                videoElement.style.filter = 'blur(0)';

                            }, 50);

                        }, 600);

                        break;

                    default:

                        callback();

                }

            }

            

            // Proses video untuk download

            processBtn.addEventListener('click', async function() {

                const targetDurationMinutes = parseInt(targetDurationInput.value);

                const targetDuration = targetDurationMinutes * 60; // Konversi ke detik

                const transitionEffect = transitionEffectSelect.value;

                

                // Validasi

                if (!originalVideoDuration || originalVideoDuration <= 0) {

                    statusText.textContent = 'Error: Durasi video asli tidak valid';

                    statusText.style.color = '#e74c3c';

                    return;

                }

                

                // Hitung berapa kali looping diperlukan

                const loopCount = Math.ceil(targetDuration / originalVideoDuration);

                

                // Tampilkan progress

                progressSection.style.display = 'block';

                statusText.textContent = 'Mempersiapkan proses looping...';

                statusText.style.color = '#2c3e50';

                

                try {

                    // Load video yang dipilih

                    const file = videoFileInput.files[0];

                    const videoUrl = URL.createObjectURL(file);

                    

                    // Buat video element untuk membaca video asli

                    originalVideo = document.createElement('video');

                    originalVideo.src = videoUrl;

                    originalVideo.muted = true;

                    

                    // Tunggu sampai metadata video dimuat

                    await new Promise((resolve, reject) => {

                        originalVideo.onloadedmetadata = resolve;

                        originalVideo.onerror = reject;

                        

                        // Timeout untuk error handling

                        setTimeout(() => {

                            reject(new Error('Timeout saat memuat video'));

                        }, 10000);

                    });

                    

                    // Buat canvas untuk rendering dengan ukuran yang sama dengan video asli

                    const canvas = document.createElement('canvas');

                    const ctx = canvas.getContext('2d');

                    

                    // Pertahankan ukuran asli video

                    canvas.width = originalVideoWidth;

                    canvas.height = originalVideoHeight;

                    

                    // Buat media recorder untuk merekam output

                    const stream = canvas.captureStream(30); // 30 FPS

                    const recorder = new MediaRecorder(stream, {

                        mimeType: 'video/mp4; codecs="avc1.42E01E"',

                        videoBitsPerSecond: 3000000 // Bitrate tetap untuk kualitas baik

                    });

                    

                    const chunks = [];

                    recorder.ondataavailable = e => chunks.push(e.data);

                    

                    recorder.onstop = () => {

                        processedVideoBlob = new Blob(chunks, { type: 'video/mp4' });

                        

                        // Tampilkan video hasil

                        const processedVideoUrl = URL.createObjectURL(processedVideoBlob);

                        outputVideo.src = processedVideoUrl;

                        videoSection.style.display = 'block';

                        downloadSection.style.display = 'block';

                        

                        statusText.textContent = 'Proses looping selesai! Kini Anda dapat mendownload video.';

                        statusText.style.color = '#27ae60';

                    };

                    

                    // Mulai merekam

                    recorder.start();

                    

                    // Render frame per frame

                    let currentLoop = 0;

                    let currentTime = 0;

                    let lastFrameTime = 0;

                    

                    statusText.textContent = `Memproses looping: 0/${loopCount}`;

                    

                    function renderFrame(timestamp) {

                        if (currentLoop >= loopCount) {

                            recorder.stop();

                            return;

                        }

                        

                        // Batasi frame rate untuk performa lebih baik

                        if (!lastFrameTime || timestamp - lastFrameTime >= 1000/30) {

                            lastFrameTime = timestamp;

                            

                            // Atur waktu video asli berdasarkan posisi dalam loop

                            const videoTime = currentTime % originalVideoDuration;

                            

                            // Pastikan videoTime adalah nilai finite yang valid

                            if (!isFinite(videoTime) || videoTime < 0) {

                                statusText.textContent = 'Error: Waktu video tidak valid';

                                statusText.style.color = '#e74c3c';

                                recorder.stop();

                                return;

                            }

                            

                            // Atur currentTime video

                            originalVideo.currentTime = videoTime;

                            

                            // Tunggu sampai video siap

                            originalVideo.onseeked = function() {

                                // Render frame ke canvas

                                ctx.clearRect(0, 0, canvas.width, canvas.height);

                                

                                // Terapkan efek shadow jika dipilih

                                if (selectedEffect === 'shadow') {

                                    ctx.shadowColor = 'rgba(0, 0, 0, 0.4)';

                                    ctx.shadowBlur = 10;

                                    ctx.shadowOffsetX = 0;

                                    ctx.shadowOffsetY = 0;

                                } else if (selectedEffect === 'glow') {

                                    ctx.shadowColor = 'rgba(255, 255, 255, 0.5)';

                                    ctx.shadowBlur = 15;

                                    ctx.shadowOffsetX = 0;

                                    ctx.shadowOffsetY = 0;

                                } else if (selectedEffect === 'vignette') {

                                    // Efek vignette akan ditambahkan setelah drawImage

                                }

                                

                                // Gambar video ke canvas

                                ctx.drawImage(originalVideo, 0, 0, canvas.width, canvas.height);

                                

                                // Terapkan efek vignette jika dipilih

                                if (selectedEffect === 'vignette') {

                                    const gradient = ctx.createRadialGradient(

                                        canvas.width / 2, canvas.height / 2, 0,

                                        canvas.width / 2, canvas.height / 2, Math.max(canvas.width, canvas.height) / 2

                                    );

                                    gradient.addColorStop(0, 'rgba(0,0,0,0)');

                                    gradient.addColorStop(0.7, 'rgba(0,0,0,0)');

                                    gradient.addColorStop(1, 'rgba(0,0,0,0.5)');

                                    

                                    ctx.fillStyle = gradient;

                                    ctx.fillRect(0, 0, canvas.width, canvas.height);

                                }

                                

                                // Reset shadow

                                ctx.shadowColor = 'transparent';

                                ctx.shadowBlur = 0;

                                

                                // Update progress

                                currentTime += 1/30; // 1 frame pada 30 FPS

                                

                                // Cek jika sudah mencapai akhir video dalam loop ini

                                if (videoTime + 1/30 >= originalVideoDuration) {

                                    currentLoop++;

                                    const progress = (currentLoop / loopCount) * 100;

                                    progressBar.style.width = `${progress}%`;

                                    statusText.textContent = `Memproses looping: ${currentLoop}/${loopCount}`;

                                }

                            };

                            

                            originalVideo.onerror = function() {

                                statusText.textContent = 'Error: Gagal memproses frame video';

                                statusText.style.color = '#e74c3c';

                                recorder.stop();

                            };

                        }

                        

                        // Request frame berikutnya

                        requestAnimationFrame(renderFrame);

                    }

                    

                    // Pastikan video diputar untuk menghindari issue pada beberapa browser

                    originalVideo.play().then(() => {

                        // Jeda video setelah mulai diputar

                        originalVideo.pause();

                        

                        // Mulai proses rendering

                        requestAnimationFrame(renderFrame);

                    }).catch(error => {

                        console.error('Error memutar video:', error);

                        statusText.textContent = 'Error: Tidak dapat memproses video';

                        statusText.style.color = '#e74c3c';

                    });

                    

                } catch (error) {

                    console.error('Error processing video:', error);

                    statusText.textContent = 'Error: ' + error.message;

                    statusText.style.color = '#e74c3c';

                }

            });

            

            // Download handlers

            downloadButtons.forEach(button => {

                button.addEventListener('click', function() {

                    if (!processedVideoBlob) return;

                    

                    const quality = this.dataset.quality;

                    let fileName = 'video-looping.mp4';

                    

                    // Sesuaikan kualitas berdasarkan pilihan

                    if (quality === 'low') {

                        fileName = 'video-looping-cepat.mp4';

                    } else if (quality === 'high') {

                        fileName = 'video-looping-hd.mp4';

                    }

                    

                    const a = document.createElement('a');

                    a.href = URL.createObjectURL(processedVideoBlob);

                    a.download = fileName;

                    document.body.appendChild(a);

                    a.click();

                    document.body.removeChild(a);

                });

            });

        });

    </script>

</body>

</html>