65

I'm doing a project with HTML and Javascript that will run local with local files. I need to select a file by input, get its information and then decide if I'll add to my list and reproduce or not. And if Ii decide to use it I'll have to put it on a queue to use later. Otherwise I'll just discard and select another file.

The problem that I'm facing is that I can't find a way to get the video duration just by selecting it on input.

I've searched a lot and I didn't find any method to get the duration. In this code below I tried to use 'file.duration' but it didn't work, it just returns 'undefined'.

This is my input, normal as you can see.

<div id="input-upload-file" class="box-shadow">
   <span>upload! (ღ˘⌣˘ღ)</span> <!--ignore the text face lol -->
   <input type="file" class="upload" id="fileUp" name="fileUpload" onchange="setFileInfo()">
</div>

And this is the function that I'm using to get all the information.

function setFileInfo(){
  showInfo(); //change div content
  var file = document.getElementById("fileUp").files[0];
  var pid =  1;
  var Pname = file.name;
  Pname = Pname.slice(0, Pname.indexOf(".")); //get filename without extension
  var Ptype = file.type;
  var Psize = bytesToSize(file.size); //turns into KB,MB, etc...
  var Pprior = setPriority(Ptype); //returns 1, 2 or 3 
  var Pdur = file.duration;
  var Pmem = getMemory(Psize); //returns size * (100 || 10 || 1)
  var Pown = 'user';
  /* a lot of stuff throwing this info to the HTML */
  console.log(Pdur);
}

Is there way to do this? If not, what are the alternatives that can help me?

3
  • Are you looking for something like this? jsfiddle.net/derickbailey/s4P2v This isn't mine, and it's for audio, but I can't imagine it will be very different for video Commented Mar 26, 2015 at 17:43
  • @RobinHellemans This one creates a <video> element and thats exactly what I don't want. :(
    – davis
    Commented Mar 26, 2015 at 18:31
  • Any idea how to get this work with avi files?
    – Spy
    Commented Mar 5, 2019 at 9:19

6 Answers 6

120

In modern browsers, You can use the URL API's URL.createObjectURL() with an non appended video element to load the content of your file.

var myVideos = [];

window.URL = window.URL || window.webkitURL;

document.getElementById('fileUp').onchange = setFileInfo;

function setFileInfo() {
  var files = this.files;
  myVideos.push(files[0]);
  var video = document.createElement('video');
  video.preload = 'metadata';

  video.onloadedmetadata = function() {
    window.URL.revokeObjectURL(video.src);
    var duration = video.duration;
    myVideos[myVideos.length - 1].duration = duration;
    updateInfos();
  }

  video.src = URL.createObjectURL(files[0]);;
}


function updateInfos() {
  var infos = document.getElementById('infos');
  infos.textContent = "";
  for (var i = 0; i < myVideos.length; i++) {
    infos.textContent += myVideos[i].name + " duration: " + myVideos[i].duration + '\n';
  }
}
<div id="input-upload-file" class="box-shadow">
  <span>upload! (ღ˘⌣˘ღ)</span>
  <input type="file" class="upload" id="fileUp" name="fileUpload">
</div>
<pre id="infos"></pre>

19
  • This one worked well, but when before I return the value it's ok then after I return It's undefined. Any idea of what could I do to solve this? i.e. 24.105 undefined
    – davis
    Commented Mar 26, 2015 at 19:20
  • yep, store it in a global variable, or even better, store your files in an array, then in the event handler, add to your stored file a duration attribute
    – Kaiido
    Commented Mar 26, 2015 at 19:23
  • @davis, see edit, you can then remove the files you don't want with myVideos.splice(the_index_of_the_video)
    – Kaiido
    Commented Mar 26, 2015 at 19:38
  • Oh, that helped me a lot, Thank you! Also, why can't I send this info to outside of the function? Even using getters and setters I still having problem to do it.
    – davis
    Commented Mar 26, 2015 at 20:51
  • It's because you are in the videos'sonmetadata scope, which works asynchronously. You'll have to store it in a global variable (like I did with myVideos = [];) and only call your setFileInfo() once this event has been triggered. or alternatively, you could use a setTimeout() loop to check if the property is set.
    – Kaiido
    Commented Mar 27, 2015 at 2:26
27

I needed to validate a single file before continuing to execute more code, here is my method with the help of Kaiido's answer!

onchange event when a user uploads a file:

$("input[type=file]").on("change", function(e) {

    var file = this.files[0]; // Get uploaded file

    validateFile(file) // Validate Duration

    e.target.value = ''; // Clear value to allow new uploads
})

Now validate duration:

function validateFile(file) {

    var video = document.createElement('video');
    video.preload = 'metadata';

    video.onloadedmetadata = function() {

        window.URL.revokeObjectURL(video.src);

        if (video.duration < 1) {

            console.log("Invalid Video! video is less than 1 second");
            return;
        }

        methodToCallIfValid();
    }

    video.src = URL.createObjectURL(file);
}
19

