<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>JS Video Trimmer</title><linkrel="stylesheet"href="https://cdnjs.cloudflare.com/ajax/libs/noUiSlider/15.7.1/nouislider.min.css"><style>body{font-family:sans-serif;padding:20px;max-width:800px;margin:0auto;}video{width:100%;margin-bottom:20px;border-radius:8px;background:#000;}/* The Slider Container */#slider-container{margin:010px30px10px;}/* Visual Styles */.controls{display:flex;gap:10px;align-items:center;}.time-display{font-family:monospace;font-weight:bold;}#download-link{display:none;color:white;background:#007bff;padding:10px20px;text-decoration:none;border-radius:4px;}button{padding:10px20px;cursor:pointer;}/* Add this simple CSS to make them look nice */.controls-wrapper{display:flex;gap:20px;margin-bottom:20px;align-items:center;justify-content:center;}.input-group{display:flex;flex-direction:column;}.input-groupinput{width:80px;padding:5px;font-size:16px;font-family:monospace;}</style></head><body><inputtype="file"id="upload"accept="video/*"><br><br><videoid="video-element"controlsplaysinline></video><divid="slider-container"></div><divclass="controls-wrapper"><divclass="input-group"><label>Start (sec)</label><inputtype="number"id="val-start"step="0.01"min="0"></div><buttonid="trim-btn"disabled>Trim Video</button><divclass="input-group"><label>End (sec)</label><inputtype="number"id="val-end"step="0.01"min="0"></div></div><aid="download-link"download="trimmed.mp4"style="display:none; margin-top: 10px; text-align: center;">Download Trimmed Video</a><pid="message"></p><script src="https://cdnjs.cloudflare.com/ajax/libs/noUiSlider/15.7.1/nouislider.min.js"></script><script src="https://unpkg.com/coi-serviceworker@0.1.7/coi-serviceworker.min.js"></script><script src="https://unpkg.com/@ffmpeg/ffmpeg@0.11.6/dist/ffmpeg.min.js"></script><script src="app.js"></script><script>/**
* VIDEO TRIMMER ENGINE (HTML5 + FFmpeg.wasm)
*/const{createFFmpeg,fetchFile}=FFmpeg;constffmpeg=createFFmpeg({log:true});// UI Element Selectorsconstvideo=document.getElementById('video-element');constupload=document.getElementById('upload');constslider=document.getElementById('slider-container');consttrimBtn=document.getElementById('trim-btn');constmessage=document.getElementById('message');constvalStart=document.getElementById('val-start');constvalEnd=document.getElementById('val-end');constdownloadLink=document.getElementById('download-link');letfileData=null;/**
* 1. INITIALIZE FFMPEG
*/constinitFFmpeg=async()=>{try{message.innerText='Loading FFmpeg Engine...';awaitffmpeg.load();message.innerText='Engine Ready.';trimBtn.disabled=false;}catch(err){message.innerText='Error loading FFmpeg. Check console.';console.error(err);}};initFFmpeg();/**
* 2. INITIALIZE SLIDER (noUiSlider)
*/noUiSlider.create(slider,{start:[0,10],// Initial dummy valuesconnect:true,range:{'min':0,'max':10},step:0.01,tooltips:false});slider.setAttribute('disabled',true);// Disable until video loads/**
* 3. FILE UPLOAD HANDLER
*/upload.addEventListener('change',(e)=>{constfile=e.target.files[0];if(!file)return;fileData=file;consturl=URL.createObjectURL(file);video.src=url;downloadLink.style.display='none';message.innerText='Video loaded. Set your trim range.';});/**
* 4. VIDEO METADATA HANDLER
* Triggered when video duration is known
*/video.addEventListener('loadedmetadata',()=>{constduration=video.duration;// Update slider to match video lengthslider.noUiSlider.updateOptions({range:{'min':0,'max':duration},start:[0,duration]});slider.removeAttribute('disabled');valStart.value=0;valEnd.value=duration.toFixed(2);});/**
* 5. SLIDER -> UI BINDING
* When slider moves, update input boxes and video frame
*/slider.noUiSlider.on('update',(values,handle)=>{constvalue=parseFloat(values[handle]);if(handle===0){valStart.value=value.toFixed(2);}else{valEnd.value=value.toFixed(2);}});// Seek video preview when dragging sliderslider.noUiSlider.on('slide',(values,handle)=>{video.currentTime=parseFloat(values[handle]);video.pause();});/**
* 6. INPUT BOX -> SLIDER BINDING (Precision Editing)
* When user types a number, update the slider and video frame
*/[valStart,valEnd].forEach((input,handle)=>{input.addEventListener('change',function(){letnewVal=parseFloat(this.value);letcurrentValues=slider.noUiSlider.get().map(Number);constduration=video.duration;// Validation: Clamp to video durationif(newVal<0)newVal=0;if(newVal>duration)newVal=duration;// Validation: Prevent handles from crossingif(handle===0&&newVal>=currentValues[1]){newVal=currentValues[1]-0.1;}elseif(handle===1&&newVal<=currentValues[0]){newVal=currentValues[0]+0.1;}this.value=newVal.toFixed(2);// Update Slider handlesletsetArray=[null,null];setArray[handle]=newVal;slider.noUiSlider.set(setArray);// Update Video Previewvideo.currentTime=newVal;});});/**
* 7. THE TRIM ACTION (FFmpeg Execution)
*/trimBtn.addEventListener('click',async()=>{if(!fileData||!ffmpeg.isLoaded())return;trimBtn.disabled=true;message.innerText='Processing... Please wait.';conststart=valStart.value;constend=valEnd.value;constduration=(parseFloat(end)-parseFloat(start)).toFixed(2);// Write file to FFmpeg's Virtual File SystemconstinputName='input.mp4';constoutputName='output.mp4';ffmpeg.FS('writeFile',inputName,awaitfetchFile(fileData));/**
* COMMAND EXPLAINED:
* -ss : Start time (placed before -i for faster seeking)
* -i : Input file
* -t : Duration to cut
* -c copy : Stream Copy (Fastest, no re-encoding)
*/awaitffmpeg.run('-ss',start,'-i',inputName,'-t',duration,'-c','copy',outputName);// Read resulting fileconstdata=ffmpeg.FS('readFile',outputName);// Create download URLconstblob=newBlob([data.buffer],{type:'video/mp4'});consturl=URL.createObjectURL(blob);downloadLink.href=url;downloadLink.style.display='inline-block';message.innerText='Trim complete!';trimBtn.disabled=false;// Cleanup virtual FS to save memoryffmpeg.FS('unlink',inputName);ffmpeg.FS('unlink',outputName);});</script></body></html>