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)
]
)
Related
I want to overlay a transparent video over top of an image using ffmpeg and python
I am able to do this successfully through terminal, but I cannot get ffmpeg commands to work in python. The following command produces the result that I want in terminal when I am in the directory with the files there.
ffmpeg -i head1.png -i hdmiSpitting.mov -filter_complex "[0:v][1:v] overlay=0:0" -pix_fmt yuv420p -:a copy output3.mov
In python, my code is simple:
import os
import subprocess
command = "ffmpeg -i head1.png -i hdmiSpitting.mov -filter_complex \"[0:v][1:v] overlay=0:0\" -pix_fmt yuv420p -c:a copy output3.mov"
subprocess.call(command,shell=True)
The code runs, there is no indication of an error, but no output is produced.
What am I missing here?
In Windows one line with spaces should work, but in Linux we have to pass the arguments as list.
We can build the command as a list:
command = ['ffmpeg', '-i', 'head1.png', '-i', 'hdmiSpitting.mov', '-filter_complex', '[0:v][1:v]overlay=0:0', '-pix_fmt', 'yuv420p', '-c:a', 'copy', 'output3.mov']
We may also use shlex.split:
import shlex
command = shlex.split('ffmpeg -i head1.png -i hdmiSpitting.mov -filter_complex "[0:v][1:v] overlay=0:0" -pix_fmt yuv420p -c:a copy output3.mov')
Adding -y argument:
If the output file output3.mov already exists, FFmpeg prints a message:
File 'output3.mov' already exists. Overwrite? [y/N]
And waits for the user to press y.
In some development environments we can't see the message.
Add -y for overwriting the output if already exists (without asking):
command = shlex.split('ffmpeg -y -i head1.png -i hdmiSpitting.mov -filter_complex "[0:v][1:v] overlay=0:0" -pix_fmt yuv420p -c:a copy output3.mov')
Path issues:
There are cases when ffmpeg executable is not in the execution path.
Using full path may be necessary.
Example for Windows (assuming ffmpeg.exe is in c:\FFmpeg\bin):
command = shlex.split('c:\\FFmpeg\\bin\\ffmpeg.exe -y -i head1.png -i hdmiSpitting.mov -filter_complex "[0:v][1:v] overlay=0:0" -pix_fmt yuv420p -c:a copy output3.mov')
In Linux, the default path is /usr/bin/ffmpeg.
Using shell=True is not recommended and considered "unsafe".
For details see Security Considerations.
The default is False, so we may use subprocess.call(command).
Note: subprocess.run supposes to replace subprocess.call.
See this post for details.
Creating log file by adding -report argument:
In some development environments we can't see FFmpeg messages, which are printed to the console (written to stderr).
Adding -report argument creates a log file with name like ffmpeg-20220624-114156.log.
The log file may tell us what went wrong when we can't see the console.
Example:
import subprocess
import shlex
subprocess.run(shlex.split('ffmpeg -y -i head1.png -i hdmiSpitting.mov -filter_complex "[0:v][1:v] overlay=0:0" -pix_fmt yuv420p -c:a copy output3.mov -report'))
I ended up using os.system() instead of subprocess and got the results I wanted before returning to see answers on this question. The answer from Rotem is incredibly useful and does solve my issue as well, with the added information of -y parameter.
I'll paste my entire code here, as it may be useful to someone in the future.
import os
os.chdir('/Users/Todd/Desktop/ffmpeg')
background = "01Background/Untitled_Artwork7.png"
backgear = "02Backgear/Surfboard-01.png"
head = "03Head/Goldhead_Goldshell1.png"
eye = "04eye/Keye-01.png"
outfit = "05Outfit/Summershirt-01.png"
headgear = "06Headgear/PopejoyHair_Goldshell1.png"
mouth = "hdmiSpitting.mov"
frontgear = "08Frontgear/TreePot-01.png"
# takes a list of 3 or more files and creates ffmpeg command to overlay them in order
# the first element in the list will be the lowest Z element (farthest back)
def generateCommand(files = []):
command = "ffmpeg"
i = 0
count = 0
alphabet = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o"]
for file in files:
command += " -i " + file
count += 1
command += " -filter_complex \"[0][1]overlay[a];["
while i < count-3:
command += alphabet[i] + "][" + str(i+2) + "]overlay[" + alphabet[i+1] + "];["
i += 1
command += alphabet[i] + "][" + str(i+2) + "]overlay\""
command += " -pix_fmt yuv420p -c:a copy output3.mov"
return command
# Takes two files and overlays file1 over file2
# This is a separate function because of the different command syntax for less than 3 files
def overlayTwoLayers(file1, file2):
command = "ffmpeg -i " + file1 + " -i " + file2 + " -filter_complex \"[0:v][1:v] overlay=0:0\" -pix_fmt yuv420p -c:a copy output3.mov"
os.system(command)
# Call this function with a list of files you want to be compiled
def generateImage(files):
command = generateCommand(files)
os.system(command)
files = []
files.append(background)
files.append(backgear)
files.append(head)
files.append(eye)
files.append(outfit)
files.append(headgear)
files.append(mouth)
files.append(frontgear)
generateImage(files)
print("done")
subprocess.call will get the status code but not output
may be you can use subprocess.Popen
In [1]: import subprocess
In [2]: res=subprocess.Popen("ifconfig|grep 192",shell=True,stdout=subprocess.PIPE)
...: res.stdout.read()
Out[2]: b' inet addr:192.168.45.134 Bcast:192.168.45.255 Mask:255.255.255.0\n'
This question already has answers here:
Getting FFProbe Information With Python
(7 answers)
How to extract the bitrate and other statistics of a video file with Python
(2 answers)
Closed last year.
I have a video downloaded from Telegram and I need to determine its bitrate.
I have moviepy (pip install moviepy, not the developer version).
Also, I have ffmpeg, but I don't know how to use it in python.
Also, any other library would work for me.
import moviepy.editor as mp
video = mp.VideoFileClip('vid.mp4')
mp3 = video.audio
if mp3 is not None:
mp3.write_audiofile("vid_audio.mp3")
mp3_size = os.path.getsize("vid_audio.mp3")
vid_size = os.path.getsize('vid.mp4')
duration = video.duration
bitrate = int((((vid_size - mp3_size)/duration)/1024*8))
http://timivanov.ru/kak-uznat-bitrate-i-fps-video-ispolzuya-python-i-ffmpeg/
try this:
def get_bitrate(file):
try:
probe = ffmpeg.probe(file)
video_bitrate = next(s for s in probe['streams'] if s['codec_type'] == 'video')
bitrate = int(int(video_bitrate['bit_rate']) / 1000)
return bitrate
except Exception as er:
return er
Here is a solution using FFprobe:
Execute ffprobe (command line tool) as sub-process and read the content of stdout.
Use the argument -print_format json for getting the output in JSON format.
For getting only the bit_rate entry, add argument -show_entries stream=bit_rate.
Convert the returned string to dictionary using dict = json.loads(data).
Get the bitrate from the dictionary and convert it to int: bit_rate = int(dict['streams'][0]['bit_rate']).
The code sample creates a sample video file for testing (using FFmpeg), and get the bitrate (using FFprobe):
import subprocess as sp
import shlex
import json
input_file_name = 'test.mp4'
# Build synthetic video for testing:
################################################################################
sp.run(shlex.split(f'ffmpeg -y -f lavfi -i testsrc=size=320x240:rate=30 -f lavfi -i sine=frequency=400 -f lavfi -i sine=frequency=1000 -filter_complex amerge -vcodec libx264 -crf 17 -pix_fmt yuv420p -acodec aac -ar 22050 -t 10 {input_file_name}'))
################################################################################
# Use FFprobe for
# Execute ffprobe (to get specific stream entries), and get the output in JSON format
data = sp.run(shlex.split(f'ffprobe -v error -select_streams v:0 -show_entries stream=bit_rate -print_format json {input_file_name}'), stdout=sp.PIPE).stdout
dict = json.loads(data) # Convert data from JSON string to dictionary
bit_rate = int(dict['streams'][0]['bit_rate']) # Get the bitrate.
print(f'bit_rate = {bit_rate}')
Notes:
For some video containers like MKV, there is no bit_rate information so different solution is needed.
The code sample assumes that ffmpeg and ffprobe (command line tools) are in the execution path.
Solution for containers that has no bit_rate information (like MKV):
Based on the following post, we can sum the size of all the video packets.
We can also sum all the packets durations.
The average bitrate equals: total_size_in_bits / total_duration_in_seconds.
Here is a code sample for computing average bitrate for MKV video file:
import subprocess as sp
import shlex
import json
input_file_name = 'test.mkv'
# Build synthetic video for testing (MKV video container):
################################################################################
sp.run(shlex.split(f'ffmpeg -y -f lavfi -i testsrc=size=320x240:rate=30 -f lavfi -i sine=frequency=400 -f lavfi -i sine=frequency=1000 -filter_complex amerge -vcodec libx264 -crf 17 -pix_fmt yuv420p -acodec aac -ar 22050 -t 10 {input_file_name}'))
################################################################################
# https://superuser.com/questions/1106343/determine-video-bitrate-using-ffmpeg
# Calculating the bitrate by summing all lines except the last one, and dividing by the value in the last line.
data = sp.run(shlex.split(f'ffprobe -select_streams v:0 -show_entries packet=size,duration -of compact=p=0:nk=1 -print_format json {input_file_name}'), stdout=sp.PIPE).stdout
dict = json.loads(data) # Convert data from JSON string to dictionary
# Sum total packets size and total packets duration.
sum_packets_size = 0
sum_packets_duration = 0
for p in dict['packets']:
sum_packets_size += float(p['size']) # Sum all the packets sizes (in bytes)
sum_packets_duration += float(p['duration']) # Sum all the packets durations (in mili-seconds).
# bitrate is the total_size / total_duration (multiply by 1000 because duration is in msec units, and by 8 for converting from bytes to bits).
bit_rate = (sum_packets_size / sum_packets_duration) * 8*1000
print(f'bit_rate = {bit_rate}')
Here is the command as written in the tsduck manual:
tsp -I dvb -a 1 #ts1028.txt \
-P svremove -s AlJazeeraEnglish \
-P merge "tsp -I dvb -a 0 #ts1022.txt -P zap TV5MondeEurope" \
-P analyze -i 30 -o merged.txt \
-O dektec #modulation.txt
Here is my version:
import sys
import subprocess
mod_values = { "bandwidth": "8-mhz",
"convolutional_rate": "7/8",
"frequency": "578000000",
"guard_interval": "1/4",
"dmb_constellation": "64-QAM",
"modulation": "DVB-T"}
tsterinfo_rate = subprocess.run(['tsterinfo',
"-h", mod_values["convolutional_rate"],
"-g", mod_values["guard_interval"],
"-c", mod_values["dmb_constellation"],
"-s"], stdout=subprocess.PIPE, universal_newlines=True)
mod_values["dvb_bitrate"] = tsterinfo_rate.stdout
infile=sys.argv[1]
run_tsp = subprocess.run(['tsp',
'--verbose',
'-b', mod_values["dvb_bitrate"],
'-I', 'null',
'-P', 'merge',
f'"tsp -I File {infile} --infinite"',
'-P', 'pcrbitrate',
'-P', 'regulate',
'-O', 'dektec',
'--frequency', mod_values["frequency"],
'--modulation', mod_values["modulation"],
'--guard-interval', mod_values["guard_interval"],
'--convolutional-rate', mod_values["convolutional_rate"],
'--dmb-constellation', mod_values["dmb_constellation"],
'-s'])
The quoted part in the command returns this error if I try keeping it as full string with spaces in double quotes surround my single quotes:
/bin/sh: 1: tsp -I File ../Videos/myts.ts --infinite: not found
without the quotes at all it errors saying too many inputs the same as it would straight into the terminal without quotes
python 3.8.5, ubuntu 20.04
I found a few things wrong with my tsp command when working through this. The answer to the question about passing quotes through to the sub-process seems to be solved by using
shell=True
In the sub-process options. Then you can pass the command line argument as one big string rather than as a list.
My final script for taking a transport stream as an argument and creating a CBR output ready for modulating through Dektec DTU-215 is this:
import sys
import subprocess
import json
# set modulation parameters in dict in order to reference once for bitrate calc and use again for modulator setup
mod_values = { "bandwidth": "8-mhz",
"convolutional_rate": "7/8",
"frequency": "578000000",
"guard_interval": "1/4",
"dmb_constellation": "64-QAM",
"modulation": "DVB-T"}
# calculate modulated bitrate and add to dict
tsterinfo_rate = subprocess.run(['tsterinfo',
"-h", mod_values["convolutional_rate"],
"-g", mod_values["guard_interval"],
"-c", mod_values["dmb_constellation"],
"-s"], stdout=subprocess.PIPE, universal_newlines=True)
mod_values["dvb_bitrate"] = tsterinfo_rate.stdout.strip()
# first argument is input file transport stream
infile=sys.argv[1]
# use mediainfo to calculate bitrate of input ts (must be CBR)
infile_mediainfo = subprocess.run(["mediainfo",
"--Output=JSON",
infile],
capture_output=True)
print(infile_mediainfo)
media_data = json.loads(infile_mediainfo.stdout)
ts_bitrate = int(media_data["media"]["track"][0]["OverallBitRate"])
print(f'ts_bitrate is: {ts_bitrate}')
# without -t option we don't have a PAT to even merge our stream with
# packet burst seems to make big difference to how smooth final playback is, default (16 according to docs) was jerky but 14 seems smooth
run_tsp = subprocess.run(f'tsp \
--verbose \
-d \
-b {mod_values["dvb_bitrate"]} \
-I null \
-P regulate --packet-burst 14 \
-P merge \
-t \
"tsp -I file {infile} \
--infinite \
-P regulate -b {ts_bitrate}" \
-O dektec \
-f {mod_values["frequency"]} \
-m {mod_values["modulation"]} \
-g {mod_values["guard_interval"]} \
-r {mod_values["convolutional_rate"]} \
--dmb-constellation {mod_values["dmb_constellation"]} \
-s', shell=True)
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