Tile a set of images and pan/scroll across them using ffmpeg - ffmpeg

I have a set of very large images that I would like to tile horizontally and pan across using ffmpeg.
The images are relatively large and can be created using magick using:
magick convert -compress lzw -size 90000x800 xc:"rgb(160,160,255)" test001.tif
... and so on
The command I've gotten closest with is the following:
ffmpeg -loop 1 -i test%03d.tif -vf "tile=4x1,scroll=horizontal=0.05,crop=800:600:0:0,format=yuv420p" -t 10 output.mp4
The issues with the above command:
it is very very slow (0.3 frames/sec)
the animation loops because i picked an arbitrary 10 sec duration (not sure how to get it to stop at end of image)
high memory usage, i'm not sure if ffmpeg is creating the tiled image first, and then going to process the video, or if there's a way to only access the areas of the tiffs that are needed for each frame
Any pointers for how scroll/pan across multiple input images would be greatly appreciated!

Start with 90000x800 images (hundreds of them), use input framerate of 1 fps (for the ease of presentation), let M = 90000
step 1: tile=2x1:overlap=1 appends the next image to the previous: [Img1|Img2],[Img2|Img3], [Img3|Img4]...
step 2: fps=N increases the frame rate to N fps by repeating each tiled frame N times
step 3: crop=w=1280:h=800:x=mod(n,N)*M/N:y=0 positions the current video frame on the image then crops out the rest. On frame n=N, the next pair of images will be set so it resets the frame position to 0.
ffmpeg -r 1 -i test%03d.tif \
-vf 'tile=2x1:overlap=1, \
fps=N, \
crop=w=1280:h=800:x=mod(n,N)*M/N:y=0' \
-pix_fmt yuv420p output.mp4
Pick your number for N and substitute the symbols with actual numbers, and you should be ready to roll (I hope...)
But now, how do you pick N? It'll depends on your input frame rate (which is preliminary set to 1), your desired output frame rate, and the image transition time...
Say you want each image to stay on screen for 2 seconds and the output frame rate to be 30 fps, then each image should appear on 2*30=60 frames. So, set N = 60 and the input frame rate 30/60 = 0.5.
I have not tested the command, so give it a try and I'd be happy to troubleshoot if not working. Also, I'm curious if it's reasonably fast.
===============================
Update: requested to change input to 800x800 images instead of 90000x800.
Change tile and crop as follows:
ffmpeg -r 1 -i test%03d.tif \
-vf 'tile=3x1:overlap=2, \
fps=N, \
crop=w=1280:h=800:x=mod(n,N)*800/N:y=0' \
-pix_fmt yuv420p output.mp4
Also N needs to be readjusted by Nnew = 800/90000 * N or thereabouts.

Related

Trying to convert multiple images into a video [duplicate]

