think outside of box. wp-client-trimmer

video encoding…. hack 😛

Since you are on shared hosting and want a 100% free solution, you cannot use the server (PHP) to process the video.

The only way to do this for free on shared hosting is to trim the video in the user’s browser (Client-Side) using JavaScript before the file is sent to your server.

Here is how this works:

  1. The user selects a video.
  2. A hidden video player plays the first 5 seconds at high speed.
  3. A “MediaRecorder” captures that 5-second stream.
  4. The browser uploads only that 5-second clip to your WordPress site.

The Plugin Code
Create a folder wp-client-trimmer and a file inside it named wp-client-trimmer.php.

<?php
/**
 * Plugin Name: High-Quality Video Trimmer (5s)
 * Description: Trims video to 5s at original resolution and high bitrate. Use shortcode: [video_trimmer]
 */

if (!defined('ABSPATH')) exit;

add_shortcode('video_trimmer', 'vqt_interface');

function vqt_interface() {
    ob_start(); ?>
    <style>
        #vqt-box {
            border: 2px solid #2271b1;
            padding: 30px;
            text-align: center;
            background: #fff;
            border-radius: 12px;
            max-width: 600px;
            margin: 20px auto;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            box-shadow: 0 10px 25px rgba(0,0,0,0.05);
        }
        #vqt-preview-container video {
            width: 100%;
            max-height: 400px;
            border-radius: 8px;
            margin-top: 20px;
            background: #000;
            display: block;
        }
        .vqt-btn {
            display: inline-block;
            margin-top: 20px;
            padding: 12px 24px;
            background: #2271b1;
            color: #fff !important;
            text-decoration: none;
            border-radius: 6px;
            font-weight: 600;
            transition: background 0.2s;
        }
        .vqt-btn:hover { background: #135e96; }
        #vqt-loading { display: none; margin-top: 15px; }
        .spinner {
            border: 4px solid #f3f3f3;
            border-top: 4px solid #2271b1;
            border-radius: 50%;
            width: 30px;
            height: 30px;
            animation: spin 1s linear infinite;
            margin: 10px auto;
        }
        @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
        canvas#vqt-canvas { display: none; }
    </style>

    <div id="vqt-box">
        <div id="vqt-upload-ui">
            <h3 style="margin-top:0;">High Quality Trimmer</h3>
            <p style="color:#666;">Video will stay original size, cut to 5 seconds.</p>
            <input type="file" id="vqt-input" accept="video/*">
            <div id="vqt-loading">
                <div class="spinner"></div>
                <p id="vqt-status-text">Processing High Quality Video...</p>
            </div>
        </div>

        <div id="vqt-result-ui" style="display:none;">
            <h3 style="color: #00a32a;">Processing Complete!</h3>
            <div id="vqt-preview-container"></div>
            <a id="vqt-download-link" href="#" class="vqt-btn" download="trimmed-high-quality.mp4">Download 5s Version</a>
            <br>
            <button onclick="location.reload()" style="margin-top:20px; background:none; border:none; color:#2271b1; cursor:pointer; text-decoration:underline; font-size:14px;">Upload Another Video</button>
        </div>
    </div>

    <canvas id="vqt-canvas"></canvas>

    <script>
    document.getElementById('vqt-input').addEventListener('change', async function(e) {
        const file = e.target.files[0];
        if (!file) return;

        const statusText = document.getElementById('vqt-status-text');
        document.getElementById('vqt-loading').style.display = 'block';
        document.getElementById('vqt-input').style.display = 'none';

        const video = document.createElement('video');
        video.src = URL.createObjectURL(file);
        video.muted = true;
        video.playsInline = true;
        
        await video.play();

        const canvas = document.getElementById('vqt-canvas');
        const ctx = canvas.getContext('2d', { alpha: false }); // High performance context
        
        // SET ORIGINAL DIMENSIONS
        canvas.width = video.videoWidth;
        canvas.height = video.videoHeight;

        const drawFrame = () => {
            if (!video.paused && !video.ended) {
                ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
                requestAnimationFrame(drawFrame);
            }
        };
        drawFrame();

        // Capture stream at 30fps
        const stream = canvas.captureStream(30); 
        
        // Detect Best Format & Force High Bitrate (10Mbps for quality)
        let mimeType = 'video/webm;codecs=vp9';
        if (!MediaRecorder.isTypeSupported(mimeType)) {
            mimeType = MediaRecorder.isTypeSupported('video/mp4') ? 'video/mp4' : 'video/webm';
        }

        const options = {
            mimeType: mimeType,
            videoBitsPerSecond: 10000000 // 10 Mbps (Very High Quality)
        };

        const recorder = new MediaRecorder(stream, options);
        const chunks = [];

        recorder.ondataavailable = e => chunks.push(e.data);
        recorder.onstop = async () => {
            const blob = new Blob(chunks, { type: mimeType });
            statusText.innerText = "Saving to Media Library...";

            const extension = mimeType.includes('mp4') ? 'mp4' : 'webm';
            const formData = new FormData();
            formData.append('action', 'vqt_upload_action');
            formData.append('video_data', blob, `trimmed-${Date.now()}.${extension}`);
            formData.append('nonce', '<?php echo wp_create_nonce('vqt_nonce'); ?>');

            fetch('<?php echo admin_url('admin-ajax.php'); ?>', {
                method: 'POST',
                body: formData
            })
            .then(res => res.json())
            .then(response => {
                if(response.success) {
                    const videoHtml = `
                        <video autoplay loop controls playsinline>
                            <source src="${response.data.url}" type="${mimeType}">
                        </video>`;
                    document.getElementById('vqt-preview-container').innerHTML = videoHtml;
                    document.getElementById('vqt-download-link').href = response.data.url;
                    
                    document.getElementById('vqt-upload-ui').style.display = 'none';
                    document.getElementById('vqt-result-ui').style.display = 'block';
                } else {
                    alert("Error: " + response.data);
                    location.reload();
                }
            });
        };

        recorder.start();
        setTimeout(() => {
            recorder.stop();
            video.pause();
            // Clean up memory
            URL.revokeObjectURL(video.src);
        }, 5000); 
    });
    </script>
    <?php
    return ob_get_clean();
}