Here is async/await Promise version:

const loadVideo = file => new Promise((resolve, reject) => {
    try {
        let video = document.createElement('video')
        video.preload = 'metadata'

        video.onloadedmetadata = function () {
            resolve(this)
        }

        video.onerror = function () {
            reject("Invalid video. Please select a video file.")
        }

        video.src = window.URL.createObjectURL(file)
    } catch (e) {
        reject(e)
    }
})

Can be used as follows:

const video = await loadVideo(e.currentTarget.files[0])
console.log(video.duration)
2
  • 2
    typescript complains about passing this to resolve if you specify the return type of loadVideo as Promise<HTMLVideoElement>. Throws the error Argument of type 'GlobalEventHandlers' is not assignable to parameter of type 'HTMLVideoElement | PromiseLike<HTMLVideoElement>' To fix this just pass the video object itself to resolve instead of this, like so resolve(video)
    – Ismael
    Commented Jan 22, 2021 at 17:17
  • Simple and easy! Commented Feb 13, 2023 at 12:13
18

It's simple to get video duration from FileReader and it's easy to manage in async/await.

const getVideoDuration = file =>
  new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => {
      const media = new Audio(reader.result);
      media.onloadedmetadata = () => resolve(media.duration);
    };
    reader.readAsDataURL(file);
    reader.onerror = error => reject(error);
  });
const duration = await getVideoDuraion(file);

where file is File object

Live Example

const getVideoDuration = (file) =>
  new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => {
      const media = new Audio(reader.result);
      media.onloadedmetadata = () => resolve(media.duration);
    };
    reader.readAsDataURL(file);
    reader.onerror = (error) => reject(error);
  });

const handleChange = async (e) => {
  const duration = await getVideoDuration(e.target.files[0]);
  document.querySelector("#duration").innerText = `Duration: ${duration}`;
};
<div>
  <input type="file" onchange="handleChange(event)" />
  <p id="duration">Duration: </p>
</div>

3
  • 1
    why do you write "For React" ? I don't see what this has to do with React, it's just JS... I encourage you to remove that line from your answer.
    – mesqueeb
    Commented Sep 12, 2021 at 1:31
  • Yes, I know there is nothing related to javascript but I added a demo of react that is why, but thanks for the question, I will convert demo into pure vanila js Commented Sep 12, 2021 at 10:21
  • Browser gets stuck if file size is over 500mb. It's better to use video.duration as mentioned in other posts.
    – Salman
    Commented Sep 23, 2021 at 16:44
5

This is how I managed to get video duration before pushing it on S3.. I am using this code to upload video files of 4+ GB.

Note - for formats like .avi,.flv, .vob, .mpeg etc, duration will not be found, so handle it with a simple message to the user

  //same method can be used for images/audio too, making some little changes
  getVideoDuration = async (f) => {
    const fileCallbackToPromise = (fileObj) => {
      return Promise.race([
        new Promise((resolve) => {
          if (fileObj instanceof HTMLImageElement) fileObj.onload = resolve;
          else fileObj.onloadedmetadata = resolve;
        }),
        new Promise((_, reject) => {
          setTimeout(reject, 1000);
        }),
      ]);
    };

    const objectUrl = URL.createObjectURL(f);
    // const isVideo = type.startsWith('video/');
    const video = document.createElement("video");
    video.src = objectUrl;
    await fileCallbackToPromise(video);
    return {
      duration: video.duration,
      width: video.videoWidth,
      height: video.videoHeight,
    };
}


//call the function
//removing unwanted code for clarity
const meta = await this.getVideoDuration(file);
//meta.width, meta.height, meta.duration is ready for you to use
4
  • Any idea why video duration is missing for the format you mentioned in your note? Actually, the onload and onloadedmetadata event are not even firing for these format (tested with video/avi).
    – Eturcim
    Commented Aug 19, 2021 at 15:56
  • Yes...its not missing but I didnt wanted to add it as there are many formats of video file like avi, mpeg etc, getting the durarion of some of these video files fails, while rest of them are handled easily, as its more of browser centric.
    – Milind
    Commented Aug 21, 2021 at 23:47
  • I have asked a similar question here (stackoverflow.com/questions/70133389/…) and some guys have mentioned that getting the file metadata on the client side (I guess by creating the video element and setting the file blob as source), is a security vulnerability. Is that so? Commented Nov 29, 2021 at 9:11
  • I agree @FloRagossnig, but many times there are video formats who's duration details are not well understood by most of the library, so this is one of the way to get the duration explicitly.
    – Milind
    Commented Dec 3, 2021 at 11:54
3
+500

I've implemented getting video duration in nano-metadata package https://github.com/kalashnikovisme/nano-metadata.

You can do something like this

import nanoMetadata from 'nano-metadata'

const change = (e) => {
  const file = e.target.files[0]
  
  nanoMetadata.video.duration(file).then((duration) => {
    console.log(duration) // will show you video duration in seconds
  })
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.