I am trying to encode a .mp4 video from a set of frames using FFMPEG using the libx264 codec.
This is the command I am running:
/usr/local/bin/ffmpeg -r 24 -i frame_%05d.jpg -vcodec libx264 -y -an video.mp4
I sometimes get the following error:
[libx264 # 0xa3b85a0] height not divisible by 2 (520x369)
After searching around a bit it seems that the issue has something to do with the scaling algorithm and can be fixed by adding a -vf argument.
However, in my case I don't want to do any scaling. Ideally, I want to keep the dimensions exactly the same as the frames. Any advice? Is there some sort of aspect ratio that h264 enforces?
The answer to the original question should not scale the video but instead fix the height not divisible by 2 error. This can be achieve using this filter:
-vf "pad=ceil(iw/2)*2:ceil(ih/2)*2"
Full command:
ffmpeg -i frame_%05d.jpg -vcodec libx264 \
-vf "pad=ceil(iw/2)*2:ceil(ih/2)*2" -r 24 \
-y -an video.mp4
Basically, .h264 needs even dimensions so this filter will:
Divide the original height and width by 2
Round it up to the nearest pixel
Multiply it by 2 again, thus making it an even number
Add black padding pixels up to this number
You can change the color of the padding by adding filter parameter :color=white. See the documentation of pad.
For width and height
Make width and height divisible by 2 with the crop filter:
ffmpeg -i input.mp4 -vf "crop=trunc(iw/2)*2:trunc(ih/2)*2" output.mp4
If you want to scale instead of crop change crop to scale.
For width or height
Using the scale filter. This will make width 1280. Height will be automatically calculated to preserve the aspect ratio, and the width will be divisible by 2:
ffmpeg -i input.mp4 -vf scale=1280:-2 output.mp4
Similar to above, but make height 720 and automatically calculate width:
ffmpeg -i input.mp4 -vf scale=-2:720 output.mp4
You can't use -2 for both width and height, but if you already specified one dimension then using -2 is a simple solution.
If you want to set some output width and have output with the same ratio as original
scale=720:-1
and not to fall with this problem then you can use
scale="720:trunc(ow/a/2)*2"
(Just for people searching how to do that with scaling)
The problem with the scale solutions here is that they distort the source image/video which is almost never what you want.
Instead, I've found the best solution is to add a 1-pixel pad to the odd dimension. (By default, the pading is black and hard to notice.)
The problem with the other pad solutions is that they do not generalize over arbitrary dimensions because they always pad.
This solution only adds a 1-pixel pad to height and/or width if they are odd:
-vf pad="width=ceil(iw/2)*2:height=ceil(ih/2)*2"
This is ideal because it always does the right thing even when no padding is necessary.
It's likely due to the the fact that H264 video is usually converted from RGB to YUV space as 4:2:0 prior to applying compression (although the format conversion itself is a lossy compression algorithm resulting in 50% space savings).
YUV-420 starts with an RGB (Red Green Blue) picture and converts it into YUV (basically one intensity channel and two "hue" channels). The Hue channels are then subsampled by creating one hue sample for every 2X2 square of that hue.
If you have an odd number of RGB pixels either horizontally or vertically, you will have incomplete data for the last pixel column or row in the subsampled hue space of the YUV frame.
LordNeckbeard has the right answer, very fast
-vf scale=1280:-2
For android, dont forget add
"-preset ultrafast" and|or "-threads n"
You may also use bitand function instead of trunc:
bitand(x, 65534)
will do the same as trunc(x/2)*2 and it is more transparent in my opinion.
(Consider 65534 a magical number here ;) )
My task was to scale automatically a lot of video files to half resolution.
scale=-2,ih/2 lead to slightly blurred images
reason:
input videos had their display aspect ratio (DAR) set
scale scales the real frame dimensions
during preview the new videos' sizes have to be corrected using DAR which in case of quite low-resoution video (360x288, DAR 16:9) may lead to blurring
solution:
-vf "scale='bitand(oh*dar, 65534)':'bitand(ih/2, 65534)', setsar=1"
explanation:
output_height = input_height / 2
output_width = output_height * original_display_aspect_ratio
both output_width and output_height are now rounded to nearest smaller number divisible by 2
setsar=1 means output_dimensions are now final, no aspect ratio correction should be applied
Someone might find this helpful.

How to specify the exact number of output image frames with ffmpeg?

I have N input animation frames as images in a folder and I want to create interpolated inbetween frames to create a smoother animation of length N * M, i.e. for every input frame I want to create M output frames that gradually morph to the next frame, e.g. with the minterpolate filter.
In other words, I want to increase the FPS M times, but I am not working with time as I am not working with any video formats, both input and output are image sequences stored as image files.
I was trying to combine the -r and FPS options, but without success as I don't know how they work together. For example:
I have 12 input frames.
I want to use the minterpolate filter to achieve 120 frames.
I use the command ffmpeg -i frames/f%04d.png -vf "fps=10, minterpolate" -r 100 interpolated_frames/f%04d.png
The result I get is 31 output frames.
Is there a specific combination of -r and FPS I should use? Or is there another way I can achieve what I need?
Thank you!
FFmpeg assigns a framerate of 25 to formats which don't have an inherent frame rate, like image sequences.
The image sequence demuxer has an option to set a framerate. And the minterpolate filter has an option for target fps.
ffmpeg -framerate 12 -i frames/f%04d.png -vf "minterpolate=fps=120" interpolated_frames/f%04d.png

How do i force ffmpeg to keep sizes divisible by 2 while maintaining aspect ratio?

