#!/bin/sh #=============================================================================== # trim-remote # trim a remote video clip with millisecond accuracy #=============================================================================== #=============================================================================== # script usage #=============================================================================== usage () { # if argument passed to function echo it [ -z "${1}" ] || echo "! ${1}" # display help echo "\ # trim a remote video clip with millisecond accuracy https://trac.ffmpeg.org/wiki/Seeking $(basename "$0") -s 00:00:00.000 -i url -t 00:00:00.000 -o output -s 00:00:00.000 : start time -i url -t 00:00:00.000 : number of seconds after start time -o output.(mp4|webm|aac|mp3|wav|ogg) : optional argument # if option not provided defaults input-name-[start end].mp4" exit 2 } #=============================================================================== # error messages #=============================================================================== INVALID_OPT_ERR='Invalid option:' REQ_ARG_ERR='requires an argument' WRONG_ARGS_ERR='wrong number of arguments passed to script' #=============================================================================== # check number of aruments passed to script #=============================================================================== [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" #=============================================================================== # getopts check options passed to script #=============================================================================== while getopts ':s:i:t:o:h' opt do case ${opt} in s) start="${OPTARG}";; i) input="${OPTARG}";; t) end="${OPTARG}";; h) usage;; o) outfile="${OPTARG}";; \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; esac done shift $((OPTIND-1)) #=============================================================================== # variables #=============================================================================== # yt-dlp url url=$(yt-dlp -i -f b -g --no-playlist --print '%(title)s' "${input}") # check number of stream links streamcount=$(echo "${url}" | awk '/^https?:\/\// {count++} END{ print count }') urllist=$(echo "${url}" | awk '/^https?:\/\// { print $0 }') # awk convert start and end time from sexagesimal time to seconds # add the start and end time to get the endpoint and convert from seconds back to sexagesimal time endtime=$(printf "%s %s\n" "${start}" "${end}" \ | awk ' { start = $1 end = $2 if (start ~ /:/) { split(start, t, ":") sseconds = (t[1] * 3600) + (t[2] * 60) + t[3] } if (end ~ /:/) { split(end, t, ":") eseconds = (t[1] * 3600) + (t[2] * 60) + t[3] } duration = eseconds + sseconds printf("%s\n"), duration }' \ | awk -F. 'NF==1 { printf("%02d:%02d:%02d\n"), ($1 / 3600), ($1 % 3600 / 60), ($1 % 60) }\ NF==2 { printf("%02d:%02d:%02d.%s\n"), ($1 / 3600), ($1 % 3600 / 60), ($1 % 60), ($2) }' ) # defaults for variables if not defined videotitle=$(echo "${url}" | awk '$1 !~ /^https?:\/\// { print $0 }') videotitle_fallback='video' videotitle_default="${videotitle:=${videotitle_fallback}}-[${start}-${endtime}].mp4" #=============================================================================== # one stream containing audio and video #=============================================================================== # trim video and re-encode one_stream_trim () { video_url=$(echo "${urllist}" | awk 'BEGIN{ RS ="" ; FS ="\n" }{print $1}') ffmpeg \ -hide_banner \ -stats -v panic \ -ss "${start}" \ -i "${video_url}" \ -t "${end}" \ -c:a aac \ -c:v libx264 -profile:v high \ -pix_fmt yuv420p -movflags +faststart \ -f mp4 \ "${outfile:=${videotitle_default}}" } #=============================================================================== # two streams containing audio and video #=============================================================================== # trim video and re-encode two_streams_trim () { video_url=$(echo "${urllist}" | awk 'BEGIN{ RS ="" ; FS ="\n" }{print $1}') audio_url=$(echo "${urllist}" | awk 'BEGIN{ RS ="" ; FS ="\n" }{print $2}') # ffmpeg join audio and video and stream ffmpeg \ -hide_banner \ -stats -v panic \ -ss "${start}" \ -i "${video_url}" \ -i "${audio_url}" \ -t "${end}" \ -map 0:0 -map 1:0 \ -c:a aac \ -c:v libx264 -profile:v high \ -pix_fmt yuv420p -movflags +faststart \ -f mp4 \ "${outfile:=${videotitle_default}}" } #=============================================================================== # case statement check for number of streams #=============================================================================== case "${streamcount}" in 1) one_stream_trim;; # single stream 2) two_streams_trim;; # audio and video stream *) usage;; esac