I am fairly new to Python and this is my first time using ffmpeg (basing this code on a colleagues).
I have some code that takes a snapshot of the screen every X seconds and then creates a video out of these files however I am having issues when creating the video. This is my code for generating the video:
def create_video(screen_resolution, image_location, subfolder, count, preset, qaulity, duplicated_frame, video_path, video_filename):
video_path = path + video_path
{1}\\%d.jpg -vcodec libx264 -preset {2} -crf {3} -r {4} -pix_fmt yuv420p {5}\\{6}.mp4"
proc = subprocess.Popen("C:\\ffmpeg\\ffmpeg.exe -r 1 -f image2 -s %s \
-pattern_type sequence \
-start_number 1 \
-i %s%s\\%s%d.png \
-vcodec libx264 \
-preset %s \
-crf %s \
-r %s \
-pix_fmt yuv420p \
%s\\%s.mp4" %
(screen_resolution, image_location, subfolder, count, preset[2], qaulity, duplicated_frame, video_path, video_filename), shell = True)
and the error I am getting is:
TypeError: %d format: a number is required, not str
I have tried searching for a solution but not getting anywhere fast. I know the %d is looking for an input from the list at the end but from what I can see this is also the way to tell the FFMPEG to use all files in the image location.
Hopefully someone can help with this, apologies if this is a duplicate question but as mentioned, I have tried searching, a lot.
Don't try to manually construct shell strings. Let subprocess do that for you.
You can pass an array of command line arguments, and Popen will take care of properly escaping everything:
proc = subprocess.Popen(
[
"C:/ffmpeg/ffmpeg.exe",
"-r", "1",
"-f", "image2",
"-s", screen_resolution,
"-i", os.path.join(path, image_location, subfolder, "%d" + extension),
"-preset", preset[1],
"-crf", str(qaulity),
"-r", str(duplicated_frame),
"-pix_fmt", "yuv420p",
os.path.join(video_path, video_filename)
]
)
Of late I have been trying different ways of telling my Raspberry Pi to send video to YouTube live stream. One of the things I wanted to be able to do is boot the Pi up, and it automatically starts the live stream on its own. The advantages to this are huge (won't have to tote around keyboard/mouse to start the stream, or have to ssh into the Pi to start the stream).
Now what I did to accomplish this was to make a Python program that pipes the stream from my encoder(FFmpeg) directly to the stream. My goal was to make the program work, and then, set it to run automatically. But every time I run the file in my terminal this is my result:
Traceback (most recent call last):
File "stream.py", line 22, in <module>
stream.stdin.close()
NameError: name 'stream' is not defined
[h264 # 0x19ed450] Could not find codec parameters for stream 0 (Video: h264, none): unspecified size
Consider increasing the value for the 'analyzeduration' and 'probesize' options
pi#raspberrypi:~ $ Input #0, h264, from 'pipe:':
Duration: N/A, bitrate: N/A
Stream #0:0: Video: h264, none, 25 tbr, 1200k tbn, 50 tbc
Unknown input format: 'alsa'
Now I think I can fix I can fix some of those errors, but the biggest thing that worries me there is: the fact that "alsa" is unknown. I installed "libsasound" which is supposed to make Alsa usable, but that clearly did not help.
I am using Python 3.
This is my syntax for this program:
import subprocess
import picamera
import time
YOUTUBE="rtmp://a.rtmp.youtube.com/live2/"
KEY = ("MY PERSONAL ENCODER KEY")
stream_cmd = 'ffmpeg -f h264 -r 25 -i - -itsoffset 5.5 -fflags nobuffer -f alsa -ac 1 -i hw:1,0 -vcodec copy -acodec aac -ac 1 -ar 8000 -ab 32k -map 0:0 -map 1:0 -strict experimental -f flv ' + YOUTUBE + KEY
stream_pipe = subprocess.Popen(stream_cmd, shell=True, stdin=subprocess.PIPE)
camera = picamera.PiCamera(resolution=(640, 480), framerate=25)
try:
now = time.strftime("%Y-%m-%d-%H:%M:%S")
camera.framerate = 25
camera.vflip = True
camera.hflip = True
camera.start_recording(stream.stdin, format='h264', bitrate = 2000000)
while True:
camera.wait_recording(1)
except KeyboardInterrupt:
camera.stop_recording()
finally:
camera.close()
stream.stdin.close()
stream.wait()
print("Camera safely shut down")
print("Good bye")
Now maybe I am missing something simple here, but I don't know what. I have tried many ideas (e.g. replacing Alsa with some other input, naming the "stream" function.) I have no idea.
I have a working BASH script with the process but I would like to migrate it to Python for flexibility, power and learning.
So In short I would like to write a function to wrap the Subprocess.open() function, but for different cases:
The main process to launch is the 'ffmpeg' programme.
The final objective it's to get to lunch lines like this:
ffmpeg -y -ss 0 -t 5 -i "MP3 sourceName" -codec:a copy "MP3 Output filename"
The params to pass are changing, following the audio process chain.
Till now i stuck in the following code:
#The 'Wrapping' function:
def proces(*args):
arg = ''.join(args)
print ("DEBUG- ffmpeg ",arg)
#these lines are for debug purpose only
try :
p = subprocess.Popen(["ffmpeg", arg], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
response, err = p.communicate()
return response, err
except:
pass
#Here the initially call in the main programme
def A_Process (files):
proces( " -y -ss 0 -t 3 -i ","\"%s\" "%(files), "%s"%(Param['ACOPY']), " \"%s\""%(fCH) ), " \"%s\""%(fCH) ] )
#-y param to indicate yes to overwrite
#-ss = param to indicate start time: decimal
#-t = param to indicate time length: decimal
#files is the MP3 sourceName as a string (pure string so i need the "")
#Param['ACOPY']= "copy" string i think?
#fCH = "Output MP3 filename" also string
#### ... more irrelevant code....
#### Launching the code gives me a print:
DEBUG- ffmpeg -y -ss 0 -t 3 -i "/media/temps/scp/fs/1.mp3" -codec:a copy "1_output.mp3"
##and stderr gives:
b'ffmpeg version 3.3.3-static http://johnvansickle.com/ffmpeg/ Copyright (c)(...etc...)...
... \n[NULL # 0x32861c0] Unable to find a suitable output
format for \' -y -ss 0 -t 3 -i "/media/temps/scp/fs/1.mp3" -codec:a copy "1_output.mp3"\'\n
-y -ss 0 -t 3 -i "/media/temps/scp/fs/1.mp3" -codec:a copy ""1_output.mp3": **Invalid argument**\n'
When i copy the DEBUG- line printed in the output and launch in Terminal, i get no errors.
I think the problem comes from the true params (-y -ss -t ) are been passed as a string but i don't know how to pass the params and its values
So someone have any ideas?
I need some python code for this function. I have installed ffmpeg in Ubuntu.
def convert_avi_to_mp4(avi_file.avi):
mp4_file = None
#some code to do the job
return mp4_file
Thank you
you can do something like this..
import os
def convert_avi_to_mp4(avi_file_path, output_name):
os.popen("ffmpeg -i '{input}' -ac 2 -b:v 2000k -c:a aac -c:v libx264 -b:a 160k -vprofile high -bf 0 -strict experimental -f mp4 '{output}.mp4'".format(input = avi_file_path, output = output_name))
return True
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!
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)
(year 2020 answer)
Solutions:
opencv 0.0065 sec ✔
ffprobe 0.0998 sec
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)
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 )
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
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.
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.
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)
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
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! :)
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
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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"))
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.
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
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
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