#!/bin/sh # settings d_bitrate_audio=448 # kb/s - max for dvd is 448 d_disc_size=4700000 # KB - standard dvd is 4.7GB so 4.7*1000*1000 d_min_bitrate_video=2300 # kb/s - video bitrate is calculated to saturate the disc d_max_bitrate_video=9000 # kb/s - max for dvd is 9800. mpeg2video is capped at 9000 however difference is negligible # functions notify() { [ -n "$V2D_NOTIFICATIONS" ] && notify-send -t 10 "$0" "$1" ; } confirm_or_quit() { notify "Confirmation needed:\n$1" while true; do printf "\n$1 [y/n] " >&2 read response case "$response" in y|Y) break ;; n|N) echo "Quitting" && exit 1 ;; esac printf "\nPlease reply with [y/n]" >&2 done } echomsg() { echo "$@" 1>&2; } get_duration() { for video in "$@"; do ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$1" done | paste -sd+ | bc | sed 's/\..*//' } create_ffconcat_file() { # ARGUMENTS concatfile="$1/ffconcat" shift for video in "$@"; do echo "file '$(realpath "$video")'" >> "$concatfile" done echo "$concatfile" } calculate_max_video_bitrate() { # ARGUMENTS duration=$1 bitrate_audio=${V2D_BITRATE_AUDIO:-$d_bitrate_audio} disc_size=${V2D_DISC_SIZE:-$d_disc_size} min_bitrate_video=${V2D_MIN_BITRATE_VIDEO:-$d_min_bitrate_video} max_bitrate_video=${V2D_MAX_BITRATE_VIDEO:-$d_max_bitrate_video} audio_filesize=$(( (bitrate_audio / 8) * duration )) # convert kb/s to KB/s bitrate_video=$(( ((disc_size - audio_filesize) / duration) * 8 )) # convert KB/s to kb/s # check bitrate if [ $bitrate_video -gt $max_bitrate_video ]; then bitrate_video=$max_bitrate_video saturated=$(( 100 * disc_size / (audio_filesize + (max_bitrate_video * duration)) )) echomsg "Video bitrate is capped to ${max_bitrate_video}kb/s" echomsg "Disc saturation: $(( saturated ))%" echomsg "Even at maximum bitrate, you are wasting $(( 100 - saturated))% of the disc! Do you still want to proceed?" # TODO #echo "Keep in mind, you can add multiple video files to a single disc by calling $0 [vid1] [vid2] ..." confirm_or_quit "Continue?" elif [ $bitrate_video -lt $min_bitrate_video ]; then echomsg "Warning low calculated bitrate: ${bitrate_video}kb/s" echomsg "This bitrate is lower than the quality threshold will allow (${min_bitrate_video}kb/s)" # TODO ##echo "At lower than ~2300kb/s, the dvd creation will likely fail since that is approximately the lowest bitrate achievable by mpeg2video" # TODO #echo #echo "You can split the video into multiple discs by using one of the following command flags:" #echo " --max-quality Split the video into multiple discs after it falls below the max dvd quality" #echo " --split-bitrate [min video bitrate] Split the video into multiple discs after it falls below the minimum bitrate" #echo " --split-time [time] Split the video into multiple discs after a max length" #echo " --split-discs [discs] Split the video evenly into X discs" confirm_or_quit "Would you still like to continue at a low bitrate?" # TODO ##if [ $bitrate_video -lt 2300 ]; then ## echo "The bitrate is below 2300kb/s, so it is nearly certain to fail" ##fi fi echo $bitrate_video } vids_to_iso() { # ARGUMENTS iso="$1" shift bitrate_audio=${V2D_BITRATE_AUDIO:-$d_bitrate_audio} # make and trap temporary files # TODO #tmpfs=${TMPDIR:-/tmp} #tmpfs_size=$(df -k --output=avail /tmp | tail -1) # Size in kB. Should be size in KB (--block-size=KB over -k) but this is more convenient and allows for extra space #[ $tmpfs_size -lt $(( disc_size * 2 )) ] && tmpfs=. # Just create it in local directory instead. Not ideal since it could persist between reboots if the trap doesnt function tmpfs=. tmpdir="$(mktemp -d "$tmpfs/vid2dvd.XXXXXX")" trap "{ rm -rf '$tmpdir'; exit 1; }" EXIT INT # create concat file concat_file="$(create_ffconcat_file "$tmpdir" "$@")" # get bitrate echomsg "Finding optimal bitrate for video..." videos_duration=$(get_duration "$@") bitrate_video=$(calculate_max_video_bitrate $videos_duration) echomsg "Audio bitrate: ${bitrate_audio}kb/s" echomsg "Video bitrate: ${bitrate_video}kb/s (Calculated)" echomsg # transcode with ffmpeg, author with dvdauthor, convert to iso with genisoimage echomsg "Transcoding videos to correct ntsc dvd-video codecs. This could take a while..." mpegvid="$tmpdir/vid.mpg" ffmpeg -hide_banner -f concat -safe 0 -i "$concat_file" -target ntsc-dvd -b:a ${bitrate_audio}k -b:v ${bitrate_video}k "$mpegvid" mpegvid_size=$(du "$mpegvid" | grep -o "^[0-9]*") #if [ $mpegvid_size -gt $disc_size ]; then # echomsg "Conversion failed! Converted video ($mpegvid) is larger than the set disc size!" # echomsg "Video size: $mpegvid_size" # echomsg "Disc size: $disc_size" # confirm_or_quit "Continue regardless? The final iso will be larger than a normal disc" #fi echomsg "Done\n" echomsg "Authoring DVD. This will also take some time..." dvddir="$tmpdir/dvd" mkdir -p "$dvddir" dvdauthor -o "$dvddir" --title "$mpegvid" 2>/dev/null # Generates the VIDEO_TS and AUDIO_TS folders with VOB files from the video stream dvdauthor -o "$dvddir" --toc 2>/dev/null # Generate table of contents for disc echomsg "Done\n" echomsg "Creating ISO image. You thought the last two steps were long? Well lucky you, this will be quick... relatively speaking..." genisoimage -dvd-video -o "$iso" "$dvddir" # Generate the disc image echomsg "Done\n" notify "Job completed iso created\n$@" echo "$iso" } # TODO # burn to blank dvd help() { echomsg "Usage: $0 [arguments] [video] ... [[-c append video] ...]" echomsg echomsg "Standard arguments:" echomsg " -h, --help Output this menu" echomsg " -n, --enable-notifications Enable sending notifications with notify-send" echomsg " -o, --output-directory [directory] Put created isos into directory" echomsg " -d, --output-iso-size [size in kb] Maximum size of iso to be created (Default: $d_disc_size)" echomsg " -a, --bitrate-audio [bitrate in kb/s] Audio bitrate. Max for standard dvd is 448 (Default: $d_bitrate_audio)" echomsg " -V, --bitrate-video-max [bitrate in kb/s] Maximum video bitrate. Max for standard dvd is 9800, but max for ffmpeg is 9000 (Default: $d_min_bitrate_video)" echomsg " -v, --bitrate-video-min [bitrate in kb/s] Minimum video bitrate. Realistic minimum is ~2300 (Default: $d_min_bitrate_video)" echomsg echomsg "Video arguments:" echomsg " [video1] [-c, --concat] [video2] ... Concatenate each video in the chain into a single dvd (still a single track)" exit ${1:-1} } # process arguments while [ -n "$1" ]; do case "$1" in -o|--output-directory) shift && export V2D_OUTDIR=$1 ;; -d|--output-iso-size) shift && export V2D_DISC_SIZE=$1 ;; -a|--bitrate-audio) shift && export V2D_BITRATE_AUDIO=$1 ;; -V|--bitrate-video-max) shift && export V2D_BITRATE_VIDEO_MAX=$1 ;; -v|--bitrate-video-min) shift && export V2D_BITRATE_VIDEO_MIN=$1 ;; -n|--enable-notifications) export V2D_NOTIFICATIONS=1 ;; -h|--help) help 0 ;; -*) echo "Invalid argument '$1'\n" && help 1 ;; *) break ;; esac shift done for video in "$@"; do if [ -n "$V2D_OUTDIR" ]; then mkdir -p "$V2D_OUTDIR" mvideo="$V2D_OUTDIR/$(basename "$video")" fi out="$(echo "${mvideo:-$video}" | sed 's/\.[^.]*$//').iso" if [ -f "$out" ]; then echomsg "Output file already exists! => $out" echomsg "Skipping" continue fi vids_to_iso "$out" "$video" done