Video Thumbnail Generator
Online Snap Video Thumb
2024-10-26 19:48:46 - Coderja
index.php
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Online Video Thumbnail Generator</title> <meta name="description" content="Online tool to create snapshots of videos (local file or online by URL)"> <link rel="stylesheet" href="css/style.css" > <style> </style> <!-- Global site tag (gtag.js) - Google Analytics --> <script async src="https://www.googletagmanager.com/gtag/js?id=G-RKLKKLZFDQ"></script> <script> window.dataLayer = window.dataLayer || []; function gtag() { dataLayer.push(arguments); } gtag('js', new Date()); gtag('config', 'G-RKLKKLZFDQ'); </script> <link href="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.css" rel="stylesheet"> <script src="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.js"></script> <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"> <script> function showInput(id, opt) { console.log(opt) document.getElementById('select-file').style.display = 'none'; document.getElementById('select-url').style.display = 'none'; document.getElementById(id).style.display = ''; } function loadPage() { var textFields = document.querySelectorAll('.mdc-text-field'); for (let index = 0; index < textFields.length; index++) { var element = textFields[index]; new mdc.textField.MDCTextField(element); } } window.addEventListener("load", loadPage); </script> </head> <body> <header class="mdc-top-app-bar"> <div class="mdc-top-app-bar__row"> <section class="mdc-top-app-bar__section mdc-top-app-bar__section--align-start"> <!--<button class="material-icons mdc-top-app-bar__navigation-icon mdc-icon-button" aria-label="Open navigation menu">menu</button>--> <span class="mdc-top-app-bar__title">Online Video Thumbnail Generator</span> </section> <!-- <section class="mdc-top-app-bar__section mdc-top-app-bar__section--align-end" role="toolbar"> <a class="toolbar-menu" target="_blank" href="https://forms.gle/AkvW2rRQsTfobxFE8" title="Feedback and feature requests">Feedback</a> <button onclick="document.location='https://github.com/fraigo/online-video-thumbnail-generator'" class="material-icons mdc-top-app-bar__action-item mdc-icon-button" aria-label="Options"> <img src="https://github.com/fluidicon.png" width="48"></button> </section> --> </div> </header> <main class="mdc-top-app-bar--fixed-adjust"> <div class="video_data"> <div class="mdc-card mdc-card--outlined"> <div> <div class="mdc-form-field" > <div class="mdc-radio"> <input class="mdc-radio__native-control" type="radio" onclick="showInput('select-url', this)" id="video_url" name="video_source"> <div class="mdc-radio__background"> <div class="mdc-radio__outer-circle"></div> <div class="mdc-radio__inner-circle"></div> </div> <div class="mdc-radio__ripple"></div> </div> <label for="video_url">Video from URL</label> </div> <div class="mdc-form-field" > <div class="mdc-radio"> <input class="mdc-radio__native-control" type="radio" onclick="showInput('select-file', this)" id="video_file" checked name="video_source"> <div class="mdc-radio__background"> <div class="mdc-radio__outer-circle"></div> <div class="mdc-radio__inner-circle"></div> </div> <div class="mdc-radio__ripple"></div> </div> <label for="video_file">Video from file</label> </div> </div> <div class="instructions mdc-form-field">(Currently, youtube/vimeo video URLS are not supported, only URLs pointing to a video resource)</div> </div> <br /> <div class="mdc-card mdc-card--outlined select-source"> <div id="select-file"> <input type="file" class="mdc-text-field__input" id="videofile" accept=".mp4" onchange="loadVideoFile()"> </div> <div id="select-url" style="display:none"> <label class="mdc-text-field mdc-text-field--filled"> <span class="mdc-text-field__ripple"></span> <span class="mdc-floating-label" id="url-label">Video URL</span> <input id="videourl" size="100" class="mdc-text-field__input" type="text" placeholder="https://" aria-labelledby="url-label"> <span class="mdc-line-ripple"></span> </label> <div class="mdc-touch-target-wrapper"> <button class="mdc-button mdc-button--raised" onclick="loadVideoURL(document.getElementById('videourl').value)"> <span class="mdc-button__ripple"></span> <span class="mdc-button__touch"></span> <span class="mdc-button__label">Load video</span> </button> </div> <div class="instructions mdc-form-field"> For youtube URLs, you can use a service to convert Youtube videos to MP4 files (Google for "Youtube mp4"). <ul> <li>Use the provided "Download link" URL here, or </li> <li>Download the file to your device and select "Video from file"</li> </ul> </div> </div> </div> <div class="mdc-card mdc-card--outlined video-preview"> <video id="video" controls width="640"> <source src="blank.mp4" type="video/mp4"> </video> <div id="videoInfo" class="mdc-card"> </div> <div>Select the frame to capture using video controls.</div> <div id="videoControls" class="video-controls" style="display:none"> <div> <input id="slider" oninput="goToTime(video,this.value)" onmouseover="this.title=this.value+'seg'" type="range" min="0" max="100" value="0" name="position"> </div> <div class="button-container"> <button category="controls" onclick="goToTime(video,0)">⏮<br><span> </span></button> <button category="controls" onclick="goToTime(video,video.currentTime-60)">⏪<br><span>-1m</span></button> <button category="controls" onclick="goToTime(video,video.currentTime-5)">⏪<br><span>-5s</span></button> <button category="controls" onclick="goToTime(video,video.currentTime-1)">⏪<br><span>-1s</span></button> <button category="controls" onclick="goToTime(video,video.currentTime-1/25)">⏪<br><span>-1fr</span></button> <button category="controls" onclick="video.paused?video.play():video.pause()">⏯<br><span> </span></button> <button category="controls" onclick="goToTime(video,video.currentTime+1/25)">⏩<br><span>+1fr</span></button> <button category="controls" onclick="goToTime(video,video.currentTime+1)">⏩<br><span>+1s</span></button> <button category="controls" onclick="goToTime(video,video.currentTime+5)">⏩<br><span>+5s</span></button> <button category="controls" onclick="goToTime(video,video.currentTime+60)">⏩<br><span>+1m</span></button> <button category="controls" onclick="goToTime(video,video.duration)">⏭<br><span> </span></button> </div> </div> </div> </div> <div class="video_output"> <div class="mdc-card mdc-card--outlined select-source"> <div> <label class="mdc-text-field mdc-text-field--filled" id="videowidth"> <span class="mdc-text-field__ripple"></span> <span class="mdc-floating-label" id="size-label">Image width</span> <input id="videow" type=number size="5" onchange="resize()" value="640" class="mdc-text-field__input" type="text" placeholder="" aria-labelledby="size-label" readonly> <span class="mdc-line-ripple"></span> </label> <button id="snap" class="mdc-button mdc-button--raised snap_photo_btn" onclick="snapPicture()" disabled> <span class="mdc-button__ripple"></span> <span class="mdc-button__touch"></span> <span class="mdc-button__label">Snap photo</span> <span></span> </button> </div> <div class="canvas-container"> <canvas id="canvas" width="640" height="480"></canvas> <div> <button id="save" class="mdc-button mdc-button--raised save_btn" onclick="savePicture(this)" disabled> <span class="mdc-button__ripple"></span> <span class="mdc-button__touch"></span> <span class="mdc-button__label save_txt">Save Image</span> <span></span> </button> <span id="snapsize"></span> </div> </div> <a href="" id="imagelink" style="display:none">Image link</a> </div> </div> </main> <!-- start the script ... within that declare variables as follows... --> <script src="js/app.js?20230605"></script> </body> </html>app.js
var video = document.querySelector('#video'); var canvas = document.querySelector('#canvas'); var file = document.querySelector('#videofile'); var videoControls = document.querySelector('#videoControls'); var videow = document.querySelector('#videow'); var snap = document.querySelector('#snap'); var save = document.querySelector('#save'); var videoInfo = document.querySelector('#videoInfo'); var snapSize = document.querySelector('#snapsize'); var context = canvas.getContext('2d'); var slider = document.querySelector('#slider'); var w, h, ratio; //add loadedmetadata which will helps to identify video attributes function timeUpdate() { slider.setAttribute('max', Math.ceil(video.duration)) slider.value=video.currentTime videoInfo.style.display='block'; videoInfo.innerHTML = [ "Video size: " + video.videoWidth + "x" + video.videoHeight, "Video length: " + (Math.round(video.duration * 10) / 10) + "sec", "Playback position: " + (Math.round(video.currentTime * 10) / 10) + "sec", ].join('<br>'); } function goToTime(video,time){ video.currentTime=Math.min(video.duration,Math.max(0,time)); timeUpdate() } video.addEventListener('timeupdate', timeUpdate) video.addEventListener('loadedmetadata', function () { console.log("Metadata loaded"); videow.value = video.videoWidth; videoInfo.innerHTML = [ "Video size: " + video.videoWidth + "x" + video.videoHeight, "Video length: " + (Math.round(video.duration * 10) / 10) + "sec", ].join('<br>'); video.objectURL = false; video.play(); video.pause(); resize(); }, false); function resize() { ratio = video.videoWidth / video.videoHeight; w = videow.value; h = parseInt(w / ratio, 10); canvas.width = w; canvas.height = h; } function snapPicture() { context.fillRect(0, 0, w, h); context.drawImage(video, 0, 0, w, h); snapSize.innerHTML = w + "x" + h; } function selectVideo() { file.click(); } function loadVideoFile() { var fileInput = file.files[0]; if (fileInput) { console.log("Loading..."); console.log(fileInput); /* var reader = new FileReader(); reader.addEventListener("error", function () { console.log("Error loading video data"); }); reader.addEventListener('progress',function(ev){ console.log("progress", ev.loaded, ev.total, Math.round(ev.loaded*100.0/ev.total)); }); reader.addEventListener("load", function () { console.log("Video data loaded"); video.preload="metadata"; video.src = reader.result; }, false); reader.readAsDataURL(fileInput); */ if (video.objectURL && video.src) { URL.revokeObjectURL(video.src); } video.pleload = "metadata"; video.objectURL = true; video.src = URL.createObjectURL(fileInput); videow.removeAttribute("readonly"); snap.disabled = false; save.disabled = false; videoControls.style.display = ''; } } function loadVideoFromFile(file) { let reader = new FileReader(); reader.readAsArrayBuffer(file); reader.onload = function (e) { // The file reader gives us an ArrayBuffer: let buffer = e.target.result; // We have to convert the buffer to a blob: let videoBlob = new Blob([new Uint8Array(buffer)], { type: 'video/mp4' }); // The blob gives us a URL to the video file: let url = window.URL.createObjectURL(videoBlob); video.src = url; } } function loadVideoURL(url) { video.preload = "metadata"; video.src = url; videow.removeAttribute("readonly"); snap.disabled = false; save.disabled = false; } function savePicture(btn) { btn.disabled = true var dataURL = canvas.toDataURL(); var link = document.getElementById("imagelink"); link.style.display = ''; link.style.opacity = 0 link.href = dataURL; var rnd = Math.round((Math.random() * 10000)); link.setAttribute("download", "bacolchina.com-" + rnd + ".png"); link.click(); setTimeout(function(){ btn.disabled = false link.style.display = 'none'; },100) } window.addEventListener("load", function () { var buttons = document.querySelectorAll('button'); for (let index = 0; index < buttons.length; index++) { var element = buttons[index]; element.addEventListener('click', function () { var name = this.innerText.trim(); var category = "button"; if (this.getAttribute('category') == 'controls') { name = 'Video Controls'; category = "controls"; } var id = name.toLowerCase().replace(' ', '_'); gtag("event", category + "-" + id, {}); }) } }) style.css ```css /* Updated CSS for a more beautiful look */ body { padding: 0; margin: 0; font-family: Arial, sans-serif; } main { display: flex; flex-wrap: wrap; justify-content: center; } canvas { border: 1px solid #ddd; max-width: 45vw; max-height: 90vh; margin-bottom: 15px; border-radius: 8px; } .mdc-top-app-bar__row { height: 50px !important; } .mdc-top-app-bar--fixed-adjust { padding-top: 50px !important; } video { max-width: 45vw; max-height: 90vh; margin: 10px auto; display: block; border-radius: 8px; } #videourl { max-width: 82%; } #videoInfo { background-color: #f5f5f5; display: none; padding: 10px; border-radius: 8px; } main>div { margin: 20px 12px; } .select-source { padding: 12px; flex-wrap: wrap; } .video-preview { text-align: center; } .instructions { padding: 12px; flex-wrap: wrap; } .toolbar-menu { color: #fff !important; padding-right: 12px; } .video_data { width: 50%; } .video_output { display: flex; flex: 1; } .video_output > .mdc-card { width: 100%; border-radius: 8px; } .instructions ul { margin-bottom: 0; } .mdc-touch-target-wrapper { position: absolute; top: 22px; right: 12px; } .mdc-text-field--filled { background-color: #fff !important; } .mdc-text-field--filled:active { background-color: #fff !important; } .snap_photo_btn { margin-bottom: 7px; margin-left: 15px; } .buttons { display: flex; } .buttons h4 { padding-right: 15px; font-family: Roboto; font-weight: normal; color: rgba(0, 0, 0, 0.87); } .space { width: 20px; } .mdc-card { font-family: Roboto, sans-serif; padding: 8px; margin: 4px 0; border-radius: 8px; } .snap_button { background: #6200ee; border: 0; border-radius: 4px; padding: 0 15px 2px; font-size: inherit; color: #fff; cursor: pointer; box-shadow: 0px 5px 3px -3px rgba(0, 0, 0, 0.50); height: 37px; margin: 10px 0; transition: background 0.3s; } .snap_button:hover { background: #8731ff; } .snap_button:active { background: #c49dff; } .canvas-container { margin-top: 16px; text-align: center; } #videoControls > div { margin: 8px 0; } #videoControls .button-container { text-align: center; } #videoControls button { font-size: 2em; border-radius: 5px; background-color: #eee; border: 1px solid #aaa; padding: 8px 16px; } #slider { width: 100%; } #videoControls button > span { font-size: 16px; } @media (max-width: 1166px) { #videoControls button { font-size: 24px; } } @media (max-width: 1023px) { main { flex-wrap: wrap; } .video_data { width: 100%; } .video_output { width: 100%; } video { max-width: 90vw; } canvas { max-width: 90vw; } } @media (max-width: 600px) { #videoControls button { font-size: 20px; } } @media (max-width: 480px) { #snap { width: 100px; } #videoControls > div { display: flex; justify-content: space-between; } #videoControls button { font-size: 18px; padding: 0 4px; } #videoControls button > span { display: block; text-align: center; } }