Skip to content

Commit f62a1a9

Browse files
committed
Better support for multi-containers capability by using a lock in the watch folder when possible.
1 parent e09d1ef commit f62a1a9

File tree

3 files changed

+119
-12
lines changed

3 files changed

+119
-12
lines changed

README.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ of modern, widely supported codecs.
5555
* [Access to Optical Drives](#access-to-optical-drives)
5656
* [Automatic Video Conversion](#automatic-video-conversion)
5757
* [Multiple Watch Folders](#multiple-watch-folders)
58+
* [Multiple Containers Capability](#multiple-containers-capability)
5859
* [Video Discs](#video-discs)
5960
* [Hooks](#hooks)
6061
* [Temporary Conversion Directory](#temporary-conversion-directory)
@@ -809,13 +810,30 @@ The maximum number of watch folders is defined by the
809810
`AUTOMATED_CONVERSION_MAX_WATCH_FOLDERS` environment variable.
810811
811812
> [!NOTE]
812-
>Each additional watch folder must be mapped to a host folder via a volume
813+
> Each additional watch folder must be mapped to a host folder via a volume
813814
> mapping during container creation.
814815
815816
> [!NOTE]
816817
> Each output folder defined via `AUTOMATED_CONVERSION_OUTPUT_DIR` must be
817818
> mapped to a host folder via a volume mapping during container creation.
818819
820+
### Multiple Containers Capability
821+
822+
Multiple container instances can operate on the same watch folder to increase
823+
throughput and parallelize video conversions. Each container monitors the folder
824+
independently and picks up available video files for processing.
825+
826+
> [!NOTE]
827+
> The watch folder must be writable by all containers, since each container
828+
> creates a lock file in the folder before starting a conversion. This ensures
829+
> that no two containers process the same video at the same time.
830+
831+
> [!NOTE]
832+
> To prevent already-converted videos from being picked up again, configure each
833+
> container to remove the source file once processing is finished. This can be
834+
> enforced by setting the `AUTOMATED_CONVERSION_KEEP_SOURCE` environment
835+
> variable to `0`.
836+
819837
### Video Discs
820838
821839
The automatic video converter supports video discs in the following format:

appdefs.yml

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,29 @@ app:
135135
`AUTOMATED_CONVERSION_MAX_WATCH_FOLDERS` environment variable.
136136
137137
> [!NOTE]
138-
>Each additional watch folder must be mapped to a host folder via a volume
138+
> Each additional watch folder must be mapped to a host folder via a volume
139139
> mapping during container creation.
140140
141141
> [!NOTE]
142142
> Each output folder defined via `AUTOMATED_CONVERSION_OUTPUT_DIR` must be
143143
> mapped to a host folder via a volume mapping during container creation.
144+
- title: Multiple Containers Capability
145+
level: 3
146+
content: |-
147+
Multiple container instances can operate on the same watch folder to increase
148+
throughput and parallelize video conversions. Each container monitors the folder
149+
independently and picks up available video files for processing.
150+
151+
> [!NOTE]
152+
> The watch folder must be writable by all containers, since each container
153+
> creates a lock file in the folder before starting a conversion. This ensures
154+
> that no two containers process the same video at the same time.
155+
156+
> [!NOTE]
157+
> To prevent already-converted videos from being picked up again, configure each
158+
> container to remove the source file once processing is finished. This can be
159+
> enforced by setting the `AUTOMATED_CONVERSION_KEEP_SOURCE` environment
160+
> variable to `0`.
144161
- title: Video Discs
145162
level: 3
146163
content: |-

rootfs/etc/services.d/autovideoconverter/run

Lines changed: 82 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@ set -u # Treat unset variables as an error.
44

55
FAILED_CONVERSIONS="/config/failed_conversions"
66
SUCCESSFUL_CONVERSIONS="/config/successful_conversions"
7+
IGNORED_CONVERSIONS="/config/ignored_conversion"
78

89
HANDBRAKE_CLI="/usr/bin/HandBrakeCLI --preset-import-file /config/ghb/presets.json"
910

11+
CONTAINER_INSTANCE_ID="$(cat /config/machine-id)"
12+
1013
# https://gist.github.com/aaomidi/0a3b5c9bd563c9e012518b495410dc0e
1114
VIDEO_FILE_EXTENSIONS_INTERNAL_LIST="\
1215
webm mkv flv vob ogv ogg rrc gifv mng mov avi qt wmv yuv rm asf amv mp4 \
@@ -42,6 +45,13 @@ log() {
4245
echo "$*"
4346
}
4447

48+
is_dir_writable() {
49+
tmpfile="$(mktemp "$1"/.test_XXXXXX 2>/dev/null)"
50+
rc=$?
51+
rm -f "$tmpfile"
52+
return "$rc"
53+
}
54+
4555
log_hb_encode_progress() {
4656
while read OUTPUT; do
4757
echo "Encoding $video: $OUTPUT"
@@ -121,6 +131,43 @@ get_video_titles() {
121131
return ${PIPESTATUS[0]}
122132
}
123133

134+
get_video_lock_dname() {
135+
video="$1"
136+
wf="$2"
137+
lock_dname="$(echo "$video" | sed "s|^$wf||" | tr '/' '.')".lock
138+
echo "$lock_dname"
139+
}
140+
141+
lock_video() {
142+
video="$1"
143+
wf="$2"
144+
145+
if ! is_dir_writable "$wf"; then
146+
return 0
147+
fi
148+
149+
lock_dname="$(get_video_lock_dname "$video" $"$wf")"
150+
err="$(mkdir "$wf"/"$lock_dname" 2>&1)"
151+
rc=$?
152+
if [ "$rc" -eq 0 ]; then
153+
mkdir "$wf"/"$lock_dname"/"$CONTAINER_INSTANCE_ID"
154+
return 0
155+
else
156+
if echo "$err" | grep -iq "File exists"; then
157+
return 1
158+
else
159+
return 2
160+
fi
161+
fi
162+
}
163+
164+
unlock_video() {
165+
video="$1"
166+
wf="$2"
167+
lock_dname="$(get_video_lock_dname "$video" $"$wf")"
168+
rm -rf "$wf"/"$lock_dname"
169+
}
170+
124171
process_video() {
125172
video="$1"
126173
wf="$2"
@@ -161,6 +208,24 @@ process_video() {
161208
return
162209
fi
163210

211+
# Skip video if it has been ignored.
212+
if [ -f "$IGNORED_CONVERSIONS" ] && grep -q -w "$hash" "$IGNORED_CONVERSIONS"; then
213+
log "Skipping video '$video' ($hash): has been processed by another instance."
214+
return
215+
fi
216+
217+
# Skip video if it is being processed by another instance.
218+
lock_video "$video" "$wf"
219+
lock_rc=$?
220+
if [ "$lock_rc" -eq 1 ]; then
221+
echo "$video $hash" >> "$IGNORED_CONVERSIONS"
222+
log "Skipping '$video': currently beging processed by another instance."
223+
return
224+
elif [ "$lock_rc" -eq 2 ]; then
225+
log "ERROR: Unable to acquire lock for video '${video}' (${hash})."
226+
return
227+
fi
228+
164229
# Set the output directory.
165230
case "$AC_OUTPUT_SUBDIR" in
166231
UNSET)
@@ -196,6 +261,7 @@ process_video() {
196261
# Get video titles.
197262
if $FILE_EXT_IN_VIDEO_LIST && $FILE_EXT_IN_NON_VIDEO_LIST; then
198263
log "ERROR: File '${video}' (${hash}) has an extension defined as both a video and non-video file."
264+
unlock_video "$video" "$wf"
199265
return
200266
elif [ -n "$AC_VIDEO_FILE_EXTENSIONS" ] && ! $FILE_EXT_IN_VIDEO_LIST; then
201267
log "File '${video}' (${hash}) has an extension not part of the inclusion list."
@@ -417,29 +483,34 @@ process_video() {
417483
fi
418484
echo "$video $hash" >> "$FAILED_CONVERSIONS"
419485
fi
486+
487+
unlock_video "$video" "$wf"
420488
}
421489

422490
process_watch_folder() {
423491
WF="$1"
424-
425492
NUM_PROCESSED_FILES=0
426493

494+
# Return now if the watch folder doesn't exist.
427495
[ -d "$WF" ] || return
496+
497+
# Remove any left-over locks for our own instance.
498+
find "$WF" -mindepth 1 -maxdepth 1 -type d -name "*.lock" | while read -r d; do
499+
if [ -e "$d"/"$CONTAINER_INSTANCE_ID" ]; then
500+
rm -rf "$d"
501+
fi
502+
done
503+
504+
# Return now if the watch folder didn't change.
428505
WATCHDIR_HASH_changed "$WF" || return
429506

430507
# Make sure the output directory is properly setup.
431508
if [ ! -d "$AC_OUTPUT_DIR" ]; then
432509
log "ERROR: Cannot process watch folder '$WF', because the associated output directory '$AC_OUTPUT_DIR' doesn't exist."
433510
return
434-
else
435-
TMPFILE="$(mktemp "$AC_OUTPUT_DIR"/.test_XXXXXX 2>/dev/null)"
436-
RC=$?
437-
if [ "$RC" -eq 0 ]; then
438-
rm "$TMPFILE"
439-
else
440-
log "ERROR: Cannot process watch folder '$WF', because the associated output directory '$AC_OUTPUT_DIR' is not writable."
441-
return
442-
fi
511+
elif ! is_dir_writable "$AC_OUTPUT_DIR"; then
512+
log "ERROR: Cannot process watch folder '$WF', because the associated output directory '$AC_OUTPUT_DIR' is not writable."
513+
return
443514
fi
444515

445516
if WATCHDIR_HASH_isset "$WF"; then
@@ -507,6 +578,7 @@ process_watch_folder() {
507578

508579
[ -f "$FAILED_CONVERSIONS" ] || touch "$FAILED_CONVERSIONS"
509580
[ -f "$SUCCESSFUL_CONVERSIONS" ] || touch "$SUCCESSFUL_CONVERSIONS"
581+
[ -f "$IGNORED_CONVERSIONS" ] || touch "$IGNORED_CONVERSIONS"
510582

511583
while true; do
512584
for i in $(seq 1 ${AUTOMATED_CONVERSION_MAX_WATCH_FOLDERS:-5}); do

0 commit comments

Comments
 (0)