From cb8925fa3ac7781b9fb59e169eed65de169b42cc Mon Sep 17 00:00:00 2001 From: paxri01 Date: Sat, 19 Sep 2026 20:03:40 -0400 Subject: [PATCH 1/2] Set the output group instead of chowning to serviio The chown to serviio:video never took effect. Changing a file's owner to another user needs root, the script runs as an ordinary user, and both output shares are exported root_squash, so running it under sudo would not help either -- root has less access there than we do. With the errors sent to /dev/null and no return code checked, the failure was invisible: the chmod on the following line still succeeded, so the files looked as though the ownership block had run. Set only the group, which an ordinary member of it is allowed to do. Serviio reads through the shared video group, so rp01:video 0664 grants it exactly what serviio:video 0664 did. Output directories also get the setgid bit so season subdirectories created later inherit the group. Failures are now logged rather than discarded. Co-Authored-By: Claude Opus 5 --- vc2.sh | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/vc2.sh b/vc2.sh index 5bf5f5e..7ec9e1e 100755 --- a/vc2.sh +++ b/vc2.sh @@ -44,6 +44,8 @@ doneDir="$baseDir/done" videoDir="/video" tempDir="/video/temp" + # Serviio's account, kept for reference only: the script cannot chown to it + # (see encodeIt). Read access comes from $group, which both accounts share. user="serviio" group="video" # Concurrent encodes. Defaults to 1 because concurrency buys nothing once @@ -800,8 +802,12 @@ declare vOpts vFilter aOpts aFilter sOpts outFile metaFile hwaccel_args killWait 1 "Could not create directory ${outDir[$l]}" exit $check fi - chown $user:$group "${outDir[$l]}" 2>/dev/null - chmod 0775 "${outDir[$l]}" 2>/dev/null + # Only the group is set: chown to another user needs root, and the + # output shares are exported root_squash, so root cannot do it either. + # $group membership is what Serviio reads through. setgid so any season + # subdirectory created later inherits the group instead of our own. + chgrp "$group" "${outDir[$l]}" || logIt "WARN: chgrp $group failed on ${outDir[$l]}" + chmod 2775 "${outDir[$l]}" || logIt "WARN: chmod 2775 failed on ${outDir[$l]}" fi if [[ $hq != 1 ]]; then outFile="${outDir[$l]}/${baseName[$l]}.mp4" @@ -892,8 +898,9 @@ declare vOpts vFilter aOpts aFilter sOpts outFile metaFile hwaccel_args } >> "$traceLog" 2>&1 logIt "outFile = $outFile" - chown $user:$group "$outFile" 2>/dev/null - chmod 0664 "$outFile" 2>/dev/null + # See the chgrp note above: user ownership is not ours to set here. + chgrp "$group" "$outFile" || logIt "WARN: chgrp $group failed on $outFile" + chmod 0664 "$outFile" || logIt "WARN: chmod 0664 failed on $outFile" fi # Logged here rather than in the main loop so the markers stay with the job From 49974017f9c686b1644bcb02424311ba04f2be56 Mon Sep 17 00:00:00 2001 From: paxri01 Date: Sat, 19 Sep 2026 20:58:31 -0400 Subject: [PATCH 2/2] Only request NVDEC for codecs the GPU can actually decode -hwaccel cuda was set once at startup and applied to every file. It is a decoder selection directive rather than a hint: it replaces the software decoder with the native hwaccel shim, which has no software path. Given a codec NVDEC cannot decode there is no fallback -- ffmpeg fails every packet in a loop and never stops. An AV1 source hit this today. The GPU is Turing, which has no AV1 decode block at all, so ffmpeg logged "Your platform doesn't support hardware accelerated AV1 decoding" and then "Error submitting packet to decoder: Function not implemented" once per packet, 3673 times in 45 seconds. It looked like a hang, and it was not: audio and subtitles kept encoding and were eventually muxed on their own. Left alone it would have written a video-less 97MB mp4, very likely exited 0, published that over the real film and moved the 1.7GB source to done/. Gate the flag on the probed codec instead. AV1 is added to the list only when the GPU reports compute capability 8.6 or higher, which is where NVDEC gained an AV1 decoder. -hwaccels cannot answer this: it reports that the method exists, never which codecs the silicon handles. The same file decodes through libdav1d at 11.9x realtime and encodes on NVENC normally, so nothing is lost beyond the decode speed. Also drop -loglevel from quiet to error. Quiet is what turned an ffmpeg failing loudly into seven minutes of silence. Co-Authored-By: Claude Opus 5 --- vc2.sh | 54 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/vc2.sh b/vc2.sh index 7ec9e1e..731929c 100755 --- a/vc2.sh +++ b/vc2.sh @@ -46,6 +46,7 @@ tempDir="/video/temp" # Serviio's account, kept for reference only: the script cannot chown to it # (see encodeIt). Read access comes from $group, which both accounts share. + # shellcheck disable=SC2034 user="serviio" group="video" # Concurrent encodes. Defaults to 1 because concurrency buys nothing once @@ -389,10 +390,28 @@ EOM video_codec='hevc_nvenc' # Decode on NVDEC in addition to encoding on NVENC. Decoded frames are # downloaded to system memory, so the CPU filter chain (scale/fps/format) - # is unaffected. ffmpeg falls back to software decode per-stream if the - # source codec is not NVDEC-supported. + # is unaffected. + # + # This is applied per file, not globally: -hwaccel cuda is a decoder + # SELECTION directive, not a hint. It replaces the software decoder with + # the native hwaccel shim, which has no software path, so handing it a + # codec NVDEC cannot decode does not fall back -- ffmpeg fails every + # packet in a loop, forever, writing nothing. With -loglevel quiet that + # looks exactly like a hang. Seen on 2026-09-19 with an AV1 source: + # "Your platform doesn't support hardware accelerated AV1 decoding" + # followed by "Error submitting packet to decoder: Function not + # implemented" once per packet. + # + # -hwaccels only reports that the method exists, never which codecs the + # silicon handles, so the codec list is ours to keep. NVDEC gained AV1 + # with Ampere GA10x (compute 8.6); Turing (7.5) has none. if $ffmpeg_bin -hwaccels 2>/dev/null | grep -qw 'cuda'; then - hwaccel_args='-hwaccel cuda' + hwaccel_decode='-hwaccel cuda' + nvdec_codecs='h264 hevc vp8 vp9 mpeg1video mpeg2video mpeg4 vc1' + computeCap=$(nvidia-smi --query-gpu=compute_cap --format=csv,noheader 2>/dev/null | head -1) + if [[ -n $computeCap ]] && awk "BEGIN{exit !($computeCap >= 8.6)}" 2>/dev/null; then + nvdec_codecs+=' av1' + fi fi fi fi @@ -400,6 +419,7 @@ EOM # Define Global variables declare -a fullName fileName extension baseName baseDir outDir declare vOpts vFilter aOpts aFilter sOpts outFile metaFile hwaccel_args +declare hwaccel_decode nvdec_codecs computeCap hwDecodeNote ## Defined Functions logIt () @@ -780,8 +800,28 @@ declare vOpts vFilter aOpts aFilter sOpts outFile metaFile hwaccel_args fi } + buildHwaccelOpts () + { + # Requires $vCodec from probeIt. See the nvdec_codecs note in the hardware + # detection block above for why this is gated on the source codec. + hwaccel_args='' + hwDecodeNote='' + [[ -z $hwaccel_decode ]] && return 0 + # shellcheck disable=SC2154 # vCodec comes from cc_probe via .probe.rc + if [[ " $nvdec_codecs " == *" $vCodec "* ]]; then + hwaccel_args="$hwaccel_decode" + else + # Reported by encodeIt rather than here: setOpts runs under displayIt's + # spinner, which owns the current line. + hwDecodeNote="NVDEC has no ${vCodec} decoder; decoding in software" + traceIt $LINENO buildHwaccelOpts " info " "$hwDecodeNote" + fi + return 0 + } + setOpts () { + buildHwaccelOpts buildVideoFilter buildVideoOpts buildAudioOpts @@ -817,7 +857,9 @@ declare vOpts vFilter aOpts aFilter sOpts outFile metaFile hwaccel_args ffmpeg_string="${ffmpeg_bin} " ffmpeg_string+="-hide_banner -y " - ffmpeg_string+="-loglevel quiet -stats " + # Not "quiet": that hid a decoder failing every packet for seven minutes. + # -stats still provides the progress line. + ffmpeg_string+="-loglevel error -stats " [[ -n $hwaccel_args ]] && ffmpeg_string+="$hwaccel_args " ffmpeg_string+="-i \"$inFile\" " ffmpeg_string+="-i \"$metaFile\" " @@ -833,6 +875,8 @@ declare vOpts vFilter aOpts aFilter sOpts outFile metaFile hwaccel_args traceIt $LINENO encodeIt " CMD " "> $ffmpeg_string $outFile" + [[ -n $hwDecodeNote ]] && echo -e " ${C8}${hwDecodeNote}${C0}" + jobLog='' if (( maxJobs > 1 )); then # Concurrent jobs would interleave ffmpeg's -stats output on the terminal, @@ -849,7 +893,7 @@ declare vOpts vFilter aOpts aFilter sOpts outFile metaFile hwaccel_args if (( STATUS > 0 )); then logIt "Re-encoding of $inFile failed!" traceIt $LINENO encodeIt "ERROR!" "STATUS=$STATUS, ffmpeg encode failed." - echo -e "> ${C1}ERROR (${baseName[$l]}): Run the following to see details why:\n${ffmpeg_string//-loglevel quiet -stats /} $tempOut${C0}\n" + echo -e "> ${C1}ERROR (${baseName[$l]}): Run the following to see details why:\n${ffmpeg_string//-loglevel error -stats /} $tempOut${C0}\n" [[ -n $jobLog ]] && echo -e "> ${C1}ffmpeg output: $jobLog${C0}" rm -f "$tempOut" else