Blog-Archiv

Montag, 4. Oktober 2021

Rotating Videos with ffmpeg

This article is about how you can rotate videos of any type with the open-source cross-platform tool ffmpeg. I will introduce a UNIX shell script that can do it for you, in case you know what kind of rotation you need to apply to your video. Mind that you need to install CYGWIN or similar on WINDOWS platfom to execute UNIX shell scripts, and of course you need ffmpeg installed.

Two Natures of Rotation

There are (at least) two natures of video rotation:

  1. the video was filmed rotated, mostly caused by the way you held the camera (portrait, landscape)
  2. the camera just marked the video as rotated but filmed it correctly, i.e. the way you saw it while taking the video

The second case happened to me when taking a video with my Android smartphone. I filmed the floor while walking. When watching the video at home, my player (LINUX Totem) showed it rotated in portrait format, although I saw a landscape format while filming, because this was how I held the phone.

Ways to Rotate Back

While experimenting with ffmpeg rotation I encountered several ways of rotating that are very different:

  1. Just manipulate the meta-data of the video, this is very fast
  2. Rotate the video physically by re-encoding it, this is very slow
  3. Do both, first remove rotation in meta-data, then rotate it physically

It is up to you now to find out the meta-data of your video and do the right thing. Always try to avoid re-encoding and keep the video lossless. The following command outputs a possible rotation value in meta-data:

ffprobe -v error -select_streams v:0 -show_entries stream_tags=rotate -of default=noprint_wrappers=1:nokey=1 $videofile

ffprobe looks into the video and outputs information about it.
The -v error option avoids too much debug outputs.
The -select_streams v:0 option navigates to the first (0) video stream (v) in the file.
The -show_entries stream_tags=rotate option navigates to a tag of name "rotate".
The -of default=noprint_wrappers=1:nokey=1 avoids output of stream names and tag names.
The shell variable $videofile specifies the videofile to analyze.

In my case I saw "90" as output of this command, meaning my video is to be rotated 90 degrees by players. Not all players understand meta-data and may show the video as it actually was filmed. I don't know the reason why my smartphone has put this rotation value into the video's meta-data.

To rotate my video back to landscape, I followed a web page that recommended to first make sure there is no rotation in meta-data, and then rotate physically. Result was another video in portrait, but flipped vertically. This was caused by the physical rotation of a landscape-video, combined with setting the rotation to zero. Of course this result was wrong again. So my suspicion was that I have to first find out about meta-data, and apply physical rotation only if the meta-data manipulation didn't help. This is how you should proceed.

Manipulating Meta-Data

Following command manipulates the video meta-data and resets any rotation to zero:

ffmpeg -v error -y -i $sourceVideo -c copy -metadata:s:v:0 rotate=0 $targetVideo

ffmpeg reads the -i $sourceVideo and outputs to $targetVideo.
The -v error option avoids too much debug outputs.
The -y ("yes") option would overwrite any existing $targetVideo without getting interactive.
The -c copy option specifies simple copying of all video data.
The -metadata:s:v:0 rotate=0 sets any occurring rotation in meta-data of the first video-stream (v:0) to zero.

Physical Rotation by Re-Encoding

In case there is no rotation in meta-data, you need the hard way. Following command rotates the video physically:

ffmpeg -v error -y -i $sourceVideo -vf "transpose=2" $targetVideo

The -vf transpose=2 option rotates by 90 degrees counterclockwise. Mind that this will change the video's time_base to 1/15360 (see script below for keeping time_base).
This command takes quite a while. Meanwhile you can study the transpose options:

#  0 = Rotate 90 deg. counterclockwise and do a vertical flip (default)
#  1 = Rotate 90 deg. clockwise
#  2 = Rotate 90 deg. counterclockwise
#  3 = Rotate 90 deg. clockwise and do a vertical flip
#  Rotate 180 degrees clockwise: -vf "transpose=1,transpose=1"

There are many more rotation flags and tags, please refer to ffmpeg documentation.

Rotation Script

You can use the following UNIX shell script for either manipulating meta-data or rotating physically, but not both at the same time. So you need to pass either "-m" (meta-data) or "-r" (re-encode) as first argument. Further, for "-r", you need to edit the transpose-value inside the script when you want something else than 90 degrees counterclockwise.

rotateVideo.sh

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
method=$1
sourceVideo=$2

[ "$method" != "-m" -a "$method" != "-r" -o -z "$sourceVideo" ] && {
    echo "SYNTAX: $0 -m|-r videofile" >&2
    echo "    -m: rotate by removing rotation from metadata (fast)" >&2
    echo "    -r: rotate by re-encoding (slow)" >&2
    echo "Try out -m first, because mostly this is enough." >&2
    echo "If you choose -r, you may need to remove rotation from metadata before additionally." >&2
    echo "With -r, please edit this script an set your desired 'transpose' parameter," >&2
    echo "    preset is 90 degrees counterclockwise (transpose=2)." >&2
    echo "The result file will be named like the source with '_rotated' postfix." >&2
    exit 1
}
[ -f "$sourceVideo" ] || {
    echo "Not a file: $sourceVideo" >&2
    exit 2
}

sourceDir=`dirname \$sourceVideo`
sourceFile=`basename \$sourceVideo`
filename=${sourceFile%.*}    # filename without extension
extension=${sourceFile#*.}    # extension without filename
targetVideo=$sourceDir/${filename}_rotated.$extension 

echo "Rotating $sourceVideo to $targetVideo" >&2
    
if [ $method = "-m" ]
then
    echo "... by removing video rotation in metadata ..." >&2
    ffmpeg -v error -y -i $sourceVideo -c copy -metadata:s:v:0 rotate=0 $targetVideo || exit 2    
else
    echo "... by re-encoding ..." >&2
    transpose="transpose=2"
    
    # keep time_base
    getInverseTimeBase()    {    # $1 = video path
        ffprobe -v error -select_streams v:0 -show_entries stream=time_base -of default=noprint_wrappers=1 $1 | sed 's/time_base=1\///'
    }
    inverseTimeBase=`getInverseTimeBase \$sourceVideo`
    keepTimeBase="-vsync 0 -enc_time_base -1 -video_track_timescale $inverseTimeBase"
    
    ffmpeg -v error -y -i $sourceVideo -vf "$transpose" $keepTimeBase $targetVideo || exit 3
fi

echo "Created $targetVideo" >&2

If you call the script without or with wrong arguments, it will output its syntax and some useful help. This is what happens until line 20.

Afterwards it builds a target file name out of the second argument, which has been verified to be a file. The result video will be named "somevideo_rotated.mp4" when the given video was "somevideo.mp4". (There is a shell trick to get the file's basename and extension, which is quite hard to read code, but Python isn't better:-)

If it goes for meta-data, the rest is quite simple, see line 31 and explanation above. If not, we have to do some things.

We have to make sure that the time_base of the rotated video is the same as the original. If we don't, ffmpeg will use its default 1/15360 time_base. To enforce a certain time_base, we have to pass three options to ffmpeg. You see them on line 41.

To find out the inverse time_base there is a shell function from line 37 to 39. It uses ffprobe and cuts down the "time_base=1/90000" result to "90000" using sed.

The transpose parameter is on line 34. Don't forget to edit this for other rotations than 90 degrees counterclockwise.

Both ffmpeg commands would exit the script when they go wrong, this was done by appending "|| exit 2" to the end of the command. This means when the preceding command doesn't exit with zero (= success), the code behind "||" is executed. You can find out the exit code of the script, immediately after its execution, by entering

echo $?



Keine Kommentare: