Last active
July 17, 2021 20:50
-
-
Save ericpruitt/dc5f74382b5d8595a92268424d30fda4 to your computer and use it in GitHub Desktop.
HDMI capture script.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
set -e -u | |
# Base command used to invoke FFmpeg; run "ffmpeg" with as high a priority as | |
# possible, and minimize its logging output. | |
FFMPEG="nice -n 20 ffmpeg -loglevel error -hide_banner" | |
# These control the format of the audio and video. | |
DEFAULT_VIDEO_EXTENSION="mkv" | |
SELF="${0##*/}" | |
run() | |
( | |
case "$1" in | |
--log=.*) log_file="${1#--log=}" | |
shift | |
echo "$@" | |
exec "$@" > "$log_file" 2>&1 ;; | |
*) echo "$@" | |
exec "$@" ;; | |
esac | |
) | |
silently() | |
( | |
"$@" >/dev/null 2>&1 || return 0 | |
) | |
usage() | |
{ | |
echo TODO | |
} | |
die() | |
{ | |
echo "$SELF:" "$@" >&2 | |
exit 2 | |
} | |
handle_sigint() | |
{ | |
echo && echo "Caught SIGINT; stopping video capture." | |
echo "Killing FFmpeg processes:" | |
run kill -INT $ffmpeg_pids | |
wait | |
echo "Combining audio and video streams into a single file:" | |
run $FFMPEG -i "$audio_tempfile" -i "$video_tempfile" -codec copy -map 0:a:0 -map 1:v:0 "$destination" | |
du -h "$destination" | |
run rm "$audio_tempfile" "$video_tempfile" "$ffmpeg_log_file" | |
run rmdir "$tempdir" | |
} | |
main() | |
{ | |
if [ "$#" -gt 1 ] || [ "${1:-}" = "--help" ]; then | |
usage | |
test "$#" -eq 1 && return || return 2 | |
elif [ "$#" -eq 0 ] || [ -d "$1" ]; then | |
basename="$(date +%Y-%m-%dT%H:%M:%S%z)" | |
extension="$DEFAULT_VIDEO_EXTENSION" | |
destination="${1+${1%/}/}$basename.$extension" | |
else | |
destination="$1" | |
extension="${destination##*.}" | |
fi | |
if [ -z "$extension" ] || [ "$extension" = "$destination" ]; then | |
die "$destination: destination path does not include an extension" | |
fi | |
# Make sure FFmpeg does not interpret the paths as URIs or options. | |
parent="$(dirname "$destination")" | |
case "$parent" in | |
[!/]*:*|-*) parent="./$parent" ;; | |
esac | |
case "$destination" in | |
[!/]*:*|-*) destination="./$destination" ;; | |
esac | |
tempdir=".$SELF.tmp" && test "$parent" = "." || tempdir="$parent/$tempdir" | |
ffmpeg_log_file="$tempdir/ffmpeg.log" | |
if [ -e "$tempdir" ]; then | |
printf "%s: %s already exists; overwrite and continue anyway? (n/Y) " \ | |
"$SELF" "$tempdir" | |
read -r response | |
case "$response" in | |
*[yY]*) FFMPEG="$FFMPEG -y" ;; | |
*) return 2 ;; | |
esac | |
else | |
mkdir "$tempdir" | |
FFMPEG="$FFMPEG -n" | |
fi | |
# The video grabber device connects and disconnects constantly for some | |
# reason, so the audio and video device identifiers are queried repeatedly | |
# until both are found. | |
while :; do | |
pulse_audio_id="$( | |
pactl list short sources \ | |
| awk '/usb-Video_Grabber_HDMI_to_U3_capture/ { print $1; exit }' | |
)" | |
video_device="$( | |
find /sys/devices/ -type f -name name \ | |
-exec grep -l -e '^HDMI to U3 capture$' {} + \ | |
| awk -F "/" '/video4linux/ { print "/dev/" $(NF - 1); exit }' | |
)" | |
if [ -n "$pulse_audio_id" ] && [ -n "$video_device" ]; then | |
break | |
elif [ -n "$pulse_audio_id" ]; then | |
what="video" | |
elif [ -n "$video_device" ]; then | |
what="audio" | |
else | |
what="audio or video" | |
fi | |
echo "$SELF: unable to find $what device; sleeping then trying again" | |
sleep 1 | |
done | |
# Invocation of these FFmpeg processes races against the disconnect, but in | |
# practice, FFmpeg is usually able to connect to the device quickly enough | |
# that it's rarely a problem. | |
> "$ffmpeg_log_file" | |
ffmpeg_pids="" | |
audio_tempfile="$tempdir/audio.wav" | |
run --log="$ffmpeg_log_file" $FFMPEG -f pulse -ac 2 -ar 44100 -i "$pulse_audio_id" -c copy -map 0 "$audio_tempfile" & | |
ffmpeg_pids="$ffmpeg_pids $!" | |
video_tempfile="$tempdir/video.$extension" | |
run --log="$ffmpeg_log_file" $FFMPEG -f v4l2 -i "$video_device" -c copy -map 0 "$video_tempfile" & | |
ffmpeg_pids="$ffmpeg_pids $!" | |
# If there was a problem invoking FFmpeg, the subprocesses usually die | |
# quickly, so wait briefly before checking to see if any invocations | |
# failed. | |
sleep 1 && | |
if ! kill -0 $ffmpeg_pids; then | |
silently kill -INT $ffmpeg_pids | |
cat "$ffmpeg_log_file" | |
die "unable to start FFmpeg" | |
fi | |
# Once everything is running, display the size of the captured data until | |
# the user stops recording with SIGINT / ^C. | |
trap handle_sigint INT | |
trap EXIT | |
( | |
while :; do | |
printf "\r%s " "$(du -h "$tempdir" | tr "\t" " ")" | |
sleep 1 | |
done | |
) || : | |
} | |
main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment