Skip to content

Instantly share code, notes, and snippets.

@zzzeek
Created December 29, 2022 15:01
Show Gist options
  • Save zzzeek/ac8ed30e96e1c6fd1b494bd1e5d895b4 to your computer and use it in GitHub Desktop.
Save zzzeek/ac8ed30e96e1c6fd1b494bd1e5d895b4 to your computer and use it in GitHub Desktop.
the record.sh script
#!/usr/bin/env bash
# notes:
# tuning camera: gtk-v4l package, v4l2-ctl
# we also have:
# ffmpeg, mpg123
# linux
#RESOLUTION="640x480"
#RESOLUTION="800x448"
#RESOLUTION="720x405"
#RESOLUTION="1024x576"
RESOLUTION="1280x720"
#RESOLUTION="1920x1080"
# linux
#CAMERA_PATTERN="C615"
CAMERA_PATTERN="C922"
# linux
FANCY_PACTL_DEVICE="alsa_input.usb-Burr-Brown_from_TI_USB_Audio_CODEC-00.iec958-stereo"
PLAIN_PACTL_DEVICE="alsa_input.pci-0000_00_1f.3.analog-stereo"
ARTIST="Wooly"
SCRIPT=$0
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
LINUX=1
elif [[ "$OSTYPE" == "darwin"* ]]; then
OSX=1
else
echo "unsupported OS $OSTYPE"
exit -1
fi
if [[ ${LINUX} ]]; then
INTERIM_VIDEO_EXT=mkv
INTERIM_AUDIO_EXT=wav
RAW_VIDEO_EXT=mkv
fi
if [[ ${OSX} ]]; then
# these have to be very compatible with the .mov format, copy codecs
# need to work well
INTERIM_VIDEO_EXT=mp4
INTERIM_AUDIO_EXT=m4a
RAW_VIDEO_EXT=mov
fi
FFMPEG_EQ='-af treble=g=12:f=4000:width_type=s:w=0.8,bass=g=12:f=70:width_type=s:w=1'
SOX_EQ="treble +12 4000 0.8s bass +12 70 1s"
# note to get this to play on Plex / Roku we had to tune down
# the format to use "main / 4:2:0" and not "high / 4:2:2"
FFMPEG_VIDEO_PARAMS="libx264 -profile:v main -pix_fmt yuv420p"
# FFMPEG LINKS, regarding different issues that generally grant some background
# on how things work
# https://askubuntu.com/questions/1294148/error-queue-input-is-backward-in-time-when-to-capture-audio-with-ffmpeg
# https://wjwoodrow.wordpress.com/2013/02/04/correcting-for-audiovideo-sync-issues-with-the-ffmpeg-programs-itsoffset-switch/
# https://trac.ffmpeg.org/wiki/Encode/H.264
# https://trac.ffmpeg.org/ticket/692
# https://superuser.com/questions/1584945/ffmpeg-audio-start-time-way-off
# https://gist.github.com/eyecatchup/0757b3d8b989fe433979db2ea7d95a01
SSH_HOST="msi@diskstation"
MUSIC_DEST="/volume1/wooly"
VIDEO_DEST="/volume1/video/wooly"
WORK="${HOME}/record"
CURRENT="${WORK}/current"
OUTGOING="${WORK}/outgoing"
TRASH="${WORK}/trash"
set -m
setup_workspace() {
mkdir -p ${WORK}
mkdir -p ${CURRENT}
mkdir -p ${OUTGOING}
mkdir -p ${TRASH}
}
find_audio () {
if [[ "${LINUX}" ]]; then
audio_dev=$( pactl list sources short | grep -A1 "${FANCY_PACTL_DEVICE}" )
if [[ ! "${audio_dev}" ]]; then
audio_dev=$( pactl list sources short | grep -A1 "${PLAIN_PACTL_DEVICE}" )
if [[ ! "${audio_dev}" ]]; then
echo "no audio device"
exit -1
else
AUDIO_DEVICE="${PLAIN_PACTL_DEVICE}"
fi
else
AUDIO_DEVICE="${FANCY_PACTL_DEVICE}"
fi
echo "Will use $AUDIO_DEVICE to record audio"
elif [[ "${OSX}" ]]; then
echo "will use quicktime player to record audio"
echo "select appropriate audio device when it opens"
fi
}
find_video() {
if [[ "${LINUX}" ]]; then
webcam_dev=$( v4l2-ctl --list-devices | grep -A1 "${CAMERA_PATTERN}" | grep "/dev/" )
if [[ ! "${webcam_dev}" ]]; then
VIDEO_DEVICE="/dev/video0"
else
VIDEO_DEVICE="${webcam_dev}"
fi
echo "Will use $VIDEO_DEVICE to record video"
elif [[ "${OSX}" ]]; then
echo "will use quicktime player to record video"
echo "select appropriate video device when quicktime recorder opens"
cat <<EOF | osascript;
tell application "QuickTime Player"
activate
new movie recording
end tell
EOF
fi
}
queue_files_for_transfer() {
local the_date="$1"
set -x
audio_filepath=$(outgoing_filepath "${the_date}" "mp3")
cp ${CURRENT}/out_processed.mp3 "${audio_filepath}"
if [ -f "${CURRENT}/out_processed.${INTERIM_VIDEO_EXT}" ]; then
video_filepath=$(outgoing_filepath "${the_date}" "raw.${INTERIM_VIDEO_EXT}")
cp ${CURRENT}/out_processed.${INTERIM_VIDEO_EXT} "${video_filepath}"
fi
$SCRIPT transfer_audio "${the_date}" >> ${WORK}/transfer.log 2>&1 &
set +x
}
format_date() {
local the_date="$1"
local format="$2"
if [[ $LINUX ]]; then
local result=`date --date "${the_date}" +"${format}"`
fi
if [[ $OSX ]]; then
local result=`date -j -f "%Y-%m-%d %H:%M:%S" "${the_date}" +"${format}"`
fi
echo $result
}
outgoing_filepath() {
local the_date="$1"
local extension="$2"
local titleinfo="$3"
local timestamp="$the_date";
if [ "${titleinfo}" != "" ]; then
echo "${OUTGOING}/${timestamp} ${titleinfo}.${extension}"
else
echo "${OUTGOING}/${timestamp}.${extension}"
fi
}
outgoing_filepath_w_counter() {
local counter="$1"
local the_date="$2"
local extension="$3"
local titleinfo="$4"
local timestamp="$the_date";
if [ "${titleinfo}" != "" ]; then
echo "${OUTGOING}/${counter} - ${timestamp} ${titleinfo}.${extension}"
else
echo "${OUTGOING}/${counter} - ${timestamp}.${extension}"
fi
}
transfer_audio() {
local the_date="$1"
local daystamp=$(format_date "${the_date}" "%Y%m%d")
local dayprefix=$(format_date "${the_date}" "%Y-%m-%d")
local year=`date +"%Y"`
audio_filepath=$(outgoing_filepath "${the_date}" "mp3")
set -x
if [ -f "${audio_filepath}" ]; then
dest_dir="${MUSIC_DEST}/${ARTIST} ${year}/${daystamp}"
ssh $SSH_HOST "mkdir -p '${dest_dir}'"
counter=$( ls "${OUTGOING}"/??" - ${dayprefix}"* | wc -l )
counter=$( expr $counter + 1 )
counter=$( printf "%.2d" $counter )
# this is an attempt to get plex to group these songs into the
# *same* album, by adding the "01 - FOO BAR" format. otherwise
# it makes an album for each track (or few tracks) with the same
# album name over and over.
audio_filepath_w_counter=$(outgoing_filepath_w_counter "${counter}" "${the_date}" "mp3")
cp "${audio_filepath}" "${audio_filepath_w_counter}"
scp "${audio_filepath_w_counter}" $SSH_HOST:"'${dest_dir}'/"
fi
}
transfer_video() {
# runs in a background process so the UX is available again
# while this compresses video and scps
local the_date="$1"
local the_title="$2"
local daystamp=$(format_date "${the_date}" "%Y%m%d")
set -e
set -x
raw_video_filepath=$(outgoing_filepath "${the_date}" "raw.${INTERIM_VIDEO_EXT}")
processed_video_filepath=$(outgoing_filepath "${the_date}" "${INTERIM_VIDEO_EXT}" "${the_title}")
if [ -f "${raw_video_filepath}" ]; then
ffmpeg -nostdin -y -i "${raw_video_filepath}" -codec:v ${FFMPEG_VIDEO_PARAMS} -codec:a copy "${processed_video_filepath}"
discard_raw_video "${the_date}"
dest_dir="${VIDEO_DEST}/${daystamp}"
ssh $SSH_HOST "mkdir -p ${dest_dir}"
scp "${processed_video_filepath}" $SSH_HOST:"${dest_dir}/"
fi
}
spawn_transfer_video() {
local the_date="$1"
local the_title="$2"
echo
video_filepath=$(outgoing_filepath "${the_date}" "raw.${INTERIM_VIDEO_EXT}")
$SCRIPT transfer_video "${the_date}" "${the_title}" >> ${WORK}/transfer.log 2>&1 &
pid=$!
renice 15 ${pid}
echo "Video ${video_filepath} scheduled for conversion and transfer"
}
discard_raw_video() {
local the_date="$1"
echo
video_filepath=$(outgoing_filepath "${the_date}" "raw.${INTERIM_VIDEO_EXT}")
mv "${video_filepath}" "${TRASH}/"
echo "Video ${video_filepath} moved to trash"
}
generate_out_processed_audio() {
ffmpeg -i out.${INTERIM_AUDIO_EXT} \
${FFMPEG_EQ} \
-metadata title="${timeonly}" -metadata artist="${ARTIST} ${year}" -metadata album="${daywithdashes}" \
-b:a 192k \
out_processed.mp3
}
record_audio() {
stty intr "0x20"
CURRENT_TIMESTAMP=`date +"%Y-%m-%d %H:%M:%S"`
local daywithdashes=`date +"%Y-%m-%d"`
local timeonly=`date +"%H:%M:%S"`
local year=`date +"%Y"`
pushd ${CURRENT}
rm -f *.mkv *.mp3 *.wav *.m4a *.mp4 *.aac
if [[ "${LINUX}" ]]; then
ffmpeg -f pulse -thread_queue_size 1024 -i ${AUDIO_DEVICE} -ar 44100 out.${INTERIM_AUDIO_EXT}
elif [[ "${OSX}" ]]; then
# even w/ audio ffmpeg / avfoundation is weird and crackly unless you set things weirdly.
# not worth it, use quicktime here too
cat <<EOF | osascript;
tell application "QuickTime Player"
activate
try
close document 1
end try
set xyz to new audio recording
start xyz
end tell
EOF
echo "press space to end quicktime player"
read -rsn1
cat <<EOF | osascript;
set thePackagePath to POSIX path of (path to desktop) & "NewAudio.qtpxcomposition/"
tell application "QuickTime Player"
activate
stop document 1
set thePackageFile to (thePackagePath as POSIX file)
close document 1 saving in thePackageFile
quit
end tell
set theAudio to POSIX file (thePackagePath & "Audio Recording.m4a")
tell application "Finder"
set theAudio to move theAudio to POSIX file "${CURRENT}"
set name of theAudio to "out.m4a"
delete thePackageFile
end tell
# open the player again
tell application "QuickTime Player"
new movie recording
end tell
EOF
fi
stty intr '^C'
generate_out_processed_audio
popd
queue_files_for_transfer "${CURRENT_TIMESTAMP}"
}
record_video() {
stty intr "0x20"
CURRENT_TIMESTAMP=`date +"%Y-%m-%d %H:%M:%S"`
if [[ $LINUX ]]; then
local daywithdashes=`date --date "${CURRENT_TIMESTAMP}" +"%Y-%m-%d"`
local timeonly=`date --date "${CURRENT_TIMESTAMP}" +"%H:%M:%S"`
local year=`date --date "${CURRENT_TIMESTAMP}" +"%Y"`
fi
if [[ $OSX ]]; then
local daywithdashes=`date -j -f "%Y-%m-%d %H:%M:%S" "${CURRENT_TIMESTAMP}" +"%Y-%m-%d"`
local timeonly=`date -j -f "%Y-%m-%d %H:%M:%S" "${CURRENT_TIMESTAMP}" +"%H:%M:%S"`
local year=`date -j -f "%Y-%m-%d %H:%M:%S" "${CURRENT_TIMESTAMP}" +"%Y"`
fi
pushd ${CURRENT}
rm -f *.mkv *.mp3 *.wav *.mov *.mp4 *.aac *.m4a
if [[ "${LINUX}" ]]; then
# framerate advice, use -c:v mjpeg:
# https://stackoverflow.com/questions/44960632/ffmpeg-records-5-frames-per-second-on-a-device-that-cheese-records-at-20-fps
ffmpeg -y \
-use_wallclock_as_timestamps 1 \
-f v4l2 -thread_queue_size 1024 -video_size ${RESOLUTION} -c:v mjpeg -framerate 30 -i ${VIDEO_DEVICE} \
-use_wallclock_as_timestamps 1 \
-f pulse -thread_queue_size 1024 -i ${AUDIO_DEVICE} -ar 44100 \
-map 1:a out.${INTERIM_AUDIO_EXT} \
-map 0:v -preset ultrafast -tune zerolatency -vcodec libx264 out.${INTERIM_VIDEO_EXT}
# create a joined movie from the separate video / audio files
ffmpeg -i out.${INTERIM_VIDEO_EXT} -i out.${INTERIM_AUDIO_EXT} -map 0:v -map 1:a -codec:v copy -codec:a aac ${FFMPEG_EQ} out_processed.${INTERIM_VIDEO_EXT}
# out.${INTERIM_AUDIO_EXT} already exists
elif [[ "${OSX}" ]]; then
# ffmpeg w/ avfoundation works like crap for video w/ audio, so use quicktime player directly
echo "starting quicktime player"
cat <<EOF | osascript;
tell application "QuickTime Player"
activate
try
start document 1
on error
new movie recording
start document 1
end try
end tell
EOF
echo "press space to end quicktime player"
read -rsn1
cat <<EOF | osascript;
# no idea why this has to be so byzantine, even "Movie Recording" seems
# to be a name that has to be hardcoded
# https://macscripter.net/viewtopic.php?id=47046
set thePackagePath to POSIX path of (path to desktop) & "NewMovie.qtpxcomposition/"
tell application "QuickTime Player"
activate
stop document 1
set thePackageFile to (thePackagePath as POSIX file)
close document 1 saving in thePackageFile
# if we don't quit, it seems to stick a "finishing movie"
# dialogue that won't otherwise go away
quit
end tell
set theMovie to POSIX file (thePackagePath & "Movie Recording.mov")
tell application "Finder"
set theMovie to move theMovie to POSIX file "${CURRENT}"
set name of theMovie to "out.mov"
delete thePackageFile
end tell
# open the player again
tell application "QuickTime Player"
new movie recording
end tell
EOF
ffmpeg -i out.mov -codec:v copy ${FFMPEG_EQ} out_processed.${INTERIM_VIDEO_EXT}
ffmpeg -i out.mov -map 0:a out.${INTERIM_AUDIO_EXT}
fi
stty intr '^C'
# here we should have:
# out.${INTERIM_VIDEO_EXT} which has EQ applied and
# out.${INTERIUM_AUDIO_EXT} which does not have the EQ applied
generate_out_processed_audio
popd
queue_files_for_transfer "${CURRENT_TIMESTAMP}"
TITLE=""
while [ 1 ]; do
if [ "${TITLE}" ]; then
echo "Title: ${TITLE}"
fi
cmd=$(getinput "(P)ublish video | (D)iscard video | (W)atch video | (T)itle video: ")
case "$cmd" in
t)
echo
read -p "Enter video title: " -r TITLE
echo
;;
p)
spawn_transfer_video "${CURRENT_TIMESTAMP}" "${TITLE}"
break
;;
d) discard_raw_video "${CURRENT_TIMESTAMP}"
break
;;
w) play_video;;
*) echo "unknown command ${cmd}"
esac
done
echo ""
}
play_audio() {
audio_filepath=$(outgoing_filepath "${CURRENT_TIMESTAMP}" "mp3")
mpg123 -v "${audio_filepath}"
}
play_video() {
video_filepath=$(outgoing_filepath "${CURRENT_TIMESTAMP}" "raw.${INTERIM_VIDEO_EXT}")
vlc --video-on-top "${video_filepath}"
}
usage() {
cat <<UEND
usage: $0 (console|transfer)
$0 record stuff and send it to wooly
'console' - run the console
'transfer_audio' - transfer audio to diskstation
'transfer_video' - process video and transfer video to diskstation
UEND
}
getinput() {
local prompt="$1"
local input=''
read -p "${prompt}" -rsn1 input
echo "$input"
}
console() {
find_audio
find_video
while [ 1 ]; do
cmd=$(getinput "(R)ecord | Record (V)ideo | (P)layback | Playback Vide(O): ")
case "$cmd" in
r) record_audio;;
v) record_video;;
p) play_audio;;
o) play_video;;
*) echo "unknown command ${cmd}"
esac
done
}
setup_workspace
case "$1" in
console) console;;
transfer_audio) transfer_audio "$2";;
transfer_video) transfer_video "$2" "$3";;
*) usage
exit -1;;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment