User Tools

Site Tools


doc:appunti:linux:video:fix_smartphone_portrait_videos

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
doc:appunti:linux:video:fix_smartphone_portrait_videos [2018/04/07 07:48] niccolodoc:appunti:linux:video:fix_smartphone_portrait_videos [2018/04/07 15:47] (current) – [The Gradient Overlay Mask] niccolo
Line 8: Line 8:
 {{original_video_black_bands.jpg?direct&300|The video viewd with lateral black bands}} {{original_video_black_bands.jpg?direct&300|The video viewd with lateral black bands}}
  
-{{final_video_interpolated.jpg?direct&300|The interpolated video}}+===== The Compromise Solution ===== 
 + 
 +A compromise solution is to **cut out a central area of the video** and **add to the sides some artifact** that simulates the presence of a portion of video that does not actually exist. I find that solution by far more acceptable than the original video: 
 + 
 +{{final_video_interpolated.jpg?direct&380|The interpolated video}} 
 + 
 +So we have to: 
 + 
 +  - Choose a **region to crop** from the original video. It can vary from a square to the full portrait rectangle. 
 +  - Make a **fake background** blurring the cropped region and stretching it to the 16:9 aspect ratio. 
 +  - **Overlay** the cropped region over the fake background. 
 + 
 +{{choose_crop_area.png?direct&280 |Choose the crop area}} 
 + 
 +We wrote a **[[#the_python_script|Python script]]** to assist in step #1, which calculate all the required numbers just provinding two parameters: the **crop_aspect** and the **offset_y**. 
 + 
 +Adjust the **crop_aspect** from 0.0 (which selects a square) to 1.0 (which selects the full rectangle of the picture). Using a square-shaped crop area we will get the **highest zoom** level into the video, but we will cut-out  a greather part of the frames, above and below the cropped region. 
 + 
 +We can move the cropped region to the top of the image by setting **offset_y** to 0.0. Move it to te bottom by setting it to 1.0. To crop the image at the **center** of the screen, use an //offset_y// of **0.5**. If you use a //crop_aspect// equal to 1.0 (full frame rectangle), the //offset_y// will be ignored. 
 +===== Cropped Region and Fake Background ===== 
 + 
 +Once selected the cropped region, a **fake background** is created by simply **stretching** the region to **16:9 aspect ratio** and applying a **blur effect**. 
 + 
 +{{cropped_region.jpg?direct&200|The Cropped Region}} 
 +{{fake_background.jpg?direct&356|The Fake Background}} 
 + 
 +===== The Gradient Overlay Mask ===== 
 + 
 +When the cropped image is placed above the background, it is quite annoying to see the **sharp edge** of the image. A better approach is to gradually fade the image at the right and at left edges. This requires to blend the image and the background using a **gradient mask**. We prepared the mask as a **{{gradient_overlay_mask.png?linkonly|PNG image}}**, using **convert** from the ImageMagick suite. 
 + 
 +<code> 
 +convert -size 768x768 \ 
 +    -define "gradient:bounding-box=768x38+0+0" -define "gradient:vector=0,37,0,0"
 +    gradient:none-black \ 
 +    -define "gradient:bounding-box=768x768+0+730" -define "gradient:vector=0,730,0,767" 
 +    gradient:none-black \ 
 +    -composite -channel a -negate -rotate 90 tmp_mask.png 
 +</code> 
 + 
 +**NOTICE**: It seems that [[https://www.imagemagick.org/discourse-server/viewtopic.php?f=3&t=33804|there is a bug]] in ImageMagick **gradient:bounding-box**, so we had to generate the mask in top-down mode and rotate it 90 degrees afterward. 
 +===== The ffmpeg Recipe ===== 
 + 
 +The first step is to **remove the rotation metatag** from the input video. It seems that ffmpeg is unable to remove the metatag and apply the required video filters in the same pass, so we need to make a temporary copy of the input video file, without re-encoding it: 
 + 
 +<code> 
 +ffmpeg -i input_video.mp4 -c copy -metadata:s:v:0 rotate=0 tmp_norotate.mp4 
 +</code> 
 + 
 +finally we invoke **ffmpeg** to do all the magic. We use a **filter_complex** incantation: 
 + 
 +<code> 
 +ffmpeg -i tmp_norotate.mp4 -loop 1 -i tmp_mask.png -filter_complex "\ 
 +  [0:v]split [a][b]; \ 
 +  [a]transpose=1,crop=720:720:0:280,scale=768:768,setdar=768/768 [crop]; \ 
 +  [b]transpose=1,crop=720:720:0:280,scale=1366:768,setdar=16/9,avgblur=54 [back]; \ 
 +  [1:v]alphaextract [mask]; \ 
 +  [crop][mask]alphamerge [masked]; \ 
 +  [back][masked]overlay=299:0 \ 
 +" video_out.mp4 
 +</code> 
 + 
 +The first video stream **%%[0:v]%%** coming from the video file is splitted in two. The first copy, called **%%[a]%%**, is used to crop the interesting part after the rotation (//transpose//), thus creating the **%%[crop]%%** stream. The second copy, called **%%[b]%%**, is used to create the background applying rotation, stretch and blur filters, thus creating the **%%[back]%%** stream. The mask image is used in loop to create another video stream **%%[1:v]%%**, from which we extract the alpha channel (transparency) creating a stream called **%%[mask]%%**. The **%%[crop]%%** stream is merged with the **%%[mask]%%**, providing the **%%[masked]%%** stream. Finally the **%%[masked]%%** stream is overlayed to the **%%[back]%%** one, to realize the final output. 
 + 
 +===== The Python Script ===== 
 + 
 +The Python script **{{smartphonevideo_2landscape.txt|smartphonevideo_2landscape}}** will make the calculation to prepare the whole incantation: it will print on the standard output a **shell script** with the proper invokation of **convert** and **ffmpeg**. Use it in this way: 
 + 
 +<code> 
 +smartphonevideo_2landscape video_file [crop_aspect] [offset_y] > ffmpeg_recipe 
 +source ffmpeg_recipe 
 +</code> 
 + 
 +You have to edit the script to adjust the size of the input video (default 1280x720), the rotation needed (default 1 = 90 deg clockwise), and the size of output video produced (default 1366x768).
doc/appunti/linux/video/fix_smartphone_portrait_videos.1523080127.txt.gz · Last modified: 2018/04/07 07:48 by niccolo