How can I display lots of subtitles at arbitrary positions in a video using ffmpeg? - ffmpeg

I am trying to write subtitles and display them at lots of positions throughout a video at arbitrary positions. I have a long list of times and positions to display the text in the video, but I don't know how to encode these times and positions into the subtitles file. A lot of the examples on the web only show how to display a single subtitle, or don't explain the syntax of the subtitles files.
I couldn't find a simple answer to how to do this, so I had to put together a lot of methods, and I'm going to answer my own question.

The main options for doing this are:
Using a GUI/standalone program (#llogan mentioned aegisub)
'drawtext' filter in ffmpeg
subtitles filter with an SRT file
subtitles filter with an SSA file
subtitles filter with an ASS file
I wanted to write my own subtitles in a text file (to make it scriptable), not use a standalone GUI. Some of methods don't allow arbitrary positioning (the subtitle will always be centered at the bottom). Others don't allow loading a long list of subtitles from a file, ie you have to encode them all into the ffmpeg command string, which is unwieldy.
Here's the documentation on the subtitles filter, but it doesn't actually explain the syntax of the accompanying files. In fact I found it very difficult to find clear documentation of ANY of the options for subtitles files. I went with ASS because it seemed to work, and I discovered enough templates on the internet that I was able to figure out the most important features by trial and error.
First, create a text file called desc.ass that looks like this:
[Script Info]
PlayResY: 600
WrapStyle: 1
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, Alignment, MarginL, MarginV
Style: N,Arial,60,&H00FFFF,1,425,550
Style: NE,Arial,60,&H00FFFF,1,650,480
Style: E,Arial,60,&H00FFFF,1,700,250
Style: SE,Arial,60,&H00FFFF,1,600,50
Style: S,Arial,60,&H00FFFF,1,400,0
Style: SW,Arial,60,&H00FFFF,1,150,50
Style: W,Arial,60,&H00FFFF,1,100,250
Style: NW,Arial,60,&H00FFFF,1,150,500
[Events]
Format: Start, End, Style, Text
Dialogue: 00:00:01.00, 00:00:05.00, E, Text to display with the E style
Dialogue: 00:00:05.00, 00:00:05.50, N, Text to display with the N style
The actual file has many many more "Dialogue" lines after this, but I truncated for readability.
I don't really understand the Script Info section yet. :(
The V4+ Styles section allows you to specify an arbitrary number of "Styles", which define color, position, font, and a lot of other things. Each actual subtitle will use one of these styles.
If you have a small number of positions that each subtitle will be displayed in, I think it's easier to define a Style for each position. Because I wanted to display subtitles at one of 8 positions, I defined 8 styles. But you can override the position of any given subtitle (see below). So another option is to use a single Style for everything, and just define the position of each subtitle as necessary.
PrimaryColour is a hex color argument (although it seems to be backwards for some reason, so yellow gives you cyan and vice versa).
Alignment refers to the numerical keypad, so 1 means lower left and 5 means center.
MarginL and MarginV allow you to apply horizontal and vertical offsets to the default alignment.
You can specify lots more attributes here just by adding elements to the Format line, and then specifying them on each Style line. This page is the most comprehensive documentation I could find.
Everybody on the internet copy/pastes the same example text with lots of formats specified (e.g., here). This is another, simpler example. Would be great if someone could post clear documentation on what each of these attributes mean!
The [Events] section defines the individual subtitles. Here, I'm only specifying the basics: start time, end time, style name, and the text.
You can override the position of any given Dialogue element by adding the string {\pos(400,570)} to the beginning of the Text element on that line. (Obviously replace 400, 570 with your desired position.)
You can preview the results of applying these subtitles to a video named video.mkv with ffplay like this:
ffplay -i video.mkv -vf "subtitles=desc.ass"
Some people call this soft-subbing because the video itself isn't being changed, the subtitles are just displayed over it.
If you want to burn the subtitles permanently into the video (sometimes called hard-subbing), you can re-encode like this:
ffmpeg -i video.mkv -vf "subtitles=desc.ass" video_with_subtitles.mkv
Ref for burning subtitles

Related

Cross-fade video to itself with FFmpeg for seamless looping

I have found some questions&answers concerning cross-fading between two images or videos, but which ffmpeg CLI commands are needed to fade a video with itself?
Explanation of the desired effect:
Some frames (let's say 1 second) are removed from the video's beginning
Starting in the video's last 1 second, the frames removed from the beginning are faded in over the end frames
This results in a smooth loop playback.
To expand a little on #llogan's answer using the times I was working with:
-filter_complex "[0]trim=end=1,setpts=PTS-STARTPTS[begin];[0]trim=start=1,setpts=PTS-STARTPTS[end];[end][begin]xfade=wipedown:duration=1:offset=2"
This is almost exactly what I was looking for (fade a video's end into its own beginning for looping) as well, after messing with the filters to figure out what they actually do. First off, here is a slightly clearer explanation of the xfade filter, which is actually super awesome and I'm stoked to know it exists. Try using one of the more dramatic fades to get a clearer picture of your transition.
Breaking down the filtergraph a bit (the timestamps reflect the 4-second video I was looping):
-filter_complex
Call the filtergraph
"[0]trim=end=1,setpts=PTS-STARTPTS[begin];
Taking the first input ([0]) use the trim filter to make the section you want to fade in at the end of the video. Set the end of this excerpt to be at second 1 (it could be whatever, this makes a 1-second exerpt). Then use the setpts filter to set the timestamp (of the excerpt?) to start at zero. [begin] is the arbitrary label for the output of this first filter chain, but you can call it [thomas] or whatever.
[0]trim=start=1,setpts=PTS-STARTPTS[end];
Now use trim to make another input that's actually the entire video, minus the same 1 second that you'll be fading into at the end. This tripped me up a bit until I realized you're setting the end of the first chunk at the same point of the start of the main chunk. This main chunk gets the label [end].
[end][begin]xfade=fade:duration=1:offset=2"
Now use the xfade filter using the fade mode (there are examples of all the others in the link above) to dissolve from [end] into [begin]. You set the duration of the fade to last 1 second and offset the beginning of the fade at the 2 second mark. Keep in mind, this was a 4-second video that just got the first second basically trimmed and overlaid onto the end, so you now have a 3-second video. You could just as easily left the [end] chunk starting at 0 as well (the OP asked for what's described here tho).
Give it an output.mov path and you're good to go.
Use the trim, setpts, and xfade filters:
ffmpeg -i input.mp4 -filter_complex "[0]trim=end=1,setpts=PTS-STARTPTS[begin];[0]trim=start=1,setpts=PTS-STARTPTS[end];[end][begin]xfade=fade:duration=0.5:offset=8.5" output.mp4

Split text in lines ffmpeg based on font and text length

How can I calculate when to put a new line based on text length and font
using function drawText from ffmpeg.
For example if i have a long text that I want to place it at bottom in the video and it needs to take how much place it needs.
drawtext=enable='between(t,0,18)':fontfile=font_simple.ttf:text='Here
is a veeeeeeery loooong long text so I must somehow split it in
multiple lines': fontcolor=white:shadowcolor=black:shadowx=1:shadowy=1:
fontsize=25: x=(w-text_w)/1.07: y=30

Overlay text on video with ffmpeg [duplicate]

It is described here how ot burn a srt file into a video.
However, I want to put a semi-transparent background to the subtitles so that the texts can be read more easily. How can I do that?
ASS subtitles can have a semi-transparent background for the text.
With aegisub
The easiest way to do this is with aegisub.
Open your subtitles file with aegisub.
Click Subtitle → Styles manager.
Under Current Script choose Default, then press the Edit button.
Experiment with the Outline and Shadow values. Check Opaque box.
Under Colors click the color under Outline or Shadows. A window will appear. Adjust the value of the Alpha box to change transparency.
Save the subtitles as an .ass file.
Now you can use the AAS file to make hardsubs or softsubs with ffmpeg.
Without aegisub
If you want hardsubs you can use the subtitles filter to add the transparent background with the force_style option.
ffmpeg -i input -filter_complex "subtitles=subs.ass:force_style='OutlineColour=&H80000000,BorderStyle=3,Outline=1,Shadow=0,MarginV=20'" output
This will work with any text based subtitles supported by FFmpeg because the filter will automatically convert them to ASS.
See SubStation Alpha (ASS) style fields for formatting options.
Issue with multiple lines
If your subtitles contains multiple lines, due to auto-wrapping of long lines or an intentional line break, the backgrounds will overlap and potentially look ugly as shown below:
You can avoid this by:
Changing the Outline and Shadow sizes to 0.
The alpha settings of the shadow will control the transparency of the background box. Click on the shadow color to adjust the Alpha of the shadow color to your desired transparency level.
Edit the ASS file in a text editor. In the Style line change the value corresponding with BorderStyle to 4. This will fill the bounding box background of each subtitle event. Example Style line:
Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H80000000,&H80000000,-1,0,0,0,100,100,0,0,4,0,0,2,10,10,10,1
Example:
Note that BorderStyle=4 is a non-standard value, so it may not work properly in all players.
Thanks to sup and wm4 for the BorderStyle suggestion.
Using drawbox
The drawbox filter can be used to create a background box.
This may be useful if you want the box to span the width.
ffmpeg -i input -filter_complex "drawbox=w=iw:h=24:y=ih-28:t=max:color=black#0.4,subtitles=subs.ass" output
Downside is that you have to account for line breaks or word wrapping for long subtitles. Simply making the box taller to compensate will suffice, but will look ugly because the subtitles baseline remains static: the single line subtitles will have more padding on the top than the bottom.
Create a png with a transparent box and a alpha channel in your favoured size. You can use e.g. gimp or photoshop.
Then use this command:
ffmpeg -i video.mp4 -i logo.png -filter_complex "[0:v][1:v]overlay=10:10" \
-codec:a copy out.mp4
where 10:10 is the distance from the upper left corner.
After that you can insert your subtitles.
ffmpeg -i input.mp4 -filter_complex "subtitles=input.srt:force_style='BackColour=&H80000000,BorderStyle=4,Fontsize=11'" output.mp4
BackColour=&H80000000 means having a %50 opaque black background.
Its a hex representation of color, AABBGGRR.
You can use this Aegisub script. This script automatically generate transparent background for every line of subtitle.
With the current version of libass (0.15) and the current version of ffmpeg (N-100402-g0320dab265, compiled from source, probably the same as version 4.2), you can use this bash script
INFILE="movie.mp4"
SUBS="subtitles.srt"
OUTFILE="result.mp4"
ffmpeg -i "${INFILE}" -vf subtitles=${SUBS}:force_style='Borderstyle=4,Fontsize=16,BackColour=&H80000000'" "${OUTFILE}"
to burn subtitles.srt into movie.mp4 and save it as result.mp4.
The subtitles will appear correctly boxed in a 50% transparent rectangle,
even when there are 2 lines in a subtitle.

FFmpeg image sequence to video with variable image durations

I have been looking for a way to convert a sequence of PNGs to a video. There are ways to do that using the CONCAT function within FFmpeg and using a script.
The problem is that I want to show certain images longer than others. And I need it to be accurate. I can set a duration (in seconds) in the script file. But I need it to be frame-accurate. So far I have not been successful.
This is what I want to make:
Quicktime video with transparancy (Prores4444 or other codec that supports transparancy + alpha channel)
25fps
This is what I have: [ TimecodeIn - TimecodeOut in destination video ]
img001.png [0:00:05:10 - 0:00:07:24]
img002.png [0:00:09:02 - 0:00:12:11]
img003.png [0:00:15:00 - 0:00:17:20]
...
img120.png [0:17:03:11 - 0:17:07:01]
Of course this is not the format of the script file. Just an idea about what kind of data I am dealing with. The PNG-imagefiles are subtitles I generate elsewhere in my application. I would like to be able to export the subtitles as a transparent movie that I can easily import in my video editing software.
I also have been thinking of using blank transparent images I will use as spacers, between the actual subtitle images.
After looking around I think this might help:
On the FFMPEG site they explain about making a timed slideshow
In the Concat demuxer section they talk about making a slideshow, based on a text file, with references to the image files and the duration of the image.
So, I create all the PNG images I need. These images have the subtitle text. Each image holds one subtitle page.
For the moments I want to hide the subtitle, I use a blank PNG.
I generate a text file as explained on the FFMPEG website.
This text file will reference to all the PNGs. For the duration I just calculate the outcue - incue. Easy... I think...

Add text to a video with effects using FFMPEG

I need to add text in between a video. I have seen drawtext and seems it wont give effects to the text to be displayed.
My intention is to show a text within a box with some background to that box which should fly from left to right in between a video at a particular time. Is it possible with FFMPEG? I have tried different options with drawtext and nothing seems to be working. Any ideas on how to make it?
or is it possible to achieve by combining imagemagick and FFMPEG command?

Resources