some additions
This commit is contained in:
@@ -0,0 +1,319 @@
|
||||
function vrot -d 'Rotate a video with ffmpeg: metadata-only by default, re-encode with -r'
|
||||
argparse -n vrot 'h/help' 'r/reencode' 'a/angle=' 'p/preset=' -- $argv
|
||||
or return 2
|
||||
|
||||
if set -q _flag_help
|
||||
__vrot_usage
|
||||
return 0
|
||||
end
|
||||
|
||||
if test (count $argv) -lt 1
|
||||
__vrot_usage
|
||||
return 2
|
||||
end
|
||||
|
||||
set -q _flag_angle; or set _flag_angle 90
|
||||
set -q _flag_preset; or set _flag_preset medium
|
||||
|
||||
switch $_flag_angle
|
||||
case 90 180 270
|
||||
case '*'
|
||||
__vrot_error "angle must be 90, 180 or 270 (got '$_flag_angle')"
|
||||
return 2
|
||||
end
|
||||
|
||||
__vrot_colors
|
||||
|
||||
set -l want_reencode 0
|
||||
set -q _flag_reencode; and set want_reencode 1
|
||||
|
||||
set -l rc 0
|
||||
for file in $argv
|
||||
if not test -f "$file"
|
||||
__vrot_error "no such file: '$file'"
|
||||
set rc 1
|
||||
continue
|
||||
end
|
||||
__vrot_one "$file" "$_flag_angle" "$_flag_preset" "$want_reencode"
|
||||
or set rc 1
|
||||
end
|
||||
return $rc
|
||||
end
|
||||
|
||||
function __vrot_one
|
||||
set -l file $argv[1]
|
||||
set -l angle $argv[2]
|
||||
set -l preset $argv[3]
|
||||
set -l want_reencode $argv[4]
|
||||
|
||||
set -l name (path basename "$file")
|
||||
set -l ext (path extension "$file")
|
||||
set -l out (path change-extension '' "$file")_rot$ext
|
||||
set -l outname (path basename "$out")
|
||||
|
||||
__vrot_banner "$name → $outname"
|
||||
|
||||
set -l lines (ffprobe -v error -select_streams v:0 \
|
||||
-show_entries stream=codec_name,width,height,r_frame_rate,nb_frames:format=duration,bit_rate \
|
||||
-of default=noprint_wrappers=1 "$file")
|
||||
|
||||
set -l codec (string match 'codec_name=*' -- $lines | string replace 'codec_name=' '')[1]
|
||||
set -l width (string match 'width=*' -- $lines | string replace 'width=' '')[1]
|
||||
set -l height (string match 'height=*' -- $lines | string replace 'height=' '')[1]
|
||||
set -l fpsraw (string match 'r_frame_rate=*' -- $lines | string replace 'r_frame_rate=' '')[1]
|
||||
set -l frames (string match 'nb_frames=*' -- $lines | string replace 'nb_frames=' '')[1]
|
||||
set -l duration (string match 'duration=*' -- $lines | string replace 'duration=' '')[1]
|
||||
set -l bitrate (string match 'bit_rate=*' -- $lines | string replace 'bit_rate=' '')[1]
|
||||
|
||||
set -l info "$width"x"$height"
|
||||
if test -n "$codec"
|
||||
set info "$info "(string upper "$codec")
|
||||
end
|
||||
if test -n "$fpsraw"; and string match -rq '^[0-9]+/[0-9]+$' "$fpsraw"; and not string match -q '*/0' "$fpsraw"
|
||||
set info "$info "(math -s 2 "$fpsraw")" fps"
|
||||
end
|
||||
if test -n "$duration"; and string match -rq '^[0-9.]+$' "$duration"
|
||||
set info "$info "(__vrot_fmtsecs "$duration")
|
||||
end
|
||||
if test -n "$bitrate"; and string match -rq '^[0-9.]+$' "$bitrate"
|
||||
set info "$info "(math -s 1 "$bitrate/1000000")" Mbit/s"
|
||||
end
|
||||
echo "$vrot_c_dim $info$vrot_c_reset"
|
||||
|
||||
if test "$file" = "$out"
|
||||
__vrot_error "output would overwrite input: '$out'"
|
||||
return 1
|
||||
end
|
||||
|
||||
if test -e "$out"
|
||||
read -l -P "vrot: '$out' exists. Overwrite? [y/N] " ans
|
||||
if not string match -qi 'y' "$ans"
|
||||
echo "$vrot_c_dim skipped: $out$vrot_c_reset"
|
||||
return 1
|
||||
end
|
||||
rm -f "$out"
|
||||
end
|
||||
|
||||
if test "$want_reencode" -eq 1
|
||||
__vrot_reencode "$file" "$out" "$angle" "$preset" "$height" "$codec" "$duration" "$frames"
|
||||
return $status
|
||||
end
|
||||
|
||||
echo "$vrot_c_dim mode: metadata-only rotation (lossless, instant)$vrot_c_reset"
|
||||
ffmpeg -hide_banner -loglevel error -nostdin -i "$file" -map 0 -c copy -ignore_unknown \
|
||||
-movflags +faststart -metadata:s:v:0 rotate=$angle "$out"
|
||||
if test $status -eq 0
|
||||
__vrot_summary "$out"
|
||||
return 0
|
||||
end
|
||||
|
||||
echo "$vrot_c_warn ↷ dropping unsupported data/attachment tracks, retrying…$vrot_c_reset"
|
||||
rm -f "$out"
|
||||
ffmpeg -hide_banner -loglevel error -nostdin -i "$file" -map 0 -map '-0:d' -map '-0:t' \
|
||||
-c copy -movflags +faststart -metadata:s:v:0 rotate=$angle "$out"
|
||||
if test $status -eq 0
|
||||
__vrot_summary "$out"
|
||||
return 0
|
||||
end
|
||||
|
||||
echo "$vrot_c_warn ↷ metadata rotation unsupported here, re-encoding instead…$vrot_c_reset"
|
||||
rm -f "$out"
|
||||
__vrot_reencode "$file" "$out" "$angle" "$preset" "$height" "$codec" "$duration" "$frames"
|
||||
end
|
||||
|
||||
function __vrot_reencode
|
||||
set -l file $argv[1]
|
||||
set -l out $argv[2]
|
||||
set -l angle $argv[3]
|
||||
set -l preset $argv[4]
|
||||
set -l height $argv[5]
|
||||
set -l codec $argv[6]
|
||||
set -l duration $argv[7]
|
||||
set -l frames $argv[8]
|
||||
|
||||
test -n "$height"; or set height 1080
|
||||
|
||||
set -l enc libx264
|
||||
switch "$codec"
|
||||
case 'hevc*' 'h265*' 'h266*'
|
||||
set enc libx265
|
||||
end
|
||||
|
||||
set -l crf 20
|
||||
set -l maxrate 20M
|
||||
set -l bufsize 40M
|
||||
if test $height -ge 2160
|
||||
set crf 22; set maxrate 40M; set bufsize 80M
|
||||
else if test $height -ge 1440
|
||||
set crf 21; set maxrate 30M; set bufsize 60M
|
||||
else if test $height -ge 1080
|
||||
set crf 20; set maxrate 20M; set bufsize 40M
|
||||
else if test $height -ge 720
|
||||
set crf 19; set maxrate 10M; set bufsize 20M
|
||||
else
|
||||
set crf 18; set maxrate 6M; set bufsize 12M
|
||||
end
|
||||
|
||||
set -l vf 'transpose=1'
|
||||
switch $angle
|
||||
case 180
|
||||
set vf 'transpose=1,transpose=1'
|
||||
case 270
|
||||
set vf 'transpose=2'
|
||||
end
|
||||
|
||||
set -l faststart
|
||||
switch (string lower (path extension "$out"))
|
||||
case '.mp4' '.mov' '.m4v'
|
||||
set faststart '-movflags' '+faststart'
|
||||
end
|
||||
|
||||
echo "$vrot_c_dim mode: re-encode ($enc · crf $crf · cap $maxrate)$vrot_c_reset"
|
||||
|
||||
set -l total_frames ''
|
||||
string match -rq '^\d+$' "$frames"; and test "$frames" -gt 0; and set total_frames $frames
|
||||
|
||||
set -l out_t ''
|
||||
set -l frame 0
|
||||
set -l fps_txt ''
|
||||
set -l bitrate_txt ''
|
||||
set -l speed_txt ''
|
||||
|
||||
ffmpeg -hide_banner -loglevel error -nostdin -progress pipe:1 -i "$file" \
|
||||
-map 0:v -map '0:a?' -map '0:s?' -ignore_unknown -vf "$vf" -c:v $enc -crf $crf \
|
||||
-maxrate $maxrate -bufsize $bufsize -preset $preset -c:a copy -c:s copy \
|
||||
$faststart "$out" | while read -l line
|
||||
switch "$line"
|
||||
case 'frame=*'
|
||||
set frame (string replace 'frame=' '' -- "$line")
|
||||
case 'fps=*'
|
||||
set fps_txt (string replace 'fps=' '' -- "$line")"fps"
|
||||
case 'bitrate=*'
|
||||
set bitrate_txt (string replace 'bitrate=' '' -- "$line")
|
||||
if string match -q '*kbits/s' "$bitrate_txt"
|
||||
set bitrate_txt (math -s 1 (string replace 'kbits/s' '' -- "$bitrate_txt") / 1000)"Mbit/s"
|
||||
else if string match -q '*Mbits/s' "$bitrate_txt"
|
||||
set bitrate_txt (string replace 'Mbits/s' 'Mbit/s' -- "$bitrate_txt")
|
||||
end
|
||||
case 'out_time=*'
|
||||
set -l t (string replace 'out_time=' '' -- "$line")
|
||||
set -l parts (string split ':' -- "$t")
|
||||
if test (count $parts) -eq 3; and string match -rq '^\d+$' "$parts[1]"; and string match -rq '^\d+$' "$parts[2]"; and string match -rq '^[\d.]+$' "$parts[3]"
|
||||
set out_t (math "$parts[1]*3600+$parts[2]*60+$parts[3]")
|
||||
end
|
||||
case 'speed=*'
|
||||
set speed_txt (string replace 'speed=' '' -- "$line")
|
||||
end
|
||||
if isatty stdout
|
||||
printf '\r\033[K'
|
||||
set -l pct '--.-%'
|
||||
set -l eta_txt ''
|
||||
set -l bar (string repeat -n 24 '░')
|
||||
set -l frac ''
|
||||
if test -n "$total_frames"; and string match -rq '^\d+$' "$frame"; and test "$total_frames" -gt 0
|
||||
set frac (math "min(1, $frame/$total_frames)")
|
||||
else if test -n "$out_t"; and string match -rq '^[0-9.]+$' "$duration"; and not string match -rq '^0+(\.0*)?$' "$duration"
|
||||
set frac (math "min(1, $out_t/$duration)")
|
||||
end
|
||||
if test -n "$frac"
|
||||
set -l filled (math "floor($frac*24)")
|
||||
set -l bar (string repeat -n $filled '█')
|
||||
set bar "$bar"(string repeat -n (math "24-$filled") '░')
|
||||
set pct (math -s 1 "$frac*100")%
|
||||
set -l sp (string replace -r '[xX]$' '' -- "$speed_txt")
|
||||
if test -n "$sp"; and string match -rq '^[0-9.]+$' "$sp"; and not string match -rq '^0+(\.0*)?$' "$sp"; and test -n "$out_t"; and string match -rq '^[0-9.]+$'
|
||||
"$duration"
|
||||
set eta_txt " ETA "(__vrot_fmtsecs (math "($duration - $out_t)/$sp"))
|
||||
end
|
||||
end
|
||||
set -l frame_txt $frame
|
||||
if test -n "$total_frames"
|
||||
set frame_txt "$frame/$total_frames"
|
||||
end
|
||||
echo -n " $vrot_c_ok$bar$vrot_c_reset $pct frame=$frame_txt $fps_txt $bitrate_txt $speed_txt$eta_txt"
|
||||
end
|
||||
end
|
||||
set -l ff_status $pipestatus[1]
|
||||
printf '\n'
|
||||
|
||||
if test $ff_status -ne 0
|
||||
__vrot_error "encoding failed (ffmpeg exited $ff_status)"
|
||||
return 1
|
||||
end
|
||||
__vrot_summary "$out"
|
||||
end
|
||||
|
||||
function __vrot_summary
|
||||
set -l out $argv[1]
|
||||
set -l size ''
|
||||
if command -q numfmt
|
||||
set size (numfmt --to=iec --suffix=B -- (stat -c %s -- "$out") 2>/dev/null)
|
||||
else
|
||||
set size (du -h -- "$out" | string replace -r '\t.*' '')
|
||||
end
|
||||
set -l dims (ffprobe -v error -select_streams v:0 \
|
||||
-show_entries stream=width,height -of default=noprint_wrappers=1:nokey=1 "$out" \
|
||||
| string match -r '^\d+$' | string join '×')
|
||||
set -l extra ''
|
||||
if test -n "$dims"
|
||||
set extra " · $dims"
|
||||
end
|
||||
echo "$vrot_c_ok ✔ $out$vrot_c_dim ($size$extra)$vrot_c_reset"
|
||||
end
|
||||
|
||||
function __vrot_banner
|
||||
set -l title $argv[1]
|
||||
echo "$vrot_c_dim"(string repeat -n 58 '─')"$vrot_c_reset"
|
||||
echo "$vrot_c_hi $title$vrot_c_reset"
|
||||
end
|
||||
|
||||
function __vrot_colors
|
||||
set -q vrot_colors_set; and return 0
|
||||
set -g vrot_colors_set 1
|
||||
if isatty stdout
|
||||
set -g vrot_c_hi (set_color brwhite --bold)
|
||||
set -g vrot_c_dim (set_color brblack)
|
||||
set -g vrot_c_ok (set_color brgreen)
|
||||
set -g vrot_c_warn (set_color bryellow)
|
||||
set -g vrot_c_err (set_color brred)
|
||||
set -g vrot_c_reset (set_color normal)
|
||||
else
|
||||
for c in hi dim ok warn err reset
|
||||
set -g vrot_c_$c ''
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function __vrot_fmtsecs
|
||||
set -l s (math "max(0, round($argv[1]))")
|
||||
set -l h (math "floor($s/3600)")
|
||||
set -l m (math "floor($s%3600/60)")
|
||||
set -l sec (math "$s%60")
|
||||
if test $h -gt 0
|
||||
printf '%02d:%02d:%02d' $h $m $sec
|
||||
else
|
||||
printf '%02d:%02d' $m $sec
|
||||
end
|
||||
end
|
||||
|
||||
function __vrot_error
|
||||
echo "$vrot_c_err""vrot: "$argv"$vrot_c_reset" >&2
|
||||
end
|
||||
|
||||
function __vrot_usage
|
||||
echo 'usage: vrot [options] <video> [<video> ...]'
|
||||
echo
|
||||
echo 'Rotates one or more videos and writes <name>_rot.<ext> next to each.'
|
||||
echo
|
||||
echo 'Default mode is metadata-only rotation: lossless and instant. Works for'
|
||||
echo 'MP4/MOV/M4V (including GoPro files; unsupported data/timecode tracks are'
|
||||
echo 'dropped automatically). If the container cannot carry the flag, vrot'
|
||||
echo 'falls back to re-encoding, with a live progress bar. Players that'
|
||||
echo 'ignore rotation flags need -r.'
|
||||
echo
|
||||
echo 'options:'
|
||||
echo ' -a, --angle <90|180|270> clockwise angle (default 90)'
|
||||
echo ' -r, --reencode rotate pixels by re-encoding'
|
||||
echo ' -p, --preset <name> x264/x265 preset (default medium)'
|
||||
echo ' -h, --help show this help'
|
||||
end
|
||||
Reference in New Issue
Block a user