73

I need to get the duration of a video in Python. The video formats that I need to get are MP4, Flash video, AVI, and MOV... I have a shared hosting solution, so I have no FFmpeg support.

What would you suggest?

Thanks!

eos87
  • 8,961
  • 12
  • 49
  • 77
  • 3
    since you don't have ffmpeg (the answer is unusable), take a look on these other answers: [0](http://stackoverflow.com/q/15041103/309483), [1](http://stackoverflow.com/q/7348505/309483), [2](http://stackoverflow.com/q/11615384/309483), [3](http://stackoverflow.com/q/10075176/309483) – Janus Troelsen Feb 24 '13 at 14:39

15 Answers15

85

You can use the external command ffprobe for this. Specifically, run this bash command from the FFmpeg Wiki:

import subprocess

def get_length(filename):
    result = subprocess.run(["ffprobe", "-v", "error", "-show_entries",
                             "format=duration", "-of",
                             "default=noprint_wrappers=1:nokey=1", filename],
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT)
    return float(result.stdout)
Boris Verkhovskiy
  • 14,854
  • 11
  • 100
  • 103
SingleNegationElimination
  • 151,563
  • 33
  • 264
  • 304
  • 9
    You can also use `-print_format json` along with `-show_streams` then json.loads the result to get a nice dictionary with all the ffprobe info. This also requires parsing out the extra info printed out by ffprobe(haven't found an option to hide that yet) – dennmat Sep 01 '12 at 19:48
  • 2
    @dennmat: `-loglevel` not `-log_level` (at least with ffprobe 2.3.3) – Von Aug 31 '14 at 20:12
  • 7
    python wrapper [ffprobe](https://pypi.python.org/pypi/ffprobe/0.5) might be handy too, `int(float(FFProbe(filename).video[0].duration) * 1000)` gets you miliseconds –  Aug 17 '15 at 01:39
  • 3
    using `ffprobe` Python module, its `from ffprobe import FFProbe; FFProbe('path/to/file').streams[0].duration` – kmonsoor Sep 27 '17 at 14:47
  • 2
    I am getting an error `File Not Found` when I run this code. Although file exists. – FAHAD SIDDIQUI May 05 '20 at 20:36
  • 1
    Be aware that FFProbe may not work with Python 3 according to this (https://stackoverflow.com/questions/44227378/importerror-cannot-import-name-ffprobe) – kostr22 Jun 28 '20 at 22:00
  • As for the accepted answer I was receiving `b'[mpeg2video @ 0xf802d0] ignoring pic cod ext after 0\n1353.984000\n'` in the stdout from some video file. So in my case there were two strings in stdout, which gave me exception in the attempt to convert it to float (`return float(result.stdout)`). The solution that I came up with was `return float(result.stdout.splitlines()[-1])`. It works both for normal and broken files. Hope this will help somebody. – kostr22 Jun 28 '20 at 22:06
68

(year 2020 answer)

Solutions:

  1. opencv 0.0065 sec ✔
  2. ffprobe 0.0998 sec
  3. moviepy 2.8239 sec

OpenCV method:

def with_opencv(filename):
    import cv2
    video = cv2.VideoCapture(filename)

    duration = video.get(cv2.CAP_PROP_POS_MSEC)
    frame_count = video.get(cv2.CAP_PROP_FRAME_COUNT)

    return duration, frame_count

Usage: print(with_opencv('my_video.webm'))


Other:

ffprobe method:

def with_ffprobe(filename):
    import subprocess, json

    result = subprocess.check_output(
            f'ffprobe -v quiet -show_streams -select_streams v:0 -of json "{filename}"',
            shell=True).decode()
    fields = json.loads(result)['streams'][0]

    duration = fields['tags']['DURATION']
    fps      = eval(fields['r_frame_rate'])
    return duration, fps

moviepy method:

def with_moviepy(filename):
    from moviepy.editor import VideoFileClip
    clip = VideoFileClip(filename)
    duration       = clip.duration
    fps            = clip.fps
    width, height  = clip.size
    return duration, fps, (width, height)
Nikolay Gogol
  • 819
  • 7
  • 6
  • 9
    Your OpenCV approach is indeed interesting. However it seems prone to errors, returning 0 values often, apparently due to codecs. I haven't been able to count correctly a single file. If you add a few words on that, I would be glad to upvote =) – Btc Sources May 13 '20 at 20:08
  • 7
    It seems it can be fixed by reading fps instead: fps = video.get(cv2.CAP_PROP_FPS) duration = frame_count / fps – pzelasko Jun 25 '20 at 17:39
  • 4
    For my videos OpenCV code returned 0 duration. But the frame count value was good, so it was possible to call `fps = video.get(cv2.CAP_PROP_FPS)` and get duration by `frame_count/fps`. OpenCV method is faster than subprocess with ffmpeg, but you have to install a lot of things in your system to get it working. And for me 'pymediainfo' seems almost twise as fast – kostr22 Jul 01 '20 at 20:20
  • 1
    opencv is unreliable, see official github issue https://github.com/opencv/opencv/issues/15749#issuecomment-796512723 – Stormsson Mar 29 '21 at 16:50
38

As reported here https://www.reddit.com/r/moviepy/comments/2bsnrq/is_it_possible_to_get_the_length_of_a_video/

you could use the moviepy module

from moviepy.editor import VideoFileClip
clip = VideoFileClip("my_video.mp4")
print( clip.duration )
mobcdi
  • 1,532
  • 2
  • 28
  • 49
17

To make things a little bit easier, the following codes put the output to JSON.

You can use it by using probe(filename), or get duration by using duration(filename):

json_info     = probe(filename)
secondes_dot_ = duration(filename) # float number of seconds

It works on Ubuntu 14.04 where of course ffprobe installed. The code is not optimized for speed or beautiful purposes but it works on my machine hope it helps.

#
# Command line use of 'ffprobe':
#
# ffprobe -loglevel quiet -print_format json \
#         -show_format    -show_streams \
#         video-file-name.mp4
#
# man ffprobe # for more information about ffprobe
#

import subprocess32 as sp
import json


def probe(vid_file_path):
    ''' Give a json from ffprobe command line

    @vid_file_path : The absolute (full) path of the video file, string.
    '''
    if type(vid_file_path) != str:
        raise Exception('Gvie ffprobe a full file path of the video')
        return

    command = ["ffprobe",
            "-loglevel",  "quiet",
            "-print_format", "json",
             "-show_format",
             "-show_streams",
             vid_file_path
             ]

    pipe = sp.Popen(command, stdout=sp.PIPE, stderr=sp.STDOUT)
    out, err = pipe.communicate()
    return json.loads(out)


def duration(vid_file_path):
    ''' Video's duration in seconds, return a float number
    '''
    _json = probe(vid_file_path)

    if 'format' in _json:
        if 'duration' in _json['format']:
            return float(_json['format']['duration'])

    if 'streams' in _json:
        # commonly stream 0 is the video
        for s in _json['streams']:
            if 'duration' in s:
                return float(s['duration'])

    # if everything didn't happen,
    # we got here because no single 'return' in the above happen.
    raise Exception('I found no duration')
    #return None


if __name__ == "__main__":
    video_file_path = "/tmp/tt1.mp4"
    duration(video_file_path) # 10.008
Andrew_1510
  • 12,258
  • 9
  • 51
  • 52
17

Find this new python library: https://github.com/sbraz/pymediainfo

To get the duration:

from pymediainfo import MediaInfo
media_info = MediaInfo.parse('my_video_file.mov')
#duration in milliseconds
duration_in_ms = media_info.tracks[0].duration

Above code is tested against a valid mp4 file and works, but you should do more checks because it is heavily relying on the output of MediaInfo.

beckert
  • 85
  • 1
  • 9
chenyi1976
  • 1,104
  • 9
  • 17
  • Needs separate library (libmediainfo). – Tomasz Gandor Nov 13 '18 at 10:37
  • This is the fastest what I found, even faster than moviepy or ffprobe. You're a hero, thanks!! – DaWe May 21 '20 at 21:02
  • You should probably do `max([float(track.duration) for track in MediaInfo.parse('my_video_file.mov').tracks])` to check all tracks because the first track doesn't necessrily have to be the same length as the actually "entire" video. – Nexarius Apr 24 '21 at 22:12
12

Use a modern method with https://github.com/kkroening/ffmpeg-python (pip install ffmpeg-python --user). Don't forget to install ffmpeg too.

Get video info:

import ffmpeg

info=ffmpeg.probe(filename)

print(f"duration={info['format']['duration']}")
print(f"framerate={info['streams'][0]['avg_frame_rate']}")

Use ffmpeg-python package to also easily create, edit and apply filters to videos.

avibrazil
  • 311
  • 2
  • 10
7
from subprocess import check_output

file_name = "movie.mp4"

#For Windows
a = str(check_output('ffprobe -i  "'+file_name+'" 2>&1 |findstr "Duration"',shell=True)) 

#For Linux
#a = str(check_output('ffprobe -i  "'+file_name+'" 2>&1 |grep "Duration"',shell=True)) 

a = a.split(",")[0].split("Duration:")[1].strip()

h, m, s = a.split(':')
duration = int(h) * 3600 + int(m) * 60 + float(s)

print(duration)
DeWil
  • 362
  • 3
  • 10
  • A downvote is fine but do have the **courtesy** to explain why ! The code posted above is a **clear cut solution** to the question asked by the OP. – DeWil May 08 '18 at 10:03
  • On a Windows machine, doesn't this answer assume that the user has `ffprobe` on the path? I get that you're just highlighting the lack of `grep` on Windows, but to use that exact syntax, it'd involve editing the environment variables or needs to be run from a the same directory as `ffprobe`. – Lovethenakedgun Sep 22 '19 at 10:26
5

A function I came up with. This is basically using only ffprobe arguments

from subprocess import  check_output, CalledProcessError, STDOUT 


def getDuration(filename):

    command = [
        'ffprobe', 
        '-v', 
        'error', 
        '-show_entries', 
        'format=duration', 
        '-of', 
        'default=noprint_wrappers=1:nokey=1', 
        filename
      ]

    try:
        output = check_output( command, stderr=STDOUT ).decode()
    except CalledProcessError as e:
        output = e.output.decode()

    return output


fn = '/app/648c89e8-d31f-4164-a1af-034g0191348b.mp4'
print( getDuration(  fn ) )

Outputs duration like this:

7.338000
sr9yar
  • 4,850
  • 5
  • 53
  • 59
  • This solution replaced my previous code. One copy and paste and my program was working again. Cheers. – Xammax Sep 21 '21 at 19:27
5

As reported here https://www.reddit.com/r/moviepy/comments/2bsnrq/is_it_possible_to_get_the_length_of_a_video/

you could use the moviepy module

from moviepy.editor import VideoFileClip 
clip = VideoFileClip("my_video.mp4") 
print( clip.duration )

If you're trying to get the duration of many videos in a folder it'll crash giving the error: AttributeError: 'AudioFileClip' object has no attribute 'reader'

So, in order to avoid that you'll need to add

clip.close()

Based on this: https://zulko.github.io/moviepy/_modules/moviepy/video/io/VideoFileClip.html

So the code would look like this:

from moviepy.editor import VideoFileClip
clip = VideoFileClip("my_video.mp4")
print( clip.duration )
clip.close()

Cheers! :)

Mina Abd El-Massih
  • 636
  • 1
  • 8
  • 13
4

The above pymediainfo answer really helped me. Thank you.

As a beginner, it did take a while to find out what was missing (sudo apt install mediainfo) and how to also address attributes in other ways (see below).

Hence this additional example:

# sudo apt install mediainfo
# pip3 install pymediainfo
from pymediainfo import MediaInfo
media_info = MediaInfo.parse('/home/pi/Desktop/a.mp4')
for track in media_info.tracks:
    #for k in track.to_data().keys():
    #    print("{}.{}={}".format(track.track_type,k,track.to_data()[k]))
    if track.track_type == 'Video':
        print("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++")
        print("{} width                 {}".format(track.track_type,track.to_data()["width"]))
        print("{} height                {}".format(track.track_type,track.to_data()["height"]))
        print("{} duration              {}s".format(track.track_type,track.to_data()["duration"]/1000.0))
        print("{} duration              {}".format(track.track_type,track.to_data()["other_duration"][3][0:8]))
        print("{} other_format          {}".format(track.track_type,track.to_data()["other_format"][0]))
        print("{} codec_id              {}".format(track.track_type,track.to_data()["codec_id"]))
        print("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++")
    elif track.track_type == 'Audio':
        print("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++")
        print("{} format                {}".format(track.track_type,track.to_data()["format"]))
        print("{} codec_id              {}".format(track.track_type,track.to_data()["codec_id"]))
        print("{} channel_s             {}".format(track.track_type,track.to_data()["channel_s"]))
        print("{} other_channel_s       {}".format(track.track_type,track.to_data()["other_channel_s"][0]))
        print("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++")
print("********************************************************************")
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Video width                 1920
Video height                1080
Video duration              383.84s
Video duration              00:06:23
Video other_format          AVC
Video codec_id              avc1
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Audio format                AAC
Audio codec_id              mp4a-40-2
Audio channel_s             2
Audio other_channel_s       2 channels
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
hydra3333
  • 167
  • 1
  • 9
3

Referring to the answer of @Nikolay Gogol using opencv-python (cv2):

His method did not work for me (Python 3.8.10, opencv-python==4.5.5.64) and the comments say that opencv can not be used in this case which is also not true.

CAP_PROP_POS_MSEC gives you the millisecond of the current frame that the VideoCapture is at and not the total milliseconds of the video, so when just loading the video this is obviously 0.

But we can actually get the frame rate and the number of total frames to calculate the total number of milliseconds of the video:

import cv2

video = cv2.VideoCapture("video.mp4")

# the frame rate or frames per second
frame_rate = video.get(cv2.CAP_PROP_FPS)

# the total number of frames
total_num_frames = video.get(cv2.CAP_PROP_FRAME_COUNT)

# the duration in seconds
duration = total_num_frames / frame_rate
sam
  • 60
  • 8
2

Open cmd terminal and install python package:mutagen using this command

python -m pip install mutagen

then use this code to get the video duration and its size:

import os
from mutagen.mp4 import MP4

audio = MP4("filePath")

print(audio.info.length)
print(os.path.getsize("filePath"))
Jansen Simanullang
  • 1,405
  • 14
  • 11
Omar Ali
  • 21
  • 2
  • For me it returns `0.0` for a 5:30 video file. Doesn't handle AVI, and needs different objects for different file types / extensions. – Tomasz Gandor Nov 13 '18 at 10:40
2

Here is what I use in prod today, using cv2 way work well for mp4, wmv and flv which is what I needed:

try:
    import cv2  # opencv-python - optional if using ffprobe
except ImportError:
    cv2 = None

import subprocess

def get_playback_duration(video_filepath, method='cv2'):  # pragma: no cover
    """
    Get video playback duration in seconds and fps
    "This epic classic car collection centres on co.webm"
    :param video_filepath: str, path to video file
    :param method: str, method cv2 or default ffprobe
    """
    if method == 'cv2':  # Use opencv-python
        video = cv2.VideoCapture(video_filepath)
        fps = video.get(cv2.CAP_PROP_FPS)
        frame_count = video.get(cv2.CAP_PROP_FRAME_COUNT)
        duration_seconds = frame_count / fps if fps else 0
    else:  # ffprobe
        result = subprocess.check_output(
            f'ffprobe -v quiet -show_streams -select_streams v:0 -of json "{video_filepath}"', shell=True).decode()
        fields = json.loads(result)['streams'][0]
        duration_seconds = fields['tags'].get('DURATION')
        fps = eval(fields.get('r_frame_rate'))
    return duration_seconds, fps

ffprobe does not work for flv and I couldn't get anything to work for webm. Otherwise, this works great and is being used in prod today.

radtek
  • 34,210
  • 11
  • 144
  • 111
0

for anyone that like using the mediainfo program:

import json
import subprocess

#===============================
def getMediaInfo(mediafile):
    cmd = "mediainfo --Output=JSON %s"%(mediafile)
    proc = subprocess.Popen(cmd, shell=True,
        stderr=subprocess.PIPE, stdout=subprocess.PIPE)
    stdout, stderr = proc.communicate()
    data = json.loads(stdout)
    return data

#===============================
def getDuration(mediafile):
    data = getMediaInfo(mediafile)
    duration = float(data['media']['track'][0]['Duration'])
    return duration
vossman77
  • 1,397
  • 14
  • 13
-1

Using ffprobe in a function it returns the duration of a video in seconds.

def video_duration(filename):
    import subprocess
    secs = subprocess.check_output(f'ffprobe -v error -select_streams v:0 -show_entries stream=duration -of default=noprint_wrappers=1:nokey=1 "{filename}"', shell=True).decode()
    return secs