zdgtl 10 months ago

Video Thumbnail Generator

Online Snap Video Thumb

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>
        &nbsp;
        <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>&nbsp;</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>&nbsp;</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>&nbsp;</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;
    }
}
0
263

Host Live Checker

1743468932.jpg
zdgtl
9 months ago

PMB Checker

1743468932.jpg
zdgtl
3 months ago
Mass Copy Files

Mass Copy Files

1743468932.jpg
zdgtl
10 months ago
Mass Users Password Reset for wordpress

Mass Users Password Reset for wordpress

1743468932.jpg
zdgtl
10 months ago
Logs Cleaner

Logs Cleaner

1743468932.jpg
zdgtl
9 months ago