Video File Size Optimization - python

I'm trying the 2-pass technique of the FFmpeg in python but couldn't find any python tutorials do this task.
is there is no way instead of using Subprocess? if there's any illustrative example please provide me.
Note:
I have tried the 2-pass in the script like that:
input_fit = {self.video_in:None}
output = {None:"-c:v h264 -b:v 260k -pass 1 -an -f mp4 NUL && ^",
self.video_out:("ffmpeg -i \"%s\" -c:v h264 -b:v 260k -pass 2 " %self.video_in)}
## video_out IS The Name of The output File ##
model = FFmpeg(inputs = input_fit, outputs= output)
print(model.cmd)
It Raises an error of
:: FFRuntimeError: exited with status 1,
but when i take the generated command and run it on the ffmpeg cmd it runs without errors and generates the video perfectly.
so anyone just could tell me what is the problem please?

Related

I cannot convert a set of images taken from a FTP to a video using FFMPEG

I am writting a python code where I am trying to convert a set of images that I take from a FTP into a video using FFMPEG but I cannot. I have tried, instead of reading the folder where the images are, to read a txt file with the name of the images that I want to use, with the format needed in order that FFMPEG could read it propertly, but I get always the same error: Protocol 'ftp' not on whitelist 'tcp'
In the same code, I also try to change the format of one video and change the resolution and size, and this part of code works well.
However, writting as input the same reference of the FTP, the images' code fail and the video's code works.
Besides, I have tried in my terminal as local the same command I write in the code for the images, and in local it works propertly, but not in the code.
Here there is a part of my code:
Video's code (it works):
command = """ffmpeg -i {i} -an -crf {r} {o}""".format(i=src_path,o=path,r=resolution)
An example of this command when I run this is the next (I dont want to write the exact ip and port):
ffmpeg -i ftp://user:user#ip:port/landing_ffmpeg/pruebas/pruebahd.mp4 -an -crf 45 tmp/pruebasalida456.mp4
And next the images' code (it doesnt work):
command = """ffmpeg -loop 1 -framerate {ips} -i {i} -t 10 -pix_fmt yuv420p {o}""".format(i=src_path,o=path,ips=img_per_sec)
An example of this command is the next:
ffmpeg -loop 1 -framerate 2 -i ftp://user:user#ip:port/landing_ffmpeg/pruebas/prueba_imagenes/prueba06.jpg -t 10 -pix_fmt yuv420p tmp/videoimagen.mp4
And the error I get with this code is the next:
[ftp # 0x560eb3e11800] Protocol 'ftp' not on whitelist 'tcp'!
[image2 # 0x560eb3e09380] Could not open file : ftp://user:user#ip:port/landing_ffmpeg/pruebas/prueba_imagenes/prueba06.jpg
I dont get this error when I try to run the command of the video, only for the images. And both commands run propertly when I write in my terminal in local, with local paths.
I would appreciate if someone can help me to solve the problem and fix my code.
Thanks!
The error is saying it all. Try to whitelist the ftp:
ffmpeg -protocol_whitelist ftp -loop 1 -framerate 2 \
-i ftp://user:user#ip:port/landing_ffmpeg/pruebas/prueba_imagenes/prueba06.jpg \
-t 10 -pix_fmt yuv420p tmp/videoimagen.mp4

Can moviepy be used without reencoding video

I wish to combine a remastered audio track to a video without re-encoding the source video.
I could achieve this with ffmpeg by running this command:
ffmpeg -i original_video.mp4 -i remastered_audio.wav -map 0:v -map 1:a -c:v copy -shortest remastered_video.mp4
The resulting remastered_video.mp4 file will be slightly larger than original_video.mp4 because the video track will be untouched and include the new encoded audio track. I wish to use moviepy to achieve the same, but I can't figure out how to do it without re-encoding the source video.
from moviepy.editor import AudioFileClip, VideoFileClip
original_video = VideoFileClip(<path_to_original_video_file.mp4>)
remastered_audio = AudioFileClip(<path_to_remastered_audio_track.wav>)
remastered_video = original_video.set_audio(remastered_audio)
remastered_video.write_videofile(<path_of_output_remastered_video.mp4>)
Here the output video file will be smaller than the source because the video track was re-encoded.
I then tried to pass my ffmpeg arguments like so:
args=['-map', '0:v', '-map', '1:a', '-c:v', 'copy']
remastered_video.write_videofile(<path_of_output_remastered_video.mp4>,
ffmpeg_params=args,
logger=None)
but that generated the following error:
#[mp4 # 00000190c731d280] Could not find tag for codec rawvideo in stream #0, codec not currently supported in container
#Could not write header for output file #0 (incorrect codec parameters ?): Invalid argument
QUESTION
I know how to use subprocess to directly run ffmpeg, but I would like to avoid this and use moviepy. Is it possible to tell moviepy to copy the source video instead of re-encoding it, similar to the ffmpeg example command I posted above?

How to ignore auto rotation when reading videos with ffmpeg python?

When reading a video with ffmpeg-python, if the video metadata contains a "rotate" attribute, it seems that by default ffmpeg transposes the incoming bytes according to the rotation value.
I would like to remove the automatic rotation. I tried the following, with no success:
import ffmpeg
process = (
ffmpeg
.input(filename)
.output('pipe:', format='rawvideo', pix_fmt='yuv420p', loglevel=0, vsync='0')
.global_args('-noautorotate')
.run_async(pipe_stdout=True)
)
The code runs without any issue, but the rotation is not ignored, as I would have expected.
According to this:https://gist.github.com/vxhviet/5faf792f9440e0511547d20d17208a76, the noautorotate argument should be passed before input.
I tried the following:
process = (
ffmpeg
.global_args('-noautorotate')
.input(filename)
.output('pipe:', format='rawvideo', pix_fmt='yuv420p', loglevel=0, vsync='0')
.run_async(pipe_stdout=True)
)
also with no success:
AttributeError: module 'ffmpeg' has no attribute 'global_args'
Any suggestion?
EDIT
Passing noautorotate as kwargs does not work either (size of video after reading is 0)
process = (
ffmpeg
.input(self.file, **{'noautorotate':''})
.output('pipe:', format='rawvideo', pix_fmt='yuv420p', loglevel=1, vsync='0')
.run_async(pipe_stdout=True)
)
Replace **{'noautorotate':''} with **{'noautorotate':None}.
Correct syntax:
process = (
ffmpeg
.input(self.file, **{'noautorotate':None})
.output('pipe:', format='rawvideo', pix_fmt='yuv420p', loglevel=1, vsync='0')
.run_async(pipe_stdout=True)
)
When using **{'noautorotate':''}, FFmpeg output an error:
Option noautorotate (automatically insert correct rotate filters) cannot be applied to output url -- you are trying to apply an input option to an output file or vice versa. Move this option before the file it belongs to.
Error parsing options for output file .
Error opening output files: Invalid argument
For testing we may add .global_args('-report'), and look at the log file.
Executing the following command:
process = (
ffmpeg
.input('input.mkv', **{'noautorotate':None})
.output('pipe:', format='rawvideo', pix_fmt='yuv420p', vsync='0')
.global_args('-report')
.run_async()
.wait()
)
The log file shows the built FFmpeg command line - looks correct:
ffmpeg -noautorotate -i input.mkv -f rawvideo -pix_fmt yuv420p -vsync 0 pipe: -report
Executing the following command:
process = (
ffmpeg
.input('input.mkv', **{'noautorotate':''})
.output('pipe:', format='rawvideo', pix_fmt='yuv420p', vsync='0')
.global_args('-report')
.run_async()
.wait()
)
The log file shows the following built FFmpeg command line:
ffmpeg -noautorotate -i input.mkv -f rawvideo -pix_fmt yuv420p -vsync 0 pipe: -report
There is just an extra space before the -i, but for some reason FFmpeg interpret it as a URL (I think it might be a Unicode related issue - the empty strings is interpret as character that is not a space).

Get result of ffmpeg and pass it to a python script

Pretty sure this is doable but I am not exactly sure on how to achieve it.
I have a Raspberry Pi streaming constantly on my local network and I use this ffmpeg script to save the video inside the Pi.
ffmpeg -i http://0.0.0.0:8080/stream/video.mjpeg -vcodec copy -map 0 -f segment -segment_time 5 -segment_format mp4 capture-%05d.mp4
The script is rather straightforward, it loads and saves 5 seconds video continuously in a local directory.
Here's what I am trying to do ultimately
Upload all saved videos to a Cloud Storage, then delete the local copy
I tried to pipe the output of ffmpeg to a python script like this but it does not work the way I imagine it would.
ffmpeg -i http://0.0.0.0:8080/stream/video.mjpeg -vcodec copy -map 0 -f segment -segment_time 5 -segment_format mp4 capture-%05d.mp4 | py test.py -p capture-%05d.mp4
This is my script, just to get the name/path of video
import argparse
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('-p', '--videoPath', type=str, help="Path to recorded video", required=True)
args = parser.parse_args()
print(args.videoPath)
I was looking into the man page of ffmpeg and came across the loglevel option.
-loglevel [repeat+]loglevel | -v [repeat+]loglevel
Set the logging level used by the library. Adding "repeat+" indicates that repeated log output should not be compressed to the first line and the "Last message repeated n
times" line will be omitted. "repeat" can also be used alone. If "repeat" is used alone, and with no prior loglevel set, the default loglevel will be used. If multiple
loglevel parameters are given, using 'repeat' will not change the loglevel. loglevel is a string or a number containing one of the following values:
verbose, 40
Same as "info", except more verbose.
So here is a simple one-liner workaround to get the things working in your case -
ffmpeg -i my_vid_feed -vcodec copy -map 0 -f segment -segment_time 5 -loglevel 40 -segment_format mp4 capture-%05d.mp4 2>&1 | grep --line-buffered -Eo "segment:.+ended" | awk -F "'" '{print $2; system("")}' | xargs -n1 python my_processor.py -p
I am just parsing the output of the ffmpeg, when a new file is written completely the verbose log emits a line like this -
[segment # 0x7fc253817000] segment:'capture-00002.mp4' count:2 ended
So I am just taking the filename from the line and passing it to that argparse python file. The -n1 option in the xargs tells it to only pass one arg at a time to the python file and then execute the python script with that file as an argument.
Here is the output of the command in action -
root$ ffmpeg -i my_vid_feed -vcodec copy -map 0 -f segment -segment_time 5 -loglevel 40 -segment_format mp4 capture-%05d.mp4 2>&1 | grep --line-buffered -Eo "segment:.+ended" | awk -F "'" '{print $2; system("")}' | xargs -n1 python my_processor.py -p
started
capture-00000.mp4
ended
started
capture-00001.mp4
ended
started
capture-00002.mp4
ended
started
capture-00003.mp4
ended
The python file used - my_processor.py
import argparse
import time
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('-p', '--videoPath', type=str, help="Path to recorded video", required=True)
args = parser.parse_args()
print("started")
print(args.videoPath)
time.sleep(3)
print("ended")
The only possible drawback to this would be at a time there will be only one instance of python running and your jobs would run sequentially after the previous job is done. In case you want to run them in parallel, you can do that as well.

Using wildcards in subprocess.Popen

I have found a couple answers that solve the problem of passing wildcards through Popen, but I can't seem to get those solutions to work for my particular case.
I have a project that will merge an audio and video file when a button is pressed, and I need to use subprocess.Popen to execute the command I want:
mergeFile = "ffmpeg -i /home/pi/Video/* -i /home/pi/Audio/test.wav -acodec copy -vcodec copymap 0:v -map 1:a /home/pi/Test/output.mkv"
proc= subprocess.Popen(shlex.split(mergeFiles), shell=True)
I basically want to take whatever file is in my Video folder (my project downloads videos from a camera and the name of the file changes every time a new video is saved) and merge the audio from a separate "Audio" folder and then save that file to yet another folder.
I have tried setting the shell to true, and nothing works.
I also don't know how the glob module would help me in this scenario.
The problem here is the shlex.split(). When used in conjunction with shell=True, it means that only the string ffmpeg is treated as a script, and that the other components of your command line are passed as arguments to that script (which it never looks at / reads).
mergeFile = "ffmpeg -i /home/pi/Video/* -i /home/pi/Audio/test.wav -acodec copy -vcodec copymap 0:v -map 1:a /home/pi/Test/output.mkv"
proc = subprocess.Popen(mergeFile, shell=True)
A better-practice alternative that still uses shell=True (if you're actually parameterizing the directory names and filenames) might be:
mergeFile=[
'ffmpeg -i "$1"/* -i "$2" -acodec copy -vcodec copymap 0:v -map 1:a "$3"',
'_', # placeholder for $0
"/home/pi/Video", # directory for $1 -- can use a variable here
"/home/pi/Audio/test.wav",
"/home/pi/Test/output.mkv",
]
subprocess.Popen(mergeFile, shell=True)
...in which case the script itself is constant (and can't have its meaning changed by values injected via filenames or other parameters), but out-of-band data can be provided.
Even better than that is to stop using shell=True altogether. Consider:
import subprocess, glob
mergeFile=[
'ffmpeg', '-i',
] + (glob.glob('/home/pi/Video/*') or ['/home/pi/Video/*']) + [
'-i', '/home/pi/Audio/test.wav',
'-acodec', 'copy',
'-vcodec', 'copymap', '0:v',
'-map', '1:a1',
'/home/pi/Test/output.mkv'
]
subprocess.Popen(mergefile)
The or ['/home/pi/Video/*'] exists to cause the same error message you'd get with a shell if no files matching the glob exist. Obviously, you could just abort in that case as well.

Categories

Resources