Created
December 29, 2022 15:01
-
-
Save zzzeek/ac8ed30e96e1c6fd1b494bd1e5d895b4 to your computer and use it in GitHub Desktop.
the record.sh 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
#!/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