Permalink
Fetching contributors…
Cannot retrieve contributors at this time
executable file 351 lines (299 sloc) 10.8 KB
#!/usr/bin/with-contenv sh
set -u # Treat unset variables as an error.
# Make sure we appear with a proper name under `ps`.
if [ ! -L "$0" ]; then
SV_NAME="$(basename "$(pwd)")"
ln -sf run "$SV_NAME"
exec ./"$SV_NAME" "$@"
fi
# Make sure we don't run as root.
if [ "$(id -u)" -eq 0 ]; then
exec $APP_NICE_CMD s6-applyuidgid -u $USER_ID -g $GROUP_ID -G ${SUP_GROUP_IDS:-$GROUP_ID} "$0" "$@"
fi
# Set umask.
if [ "${UMASK:-UNSET}" != "UNSET" ]; then
umask "$UMASK"
fi
FAILED_CONVERSIONS="/config/failed_conversions"
SUCCESSFUL_CONVERSIONS="/config/successful_conversions"
HANDBRAKE_CLI="/usr/bin/HandBrakeCLI --preset-import-file /config/ghb/presets.json"
if [ "${HANDBRAKE_DEBUG:-0}" -eq 1 ]; then
HANDBRAKE_CLI="$HANDBRAKE_CLI --verbose=3"
fi
WATCHDIR_HASH="$(mktemp -d)"
trap "exit" TERM QUIT INT
trap "clean_exit" EXIT
clean_exit() {
rm -rf "$WATCHDIR_HASH"
}
# http://cfajohnson.com/shell/cus-faq-2.html#Q11
run() {
j=1
while eval "\${pipestatus_$j+:} false"; do
unset pipestatus_$j
j=$(($j+1))
done
j=1 com= k=1 l=
for a; do
if [ "x$a" = 'x|' ]; then
com="$com { $l "'3>&-
echo "pipestatus_'$j'=$?" >&3
} 4>&- |'
j=$(($j+1)) l=
else
l="$l \"\$$k\""
fi
k=$(($k+1))
done
com="$com $l"' 3>&- >&4 4>&-
echo "pipestatus_'$j'=$?"'
exec 4>&1
eval "$(exec 3>&1; eval "$com")"
exec 4>&-
j=1
while eval "\${pipestatus_$j+:} false"; do
eval "[ \$pipestatus_$j -eq 0 ]" || return 1
j=$(($j+1))
done
return 0
}
log() {
echo "[$(basename "$0")] $*"
}
log_hb_encode_progress() {
LINE_COUNT=0
while read OUTPUT; do
if [ "$(expr $LINE_COUNT % 6)" = "0" ]; then
log "Encoding:$(echo "$OUTPUT" | cut -d',' -f2-)"
fi
LINE_COUNT="$(expr $LINE_COUNT + 1)"
done
}
WATCHDIR_HASH_calculate() {
WATCHDIR="$1"
find "$WATCHDIR" -follow -type f -not -path '*/\.*' -printf '%T@:%s:%p\n' | md5sum | cut -d' ' -f1
}
WATCHDIR_HASH_isset() {
WATCHDIR="$1"
[ -f "$WATCHDIR_HASH/$WATCHDIR/hash" ]
}
WATCHDIR_HASH_update() {
WATCHDIR="$1"
mkdir -p "$WATCHDIR_HASH/$WATCHDIR"
WATCHDIR_HASH_calculate "$WATCHDIR" > "$WATCHDIR_HASH/$WATCHDIR/hash"
}
WATCHDIR_HASH_changed() {
WATCHDIR="$1"
[ ! -f "$WATCHDIR_HASH/$WATCHDIR/hash" ] || \
[ "$(cat "$WATCHDIR_HASH/$WATCHDIR/hash")" != "$(WATCHDIR_HASH_calculate "$WATCHDIR")" ]
}
get_video_hash() {
video="$1"
if [ -f "$video" ]; then
stat -c '%n %s %Y' "$video" | md5sum | cut -d' ' -f1
else
find "$video" -type f -exec stat -c '%n %s %Y' {} \; | md5sum | cut -d' ' -f1
fi
}
get_video_titles() {
video="$1"
$HANDBRAKE_CLI -i "$video" \
-t0 \
--min-duration $AC_SOURCE_MIN_DURATION 2>&1 |
grep "^+ title " | sed 's/^+ title \([0-9]\+\):$/\1/'
}
process_video() {
video="$1"
wf="$2"
# Skip video if it doesn't exists (may have been removed while processing
# the watch directory).
if [ ! -f "$video" ] && [ ! -d "$video" ]; then
log "Skipping '$video': no longer exists."
return
fi
# Skip video if it is not readable.
if [ ! -r "$video" ]; then
log "Skipping '$video': not readable, check permissions."
return
fi
# Get hash of the video from its properties.
hash="$(get_video_hash "$video")"
# Skip video if it has been already successfully processed.
if [ -f "$SUCCESSFUL_CONVERSIONS" ] && grep -q -w "$hash" "$SUCCESSFUL_CONVERSIONS"; then
log "Skipping video '$video' ($hash): already processed successfully."
return
fi
# Skip video if we already failed to process it.
if [ -f "$FAILED_CONVERSIONS" ] && grep -q -w "$hash" "$FAILED_CONVERSIONS"; then
log "Skipping '$video' ($hash): already processed with failure."
return
fi
# Skip video if it is not stable.
log "Waiting $AC_SOURCE_STABLE_TIME seconds before processing '$video'..."
sleep $AC_SOURCE_STABLE_TIME
if [ "$hash" != "$(get_video_hash "$video")" ]; then
log "Skipping '$video': currently being copied."
return
fi
# Set the output directory.
case "$AC_OUTPUT_SUBDIR" in
UNSET)
OUTPUT_DIR="/output"
;;
SAME_AS_SRC)
dirname="$(dirname "$video" | sed "s|^$wf||")"
OUTPUT_DIR="/output/$dirname"
;;
*)
OUTPUT_DIR="/output/$AC_OUTPUT_SUBDIR"
;;
esac
OUTPUT_DIR="$(echo "$OUTPUT_DIR" | sed 's|/\+|/|g' | sed 's|/\+$||')"
mkdir -p "$OUTPUT_DIR"
# Get video titles.
VIDEO_TITLES="$(get_video_titles "$video")"
VIDEO_TITLES="${VIDEO_TITLES:-UNSET}"
if [ "$VIDEO_TITLES" != "UNSET" ]; then
NUM_VIDEO_TITLES="$(echo "$VIDEO_TITLES" | wc -l)"
else
NUM_VIDEO_TITLES="0"
fi
log "Starting conversion of '$video' ($hash) using preset '$AC_PRESET'..."
log "$NUM_VIDEO_TITLES title(s) to process."
hb_rc=0
CUR_VIDEO_TITLE=0
for TITLE in $VIDEO_TITLES; do
[ "$TITLE" != "UNSET" ] || continue
CUR_VIDEO_TITLE="$(expr $CUR_VIDEO_TITLE + 1)"
[ "$NUM_VIDEO_TITLES" -eq 1 ] || log "Processing title $TITLE ($CUR_VIDEO_TITLE/$NUM_VIDEO_TITLES)..."
# Get the output file basename: start with the one of the input file.
basename="$(basename "$video" | sed 's/\.[^.]*$//')"
# Special case when video is a DVD/Blu-ray folder: use the parent's
# directory.
if [ "$basename" = "VIDEO_TS" ] || [ "$basename" = "BDMV" ]; then
if [ "$(dirname "$video")" != "$wf" ]; then
basename="$(basename "$(dirname "$video")")"
fi
fi
# If multiple titles, add the '.title-XX' suffix.
[ "$NUM_VIDEO_TITLES" -eq 1 ] || basename="$basename.title-$TITLE"
# Now set the final output filename by adding the extension.
OUTPUT_FILE="$OUTPUT_DIR/$basename.$AC_FORMAT"
if [ -f "$OUTPUT_FILE" ]; then
hb_rc=1
log "ERROR: Destination file '$OUTPUT_FILE' already exists."
break
fi
# Call pre conversion hook.
if [ -f /config/hooks/pre_conversion.sh ]; then
log "Executing pre-conversion hook..."
/usr/bin/with-contenv sh /config/hooks/pre_conversion.sh "$OUTPUT_FILE" "$video" "$AC_PRESET"
log "Pre-conversion hook exited with $?"
fi
# Skip video if it doesn't exists (may have been removed by the
# pre-conversion hook).
if [ ! -f "$video" ] && [ ! -d "$video" ]; then
log "Skipping '$video': no longer exists."
continue
fi
# Set the temporary output directory: this is where the video will be
# actually written before being moved its final location once conversion is
# terminated.
OUTPUT_DIR_TMP="$(mktemp -d "/output/.XXXXXX")"
# Set the temporary output filename.
OUTPUT_FILE_TMP="$OUTPUT_DIR_TMP/$basename.$AC_FORMAT"
# Invoke HandBrake.
echo "------- CONVERSION OUTPUT $(date) -------" >> \
/config/log/hb/conversion.log
run $HANDBRAKE_CLI -i "$video" \
-o "$OUTPUT_FILE_TMP" \
--title "$TITLE" \
--preset "$AC_PRESET" 2>> \
/config/log/hb/conversion.log \| \
/usr/bin/unbuffer -p grep "^Encoding" \| \
log_hb_encode_progress
hb_rc=$pipestatus_1
# Move the file to its final location if conversion terminated
# successfully.
if [ $hb_rc -eq 0 ]; then
mv "$OUTPUT_FILE_TMP" "$OUTPUT_FILE"
fi
rm -rf "$OUTPUT_DIR_TMP"
# Call post conversion hook.
if [ -f /config/hooks/post_conversion.sh ]; then
log "Executing post-conversion hook..."
/usr/bin/with-contenv sh /config/hooks/post_conversion.sh $hb_rc "$OUTPUT_FILE" "$video" "$AC_PRESET"
log "Post-conversion hook exited with $?"
fi
[ $hb_rc -eq 0 ] || break
done
if [ $hb_rc -eq 0 ]; then
log "Conversion ended successfully."
echo "$video $hash" >> "$SUCCESSFUL_CONVERSIONS"
if [ "$AC_KEEP_SOURCE" -eq 0 ]; then
rm -r "$video"
log "Removed $video'."
# Remove directory if empty (hidden files/folders are ignored).
videodir="$(dirname "$video")"
while [ "$videodir" != "$wf" ] && [ -z "$(ls "$videodir")" ]; do
log "Removed directory '$videodir'."
rm -rf "$videodir"
videodir="$(dirname "$videodir")"
done
fi
else
log "Conversion failed."
echo "$video $hash" >> "$FAILED_CONVERSIONS"
fi
}
process_watch_folder() {
WF="$1"
[ -d "$WF" ] || return
WATCHDIR_HASH_changed "$WF" || return
if WATCHDIR_HASH_isset "$WF"; then
log "Change detected in watch folder '$WF'."
fi
# Make sure to update the watch directory hash before processing it.
# This is to make sure we catch, on the next round, changes occuring
# during the processing.
WATCHDIR_HASH_update "$WF"
log "Processing watch folder '$WF'..."
FILELIST="$(mktemp)"
find "$WF" -follow -type f -not -path '*/\.*' -printf "%T@ %p\n" | \
sort -n | \
cut -d' ' -f2- | \
sed 's|/VIDEO_TS/.*$|/VIDEO_TS|g' | \
sed 's|/BDMV/.*$|/BDMV|g' | \
uniq > "$FILELIST"
while read -u 3 FILE
do
process_video "$FILE" "$WF"
done 3<"$FILELIST"
rm "$FILELIST"
log "Watch folder '$WF' processing terminated."
}
log "starting..."
[ -f "$FAILED_CONVERSIONS" ] || touch "$FAILED_CONVERSIONS"
[ -f "$SUCCESSFUL_CONVERSIONS" ] || touch "$SUCCESSFUL_CONVERSIONS"
while true; do
for DIR in /watch /watch2 /watch3 /watch4 /watch5; do
# Set default settings.
AC_PRESET="${AUTOMATED_CONVERSION_PRESET:-Very Fast 1080p30}"
AC_FORMAT="${AUTOMATED_CONVERSION_FORMAT:-mp4}"
AC_SOURCE_STABLE_TIME="${AUTOMATED_CONVERSION_SOURCE_STABLE_TIME:-5}"
AC_SOURCE_MIN_DURATION="${AUTOMATED_CONVERSION_SOURCE_MIN_DURATION:-10}"
AC_OUTPUT_SUBDIR="${AUTOMATED_CONVERSION_OUTPUT_SUBDIR:-UNSET}"
AC_KEEP_SOURCE="${AUTOMATED_CONVERSION_KEEP_SOURCE:-1}"
# Apply per-watch folder settings.
if [ -n "${DIR#*/watch}" ]; then
for VAR in PRESET FORMAT SOURCE_STABLE_TIME SOURCE_MIN_DURATION OUTPUT_SUBDIR KEEP_SOURCE
do
eval "AC_$VAR=\"\${AUTOMATED_CONVERSION_${VAR}_${DIR#*/watch}:-\$AC_$VAR}\""
done
fi
# Process watch folder.
process_watch_folder "$DIR"
done
sleep "${AUTOMATED_CONVERSION_CHECK_INTERVAL:-5}"
done
# vim: set ft=sh :