for example, this command line:
ffmpeg -i rtsp://184.72.239.149/vod/mp4:BigBuckBunny_175k.mov -vf "scale=w=416:h=234:force_original_aspect_ratio=decrease" -an -f rawvideo -pix_fmt yuv420p -r 15 -
works fine except if the source video was 360x240, output will be 351x234. which kinda sucks as yuv420p video with odd sizes is difficult to handle due to the way colour data is stored.
is there a way i could force ffmpeg to give nearest possible even values?
If you're resizing use just one of the dimensions with an absolute value, example:
Change:
-vf "scale=w=416:h=234:force_original_aspect_ratio=decrease"
To:
-vf "scale=w=416:h=-2"
Should scale to a width of 416 and scale the height appropriately so the aspect ratio keeps the same.
-2 = scale using mod 2
-4 = scale using mod 4 etc....
you can achieve that by using force_divisible_by=2 in your filter like this :
-vf scale=w=852:h=480:force_original_aspect_ratio=decrease:force_divisible_by=2
i know the question is old but hope this help someone.

using ffmpeg to create a wavefile image from opus

I have been trying to use ffmpeg to create a wavefile image from an opus file. so far i have found three different methods but cannot seem to determine which one is the best.
The end result is hopefully to have a sound-wave that is only approx. 55px in height. The image will become part of a css background-image.
Adapted from Generating a waveform using ffmpeg:
ffmpeg -i file.opus -filter_complex
"showwavespic,colorbalance=bs=0.5:gm=0.3:bh=-0.5,drawbox=x=(iw-w)/2:y=(ih-h)/2:w=iw:h=1:color=black#0.5"
file.png
which produces this image:
Next, I found this one (and my favorite because of the simplicity):
ffmpeg -i test.opus -lavfi showwavespic=split_channels=1:s=1024x800 test.png
And here is what that one looks like:
Finally, this one from FFmpeg Wiki: Waveform, but it seems less efficient using a second utility (gnuplot) rather than just ffmpeg:
ffmpeg -i file.opus -ac 1 -filter:a
aresample=4000 -map 0:a -c:a pcm_s16le -f data - | \
gnuplot -e "set
terminal png size 525,050;set output
'file.png';unset key;unset tics;unset border; set
lmargin 0;set rmargin 0;set tmargin 0;set bmargin 0; plot '
Option two is my favorite, but i dont like the margins on the top and bottom of the waveforms.
Option three (using gnuplot) makes the best 'shaped' image for our needs, since the initial spike in sound seems to make the rest almost too small to use (lines tend to almost disappear) when the image is sized at only 50 pixels high.
Any suggestions how might best approach this? I really understand very little about any of the options I see, except of course for the size. Note too i have 10's of thousands to process, so naturally i want to make a wise choice at the very beginning.
Original and manipulated waveforms.
You can use the compand filter to adjust the dynamic range. drawbox is then used to make the horizontal line.
ffmpeg -i test.opus -filter_complex \
"compand=gain=-6,showwavespic=s=525x50, \
drawbox=x=(iw-w)/2:y=(ih-h)/2:w=iw:h=1:color=white" \
-vframes 1 output.png
It won't be quite as accurate of a representation of your audio as the original waveform, but it may be an improvement visually; especially on such a wide scale.
Also see FFmpeg Wiki: Waveform.

ffmpeg: thumbnail of frame, preserve aspect ratio, apply background / padding / fill colour

I already have found out how to scale the thumbnail to stay within specified bounding dimensions while maintaining aspect ratio. For example, to get the frame shown at 6 seconds into the input.mp4 video file, and scale it to fit into 96x60 (16:10 aspect ratio):
ffmpeg -y -i input.mp4 -ss 6 -vframes 1 -vf scale="'if(gt(a,16/10),96,-1)':'if(gt(a,16/10),-1,60)'" output.png
This is fine, it works.
Next, I would like to do the same, but if the video's aspect ratio is not exactly 16:10, then I would like to force the output image to have an aspect ratio of 16:10 by taking the above transformation, and filling or padding the space with white. That is, I want the output to be as if I took, say, a 96x48 image, and laid it over a 96x60 white background, resulting in white bars above and below the 96x48 image.
Ideally, I do not want to resort to using another tool or library, such as ImageMagick. It would be best if ffmpeg could do this on its own.
Here's what I went with. For the -vf argument:
-vf "scale='if(gt(a,16/10),96,-1)':'if(gt(a,16/10),-1,60)', pad=w=96:h=60:x=(ow-iw)/2:y=(oh-ih)/2:color=white"
This applies two filters in sequence, separated by a comma.
target_H = 2436
target_W = 1124
ffmpeg -i 1.mp4 -ss 1 -vframes 1 -vf "scale=min(iw*2436/ih\,1124):min(2436\,ih*1124/iw),pad=1124:2436:(1124-iw)/2:(2436-ih)/2:green" output.png

Resources