add_action('wp_ajax_vqt_upload_action', 'vqt_handle_upload');
add_action('wp_ajax_nopriv_vqt_upload_action', 'vqt_handle_upload');

function vqt_handle_upload() {
    check_ajax_referer('vqt_nonce', 'nonce');
    if (empty($_FILES['video_data'])) wp_send_json_error('Upload failed.');

    require_once(ABSPATH . 'wp-admin/includes/file.php');
    require_once(ABSPATH . 'wp-admin/includes/image.php');
    require_once(ABSPATH . 'wp-admin/includes/media.php');

    add_filter('upload_mimes', function($mimes) {
        $mimes['webm'] = 'video/webm';
        $mimes['mp4'] = 'video/mp4';
        return $mimes;
    });

    $attachment_id = media_handle_upload('video_data', 0);
    if (is_wp_error($attachment_id)) wp_send_json_error($attachment_id->get_error_message());

    wp_send_json_success(['url' => wp_get_attachment_url($attachment_id)]);
}

updated code why!!!??

What changed?

  1. Canvas Capture: Instead of video.captureStream() (which failed), we now use canvas.captureStream(). This is the industry-standard way to record video in the browser for Safari compatibility.
  2. MP4/WebM Detection: Safari prefers MP4, Chrome prefers WebM. The code now detects which one your browser likes best and uses it.
  3. UI Updates: After the 5-second process finishes, the “Done” message appears, the video autoplays in the same spot, and a Download Link is provided immediately.
  4. Security: Added playsinline and muted to ensure the browser allows the auto-processing to start without user interaction.

update again why???

Key Technical Details for You:

  1. Resolution: canvas.width = video.videoWidth; This ensures if the user uploads a 1080p video, the output is 1080p. If they upload 4K, the output is 4K.
  2. Bitrate: videoBitsPerSecond: 10000000 (10 Megabits). Standard web video is usually 2-5 Mbps. Setting it to 10 Mbps ensures the 5-second clip looks almost identical to the original.
  3. Performance: I added { alpha: false } to the canvas context and requestAnimationFrame. This tells the browser to focus entirely on video frames to prevent “stuttering” during recording.
  4. Compatibility: It checks for VP9 (the highest quality free codec) first. If the browser is Safari, it falls back to MP4.
  5. User Experience: As soon as the upload finishes, the video appears in the exact same box, starts playing immediately (autoplay), and a clear “Download” button appears.