From 3a0f224b47a5bdf4304568113829033588e7c0d5 Mon Sep 17 00:00:00 2001 From: Dave Rice Date: Tue, 25 Aug 2026 10:28:48 -0400 Subject: [PATCH 01/31] remove redundant rule --- vrecord | 1 - 1 file changed, 1 deletion(-) diff --git a/vrecord b/vrecord index fa261f70..b70f5953 100755 --- a/vrecord +++ b/vrecord @@ -2808,7 +2808,6 @@ CONTAINER_MATROSKA_TEST=' ' CODEC_GENERAL_TEST=' - YUV CFR YUV From 1976d536c0ea32974873644dc4ceba2fb98e583a Mon Sep 17 00:00:00 2001 From: Dave Rice Date: Tue, 25 Aug 2026 10:30:51 -0400 Subject: [PATCH 02/31] brackets --- vrecord | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vrecord b/vrecord index b70f5953..7b2f603c 100755 --- a/vrecord +++ b/vrecord @@ -3564,7 +3564,7 @@ if [[ "${DEVICE_INPUT_CHOICE}" = 0 ]] ; then fi # policy checks with mediaconch - if "$MEDIACONCH_INSTALLED" ; then + if "${MEDIACONCH_INSTALLED}" ; then _report -d "Checking file conformance against a mediaconch policy..." STATUS=$(mediaconch -fx -p "${MEDIACONCH_POLICY_TMP}" "${VRECORD_OUTPUT}" | xmlstarlet sel -N mc="https://mediaarea.net/mediaconch" -t -v mc:MediaConch/mc:media/mc:policy/@outcome -n) if [[ "${STATUS}" = "pass" ]] ; then From c970b198b61e85e65d6de4d7e2e26ff0fb5731b3 Mon Sep 17 00:00:00 2001 From: Dave Rice Date: Tue, 25 Aug 2026 11:03:37 -0400 Subject: [PATCH 03/31] mediaconch report management updates --- vrecord | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/vrecord b/vrecord index 7b2f603c..fe78056b 100755 --- a/vrecord +++ b/vrecord @@ -3566,21 +3566,27 @@ if [[ "${DEVICE_INPUT_CHOICE}" = 0 ]] ; then # policy checks with mediaconch if "${MEDIACONCH_INSTALLED}" ; then _report -d "Checking file conformance against a mediaconch policy..." - STATUS=$(mediaconch -fx -p "${MEDIACONCH_POLICY_TMP}" "${VRECORD_OUTPUT}" | xmlstarlet sel -N mc="https://mediaarea.net/mediaconch" -t -v mc:MediaConch/mc:media/mc:policy/@outcome -n) + MEDIACONCH_REPORT="${DIR}/${FULL_OUTPUT_ID}_mediaconchreport.xml" + MEDIACONCH_REPORT_TMP="$(_maketemp _mediaconchreport.xml)" + MEDIACONCH_POLICY="${DIR}/${FULL_OUTPUT_ID}_mediaconch_policy.xml" + mediaconch -fx -p "${MEDIACONCH_POLICY_TMP}" "${VRECORD_OUTPUT}" | xmlstarlet fo > "${MEDIACONCH_REPORT_TMP}" + STATUS="$(xmlstarlet sel -N mc="https://mediaarea.net/mediaconch" -t -v mc:MediaConch/mc:media/mc:policy/@outcome -n "${MEDIACONCH_REPORT_TMP}")" if [[ "${STATUS}" = "pass" ]] ; then _report -dt "File passed policy check for the video." _writeingestlog "mediaconch_outcome" "passed" elif [[ "${STATUS}" = "fail" ]] ; then _report -wt "File did not pass vrecord policy check and may not conform to digital preservation standards." - mediaconch -fx -p "${MEDIACONCH_POLICY_TMP}" "${VRECORD_OUTPUT}" | xmlstarlet fo > "${DIR}/${FULL_OUTPUT_ID}_mediaconchreport.xml" - _mv_tmp_file "${MEDIACONCH_POLICY_TMP}" "${DIR}/${FULL_OUTPUT_ID}_mediaconch_policy.xml" - _writeingestlog "mediaconch_outcome" "failed ${DIR}/${FULL_OUTPUT_ID}_mediaconch_policy.xml" - _report -wt "See ${DIR}/${FULL_OUTPUT_ID}_mediaconchreport.xml for a full MediaConch policy report." + _mv_tmp_file "${MEDIACONCH_REPORT_TMP}" "${MEDIACONCH_REPORT}" + _mv_tmp_file "${MEDIACONCH_POLICY_TMP}" "${MEDIACONCH_POLICY}" + _writeingestlog "mediaconch_outcome" "failed ${MEDIACONCH_POLICY}" + _report -wt "See ${MEDIACONCH_REPORT} for a full MediaConch policy report." _report -wt "Or review this attempt to summarize the issue:" - xmlstarlet edit -N "mc=https://mediaarea.net/mediaconch" -d "//*[@outcome='pass']" "${DIR}/${FULL_OUTPUT_ID}_mediaconchreport.xml" + xmlstarlet edit -N "mc=https://mediaarea.net/mediaconch" -d "//*[@outcome='pass']" "${MEDIACONCH_REPORT}" | xmlstarlet fo else - _report -wt "Mediaconch ended in an unexpected way (${STATUS}). Here is its output." - mediaconch -p "${MEDIACONCH_POLICY_TMP}" "${VRECORD_OUTPUT}" + _report -wt "Mediaconch ended in an unexpected way (status: ${STATUS}). Here is its output." + _report -wt "${MEDIACONCH_REPORT}:" + cat "${MEDIACONCH_REPORT}" + _writeingestlog "mediaconch_outcome" "error ${STATUS}" fi fi From 9602ed4ae0ffdca9eb7b81cbf8e939c0eb06a559 Mon Sep 17 00:00:00 2001 From: Dave Rice Date: Tue, 25 Aug 2026 11:08:14 -0400 Subject: [PATCH 04/31] add test for file truncation --- vrecord | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/vrecord b/vrecord index fe78056b..ac0dd1d4 100755 --- a/vrecord +++ b/vrecord @@ -2919,6 +2919,18 @@ _add_mediaconch_rule_set(){ fi } +_check_file_truncation() { + TEST_FILE="${1}" + TEST_LABEL="${2}" + if [[ -f "${TEST_FILE}" ]] ; then + IS_TRUNCATED=$(mediainfo --Output=XML "${TEST_FILE}" | xmlstarlet sel -t -v "//*[local-name()='IsTruncated']" -n 2>/dev/null || echo "") + if [[ "${IS_TRUNCATED}" = "Yes" ]] ; then + cowsay "$(_report -w "WARNING: MediaInfo reports that the ${TEST_LABEL} file (${TEST_FILE}) may be truncated or incomplete.")" + fi + _writeingestlog "${TEST_LABEL}_truncated" "${IS_TRUNCATED}" + fi +} + _add_fadgi_header2vtt(){ VTT_FILE="${1}" VTT_TYPE="${2}" @@ -3590,6 +3602,9 @@ if [[ "${DEVICE_INPUT_CHOICE}" = 0 ]] ; then fi fi + # check file truncation + _check_file_truncation "${VRECORD_OUTPUT}" "preservation" + # embed logs in Matroska files if "$MKVPROPEDIT_INSTALLED" ; then if [[ "${CONTAINER_CHOICE}" = "Matroska" ]] && [[ "${EMBED_LOGS_CHOICE}" = "Yes" ]] ; then From 0497f2f36e366ac0475006ba6068038720e238a4 Mon Sep 17 00:00:00 2001 From: Dave Rice Date: Tue, 25 Aug 2026 11:08:47 -0400 Subject: [PATCH 05/31] add truncation test for access mp4 as well --- vrecord | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vrecord b/vrecord index ac0dd1d4..2f03c9ec 100755 --- a/vrecord +++ b/vrecord @@ -3604,6 +3604,9 @@ if [[ "${DEVICE_INPUT_CHOICE}" = 0 ]] ; then # check file truncation _check_file_truncation "${VRECORD_OUTPUT}" "preservation" + if [[ "${MP4_CHOICE}" = "true" ]] && [[ -f "${MP4NAME}" ]] ; then + _check_file_truncation "${MP4NAME}" "access_mp4" + fi # embed logs in Matroska files if "$MKVPROPEDIT_INSTALLED" ; then From 27ecda61016e2bcbf90b8564189237cc4fe69d29 Mon Sep 17 00:00:00 2001 From: Dave Rice Date: Tue, 25 Aug 2026 11:20:15 -0400 Subject: [PATCH 06/31] reposition a few policies --- vrecord | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/vrecord b/vrecord index 2f03c9ec..67a4d1ab 100755 --- a/vrecord +++ b/vrecord @@ -2003,10 +2003,12 @@ _lookup_choice(){ # audio codec "24-bit PCM") + _add_mediaconch_rule_set "${AUDIO_CODEC_PCM_TEST}" MIDDLEOPTIONS_PRES+=(-c:a pcm_s24le) MIDDLEOPTIONS_PRES+=(-rf64 auto) AUDIO_EXT='wav' ;; "24-bit FLAC") + _add_mediaconch_rule_set "${AUDIO_CODEC_FLAC_TEST}" MIDDLEOPTIONS_PRES+=(-c:a flac) MIDDLEOPTIONS_PRES+=(-sample_fmt s32) MIDDLEOPTIONS_PRES+=(-bits_per_raw_sample:a 24) @@ -3155,11 +3157,6 @@ if [[ ! "${VIDEO_CODEC_CHOICE}" = "h264" ]] ; then _add_mediaconch_rule_set "${CODEC_GENERAL_TEST}" fi _add_mediaconch_rule_set "${AUDIO_CODEC_GENERAL_TEST}" -if [[ "${AUDIO_CODEC_CHOICE}" = "24-bit PCM" ]] ; then - _add_mediaconch_rule_set "${AUDIO_CODEC_PCM_TEST}" -elif [[ "${AUDIO_CODEC_CHOICE}" = "24-bit FLAC" ]] ; then - _add_mediaconch_rule_set "${AUDIO_CODEC_FLAC_TEST}" -fi # CLI passthrough and audiopassthrough modes if [[ "${RUNTYPE}" = "passthrough" ]] ; then From df4de5d55eb46d9f8d87836e13573f8b016b9e5c Mon Sep 17 00:00:00 2001 From: Dave Rice Date: Tue, 25 Aug 2026 11:20:29 -0400 Subject: [PATCH 07/31] fix a description so it applies for all tests --- vrecord | 1 + 1 file changed, 1 insertion(+) diff --git a/vrecord b/vrecord index 67a4d1ab..8566f52f 100755 --- a/vrecord +++ b/vrecord @@ -2904,6 +2904,7 @@ cat < "${MEDIACONCH_POLICY_TMP}" This policy tests FFV1 Matroska and Quicktime files made using vrecord and checks their validity + This policy tests video files made using vrecord and checks their validity against vrecord's specifications MC_START } From 2c11cb6f6c65adec1413bcdabfa677126959f967 Mon Sep 17 00:00:00 2001 From: Dave Rice Date: Tue, 25 Aug 2026 11:32:28 -0400 Subject: [PATCH 08/31] add mediaconch tests for the access mp4 --- vrecord | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 98 insertions(+), 3 deletions(-) diff --git a/vrecord b/vrecord index 8566f52f..f95a49bb 100755 --- a/vrecord +++ b/vrecord @@ -2896,6 +2896,52 @@ AUDIO_CODEC_FLAC_TEST=' 24 ' + +CONTAINER_MP4_TEST=' + mp4 + MPEG-4 + Base Media +' + +CODEC_H264_TEST=' + AVC + 4:2:0 + Progressive + CFR +' + +STANDARD_NTSC_ACCESS_DEINTERLACED_TEST=' + + 480 + 720 + 29.970 + Progressive + BT.601 NTSC + BT.601 + +' + +STANDARD_PAL_ACCESS_DEINTERLACED_TEST=' + + 576 + 720 + 25.000 + Progressive + BT.601 PAL + BT.601 + +' + +AUDIO_CODEC_AAC_TEST=' + AAC + + 1 + 2 + + 48000 +' + + # define mediaconch test fragment -end _start_mediaconch_policy(){ @@ -2922,6 +2968,53 @@ _add_mediaconch_rule_set(){ fi } +_check_access_mp4_mediaconch() { + if [[ "${MP4_CHOICE}" = "true" ]] && [[ -f "${MP4NAME}" ]] && "${MEDIACONCH_INSTALLED}" ; then + _report -d "Checking access MP4 conformance against a mediaconch policy..." + MP4_POLICY_TMP="$(_maketemp .mediaconch_mp4.xml)" + cat < "${MP4_POLICY_TMP}" + + + This policy tests the access MP4 file made using vrecord and checks its validity against vrecord's access specifications +MC_MP4_START + echo "${CONTAINER_GENERAL_TEST}" >> "${MP4_POLICY_TMP}" + echo "${CONTAINER_MP4_TEST}" >> "${MP4_POLICY_TMP}" + echo "${CODEC_H264_TEST}" >> "${MP4_POLICY_TMP}" + if [[ "${STANDARD}" = "ntsc" ]] ; then + echo "${STANDARD_NTSC_ACCESS_DEINTERLACED_TEST}" >> "${MP4_POLICY_TMP}" + elif [[ "${STANDARD}" = "pal" ]] ; then + echo "${STANDARD_PAL_ACCESS_DEINTERLACED_TEST}" >> "${MP4_POLICY_TMP}" + fi + echo "${AUDIO_CODEC_AAC_TEST}" >> "${MP4_POLICY_TMP}" + cat <> "${MP4_POLICY_TMP}" + + +MC_MP4_END + MP4_REPORT_TMP="$(_maketemp _mediaconch_mp4report.xml)" + MP4_REPORT="${DIR}/${FULL_OUTPUT_ID}_mp4_mediaconch_report.xml" + MP4_POLICY="${DIR}/${FULL_OUTPUT_ID}_mp4_mediaconch_policy.xml" + mediaconch -fx -p "${MP4_POLICY_TMP}" "${MP4NAME}" | xmlstarlet fo > "${MP4_REPORT_TMP}" + MP4_STATUS="$(xmlstarlet sel -N mc="https://mediaarea.net/mediaconch" -t -v mc:MediaConch/mc:media/mc:policy/@outcome -n "${MP4_REPORT_TMP}")" + if [[ "${MP4_STATUS}" = "pass" ]] ; then + _report -dt "Access MP4 passed policy check." + _writeingestlog "mediaconch_mp4_outcome" "passed" + elif [[ "${MP4_STATUS}" = "fail" ]] ; then + _report -wt "The access MP4 file did not pass vrecord policy check." + _mv_tmp_file "${MP4_REPORT_TMP}" "${MP4_REPORT}" + _mv_tmp_file "${MP4_POLICY_TMP}" "${MP4_POLICY}" + _writeingestlog "mediaconch_mp4_outcome" "failed ${MP4_POLICY}" + _report -wt "See ${MP4_REPORT} for a full MediaConch policy report." + _report -wt "Or review this attempt to summarize the issue:" + xmlstarlet edit -N "mc=https://mediaarea.net/mediaconch" -d "//*[@outcome='pass']" "${MP4_REPORT}" | xmlstarlet fo + else + _report -wt "Mediaconch ended in an unexpected way for the access MP4 (status: ${MP4_STATUS})." + _report -wt "${MP4_REPORT_TMP}:" + cat "${MP4_REPORT_TMP}" + _writeingestlog "mediaconch_mp4_outcome" "error ${MP4_STATUS}" + fi + fi +} + _check_file_truncation() { TEST_FILE="${1}" TEST_LABEL="${2}" @@ -3573,11 +3666,11 @@ if [[ "${DEVICE_INPUT_CHOICE}" = 0 ]] ; then _writeingestlog "Decklink input buffer overrun" "Yes" fi - # policy checks with mediaconch + # mediaconch policy check on the preservation output if "${MEDIACONCH_INSTALLED}" ; then _report -d "Checking file conformance against a mediaconch policy..." - MEDIACONCH_REPORT="${DIR}/${FULL_OUTPUT_ID}_mediaconchreport.xml" - MEDIACONCH_REPORT_TMP="$(_maketemp _mediaconchreport.xml)" + MEDIACONCH_REPORT="${DIR}/${FULL_OUTPUT_ID}_mediaconch_report.xml" + MEDIACONCH_REPORT_TMP="$(_maketemp _mediaconch_report.xml)" MEDIACONCH_POLICY="${DIR}/${FULL_OUTPUT_ID}_mediaconch_policy.xml" mediaconch -fx -p "${MEDIACONCH_POLICY_TMP}" "${VRECORD_OUTPUT}" | xmlstarlet fo > "${MEDIACONCH_REPORT_TMP}" STATUS="$(xmlstarlet sel -N mc="https://mediaarea.net/mediaconch" -t -v mc:MediaConch/mc:media/mc:policy/@outcome -n "${MEDIACONCH_REPORT_TMP}")" @@ -3599,6 +3692,8 @@ if [[ "${DEVICE_INPUT_CHOICE}" = 0 ]] ; then _writeingestlog "mediaconch_outcome" "error ${STATUS}" fi fi + # mediaconch policy check on the access MP4 + _check_access_mp4_mediaconch # check file truncation _check_file_truncation "${VRECORD_OUTPUT}" "preservation" From 703fc2006c7e6725b8cf6b2be39c9c85158ad712 Mon Sep 17 00:00:00 2001 From: Dave Rice Date: Tue, 25 Aug 2026 11:41:45 -0400 Subject: [PATCH 09/31] use frame-output mode for bwdif otherwise framerate is doubled --- vrecord | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vrecord b/vrecord index f95a49bb..5ac5d248 100755 --- a/vrecord +++ b/vrecord @@ -3349,7 +3349,7 @@ if [[ "${DEVICE_INPUT_CHOICE}" = "0" ]] ; then AUDIOMAP=$(sed "s/0:a:0/1:a:0/g" <<< "$AUDIOMAP") fi if [[ "${MP4_CHOICE}" = "true" ]] ; then - RECORDINGFILTER_MP4+=",bwdif,format=yuv420p" + RECORDINGFILTER_MP4+=",bwdif=send_frame,format=yuv420p" RECORD_COMMAND_MP4+=(-filter_complex "[0:v:0]${RECORDINGFILTER_MP4#,*}[mp4_v_out];${AUDIOMAP_MP4}") MP4NAME="${DIR}/${FULL_OUTPUT_ID}.mp4" EXTRAOUTPUTS+=("${MIDDLEOPTIONS_ALL[@]}" -movflags write_colr+faststart "${RECORD_COMMAND_MP4[@]}" -c:v h264 -g 12 -profile:v main -level "${ACCESS_MP4_LEVEL}" -b:v "${ACCESS_MP4_BITRATE}" -c:a aac -map "[mp4_v_out]" "${AUDIO_CHANNEL_MAP[@]:2}" "${MP4NAME}") From c6f518fe14dcee430d74a4045f32c10dd1e3a7c0 Mon Sep 17 00:00:00 2001 From: Dave Rice Date: Tue, 23 Jun 2026 18:19:13 -0400 Subject: [PATCH 10/31] add the framemd5 output to the tee muxer --- vrecord | 76 +++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 50 insertions(+), 26 deletions(-) diff --git a/vrecord b/vrecord index 5ac5d248..3c44f906 100755 --- a/vrecord +++ b/vrecord @@ -586,8 +586,28 @@ _setup_vrecord_process(){ fi RECORD_COMMAND+=("${MIDDLEOPTIONS_PRES[@]}" "${MIDDLEOPTIONS_ALL[@]}") if [[ "${RUNTYPE}" = "record" ]] ; then - RECORD_COMMAND+=(-filter_complex "[0:v:0]${RECORDINGFILTER#,*}${TC_WRITE}${CAPTION_WRITE}[vout];${AUDIOMAP}" -map "[vout]" "${AUDIO_CHANNEL_MAP[@]}") - RECORD_COMMAND+=(-f tee [f=${FORMAT}:select=v,${AUDIO_TEE_SELECT_MAP}]"${VRECORD_OUTPUT}"\|[f=nut:onfail=abort:select=v,${AUDIO_TEE_SELECT_MAP},a\\\\:0]pipe:1) + if [[ "${FRAMEMD5_CHOICE}" = "true" ]] ; then + FRAMEMD5_IDX=1 + FRAMEMD5NAME="${LOGDIR}/${FULL_OUTPUT_ID}.framemd5" + RECORD_COMMAND_TEE+=(\|[f=framehash:hash=MD5:onfail=abort:select=v\\\\:"${FRAMEMD5_IDX}"]"${FRAMEMD5NAME}") + RECORD_COMMAND_SPLIT=",split=2[vout][MD5_VOUT]" + RECORD_COMMAND_MAP+=(-map "[vout]" -map "[MD5_VOUT]") + EXTRAOPTIONS+=(-c:v:"${FRAMEMD5_IDX}" rawvideo) + else + RECORD_COMMAND_SPLIT="[vout]" + RECORD_COMMAND_MAP+=(-map "[vout]") + fi + AUDIO_TEE_SELECT_MAP="" + RECORD_COMMAND+=(-c:a:0 pcm_s24le) + for AUD_IDX in "${AUDIO_CHANNEL_IDX[@]}" ; do + AUDIO_TEE_SELECT_MAP+="${AUDIO_TEE_SELECT_MAP:+,}a\\\\:${AUD_IDX}" + for OPTION in "${AUDIO_MIDDLEOPTIONS_TEMPLATE[@]}" ; do + RECORD_COMMAND+=("${OPTION//:X/:${AUD_IDX}}") + done + done + RECORD_COMMAND+=(-filter_complex "[0:v:0]${RECORDINGFILTER#,*}${TC_WRITE}${CAPTION_WRITE}${RECORD_COMMAND_SPLIT};${AUDIOMAP}" "${RECORD_COMMAND_MAP[@]}" "${AUDIO_CHANNEL_MAP[@]}" "${EXTRAOPTIONS[@]}") + RECORD_COMMAND+=(-f tee [f=${FORMAT}:select=v\\\\:0,${AUDIO_TEE_SELECT_MAP}]"${VRECORD_OUTPUT}"\|[f=nut:onfail=abort:select=v\\\\:0,${AUDIO_TEE_SELECT_MAP},a\\\\:0]pipe:1${RECORD_COMMAND_TEE[@]}) + RECORD_COMMAND+=("${EXTRAOUTPUTS[@]}") else if [[ "${SIGNAL_INPUT}" = 'true' ]] ; then @@ -637,7 +657,7 @@ _setup_vrecord_process(){ VRECORD_STEPS="2" # Steps: record | player (record is adjusted if actually recording) RECORD_COMMAND=("${FFMPEG_BIN}") RECORD_COMMAND+=(-nostdin -nostats "${TIME_LIMIT[@]}" "${GRAB_INPUT[@]}") - RECORD_COMMAND+=("${MIDDLEOPTIONS_PRES[@]}" "${MIDDLEOPTIONS_ALL[@]}") + RECORD_COMMAND+=("${MIDDLEOPTIONS_PRES_AUDIO[@]}" "${MIDDLEOPTIONS_ALL[@]}") if [[ "${RUNTYPE}" = "record" ]] ; then RECORD_COMMAND+=(-f "${FORMAT}" -filter_complex "${AUDIOMAP}" "${AUDIO_CHANNEL_MAP[@]}" "${VRECORD_OUTPUT}") fi @@ -1962,38 +1982,38 @@ _lookup_choice(){ _add_mediaconch_rule_set "${CODEC_UNCOMPRESSED_TEST}" if [[ "${PIXEL_FORMAT}" = "yuv422p10" ]] ; then VIDEOCODECNAME="Uncompressed 10-bit 4:2:2" - MIDDLEOPTIONS_PRES+=(-c:v v210) + MIDDLEOPTIONS_PRES+=(-c:v:0 v210) elif [[ "${PIXEL_FORMAT}" = "uyvy422" ]] ; then VIDEOCODECNAME="Uncompressed 8-bit 4:2:2" - MIDDLEOPTIONS_PRES+=(-c:v rawvideo -pix_fmt uyvy422 -tag:v 2vuy) + MIDDLEOPTIONS_PRES+=(-c:v:0 rawvideo -pix_fmt:v:0 uyvy422 -tag:v:0 2vuy) fi ;; "FFV1 version 3") _add_mediaconch_rule_set "${CODEC_FFV1_TEST}" VIDEOCODECNAME="FFV1 version 3" - MIDDLEOPTIONS_PRES+=(-c:v ffv1 -level 3 -g 1 -slices "${FFV1_SLICES}" -slicecrc 1) + MIDDLEOPTIONS_PRES+=(-c:v:0 ffv1 -level:v:0 3 -g:v:0 1 -slices:v:0 "${FFV1_SLICES}" -slicecrc:v:0 1) SUFFIX="_ffv1" ;; "JPEG2000") VIDEOCODECNAME="JPEG2000" - MIDDLEOPTIONS_PRES+=(-c:v libopenjpeg) + MIDDLEOPTIONS_PRES+=(-c:v:0 libopenjpeg) SUFFIX="_j2k" ;; "ProRes") VIDEOCODECNAME="Apple ProRes 422" - MIDDLEOPTIONS_PRES+=(-c:v prores_ks -flags +ildct -profile:v 2) + MIDDLEOPTIONS_PRES+=(-c:v:0 prores_ks -flags:v:0 +ildct -profile:v:0 2) SUFFIX="_prores" ;; "ProRes (HQ)") VIDEOCODECNAME="Apple ProRes 422 HQ" - MIDDLEOPTIONS_PRES+=(-c:v prores_ks -flags +ildct -profile:v 3) + MIDDLEOPTIONS_PRES+=(-c:v:0 prores_ks -flags:v:0 +ildct -profile:v:0 3) SUFFIX="_prores" ;; "h264") VIDEOCODECNAME="H.264" - MIDDLEOPTIONS_PRES+=(-c:v h264 -pix_fmt yuv420p) + MIDDLEOPTIONS_PRES+=(-c:v:0 h264 -pix_fmt:v:0 yuv420p) SUFFIX="_h264" ;; "HuffYUV") VIDEOCODECNAME="HuffYUV" if [[ "${PIXEL_FORMAT}" = "yuv422p10" ]] ; then - MIDDLEOPTIONS_PRES+=(-c:v ffvhuff -pix_fmt yuv422p10le -pred median -context 1) + MIDDLEOPTIONS_PRES+=(-c:v:0 ffvhuff -pix_fmt:v:0 yuv422p10le -pred:v:0 median -context:v:0 1) elif [[ "${PIXEL_FORMAT}" = "uyvy422" ]] ; then - MIDDLEOPTIONS_PRES+=(-c:v ffvhuff -pix_fmt yuv422p -pred median -context 1) + MIDDLEOPTIONS_PRES+=(-c:v:0 ffvhuff -pix_fmt:v:0 yuv422p -pred:v:0 median -context:v:0 1) fi SUFFIX="_huff" ;; @@ -2001,20 +2021,25 @@ _lookup_choice(){ "10 bit") PIXEL_FORMAT="yuv422p10" ;; "8 bit") PIXEL_FORMAT="uyvy422" ;; + + # audio codec "24-bit PCM") _add_mediaconch_rule_set "${AUDIO_CODEC_PCM_TEST}" - MIDDLEOPTIONS_PRES+=(-c:a pcm_s24le) - MIDDLEOPTIONS_PRES+=(-rf64 auto) + MIDDLEOPTIONS_PRES_AUDIO+=(-c:a pcm_s24le) + MIDDLEOPTIONS_PRES_AUDIO+=(-rf64 auto) + AUDIO_MIDDLEOPTIONS_TEMPLATE+=(-c:a:X pcm_s24le) AUDIO_EXT='wav' ;; "24-bit FLAC") _add_mediaconch_rule_set "${AUDIO_CODEC_FLAC_TEST}" - MIDDLEOPTIONS_PRES+=(-c:a flac) - MIDDLEOPTIONS_PRES+=(-sample_fmt s32) - MIDDLEOPTIONS_PRES+=(-bits_per_raw_sample:a 24) + MIDDLEOPTIONS_PRES_AUDIO+=(-c:a flac) + MIDDLEOPTIONS_PRES_AUDIO+=(-sample_fmt s32) + MIDDLEOPTIONS_PRES_AUDIO+=(-bits_per_raw_sample:a 24) + AUDIO_MIDDLEOPTIONS_TEMPLATE+=(-c:a:X flac -sample_fmt:a:X s32 -bits_per_raw_sample:a:X 24) AUDIO_EXT='flac' ;; "AAC") - MIDDLEOPTIONS_PRES+=(-c:a aac) ;; + MIDDLEOPTIONS_PRES_AUDIO+=(-c:a aac) + AUDIO_MIDDLEOPTIONS_TEMPLATE+=(-c:a:X aac) ;; # audio mappings "2 Stereo Tracks (Channels 1 & 2 -> 1st Track Stereo, Channels 3 & 4 -> 2nd Track Stereo)") @@ -2025,7 +2050,7 @@ _lookup_choice(){ AUDIO_CHANNEL_MAP+=(-map "[orig]") AUDIO_CHANNEL_MAP+=(-map "[stereo1]") AUDIO_CHANNEL_MAP+=(-map "[stereo2]") - AUDIO_TEE_SELECT_MAP="a\\\\:1,a\\\\:2" ;; + AUDIO_CHANNEL_IDX=(1 2) ;; "1 Stereo Track (From Channels 1 & 2)") AUDIOMAP="[0:a:0]asplit[orig],pan=stereo| c0=c0 | c1=${PHASE_VALUE_2}c1[stereo1]" AUDIOMAP_MP4="[0:a:0]pan=stereo| c0=c0 | c1=${PHASE_VALUE_2}c1[stereo1]" @@ -2033,7 +2058,7 @@ _lookup_choice(){ AUDIO_PLAY_LABELS=",drawtext=fontfile=${DEFAULTFONT}:text='L(1)':fontcolor=white:fontsize=18:x=0:y=0,drawtext=fontfile=${DEFAULTFONT}:text='R(1)':fontcolor=white:fontsize=18:x=0:y=20" AUDIO_CHANNEL_MAP+=(-map "[orig]") AUDIO_CHANNEL_MAP+=(-map "[stereo1]") - AUDIO_TEE_SELECT_MAP="a\\\\:1" ;; + AUDIO_CHANNEL_IDX=(1) ;; "1 Stereo Track (From Channels 3 & 4)") AUDIOMAP="[0:a:0]asplit[orig],pan=stereo| c0=c2 | c1=${PHASE_VALUE_2}c3[stereo1]" AUDIOMAP_MP4="[0:a:0]pan=stereo| c0=c2 | c1=${PHASE_VALUE_2}c3[stereo1]" @@ -2041,7 +2066,7 @@ _lookup_choice(){ AUDIO_PLAY_LABELS=",drawtext=fontfile=${DEFAULTFONT}:text='L(1)':fontcolor=white:fontsize=18:x=0:y=40,drawtext=fontfile=${DEFAULTFONT}:text='R(1)':fontcolor=white:fontsize=18:x=0:y=60" AUDIO_CHANNEL_MAP+=(-map "[orig]") AUDIO_CHANNEL_MAP+=(-map "[stereo1]") - AUDIO_TEE_SELECT_MAP="a\\\\:1" ;; + AUDIO_CHANNEL_IDX=(1) ;; "Channel 1 -> 1st Track Mono, Channel 2 -> 2nd Track Mono") AUDIOMAP="[0:a:0]asplit[orig],pan=mono| c0=c0[mono1];[0:a:0]pan=mono| c0=${PHASE_VALUE_2}c1[mono2]" AUDIOMAP_MP4="[0:a:0]pan=mono| c0=c0[mono1];[0:a:0]pan=mono| c0=${PHASE_VALUE_2}c1[mono2]" @@ -2050,7 +2075,7 @@ _lookup_choice(){ AUDIO_CHANNEL_MAP+=(-map "[orig]") AUDIO_CHANNEL_MAP+=(-map "[mono1]") AUDIO_CHANNEL_MAP+=(-map "[mono2]") - AUDIO_TEE_SELECT_MAP="a\\\\:1,a\\\\:2" ;; + AUDIO_CHANNEL_IDX=(1 2) ;; "Channel 2 -> 1st Track Mono, Channel 1 -> 2nd Track Mono") AUDIOMAP="[0:a:0]asplit[orig],pan=mono| c0=${PHASE_VALUE_2}c1[mono1];[0:a:0]pan=mono| c0=c0[mono2]" AUDIOMAP_MP4="[0:a:0]pan=mono| c0=${PHASE_VALUE_2}c1[mono1];[0:a:0]pan=mono| c0=c0[mono2]" @@ -2059,7 +2084,7 @@ _lookup_choice(){ AUDIO_CHANNEL_MAP+=(-map "[orig]") AUDIO_CHANNEL_MAP+=(-map "[mono1]") AUDIO_CHANNEL_MAP+=(-map "[mono2]") - AUDIO_TEE_SELECT_MAP="a\\\\:1,a\\\\:2" ;; + AUDIO_CHANNEL_IDX=(1 2) ;; "Channel 1 -> Single Track Mono") AUDIOMAP="[0:a:0]asplit[orig],pan=mono| c0=c0[mono1]" AUDIOMAP_MP4="[0:a:0]pan=mono| c0=c0[mono1]" @@ -2067,7 +2092,7 @@ _lookup_choice(){ AUDIO_PLAY_LABELS=",drawtext=fontfile=${DEFAULTFONT}:text='M(1)':fontcolor=white:fontsize=18:x=0:y=0" AUDIO_CHANNEL_MAP+=(-map "[orig]") AUDIO_CHANNEL_MAP+=(-map "[mono1]") - AUDIO_TEE_SELECT_MAP="a\\\\:1" ;; + AUDIO_CHANNEL_IDX=(1) ;; "Channel 2 -> Single Track Mono") AUDIOMAP="[0:a:0]asplit[orig],pan=mono| c0=c1[mono1]" AUDIOMAP_MP4="[0:a:0]pan=mono| c0=c1[mono1]" @@ -2075,7 +2100,7 @@ _lookup_choice(){ AUDIO_PLAY_LABELS=",drawtext=fontfile=${DEFAULTFONT}:text='M(1)':fontcolor=white:fontsize=18:x=0:y=20" AUDIO_CHANNEL_MAP+=(-map "[orig]") AUDIO_CHANNEL_MAP+=(-map "[mono1]") - AUDIO_TEE_SELECT_MAP="a\\\\:1" ;; + AUDIO_CHANNEL_IDX=(1) ;; # Audio mode channel options "Mono") AUDIOMAP="[0:a:0]pan=mono| c0=c0[mono1]" @@ -3343,7 +3368,6 @@ if [[ "${DEVICE_INPUT_CHOICE}" = "0" ]] ; then fi if [[ "${FRAMEMD5_CHOICE}" = "true" ]] ; then FRAMEMD5NAME="${LOGDIR}/${FULL_OUTPUT_ID}.framemd5" - EXTRAOUTPUTS+=(-an -f framemd5 "${FRAMEMD5NAME}") fi if [[ "${SIGNAL_INPUT}" = 'true' ]] ; then AUDIOMAP=$(sed "s/0:a:0/1:a:0/g" <<< "$AUDIOMAP") From ac1d86fa6e879e656af38ff0f143e670bec5db81 Mon Sep 17 00:00:00 2001 From: Dave Rice Date: Sun, 28 Jun 2026 17:10:06 -0400 Subject: [PATCH 11/31] just put adding audio mappings in a single step --- vrecord | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/vrecord b/vrecord index 3c44f906..41d2f69a 100755 --- a/vrecord +++ b/vrecord @@ -2047,59 +2047,49 @@ _lookup_choice(){ AUDIOMAP_MP4="[0:a:0]pan=stereo| c0=c0 | c1=${PHASE_VALUE_2}c1[stereo1];[0:a:0]pan=stereo| c0=c2 | c1=${PHASE_VALUE_4}c3[stereo2]" AUDIO_PLAY_MAP="pan=4c|c0=c0|c1=${PHASE_VALUE_2}c1|c2=c2|c3=${PHASE_VALUE_4}c3" AUDIO_PLAY_LABELS=",drawtext=fontfile=${DEFAULTFONT}:text='L(1)':fontcolor=white:fontsize=18:x=0:y=0,drawtext=fontfile=${DEFAULTFONT}:text='R(1)':fontcolor=white:fontsize=18:x=0:y=20,drawtext=fontfile=${DEFAULTFONT}:text='L(2)':fontcolor=white:fontsize=18:x=0:y=40,drawtext=fontfile=${DEFAULTFONT}:text='R(2)':fontcolor=white:fontsize=18:x=0:y=60" - AUDIO_CHANNEL_MAP+=(-map "[orig]") - AUDIO_CHANNEL_MAP+=(-map "[stereo1]") - AUDIO_CHANNEL_MAP+=(-map "[stereo2]") + AUDIO_CHANNEL_MAP+=(-map "[orig]" -map "[stereo1]" -map "[stereo2]") AUDIO_CHANNEL_IDX=(1 2) ;; "1 Stereo Track (From Channels 1 & 2)") AUDIOMAP="[0:a:0]asplit[orig],pan=stereo| c0=c0 | c1=${PHASE_VALUE_2}c1[stereo1]" AUDIOMAP_MP4="[0:a:0]pan=stereo| c0=c0 | c1=${PHASE_VALUE_2}c1[stereo1]" AUDIO_PLAY_MAP="pan=stereo|c0=c0|c1=${PHASE_VALUE_2}c1" AUDIO_PLAY_LABELS=",drawtext=fontfile=${DEFAULTFONT}:text='L(1)':fontcolor=white:fontsize=18:x=0:y=0,drawtext=fontfile=${DEFAULTFONT}:text='R(1)':fontcolor=white:fontsize=18:x=0:y=20" - AUDIO_CHANNEL_MAP+=(-map "[orig]") - AUDIO_CHANNEL_MAP+=(-map "[stereo1]") + AUDIO_CHANNEL_MAP+=(-map "[orig]" -map "[stereo1]") AUDIO_CHANNEL_IDX=(1) ;; "1 Stereo Track (From Channels 3 & 4)") AUDIOMAP="[0:a:0]asplit[orig],pan=stereo| c0=c2 | c1=${PHASE_VALUE_2}c3[stereo1]" AUDIOMAP_MP4="[0:a:0]pan=stereo| c0=c2 | c1=${PHASE_VALUE_2}c3[stereo1]" AUDIO_PLAY_MAP="pan=stereo|c0=c2|c1=${PHASE_VALUE_2}c3" AUDIO_PLAY_LABELS=",drawtext=fontfile=${DEFAULTFONT}:text='L(1)':fontcolor=white:fontsize=18:x=0:y=40,drawtext=fontfile=${DEFAULTFONT}:text='R(1)':fontcolor=white:fontsize=18:x=0:y=60" - AUDIO_CHANNEL_MAP+=(-map "[orig]") - AUDIO_CHANNEL_MAP+=(-map "[stereo1]") + AUDIO_CHANNEL_MAP+=(-map "[orig]" -map "[stereo1]") AUDIO_CHANNEL_IDX=(1) ;; "Channel 1 -> 1st Track Mono, Channel 2 -> 2nd Track Mono") AUDIOMAP="[0:a:0]asplit[orig],pan=mono| c0=c0[mono1];[0:a:0]pan=mono| c0=${PHASE_VALUE_2}c1[mono2]" AUDIOMAP_MP4="[0:a:0]pan=mono| c0=c0[mono1];[0:a:0]pan=mono| c0=${PHASE_VALUE_2}c1[mono2]" AUDIO_PLAY_MAP="pan=stereo|c0=c0|c1=${PHASE_VALUE_2}c1" AUDIO_PLAY_LABELS=",drawtext=fontfile=${DEFAULTFONT}:text='M(1)':fontcolor=white:fontsize=18:x=0:y=0,drawtext=fontfile=${DEFAULTFONT}:text='M(2)':fontcolor=white:fontsize=18:x=0:y=20" - AUDIO_CHANNEL_MAP+=(-map "[orig]") - AUDIO_CHANNEL_MAP+=(-map "[mono1]") - AUDIO_CHANNEL_MAP+=(-map "[mono2]") + AUDIO_CHANNEL_MAP+=(-map "[orig]" -map "[mono1]" -map "[mono2]") AUDIO_CHANNEL_IDX=(1 2) ;; "Channel 2 -> 1st Track Mono, Channel 1 -> 2nd Track Mono") AUDIOMAP="[0:a:0]asplit[orig],pan=mono| c0=${PHASE_VALUE_2}c1[mono1];[0:a:0]pan=mono| c0=c0[mono2]" AUDIOMAP_MP4="[0:a:0]pan=mono| c0=${PHASE_VALUE_2}c1[mono1];[0:a:0]pan=mono| c0=c0[mono2]" AUDIO_PLAY_MAP="pan=stereo|c0=${PHASE_VALUE_2}c1|c1=c0" AUDIO_PLAY_LABELS=",drawtext=fontfile=${DEFAULTFONT}:text='M(2)':fontcolor=white:fontsize=18:x=0:y=0,drawtext=fontfile=${DEFAULTFONT}:text='M(1)':fontcolor=white:fontsize=18:x=0:y=20" - AUDIO_CHANNEL_MAP+=(-map "[orig]") - AUDIO_CHANNEL_MAP+=(-map "[mono1]") - AUDIO_CHANNEL_MAP+=(-map "[mono2]") + AUDIO_CHANNEL_MAP+=(-map "[orig]" -map "[mono1]" -map "[mono2]") AUDIO_CHANNEL_IDX=(1 2) ;; "Channel 1 -> Single Track Mono") AUDIOMAP="[0:a:0]asplit[orig],pan=mono| c0=c0[mono1]" AUDIOMAP_MP4="[0:a:0]pan=mono| c0=c0[mono1]" AUDIO_PLAY_MAP="pan=mono| c0=c0" AUDIO_PLAY_LABELS=",drawtext=fontfile=${DEFAULTFONT}:text='M(1)':fontcolor=white:fontsize=18:x=0:y=0" - AUDIO_CHANNEL_MAP+=(-map "[orig]") - AUDIO_CHANNEL_MAP+=(-map "[mono1]") + AUDIO_CHANNEL_MAP+=(-map "[orig]" -map "[mono1]") AUDIO_CHANNEL_IDX=(1) ;; "Channel 2 -> Single Track Mono") AUDIOMAP="[0:a:0]asplit[orig],pan=mono| c0=c1[mono1]" AUDIOMAP_MP4="[0:a:0]pan=mono| c0=c1[mono1]" AUDIO_PLAY_MAP="pan=mono| c0=c1" AUDIO_PLAY_LABELS=",drawtext=fontfile=${DEFAULTFONT}:text='M(1)':fontcolor=white:fontsize=18:x=0:y=20" - AUDIO_CHANNEL_MAP+=(-map "[orig]") - AUDIO_CHANNEL_MAP+=(-map "[mono1]") + AUDIO_CHANNEL_MAP+=(-map "[orig]" -map "[mono1]") AUDIO_CHANNEL_IDX=(1) ;; # Audio mode channel options "Mono") From 4904fb624b49cfabe1a7fe21f8d878e43f9f463b Mon Sep 17 00:00:00 2001 From: Dave Rice Date: Sun, 28 Jun 2026 17:10:18 -0400 Subject: [PATCH 12/31] fix an indent --- vrecord | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vrecord b/vrecord index 41d2f69a..d1b6ffbc 100755 --- a/vrecord +++ b/vrecord @@ -3360,7 +3360,7 @@ if [[ "${DEVICE_INPUT_CHOICE}" = "0" ]] ; then FRAMEMD5NAME="${LOGDIR}/${FULL_OUTPUT_ID}.framemd5" fi if [[ "${SIGNAL_INPUT}" = 'true' ]] ; then - AUDIOMAP=$(sed "s/0:a:0/1:a:0/g" <<< "$AUDIOMAP") + AUDIOMAP=$(sed "s/0:a:0/1:a:0/g" <<< "$AUDIOMAP") fi if [[ "${MP4_CHOICE}" = "true" ]] ; then RECORDINGFILTER_MP4+=",bwdif=send_frame,format=yuv420p" From 76aa6328ed6d7871a046505b22e1690e8d3f0390 Mon Sep 17 00:00:00 2001 From: Dave Rice Date: Fri, 3 Jul 2026 11:09:40 -0400 Subject: [PATCH 13/31] consolidate a mostly repeating section into a shared function --- vrecord | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/vrecord b/vrecord index d1b6ffbc..c25eb741 100755 --- a/vrecord +++ b/vrecord @@ -715,6 +715,13 @@ _setup_vrecord_process(){ fi PLAYER_COMMAND=("${FFPLAY_BIN}" "${FFPLAY_OPTIONS[@]}") fi + if [[ "${VERBOSE_GTKDIALOG}" = "Y" ]] ; then + echo "RUNTYPE ${RUNTYPE}" + echo "STEPS ${VRECORD_STEPS}" + echo "PLAY ${PLAYER_COMMAND[@]}" + echo "RECORD ${RECORD_COMMAND[@]}" + echo "QCLI ${QCLI_COMMAND_PIPE[@]}" + fi } _get_inputs(){ @@ -1636,12 +1643,6 @@ _edit_mode(){ # passthrough and audiopassthrough modes _passthrough_mode(){ _setup_vrecord_process - if [[ "${VERBOSE_GTKDIALOG}" = "Y" ]] ; then - echo "RUNTYPE ${RUNTYPE}" - echo "STEPS ${VRECORD_STEPS}" - echo "PLAY ${PLAYER_COMMAND[@]}" - echo "RECORD ${RECORD_COMMAND[@]}" - fi if [[ "${PLAYBACKVIEW_CHOICE_PASS}" == "Captions" ]] && [[ "${RUNTYPE}" == "passthrough" ]] ; then "${SCRIPTDIR}/cchex_to_display" "${FFREPORT_TMP}" "${CAP_DISPLAY_TMP}" > /dev/null 2> /dev/null & @@ -3483,12 +3484,6 @@ _report -d "Close the playback window to stop recording." # vrecord process! _setup_vrecord_process -if [[ "${VERBOSE_GTKDIALOG}" = "Y" ]] ; then - echo "STEPS ${VRECORD_STEPS}" - echo "PLAY ${PLAYER_COMMAND[@]}" - echo "RECORD ${RECORD_COMMAND[@]}" - echo "QCLI ${QCLI_COMMAND_PIPE[@]}" -fi if [[ "${VRECORD_STEPS}" = "2" ]] ; then _writeingestlog "Record command" "${RECORD_COMMAND[@]}" _writeingestlog "Playback command" "${PLAYER_COMMAND[@]}" From 82e3f79388075c3d76aa354a818769d37991d8ed Mon Sep 17 00:00:00 2001 From: Dave Rice Date: Fri, 3 Jul 2026 13:07:13 -0400 Subject: [PATCH 14/31] add mp4 to tee output standardize var names, some comments, --- vrecord | 145 +++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 97 insertions(+), 48 deletions(-) diff --git a/vrecord b/vrecord index c25eb741..3f4a8eb7 100755 --- a/vrecord +++ b/vrecord @@ -586,29 +586,78 @@ _setup_vrecord_process(){ fi RECORD_COMMAND+=("${MIDDLEOPTIONS_PRES[@]}" "${MIDDLEOPTIONS_ALL[@]}") if [[ "${RUNTYPE}" = "record" ]] ; then + + # construct the video stream processing + MAIN_V_PADNAME="MAIN_V" + SPLIT_PADS=() + SPLIT_MAPS=(-map "[${MAIN_V_PADNAME}]") + VIDEO_STREAM_IDX=1 if [[ "${FRAMEMD5_CHOICE}" = "true" ]] ; then - FRAMEMD5_IDX=1 + FRAMEMD5_V_IDX="${VIDEO_STREAM_IDX}" FRAMEMD5NAME="${LOGDIR}/${FULL_OUTPUT_ID}.framemd5" - RECORD_COMMAND_TEE+=(\|[f=framehash:hash=MD5:onfail=abort:select=v\\\\:"${FRAMEMD5_IDX}"]"${FRAMEMD5NAME}") - RECORD_COMMAND_SPLIT=",split=2[vout][MD5_VOUT]" - RECORD_COMMAND_MAP+=(-map "[vout]" -map "[MD5_VOUT]") - EXTRAOPTIONS+=(-c:v:"${FRAMEMD5_IDX}" rawvideo) + FRAMEMD5_PADNAME="MD5_V" + SPLIT_PADS+=("${FRAMEMD5_PADNAME}") + SPLIT_MAPS+=(-map "[${FRAMEMD5_PADNAME}]") + (( VIDEO_STREAM_IDX++ )) + EXTRAOPTIONS+=(-c:v:"${FRAMEMD5_V_IDX}" rawvideo) + fi + RECORD_COMMAND_MP4_FILTER="" + if [[ "${MP4_CHOICE}" = "true" ]] ; then + MP4_V_IDX="${VIDEO_STREAM_IDX}" + MP4NAME="${DIR}/${FULL_OUTPUT_ID}.mp4" + MP4_V_PADNAME="MP4_V" + SPLIT_PADS+=("${MP4_V_PADNAME}") + RECORD_COMMAND_MP4_FILTER=";[MP4_V]${RECORDINGFILTER_MP4#,},bwdif=send_frame,format=yuv420p[${MP4_V_PADNAME}]" + SPLIT_MAPS+=(-map "[${MP4_V_PADNAME}]") + (( VIDEO_STREAM_IDX++ )) + EXTRAOPTIONS+=(-c:v:"${MP4_V_IDX}" h264 -g:v:"${MP4_V_IDX}" 12 -profile:v:"${MP4_V_IDX}" main -level:v:"${MP4_V_IDX}" "${ACCESS_MP4_LEVEL}" -b:v:"${MP4_V_IDX}" "${ACCESS_MP4_BITRATE}") + fi + + # construct end of video filterchain + SPLIT_COUNT=$(( 1 + ${#SPLIT_PADS[@]} )) + if [[ "${SPLIT_COUNT}" -gt 1 ]] ; then + RECORD_COMMAND_SPLIT=",split=${SPLIT_COUNT}[${MAIN_V_PADNAME}]" + for OUTPUT_PAD_NAME in "${SPLIT_PADS[@]}" ; do + RECORD_COMMAND_SPLIT+="[${OUTPUT_PAD_NAME}]" + done else - RECORD_COMMAND_SPLIT="[vout]" - RECORD_COMMAND_MAP+=(-map "[vout]") + RECORD_COMMAND_SPLIT="[${MAIN_V_PADNAME}]" fi + + # construct the audio stream processing AUDIO_TEE_SELECT_MAP="" - RECORD_COMMAND+=(-c:a:0 pcm_s24le) + EXTRAOPTIONS+=(-c:a:0 pcm_s24le) + AUDIO_STREAM_N=$(( 1 + ${#AUDIO_CHANNEL_IDX[@]} )) + MP4_A_INDICES=() + MP4_A_TEE_SELECT="" for AUD_IDX in "${AUDIO_CHANNEL_IDX[@]}" ; do AUDIO_TEE_SELECT_MAP+="${AUDIO_TEE_SELECT_MAP:+,}a\\\\:${AUD_IDX}" + # fill out audio template per stream for OPTION in "${AUDIO_MIDDLEOPTIONS_TEMPLATE[@]}" ; do RECORD_COMMAND+=("${OPTION//:X/:${AUD_IDX}}") done done - RECORD_COMMAND+=(-filter_complex "[0:v:0]${RECORDINGFILTER#,*}${TC_WRITE}${CAPTION_WRITE}${RECORD_COMMAND_SPLIT};${AUDIOMAP}" "${RECORD_COMMAND_MAP[@]}" "${AUDIO_CHANNEL_MAP[@]}" "${EXTRAOPTIONS[@]}") - RECORD_COMMAND+=(-f tee [f=${FORMAT}:select=v\\\\:0,${AUDIO_TEE_SELECT_MAP}]"${VRECORD_OUTPUT}"\|[f=nut:onfail=abort:select=v\\\\:0,${AUDIO_TEE_SELECT_MAP},a\\\\:0]pipe:1${RECORD_COMMAND_TEE[@]}) - - RECORD_COMMAND+=("${EXTRAOUTPUTS[@]}") + if [[ "${MP4_CHOICE}" = "true" ]] ; then + for (( MAP_N=0; MAP_N<${#AUDIO_MP4_MAP[@]}/2; MAP_N++ )) ; do + MP4_A_INDICES+=("${AUDIO_STREAM_N}") + MP4_A_TEE_SELECT+=",a\\\\:${AUDIO_STREAM_N}" + EXTRAOPTIONS+=(-c:a:"${AUDIO_STREAM_N}" aac) + (( AUDIO_STREAM_N++ )) + done + fi + + RECORD_COMMAND+=(-filter_complex "[0:v:0]${RECORDINGFILTER#,*}${TC_WRITE}${CAPTION_WRITE}${RECORD_COMMAND_SPLIT};${AUDIOMAP}${RECORD_COMMAND_MP4_FILTER}" "${SPLIT_MAPS[@]}" "${AUDIO_CHANNEL_MAP[@]}" $([[ "${MP4_CHOICE}" = "true" ]] && echo "${AUDIO_MP4_MAP[@]}") "${EXTRAOPTIONS[@]}") + + # Build tee string + RECORD_COMMAND_TEE_STR="[f=${FORMAT}:select=v\\\\:0,${AUDIO_TEE_SELECT_MAP}]${VRECORD_OUTPUT}" + RECORD_COMMAND_TEE_STR+="|[f=nut:onfail=abort:select=v\\\\:0,${AUDIO_TEE_SELECT_MAP},a\\\\:0]pipe:1" + if [[ "${FRAMEMD5_CHOICE}" = "true" ]] ; then + RECORD_COMMAND_TEE_STR+="|[f=framemd5:hash=MD5:select=v\\\\:${FRAMEMD5_V_IDX}]${FRAMEMD5NAME}" + fi + if [[ "${MP4_CHOICE}" = "true" ]] ; then + RECORD_COMMAND_TEE_STR+="|[f=mp4:onfail=ignore:movflags=write_colr+faststart:select=v\\\\:${MP4_V_IDX}${MP4_A_TEE_SELECT}]${MP4NAME}" + fi + RECORD_COMMAND+=(-f tee "${RECORD_COMMAND_TEE_STR}") else if [[ "${SIGNAL_INPUT}" = 'true' ]] ; then PIPE_OUTPUT=(-c:v ffv1) @@ -721,6 +770,10 @@ _setup_vrecord_process(){ echo "PLAY ${PLAYER_COMMAND[@]}" echo "RECORD ${RECORD_COMMAND[@]}" echo "QCLI ${QCLI_COMMAND_PIPE[@]}" + echo "SPLIT_PADS ${SPLIT_PADS[@]}" + echo "SPLIT_MAPS ${SPLIT_MAPS[@]}" + echo "RECORD_COMMAND_SPLIT ${RECORD_COMMAND_SPLIT[@]}" + echo "EXTRAOPTIONS ${EXTRAOPTIONS[@]}" fi } @@ -2022,8 +2075,6 @@ _lookup_choice(){ "10 bit") PIXEL_FORMAT="yuv422p10" ;; "8 bit") PIXEL_FORMAT="uyvy422" ;; - - # audio codec "24-bit PCM") _add_mediaconch_rule_set "${AUDIO_CODEC_PCM_TEST}" @@ -2044,63 +2095,70 @@ _lookup_choice(){ # audio mappings "2 Stereo Tracks (Channels 1 & 2 -> 1st Track Stereo, Channels 3 & 4 -> 2nd Track Stereo)") - AUDIOMAP="[0:a:0]asplit[orig],pan=stereo| c0=c0 | c1=${PHASE_VALUE_2}c1[stereo1];[0:a:0]pan=stereo| c0=c2 | c1=${PHASE_VALUE_4}c3[stereo2]" - AUDIOMAP_MP4="[0:a:0]pan=stereo| c0=c0 | c1=${PHASE_VALUE_2}c1[stereo1];[0:a:0]pan=stereo| c0=c2 | c1=${PHASE_VALUE_4}c3[stereo2]" + ASPLIT_N=$([[ "${MP4_CHOICE}" = "true" ]] && echo 2 || echo 1) + AUDIOMAP="[0:a:0]asplit=2[ORIG_A][ORIG_A2];[ORIG_A2]pan=stereo|c0=c0|c1=${PHASE_VALUE_2}c1,asplit=${ASPLIT_N}[MAIN_STEREO_A1]$([[ "${MP4_CHOICE}" = "true" ]] && echo "[MP4_A1]");[0:a:0]pan=stereo|c0=c2|c1=${PHASE_VALUE_4}c3,asplit=${ASPLIT_N}[MAIN_STEREO_A2]$([[ "${MP4_CHOICE}" = "true" ]] && echo "[MP4_A2]")" AUDIO_PLAY_MAP="pan=4c|c0=c0|c1=${PHASE_VALUE_2}c1|c2=c2|c3=${PHASE_VALUE_4}c3" AUDIO_PLAY_LABELS=",drawtext=fontfile=${DEFAULTFONT}:text='L(1)':fontcolor=white:fontsize=18:x=0:y=0,drawtext=fontfile=${DEFAULTFONT}:text='R(1)':fontcolor=white:fontsize=18:x=0:y=20,drawtext=fontfile=${DEFAULTFONT}:text='L(2)':fontcolor=white:fontsize=18:x=0:y=40,drawtext=fontfile=${DEFAULTFONT}:text='R(2)':fontcolor=white:fontsize=18:x=0:y=60" - AUDIO_CHANNEL_MAP+=(-map "[orig]" -map "[stereo1]" -map "[stereo2]") + AUDIO_CHANNEL_MAP+=(-map "[ORIG_A]" -map "[MAIN_STEREO_A1]" -map "[MAIN_STEREO_A2]") + [[ "${MP4_CHOICE}" = "true" ]] && AUDIO_MP4_MAP+=(-map "[MP4_A1]" -map "[MP4_A2]") AUDIO_CHANNEL_IDX=(1 2) ;; "1 Stereo Track (From Channels 1 & 2)") - AUDIOMAP="[0:a:0]asplit[orig],pan=stereo| c0=c0 | c1=${PHASE_VALUE_2}c1[stereo1]" - AUDIOMAP_MP4="[0:a:0]pan=stereo| c0=c0 | c1=${PHASE_VALUE_2}c1[stereo1]" + ASPLIT_N=$([[ "${MP4_CHOICE}" = "true" ]] && echo 2 || echo 1) + AUDIOMAP="[0:a:0]asplit=2[ORIG_A][ORIG_A2];[ORIG_A2]pan=stereo|c0=c0|c1=${PHASE_VALUE_2}c1,asplit=${ASPLIT_N}[MAIN_STEREO_A1]$([[ "${MP4_CHOICE}" = "true" ]] && echo "[MP4_A1]")" AUDIO_PLAY_MAP="pan=stereo|c0=c0|c1=${PHASE_VALUE_2}c1" AUDIO_PLAY_LABELS=",drawtext=fontfile=${DEFAULTFONT}:text='L(1)':fontcolor=white:fontsize=18:x=0:y=0,drawtext=fontfile=${DEFAULTFONT}:text='R(1)':fontcolor=white:fontsize=18:x=0:y=20" - AUDIO_CHANNEL_MAP+=(-map "[orig]" -map "[stereo1]") + AUDIO_CHANNEL_MAP+=(-map "[ORIG_A]" -map "[MAIN_STEREO_A1]") + [[ "${MP4_CHOICE}" = "true" ]] && AUDIO_MP4_MAP+=(-map "[MP4_A1]") AUDIO_CHANNEL_IDX=(1) ;; "1 Stereo Track (From Channels 3 & 4)") - AUDIOMAP="[0:a:0]asplit[orig],pan=stereo| c0=c2 | c1=${PHASE_VALUE_2}c3[stereo1]" - AUDIOMAP_MP4="[0:a:0]pan=stereo| c0=c2 | c1=${PHASE_VALUE_2}c3[stereo1]" + ASPLIT_N=$([[ "${MP4_CHOICE}" = "true" ]] && echo 2 || echo 1) + AUDIOMAP="[0:a:0]asplit=2[ORIG_A][ORIG_A2];[ORIG_A2]pan=stereo|c0=c2|c1=${PHASE_VALUE_2}c3,asplit=${ASPLIT_N}[MAIN_STEREO_A1]$([[ "${MP4_CHOICE}" = "true" ]] && echo "[MP4_A1]")" AUDIO_PLAY_MAP="pan=stereo|c0=c2|c1=${PHASE_VALUE_2}c3" AUDIO_PLAY_LABELS=",drawtext=fontfile=${DEFAULTFONT}:text='L(1)':fontcolor=white:fontsize=18:x=0:y=40,drawtext=fontfile=${DEFAULTFONT}:text='R(1)':fontcolor=white:fontsize=18:x=0:y=60" - AUDIO_CHANNEL_MAP+=(-map "[orig]" -map "[stereo1]") + AUDIO_CHANNEL_MAP+=(-map "[ORIG_A]" -map "[MAIN_STEREO_A1]") + [[ "${MP4_CHOICE}" = "true" ]] && AUDIO_MP4_MAP+=(-map "[MP4_A1]") AUDIO_CHANNEL_IDX=(1) ;; "Channel 1 -> 1st Track Mono, Channel 2 -> 2nd Track Mono") - AUDIOMAP="[0:a:0]asplit[orig],pan=mono| c0=c0[mono1];[0:a:0]pan=mono| c0=${PHASE_VALUE_2}c1[mono2]" - AUDIOMAP_MP4="[0:a:0]pan=mono| c0=c0[mono1];[0:a:0]pan=mono| c0=${PHASE_VALUE_2}c1[mono2]" + ASPLIT_N=$([[ "${MP4_CHOICE}" = "true" ]] && echo 2 || echo 1) + AUDIOMAP="[0:a:0]asplit=2[ORIG_A][ORIG_A2];[ORIG_A2]pan=mono|c0=c0,asplit=${ASPLIT_N}[MAIN_MONO_A1]$([[ "${MP4_CHOICE}" = "true" ]] && echo "[MP4_A1]");[0:a:0]pan=mono| c0=${PHASE_VALUE_2}c1,asplit=${ASPLIT_N}[MAIN_MONO_A2]$([[ "${MP4_CHOICE}" = "true" ]] && echo "[MP4_A2]")" AUDIO_PLAY_MAP="pan=stereo|c0=c0|c1=${PHASE_VALUE_2}c1" AUDIO_PLAY_LABELS=",drawtext=fontfile=${DEFAULTFONT}:text='M(1)':fontcolor=white:fontsize=18:x=0:y=0,drawtext=fontfile=${DEFAULTFONT}:text='M(2)':fontcolor=white:fontsize=18:x=0:y=20" - AUDIO_CHANNEL_MAP+=(-map "[orig]" -map "[mono1]" -map "[mono2]") + AUDIO_CHANNEL_MAP+=(-map "[ORIG_A]" -map "[MAIN_MONO_A1]" -map "[MAIN_MONO_A2]") + [[ "${MP4_CHOICE}" = "true" ]] && AUDIO_MP4_MAP+=(-map "[MP4_A1]" -map "[MP4_A2]") AUDIO_CHANNEL_IDX=(1 2) ;; "Channel 2 -> 1st Track Mono, Channel 1 -> 2nd Track Mono") - AUDIOMAP="[0:a:0]asplit[orig],pan=mono| c0=${PHASE_VALUE_2}c1[mono1];[0:a:0]pan=mono| c0=c0[mono2]" - AUDIOMAP_MP4="[0:a:0]pan=mono| c0=${PHASE_VALUE_2}c1[mono1];[0:a:0]pan=mono| c0=c0[mono2]" + ASPLIT_N=$([[ "${MP4_CHOICE}" = "true" ]] && echo 2 || echo 1) + AUDIOMAP="[0:a:0]asplit=2[ORIG_A][ORIG_A2];[ORIG_A2]pan=mono|c0=${PHASE_VALUE_2}c1,asplit=${ASPLIT_N}[MAIN_MONO_A1]$([[ "${MP4_CHOICE}" = "true" ]] && echo "[MP4_A1]");[0:a:0]pan=mono| c0=c0,asplit=${ASPLIT_N}[MAIN_MONO_A2]$([[ "${MP4_CHOICE}" = "true" ]] && echo "[MP4_A2]")" AUDIO_PLAY_MAP="pan=stereo|c0=${PHASE_VALUE_2}c1|c1=c0" AUDIO_PLAY_LABELS=",drawtext=fontfile=${DEFAULTFONT}:text='M(2)':fontcolor=white:fontsize=18:x=0:y=0,drawtext=fontfile=${DEFAULTFONT}:text='M(1)':fontcolor=white:fontsize=18:x=0:y=20" - AUDIO_CHANNEL_MAP+=(-map "[orig]" -map "[mono1]" -map "[mono2]") + AUDIO_CHANNEL_MAP+=(-map "[ORIG_A]" -map "[MAIN_MONO_A1]" -map "[MAIN_MONO_A2]") + [[ "${MP4_CHOICE}" = "true" ]] && AUDIO_MP4_MAP+=(-map "[MP4_A1]" -map "[MP4_A2]") AUDIO_CHANNEL_IDX=(1 2) ;; "Channel 1 -> Single Track Mono") - AUDIOMAP="[0:a:0]asplit[orig],pan=mono| c0=c0[mono1]" - AUDIOMAP_MP4="[0:a:0]pan=mono| c0=c0[mono1]" + ASPLIT_N=$([[ "${MP4_CHOICE}" = "true" ]] && echo 2 || echo 1) + AUDIOMAP="[0:a:0]asplit=2[ORIG_A][ORIG_A2];[ORIG_A2]pan=mono|c0=c0,asplit=${ASPLIT_N}[MAIN_MONO_A1]$([[ "${MP4_CHOICE}" = "true" ]] && echo "[MP4_A1]")" AUDIO_PLAY_MAP="pan=mono| c0=c0" AUDIO_PLAY_LABELS=",drawtext=fontfile=${DEFAULTFONT}:text='M(1)':fontcolor=white:fontsize=18:x=0:y=0" - AUDIO_CHANNEL_MAP+=(-map "[orig]" -map "[mono1]") + AUDIO_CHANNEL_MAP+=(-map "[ORIG_A]" -map "[MAIN_MONO_A1]") + [[ "${MP4_CHOICE}" = "true" ]] && AUDIO_MP4_MAP+=(-map "[MP4_A1]") AUDIO_CHANNEL_IDX=(1) ;; "Channel 2 -> Single Track Mono") - AUDIOMAP="[0:a:0]asplit[orig],pan=mono| c0=c1[mono1]" - AUDIOMAP_MP4="[0:a:0]pan=mono| c0=c1[mono1]" + ASPLIT_N=$([[ "${MP4_CHOICE}" = "true" ]] && echo 2 || echo 1) + AUDIOMAP="[0:a:0]asplit=2[ORIG_A][ORIG_A2];[ORIG_A2]pan=mono|c0=c1,asplit=${ASPLIT_N}[MAIN_MONO_A1]$([[ "${MP4_CHOICE}" = "true" ]] && echo "[MP4_A1]")" AUDIO_PLAY_MAP="pan=mono| c0=c1" AUDIO_PLAY_LABELS=",drawtext=fontfile=${DEFAULTFONT}:text='M(1)':fontcolor=white:fontsize=18:x=0:y=20" - AUDIO_CHANNEL_MAP+=(-map "[orig]" -map "[mono1]") + AUDIO_CHANNEL_MAP+=(-map "[ORIG_A]" -map "[MAIN_MONO_A1]") + [[ "${MP4_CHOICE}" = "true" ]] && AUDIO_MP4_MAP+=(-map "[MP4_A1]") AUDIO_CHANNEL_IDX=(1) ;; # Audio mode channel options "Mono") - AUDIOMAP="[0:a:0]pan=mono| c0=c0[mono1]" + AUDIOMAP="[0:a:0]pan=mono| c0=c0[MAIN_MONO_A1]" PASSTHROUGH_MAP="pan=mono|c0=c0" - AUDIO_CHANNEL_MAP+=(-map "[mono1]") ;; + AUDIO_CHANNEL_MAP+=(-map "[MAIN_MONO_A1]") ;; "Stereo") - AUDIOMAP="[0:a:0]pan=stereo| c0=c0 | c1=${PHASE_VALUE_2}c1[stereo1]" + AUDIOMAP="[0:a:0]pan=stereo| c0=c0 | c1=${PHASE_VALUE_2}c1[MAIN_STEREO_A1]" PASSTHROUGH_MAP="pan=stereo|c0=c0|c1=${PHASE_VALUE_2}c1" - AUDIO_CHANNEL_MAP+=(-map "[stereo1]") ;; + AUDIO_CHANNEL_MAP+=(-map "[MAIN_STEREO_A1]") ;; # Audio mode sample rate options "96 kHz") S_RATE='-ar 96k' @@ -3357,18 +3415,9 @@ if [[ "${DEVICE_INPUT_CHOICE}" = "0" ]] ; then _report -w "Such as \`brew install qcli\`." exit 1 fi - if [[ "${FRAMEMD5_CHOICE}" = "true" ]] ; then - FRAMEMD5NAME="${LOGDIR}/${FULL_OUTPUT_ID}.framemd5" - fi if [[ "${SIGNAL_INPUT}" = 'true' ]] ; then AUDIOMAP=$(sed "s/0:a:0/1:a:0/g" <<< "$AUDIOMAP") fi - if [[ "${MP4_CHOICE}" = "true" ]] ; then - RECORDINGFILTER_MP4+=",bwdif=send_frame,format=yuv420p" - RECORD_COMMAND_MP4+=(-filter_complex "[0:v:0]${RECORDINGFILTER_MP4#,*}[mp4_v_out];${AUDIOMAP_MP4}") - MP4NAME="${DIR}/${FULL_OUTPUT_ID}.mp4" - EXTRAOUTPUTS+=("${MIDDLEOPTIONS_ALL[@]}" -movflags write_colr+faststart "${RECORD_COMMAND_MP4[@]}" -c:v h264 -g 12 -profile:v main -level "${ACCESS_MP4_LEVEL}" -b:v "${ACCESS_MP4_BITRATE}" -c:a aac -map "[mp4_v_out]" "${AUDIO_CHANNEL_MAP[@]:2}" "${MP4NAME}") - fi if [[ "${FORMAT}" = "matroska" ]] ; then _review_option "EMBED_LOGS_CHOICE" "${EMBED_LOGS_OPTIONS[@]}" fi From 70a6afa48ef99535ebd8bd49672f8de9148d9092 Mon Sep 17 00:00:00 2001 From: Dave Rice Date: Fri, 3 Jul 2026 13:13:36 -0400 Subject: [PATCH 15/31] remove pointless line as it's reset a few lines later --- vrecord | 1 - 1 file changed, 1 deletion(-) diff --git a/vrecord b/vrecord index 3f4a8eb7..02799dae 100755 --- a/vrecord +++ b/vrecord @@ -660,7 +660,6 @@ _setup_vrecord_process(){ RECORD_COMMAND+=(-f tee "${RECORD_COMMAND_TEE_STR}") else if [[ "${SIGNAL_INPUT}" = 'true' ]] ; then - PIPE_OUTPUT=(-c:v ffv1) AUD_PIPE_MAP='1:a' else AUD_PIPE_MAP='0:a' From 74253ad2ea5a8c1126d558c74aeb146817a22268 Mon Sep 17 00:00:00 2001 From: Dave Rice Date: Fri, 3 Jul 2026 13:14:32 -0400 Subject: [PATCH 16/31] remove unused array --- vrecord | 2 -- 1 file changed, 2 deletions(-) diff --git a/vrecord b/vrecord index 02799dae..ddc39b07 100755 --- a/vrecord +++ b/vrecord @@ -628,7 +628,6 @@ _setup_vrecord_process(){ AUDIO_TEE_SELECT_MAP="" EXTRAOPTIONS+=(-c:a:0 pcm_s24le) AUDIO_STREAM_N=$(( 1 + ${#AUDIO_CHANNEL_IDX[@]} )) - MP4_A_INDICES=() MP4_A_TEE_SELECT="" for AUD_IDX in "${AUDIO_CHANNEL_IDX[@]}" ; do AUDIO_TEE_SELECT_MAP+="${AUDIO_TEE_SELECT_MAP:+,}a\\\\:${AUD_IDX}" @@ -639,7 +638,6 @@ _setup_vrecord_process(){ done if [[ "${MP4_CHOICE}" = "true" ]] ; then for (( MAP_N=0; MAP_N<${#AUDIO_MP4_MAP[@]}/2; MAP_N++ )) ; do - MP4_A_INDICES+=("${AUDIO_STREAM_N}") MP4_A_TEE_SELECT+=",a\\\\:${AUDIO_STREAM_N}" EXTRAOPTIONS+=(-c:a:"${AUDIO_STREAM_N}" aac) (( AUDIO_STREAM_N++ )) From 7f2e56d43270add589f6fbcf4a0eaf6956ac8ed4 Mon Sep 17 00:00:00 2001 From: Dave Rice Date: Fri, 3 Jul 2026 13:20:50 -0400 Subject: [PATCH 17/31] redistribute some conditions for clarity --- vrecord | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/vrecord b/vrecord index ddc39b07..484c05e9 100755 --- a/vrecord +++ b/vrecord @@ -496,6 +496,11 @@ _setup_vrecord_input(){ GRAB_INPUT+=("${TIME_LIMIT[@]}") GRAB_INPUT+=(-f lavfi -i sine=440:r=48000) GRAB_INPUT+=(-pix_fmt yuv422p10le) + echo "AAA ${AUDIOMAP[@]}" + if [[ "${SIGNAL_INPUT}" = 'true' ]] ; then + AUDIOMAP=$(sed "s/0:a:0/1:a:0/g" <<< "$AUDIOMAP") + fi + echo "BBB ${AUDIOMAP[@]}" elif [[ "${DEVICE_INPUT_CHOICE}" = 0 ]] ; then _set_ffmpeg_loglevel GRAB_INPUT+=(-f decklink) @@ -3371,6 +3376,15 @@ if [[ "${DEVICE_INPUT_CHOICE}" = "0" ]] ; then _review_option "CONTAINER_CHOICE" "${CONTAINER_OPTIONS[@]}" _review_option "VIDEO_CODEC_CHOICE" "${VIDEO_CODEC_OPTIONS[@]}" _review_option "AUDIO_CODEC_CHOICE" "${AUDIO_CODEC_OPTIONS[@]}" + _review_option "QCTOOLSXML_CHOICE" "${QCTOOLSXML_OPTIONS[@]}" + if [[ "${QCTOOLSXML_CHOICE}" != "No" && ! "$(command -v qcli)" ]] ; then + _report -w "Please install qcli to use the qctools reporting option." + _report -w "Such as \`brew install qcli\`." + exit 1 + fi + if [[ "${FORMAT}" = "matroska" ]] ; then + _review_option "EMBED_LOGS_CHOICE" "${EMBED_LOGS_OPTIONS[@]}" + fi elif [[ "${DEVICE_INPUT_CHOICE}" = "1" ]] ; then _review_option "DV_CONTAINER_CHOICE" "${DV_CONTAINER_OPTIONS[@]}" FORMAT="rawvideo" @@ -3405,21 +3419,6 @@ if [[ -f "${VRECORD_OUTPUT}" ]] ; then exit fi -if [[ "${DEVICE_INPUT_CHOICE}" = "0" ]] ; then - _review_option "QCTOOLSXML_CHOICE" "${QCTOOLSXML_OPTIONS[@]}" - if [[ "${QCTOOLSXML_CHOICE}" != "No" && ! "$(command -v qcli)" ]] ; then - _report -w "Please install qcli to use the qctools reporting option." - _report -w "Such as \`brew install qcli\`." - exit 1 - fi - if [[ "${SIGNAL_INPUT}" = 'true' ]] ; then - AUDIOMAP=$(sed "s/0:a:0/1:a:0/g" <<< "$AUDIOMAP") - fi - if [[ "${FORMAT}" = "matroska" ]] ; then - _review_option "EMBED_LOGS_CHOICE" "${EMBED_LOGS_OPTIONS[@]}" - fi -fi - if [[ -n "${DURATION}" ]] ; then DUR_SECONDS=$(bc <<< "${DURATION} * 60" | sed "s/^\./0./") TIME_LIMIT=(-t "${DUR_SECONDS}") From 3f7cec56527d6225ec371d187b0be2e8d350cb5a Mon Sep 17 00:00:00 2001 From: Dave Rice Date: Fri, 3 Jul 2026 13:26:38 -0400 Subject: [PATCH 18/31] remove unneeded check and debugging --- vrecord | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/vrecord b/vrecord index 484c05e9..9e2b2a71 100755 --- a/vrecord +++ b/vrecord @@ -496,11 +496,7 @@ _setup_vrecord_input(){ GRAB_INPUT+=("${TIME_LIMIT[@]}") GRAB_INPUT+=(-f lavfi -i sine=440:r=48000) GRAB_INPUT+=(-pix_fmt yuv422p10le) - echo "AAA ${AUDIOMAP[@]}" - if [[ "${SIGNAL_INPUT}" = 'true' ]] ; then - AUDIOMAP=$(sed "s/0:a:0/1:a:0/g" <<< "$AUDIOMAP") - fi - echo "BBB ${AUDIOMAP[@]}" + AUDIOMAP=$(sed "s/0:a:0/1:a:0/g" <<< "$AUDIOMAP") elif [[ "${DEVICE_INPUT_CHOICE}" = 0 ]] ; then _set_ffmpeg_loglevel GRAB_INPUT+=(-f decklink) From d1db1113315e3f3847553c414f090012b5a9f0e3 Mon Sep 17 00:00:00 2001 From: Dave Rice Date: Fri, 3 Jul 2026 13:42:11 -0400 Subject: [PATCH 19/31] just set these once rather than for each standard-choice option --- vrecord | 54 +++++++++++++++--------------------------------------- 1 file changed, 15 insertions(+), 39 deletions(-) diff --git a/vrecord b/vrecord index 9e2b2a71..989d29d9 100755 --- a/vrecord +++ b/vrecord @@ -2181,11 +2181,9 @@ _lookup_choice(){ _add_mediaconch_rule_set "${STANDARD_NTSC_TEST}" fi RECORDINGFILTER_MP4+=",crop=w=720:h=480:x=0:y=4" - _set_some_params SET_COLOR_PRIMARIES="smpte170m" SET_COLOR_TRC="bt709" - SET_COLORSPACE="smpte170m" - RECORDINGFILTER+=",setparams=range=${SET_RANGE}:color_primaries=${SET_COLOR_PRIMARIES}:color_trc=${SET_COLOR_TRC}:colorspace=${SET_COLORSPACE}:field_mode=${SET_FIELD_MODE}" ;; + SET_COLORSPACE="smpte170m" ;; "PAL") STANDARD="pal " DECKLINK_FPS="25000/1000" @@ -2193,132 +2191,108 @@ _lookup_choice(){ FFV1_SLICES=24 SIG_INTERLACEMENT="tff" _add_mediaconch_rule_set "${STANDARD_PAL_TEST}" - _set_some_params SET_COLOR_PRIMARIES="bt470bg" SET_COLOR_TRC="bt709" - SET_COLORSPACE="bt470bg" - RECORDINGFILTER+=",setparams=range=${SET_RANGE}:color_primaries=${SET_COLOR_PRIMARIES}:color_trc=${SET_COLOR_TRC}:colorspace=${SET_COLORSPACE}:field_mode=${SET_FIELD_MODE}" ;; + SET_COLORSPACE="bt470bg" ;; "23ps - 1080p23.98") STANDARD="23ps" DECKLINK_FPS="24000/1001" SIG_GEN_SIZE="1920x1080" FFV1_SLICES=128 SIG_INTERLACEMENT="prog" - _set_some_params SET_COLOR_PRIMARIES="bt709" SET_COLOR_TRC="bt709" - SET_COLORSPACE="bt709" - RECORDINGFILTER+=",setparams=range=${SET_RANGE}:color_primaries=${SET_COLOR_PRIMARIES}:color_trc=${SET_COLOR_TRC}:colorspace=${SET_COLORSPACE}:field_mode=${SET_FIELD_MODE}" ;; + SET_COLORSPACE="bt709" ;; "24ps - 1080p24") STANDARD="24ps" DECKLINK_FPS="24000/1000" SIG_GEN_SIZE="1920x1080" FFV1_SLICES=128 SIG_INTERLACEMENT="prog" - _set_some_params SET_COLOR_PRIMARIES="bt709" SET_COLOR_TRC="bt709" - SET_COLORSPACE="bt709" - RECORDINGFILTER+=",setparams=range=${SET_RANGE}:color_primaries=${SET_COLOR_PRIMARIES}:color_trc=${SET_COLOR_TRC}:colorspace=${SET_COLORSPACE}:field_mode=${SET_FIELD_MODE}" ;; + SET_COLORSPACE="bt709" ;; "Hp25 - 1080p25") STANDARD="Hp25" DECKLINK_FPS="25000/1000" SIG_GEN_SIZE="1920x1080" FFV1_SLICES=128 SIG_INTERLACEMENT="prog" - _set_some_params SET_COLOR_PRIMARIES="bt709" SET_COLOR_TRC="bt709" - SET_COLORSPACE="bt709" - RECORDINGFILTER+=",setparams=range=${SET_RANGE}:color_primaries=${SET_COLOR_PRIMARIES}:color_trc=${SET_COLOR_TRC}:colorspace=${SET_COLORSPACE}:field_mode=${SET_FIELD_MODE}" ;; + SET_COLORSPACE="bt709" ;; "Hp29 - 1080p29.97") STANDARD="Hp29" DECKLINK_FPS="30000/1001" SIG_GEN_SIZE="1920x1080" FFV1_SLICES=128 SIG_INTERLACEMENT="prog" - _set_some_params SET_COLOR_PRIMARIES="bt709" SET_COLOR_TRC="bt709" - SET_COLORSPACE="bt709" - RECORDINGFILTER+=",setparams=range=${SET_RANGE}:color_primaries=${SET_COLOR_PRIMARIES}:color_trc=${SET_COLOR_TRC}:colorspace=${SET_COLORSPACE}:field_mode=${SET_FIELD_MODE}" ;; + SET_COLORSPACE="bt709" ;; "Hp30 - 1080p30") STANDARD="Hp30" DECKLINK_FPS="30000/1000" SIG_GEN_SIZE="1920x1080" FFV1_SLICES=128 SIG_INTERLACEMENT="prog" - _set_some_params SET_COLOR_PRIMARIES="bt709" SET_COLOR_TRC="bt709" - SET_COLORSPACE="bt709" - RECORDINGFILTER+=",setparams=range=${SET_RANGE}:color_primaries=${SET_COLOR_PRIMARIES}:color_trc=${SET_COLOR_TRC}:colorspace=${SET_COLORSPACE}:field_mode=${SET_FIELD_MODE}" ;; + SET_COLORSPACE="bt709" ;; "Hi50 - 1080i50") STANDARD="Hi50" DECKLINK_FPS="25000/1000" SIG_GEN_SIZE="1920x1080" FFV1_SLICES=128 SIG_INTERLACEMENT="tff" - _set_some_params SET_COLOR_PRIMARIES="bt709" SET_COLOR_TRC="bt709" - SET_COLORSPACE="bt709" - RECORDINGFILTER+=",setparams=range=${SET_RANGE}:color_primaries=${SET_COLOR_PRIMARIES}:color_trc=${SET_COLOR_TRC}:colorspace=${SET_COLORSPACE}:field_mode=${SET_FIELD_MODE}" ;; + SET_COLORSPACE="bt709" ;; "Hi59 - 1080i59.94") STANDARD="Hi59" DECKLINK_FPS="30000/1001" SIG_GEN_SIZE="1920x1080" FFV1_SLICES=128 SIG_INTERLACEMENT="tff" - _set_some_params SET_COLOR_PRIMARIES="bt709" SET_COLOR_TRC="bt709" - SET_COLORSPACE="bt709" - RECORDINGFILTER+=",setparams=range=${SET_RANGE}:color_primaries=${SET_COLOR_PRIMARIES}:color_trc=${SET_COLOR_TRC}:colorspace=${SET_COLORSPACE}:field_mode=${SET_FIELD_MODE}" ;; + SET_COLORSPACE="bt709" ;; "Hi60 - 1080i60") STANDARD="Hi60" DECKLINK_FPS="30000/1000" SIG_GEN_SIZE="1920x1080" FFV1_SLICES=128 SIG_INTERLACEMENT="tff" - _set_some_params SET_COLOR_PRIMARIES="bt709" SET_COLOR_TRC="bt709" - SET_COLORSPACE="bt709" - RECORDINGFILTER+=",setparams=range=${SET_RANGE}:color_primaries=${SET_COLOR_PRIMARIES}:color_trc=${SET_COLOR_TRC}:colorspace=${SET_COLORSPACE}:field_mode=${SET_FIELD_MODE}" ;; + SET_COLORSPACE="bt709" ;; "hp50 - 720p50") STANDARD="hp50" DECKLINK_FPS="50000/1000" SIG_GEN_SIZE="1280x720" FFV1_SLICES=64 SIG_INTERLACEMENT="prog" - _set_some_params SET_COLOR_PRIMARIES="bt709" SET_COLOR_TRC="bt709" - SET_COLORSPACE="bt709" - RECORDINGFILTER+=",setparams=range=${SET_RANGE}:color_primaries=${SET_COLOR_PRIMARIES}:color_trc=${SET_COLOR_TRC}:colorspace=${SET_COLORSPACE}:field_mode=${SET_FIELD_MODE}" ;; + SET_COLORSPACE="bt709" ;; "hp59 - 720p59.94") STANDARD="hp59" DECKLINK_FPS="60000/1001" SIG_GEN_SIZE="1280x720" FFV1_SLICES=64 SIG_INTERLACEMENT="prog" - _set_some_params SET_COLOR_PRIMARIES="bt709" SET_COLOR_TRC="bt709" - SET_COLORSPACE="bt709" - RECORDINGFILTER+=",setparams=range=${SET_RANGE}:color_primaries=${SET_COLOR_PRIMARIES}:color_trc=${SET_COLOR_TRC}:colorspace=${SET_COLORSPACE}:field_mode=${SET_FIELD_MODE}" ;; + SET_COLORSPACE="bt709" ;; "hp60 - 720p60") STANDARD="hp60" DECKLINK_FPS="60000/1000" SIG_GEN_SIZE="1280x720" FFV1_SLICES=64 SIG_INTERLACEMENT="prog" - _set_some_params SET_COLOR_PRIMARIES="bt709" SET_COLOR_TRC="bt709" - SET_COLORSPACE="bt709" - RECORDINGFILTER+=",setparams=range=${SET_RANGE}:color_primaries=${SET_COLOR_PRIMARIES}:color_trc=${SET_COLOR_TRC}:colorspace=${SET_COLORSPACE}:field_mode=${SET_FIELD_MODE}" ;; + SET_COLORSPACE="bt709" ;; # aspect ratio options "4/3") if [[ "${STANDARD}" = "ntsc" ]] ; then @@ -2784,6 +2758,8 @@ _review_all_options(){ _review_option "VIDEO_BIT_DEPTH_CHOICE" "${VIDEO_BITDEPTH_OPTIONS[@]}" _review_option "AUDIO_MAPPING_CHOICE" "${CHANNEL_MAPPING_OPTIONS[@]}" _review_option "STANDARD_CHOICE" "${STANDARD_OPTIONS[@]}" + _set_some_params + RECORDINGFILTER+=",setparams=range=${SET_RANGE}:color_primaries=${SET_COLOR_PRIMARIES}:color_trc=${SET_COLOR_TRC}:colorspace=${SET_COLORSPACE}:field_mode=${SET_FIELD_MODE}" _review_option -d "4/3" "ASPECT_RATIO_CHOICE" "${ASPECT_RATIO_OPTIONS[@]}" _review_option -n -d "10/11" "NTSC_43_SAR_CHOICE" "${NTSC_43_SAR_OPTIONS[@]}" _review_option -n -d "40/33" "NTSC_169_SAR_CHOICE" "${NTSC_169_SAR_OPTIONS[@]}" From 48020542e293a7f986311ae68d35a95af09110d9 Mon Sep 17 00:00:00 2001 From: Dave Rice Date: Fri, 3 Jul 2026 14:57:38 -0400 Subject: [PATCH 20/31] add output stream labels to the benchmark summary --- vrecord | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/vrecord b/vrecord index 989d29d9..03a0ac79 100755 --- a/vrecord +++ b/vrecord @@ -624,6 +624,16 @@ _setup_vrecord_process(){ else RECORD_COMMAND_SPLIT="[${MAIN_V_PADNAME}]" fi + + # map of the output mapping to the names, used in the benchmark summary + OUTPUT_LABELS="" + MAP_IDX="0" + for PAD in "${SPLIT_MAPS[@]}" "${AUDIO_CHANNEL_MAP[@]}" "${AUDIO_MP4_MAP[@]}" ; do + if [[ "${PAD:0:1}" == "[" ]] ; then + OUTPUT_LABELS+="${OUTPUT_LABELS:+,}0.${MAP_IDX}=${PAD//[\[\]]/}" + ((MAP_IDX++)) + fi + done # construct the audio stream processing AUDIO_TEE_SELECT_MAP="" @@ -3555,8 +3565,14 @@ if [[ "${DEVICE_INPUT_CHOICE}" = 0 ]] ; then mv "${FULL_CAPTURE_LOG}.tmp" "${FULL_CAPTURE_LOG}" echo "# vrecord benchmarking summary data" > "${BENCHMARK_SUMMARY_NAME}" echo "# capture_log_file: ${INGESTLOG}, cores=${CORE_COUNT}" >> "${BENCHMARK_SUMMARY_NAME}" - awk 'BEGIN { printf "%-8s %-6s %-8s %-12s %-5s %-10s %-10s %-10s %-10s %-10s %-8s %-10s\n", "action","media","stream","label","n","user","sys","real","real/user","sys/user","%user","avg_us" - CEIL = 10000000 } + awk -v labels="${OUTPUT_LABELS}" 'BEGIN { printf "%-8s %-6s %-8s %-16s %-5s %-10s %-10s %-10s %-10s %-10s %-8s %-10s\n", "action","media","stream","label","n","user","sys","real","real/user","sys/user","%user","avg_us" + CEIL = 10000000 + nlbl = split(labels, pairs, ",") + for (i = 1; i <= nlbl; i++) { + split(pairs[i], kv, "=") + label[kv[1]] = kv[2] + } + } $1 == "#label" { label[$2] = $3; next } $2 == "bench:" && $4 == "user" && $6 == "sys" && $8 == "real" { if ($3 !~ /^[0-9]+$/ || $5 !~ /^[0-9]+$/ || $7 !~ /^[0-9]+$/ || @@ -3575,7 +3591,7 @@ if [[ "${DEVICE_INPUT_CHOICE}" = 0 ]] ; then sysratio = (u[k] > 0) ? s[k] / u[k] : 0 share = (TOTAL_U > 0) ? 100 * u[k] / TOTAL_U : 0 avgus = (n[k] > 0) ? r[k] / n[k] : 0 - printf "%-8s %-6s %-8s %-12s %-5d %-10d %-10d %-10d %-10.3f %-10.3f %-8.2f %-10.1f\n", \ + printf "%-8s %-6s %-8s %-16s %-5d %-10d %-10d %-10d %-10.3f %-10.3f %-8.2f %-10.1f\n", \ f[1], f[2], f[3], lbl[k], n[k], u[k], s[k], r[k], ratio, sysratio, share, avgus } if (bad > 0) printf "\n[skipped %d malformed/out-of-range bench line(s)]\n", bad From b97a57b119c98788063286294162edcecc5f6c6c Mon Sep 17 00:00:00 2001 From: Dave Rice Date: Fri, 3 Jul 2026 16:10:54 -0400 Subject: [PATCH 21/31] move the pixel format setting out of the filterchain somehow the filterchain format for mp4 ends up applies to the other outputs --- vrecord | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vrecord b/vrecord index 03a0ac79..5acb878d 100755 --- a/vrecord +++ b/vrecord @@ -611,7 +611,7 @@ _setup_vrecord_process(){ RECORD_COMMAND_MP4_FILTER=";[MP4_V]${RECORDINGFILTER_MP4#,},bwdif=send_frame,format=yuv420p[${MP4_V_PADNAME}]" SPLIT_MAPS+=(-map "[${MP4_V_PADNAME}]") (( VIDEO_STREAM_IDX++ )) - EXTRAOPTIONS+=(-c:v:"${MP4_V_IDX}" h264 -g:v:"${MP4_V_IDX}" 12 -profile:v:"${MP4_V_IDX}" main -level:v:"${MP4_V_IDX}" "${ACCESS_MP4_LEVEL}" -b:v:"${MP4_V_IDX}" "${ACCESS_MP4_BITRATE}") + EXTRAOPTIONS+=(-c:v:"${MP4_V_IDX}" h264 -pix_fmt:v:"${MP4_V_IDX}" yuv420p -g:v:"${MP4_V_IDX}" 12 -profile:v:"${MP4_V_IDX}" main -level:v:"${MP4_V_IDX}" "${ACCESS_MP4_LEVEL}" -b:v:"${MP4_V_IDX}" "${ACCESS_MP4_BITRATE}") fi # construct end of video filterchain From 3a7cfa393c74319ae1614b151d6b6cc8608c6098 Mon Sep 17 00:00:00 2001 From: Dave Rice Date: Fri, 3 Jul 2026 16:13:48 -0400 Subject: [PATCH 22/31] test that preservation output is 4:2:2 --- vrecord | 1 + 1 file changed, 1 insertion(+) diff --git a/vrecord b/vrecord index 5acb878d..876fa370 100755 --- a/vrecord +++ b/vrecord @@ -2870,6 +2870,7 @@ CONTAINER_MATROSKA_TEST=' CODEC_GENERAL_TEST=' CFR YUV + 4:2:2 2vuy From cfd88422d8a2ade82b385bccb9709f0849cf93af Mon Sep 17 00:00:00 2001 From: Dave Rice Date: Mon, 3 Aug 2026 17:21:26 -0400 Subject: [PATCH 23/31] add qctools report to tee muxer output --- vrecord | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 182 insertions(+), 19 deletions(-) diff --git a/vrecord b/vrecord index 876fa370..87d0f994 100755 --- a/vrecord +++ b/vrecord @@ -152,9 +152,9 @@ _process_vars_for_gtk(){ fi fi - if [[ -z "${FFMPEG_BIN}" ]] ; then + if [[ -z "${FFMPEG_BIN}" ]] || [[ -z "${FFPROBE_BIN}" ]] ; then _gather_ffmpeg_vars - _include_var CAPTURELOGSUFFIX TIMECODELOGSUFFIX BREW_PREFIX FFMPEG_BIN FFPLAY_BIN + _include_var CAPTURELOGSUFFIX TIMECODELOGSUFFIX BREW_PREFIX FFMPEG_BIN FFPLAY_BIN FFPROBE_BIN fi } @@ -164,7 +164,7 @@ _remove_env_vars(){ unset VRECORD_VARS_FILE unset OS_TYPE RESOURCE_PATH DEFAULTFONT CORE_COUNT OPEN_COMMAND ZCAT_COMMAND COMPUTER_MODEL_NAME DIR_SELECTION_DIALOG unset COMPUTER_MODEL_ID COMPUTER_PROCESSOR_NAME COMPUTER_PROCESSOR_SPEED COMPUTER_PROCESSOR_COUNT COMPUTER_MEMORY COMPUTER_SERIAL - unset CAPTURELOGSUFFIX TIMECODELOGSUFFIX BREW_PREFIX FFMPEG_BIN FFPLAY_BIN GTKDIALOGBIN + unset CAPTURELOGSUFFIX TIMECODELOGSUFFIX BREW_PREFIX FFMPEG_BIN FFPLAY_BIN FFPROBE_BIN GTKDIALOGBIN VRECORD_VARS_FILE="/tmp/v_$(echo "${0}" | sed -e "s/[^A-Za-z0-9.]/_/g")_variables.txt" } @@ -271,16 +271,20 @@ _gather_ffmpeg_vars(){ if [[ -n "${FFMPEGALT}" ]] ; then FFMPEG_BIN="${FFMPEGALT}" FFPLAY_BIN=$(find "${DEPENDENCYDIR}" -iname "ffplay-ma" | head -n1) + FFPROBE_BIN=$(find "${DEPENDENCYDIR}" -iname "ffprobe-ma" | head -n1) elif which ffmpeg-ma &> /dev/null ; then FFMPEG_BIN="$(which ffmpeg-ma)" FFPLAY_BIN="$(which ffplay-ma)" + FFPROBE_BIN="$(which ffprobe-ma)" elif [[ -d "${BREW_PREFIX}/bin" ]] ; then FFMPEG_BIN="${BREW_PREFIX}/bin/ffmpeg-ma" FFPLAY_BIN="${BREW_PREFIX}/bin/ffplay-ma" + FFPROBE_BIN="${BREW_PREFIX}/bin/ffprobe-ma" else FFMPEG_BIN="$(which ffmpeg)" FFPLAY_BIN="$(which ffplay)" + FFPROBE_BIN="$(which ffprobe)" fi } @@ -361,6 +365,118 @@ _eia608dump2scc(){ _end_scc } +_qc_frame_to_records(){ + local DUMP="${1}" MEDIA_TYPE="${2}" STREAM_INDEX="${3}" + local WIDTH="${4:-}" HEIGHT="${5:-}" PIX_FMT="${6:-}" + awk -v MEDIA_TYPE="${MEDIA_TYPE}" -v STREAM_INDEX="${STREAM_INDEX}" -v WIDTH="${WIDTH}" -v HEIGHT="${HEIGHT}" -v PIX_FMT="${PIX_FMT}" ' + BEGIN { NL = sprintf("%c", 1) } # placeholder newline; replaced with \n after sort/cut + function XMLESC(S) { + gsub(/&/, "\\&", S) + gsub(//, "\\>", S) + gsub(/"/, "\\"", S) + return S + } + function FLUSH(NEXT_PTS_TIME, DUR, PTT, ATTRS, LINE, I) { + if (B_PTS_TIME == "") return + if (NEXT_PTS_TIME == "") DUR = LAST_DUR + else DUR = NEXT_PTS_TIME - B_PTS_TIME + LAST_DUR = DUR + PTT = sprintf("%.7f", B_PTS_TIME + 0) + ATTRS = "media_type=\"" MEDIA_TYPE "\" stream_index=\"" STREAM_INDEX "\"" \ + " key_frame=\"1\" pkt_pts=\"" B_PTS "\" pkt_pts_time=\"" PTT "\"" \ + " pkt_duration_time=\"" sprintf("%.7f", DUR + 0) "\"" + if (MEDIA_TYPE == "video") + ATTRS = ATTRS " width=\"" WIDTH "\" height=\"" HEIGHT "\" pix_fmt=\"" PIX_FMT "\" pict_type=\"I\"" + LINE = " " + } else { + LINE = LINE ">" NL + for (I = 1; I <= B_NTAGS; I++) + LINE = LINE " " NL + LINE = LINE " " + } + printf "%s\t%s\n", B_PTS_TIME, LINE + } + /^frame:/ { + FLUSH(PTS_TIME) + B_PTS = PTS; B_PTS_TIME = PTS_TIME; B_NTAGS = NTAGS + for (I = 1; I <= NTAGS; I++) { B_TAGKEY[I] = TAGKEY[I]; B_TAGVAL[I] = TAGVAL[I] } + + split($1, F1, ":"); FRAME = F1[2] + split($2, F2, ":"); PTS = F2[2] + split($3, F3, ":"); PTS_TIME = F3[2] + NTAGS = 0 + next + } + /^lavfi\.(signalstats|astats)\./ { + split($0, KV, "=") + VAL = KV[2] + if (VAL != "nan" && VAL != "-nan") + VAL = sprintf("%.6f", VAL + 0) + NTAGS++ + TAGKEY[NTAGS] = KV[1] + TAGVAL[NTAGS] = VAL + next + } + END { + FLUSH(PTS_TIME) + B_PTS = PTS; B_PTS_TIME = PTS_TIME; B_NTAGS = NTAGS + for (I = 1; I <= NTAGS; I++) { B_TAGKEY[I] = TAGKEY[I]; B_TAGVAL[I] = TAGVAL[I] } + FLUSH("") + }' "${DUMP}" +} + +_qc_dump_to_xml() { + local OUT_GZ="${1}" WIDTH="${2}" HEIGHT="${3}" PIX_FMT="${4}" SOURCE_FILE="${5:-}" + shift 5 # remaining args are triplets of DUMP, MEDIA_TYPE, and STREAM_INDEX + + local HEADER_XML="" + if [[ -n "${SOURCE_FILE}" ]] && [[ -s "${SOURCE_FILE}" ]] ; then + HEADER_XML="$("${FFPROBE_BIN}" -v quiet -print_format xml -show_program_version -show_library_versions -show_format -show_streams "${SOURCE_FILE}" | xmlstarlet sel -t -c "/ffprobe/program_version" -c "/ffprobe/library_versions" -c "/ffprobe/streams" -c "/ffprobe/format" 2>/dev/null)" + fi + + local SORTED_PARTS=() FOUND_ANY="false" + while [[ $# -gt 0 ]] ; do + local DUMP="${1}" MEDIA_TYPE="${2}" STREAM_INDEX="${3}" + shift 3 + if [[ -s "${DUMP}" ]] ; then + FOUND_ANY="true" + local PART + PART=$(_maketemp ".qc_part_${MEDIA_TYPE}${STREAM_INDEX}.txt") || return 1 + _qc_frame_to_records "${DUMP}" "${MEDIA_TYPE}" "${STREAM_INDEX}" "${WIDTH}" "${HEIGHT}" "${PIX_FMT}" \ + | sort -t$'\t' -k1,1n -s > "${PART}" + SORTED_PARTS+=("${PART}") + fi + done + if [[ "${FOUND_ANY}" = "false" ]] ; then + _report -w "No qctools metadata dumps found to convert" + rm -f "${SORTED_PARTS[@]}" + return 1 + fi + + local TMP + TMP=$(_maketemp .qc_merge.txt) || return 1 + # merge N pre-sorted streams in one linear pass, instead of a full sort + # over the concatenation of all frames from all streams + sort -t$'\t' -k1,1n -m -s "${SORTED_PARTS[@]}" > "${TMP}" + + { + echo "" + echo "" + echo "" + [[ -n "${HEADER_XML}" ]] && echo "${HEADER_XML}" + echo '' + cut -f2- "${TMP}" | tr '\001' '\n' + echo '' + echo '' + } | gzip > "${OUT_GZ}" + rm -f "${TMP}" "${SORTED_PARTS[@]}" +} + _cleanup(){ _report -wt "Vrecord is being stopped at $(_get_iso8601), noting this in the capture log." _writeingestlog "exit status" "vrecord was forced to quit early at $(_get_iso8601). Some processing may be incomplete." @@ -613,6 +729,26 @@ _setup_vrecord_process(){ (( VIDEO_STREAM_IDX++ )) EXTRAOPTIONS+=(-c:v:"${MP4_V_IDX}" h264 -pix_fmt:v:"${MP4_V_IDX}" yuv420p -g:v:"${MP4_V_IDX}" 12 -profile:v:"${MP4_V_IDX}" main -level:v:"${MP4_V_IDX}" "${ACCESS_MP4_LEVEL}" -b:v:"${MP4_V_IDX}" "${ACCESS_MP4_BITRATE}") fi + RECORD_COMMAND_QC_FILTER="" + if [[ "${QCTOOLSXML_V2_CHOICE}" = "true" ]] ; then + QC_SRC_V_PADNAME="QC_SRC_V" + QC_THUMB_PADNAME="QC_THUMB_V" + SPLIT_PADS+=("${QC_SRC_V_PADNAME}") + QC_THUMB_V_IDX="${VIDEO_STREAM_IDX}" + SPLIT_MAPS+=(-map "[${QC_THUMB_PADNAME}]") + (( VIDEO_STREAM_IDX++ )) + EXTRAOPTIONS+=(-c:v:"${QC_THUMB_V_IDX}" mjpeg -pix_fmt:v:"${QC_THUMB_V_IDX}" yuvj420p -qmin:v:"${QC_THUMB_V_IDX}" 8 -qmax:v:"${QC_THUMB_V_IDX}" 12 -metadata:s:v:"${QC_THUMB_V_IDX}" title="Frame Thumbnails") + QC_VIDEO_DUMP="$(_maketemp .qc_video.txt)" + RECORD_COMMAND_QC_FILTER=";[${QC_SRC_V_PADNAME}]split=2[${QC_THUMB_PADNAME}_pre][QC_STATS_V]" + RECORD_COMMAND_QC_FILTER+=";[${QC_THUMB_PADNAME}_pre]scale=72:72[${QC_THUMB_PADNAME}]" + RECORD_COMMAND_QC_FILTER+=";[QC_STATS_V]signalstats=stat=tout+vrep+brng,metadata=mode=print:file=${QC_VIDEO_DUMP},nullsink" + QC_AUDIO_DUMPS=() + for IDX in "${AUDIO_CHANNEL_IDX[@]}" ; do + QC_AUDIO_DUMP="$(_maketemp ".qc_audio${IDX}.txt")" + QC_AUDIO_DUMPS+=("${QC_AUDIO_DUMP}") + RECORD_COMMAND_QC_FILTER+=";[QC_A${IDX}]aformat=sample_fmts=flt|fltp,astats=metadata=1:reset=1:length=0.4,ametadata=mode=print:file=${QC_AUDIO_DUMP},anullsink" + done + fi # construct end of video filterchain SPLIT_COUNT=$(( 1 + ${#SPLIT_PADS[@]} )) @@ -624,7 +760,7 @@ _setup_vrecord_process(){ else RECORD_COMMAND_SPLIT="[${MAIN_V_PADNAME}]" fi - + # map of the output mapping to the names, used in the benchmark summary OUTPUT_LABELS="" MAP_IDX="0" @@ -655,7 +791,7 @@ _setup_vrecord_process(){ done fi - RECORD_COMMAND+=(-filter_complex "[0:v:0]${RECORDINGFILTER#,*}${TC_WRITE}${CAPTION_WRITE}${RECORD_COMMAND_SPLIT};${AUDIOMAP}${RECORD_COMMAND_MP4_FILTER}" "${SPLIT_MAPS[@]}" "${AUDIO_CHANNEL_MAP[@]}" $([[ "${MP4_CHOICE}" = "true" ]] && echo "${AUDIO_MP4_MAP[@]}") "${EXTRAOPTIONS[@]}") + RECORD_COMMAND+=(-filter_complex "[0:v:0]${RECORDINGFILTER#,*}${TC_WRITE}${CAPTION_WRITE}${RECORD_COMMAND_SPLIT};${AUDIOMAP}${RECORD_COMMAND_MP4_FILTER}${RECORD_COMMAND_QC_FILTER}" "${SPLIT_MAPS[@]}" "${AUDIO_CHANNEL_MAP[@]}" $([[ "${MP4_CHOICE}" = "true" ]] && echo "${AUDIO_MP4_MAP[@]}") "${EXTRAOPTIONS[@]}") # Build tee string RECORD_COMMAND_TEE_STR="[f=${FORMAT}:select=v\\\\:0,${AUDIO_TEE_SELECT_MAP}]${VRECORD_OUTPUT}" @@ -666,6 +802,10 @@ _setup_vrecord_process(){ if [[ "${MP4_CHOICE}" = "true" ]] ; then RECORD_COMMAND_TEE_STR+="|[f=mp4:onfail=ignore:movflags=write_colr+faststart:select=v\\\\:${MP4_V_IDX}${MP4_A_TEE_SELECT}]${MP4NAME}" fi + if [[ "${QCTOOLSXML_V2_CHOICE}" = "true" ]] ; then + QCTOOLS_THUMB_MKV="$(_maketemp .qc_thumb.mkv)" + RECORD_COMMAND_TEE_STR+="|[f=matroska:onfail=ignore:select=v\\\\:${QC_THUMB_V_IDX}]${QCTOOLS_THUMB_MKV}" + fi RECORD_COMMAND+=(-f tee "${RECORD_COMMAND_TEE_STR}") else if [[ "${SIGNAL_INPUT}" = 'true' ]] ; then @@ -880,6 +1020,11 @@ SIDECAR_FILES_GUI=" \"${TIMECODEVTT_CHOICE}\" TIMECODEVTT_CHOICE + + + \"${QCTOOLSXML_V2_CHOICE}\" + QCTOOLSXML_V2_CHOICE + $(_gtk_vbox_list "QCTOOLSXML_CHOICE" "180" "QCTools XML?" "${QCTOOLSXML_OPTIONS[@]}") @@ -2103,56 +2248,49 @@ _lookup_choice(){ # audio mappings "2 Stereo Tracks (Channels 1 & 2 -> 1st Track Stereo, Channels 3 & 4 -> 2nd Track Stereo)") - ASPLIT_N=$([[ "${MP4_CHOICE}" = "true" ]] && echo 2 || echo 1) - AUDIOMAP="[0:a:0]asplit=2[ORIG_A][ORIG_A2];[ORIG_A2]pan=stereo|c0=c0|c1=${PHASE_VALUE_2}c1,asplit=${ASPLIT_N}[MAIN_STEREO_A1]$([[ "${MP4_CHOICE}" = "true" ]] && echo "[MP4_A1]");[0:a:0]pan=stereo|c0=c2|c1=${PHASE_VALUE_4}c3,asplit=${ASPLIT_N}[MAIN_STEREO_A2]$([[ "${MP4_CHOICE}" = "true" ]] && echo "[MP4_A2]")" + AUDIOMAP="[0:a:0]asplit=2[ORIG_A][ORIG_A2];[ORIG_A2]pan=stereo|c0=c0|c1=${PHASE_VALUE_2}c1,asplit=${ASPLIT_N}[MAIN_STEREO_A1]$([[ "${MP4_CHOICE}" = "true" ]] && echo "[MP4_A1]")$([[ "${QCTOOLSXML_V2_CHOICE}" = "true" ]] && echo "[QC_A1]");[0:a:0]pan=stereo|c0=c2|c1=${PHASE_VALUE_4}c3,asplit=${ASPLIT_N}[MAIN_STEREO_A2]$([[ "${MP4_CHOICE}" = "true" ]] && echo "[MP4_A2]")$([[ "${QCTOOLSXML_V2_CHOICE}" = "true" ]] && echo "[QC_A2]")" AUDIO_PLAY_MAP="pan=4c|c0=c0|c1=${PHASE_VALUE_2}c1|c2=c2|c3=${PHASE_VALUE_4}c3" AUDIO_PLAY_LABELS=",drawtext=fontfile=${DEFAULTFONT}:text='L(1)':fontcolor=white:fontsize=18:x=0:y=0,drawtext=fontfile=${DEFAULTFONT}:text='R(1)':fontcolor=white:fontsize=18:x=0:y=20,drawtext=fontfile=${DEFAULTFONT}:text='L(2)':fontcolor=white:fontsize=18:x=0:y=40,drawtext=fontfile=${DEFAULTFONT}:text='R(2)':fontcolor=white:fontsize=18:x=0:y=60" AUDIO_CHANNEL_MAP+=(-map "[ORIG_A]" -map "[MAIN_STEREO_A1]" -map "[MAIN_STEREO_A2]") [[ "${MP4_CHOICE}" = "true" ]] && AUDIO_MP4_MAP+=(-map "[MP4_A1]" -map "[MP4_A2]") AUDIO_CHANNEL_IDX=(1 2) ;; "1 Stereo Track (From Channels 1 & 2)") - ASPLIT_N=$([[ "${MP4_CHOICE}" = "true" ]] && echo 2 || echo 1) - AUDIOMAP="[0:a:0]asplit=2[ORIG_A][ORIG_A2];[ORIG_A2]pan=stereo|c0=c0|c1=${PHASE_VALUE_2}c1,asplit=${ASPLIT_N}[MAIN_STEREO_A1]$([[ "${MP4_CHOICE}" = "true" ]] && echo "[MP4_A1]")" + AUDIOMAP="[0:a:0]asplit=2[ORIG_A][ORIG_A2];[ORIG_A2]pan=stereo|c0=c0|c1=${PHASE_VALUE_2}c1,asplit=${ASPLIT_N}[MAIN_STEREO_A1]$([[ "${MP4_CHOICE}" = "true" ]] && echo "[MP4_A1]")$([[ "${QCTOOLSXML_V2_CHOICE}" = "true" ]] && echo "[QC_A1]")" AUDIO_PLAY_MAP="pan=stereo|c0=c0|c1=${PHASE_VALUE_2}c1" AUDIO_PLAY_LABELS=",drawtext=fontfile=${DEFAULTFONT}:text='L(1)':fontcolor=white:fontsize=18:x=0:y=0,drawtext=fontfile=${DEFAULTFONT}:text='R(1)':fontcolor=white:fontsize=18:x=0:y=20" AUDIO_CHANNEL_MAP+=(-map "[ORIG_A]" -map "[MAIN_STEREO_A1]") [[ "${MP4_CHOICE}" = "true" ]] && AUDIO_MP4_MAP+=(-map "[MP4_A1]") AUDIO_CHANNEL_IDX=(1) ;; "1 Stereo Track (From Channels 3 & 4)") - ASPLIT_N=$([[ "${MP4_CHOICE}" = "true" ]] && echo 2 || echo 1) - AUDIOMAP="[0:a:0]asplit=2[ORIG_A][ORIG_A2];[ORIG_A2]pan=stereo|c0=c2|c1=${PHASE_VALUE_2}c3,asplit=${ASPLIT_N}[MAIN_STEREO_A1]$([[ "${MP4_CHOICE}" = "true" ]] && echo "[MP4_A1]")" + AUDIOMAP="[0:a:0]asplit=2[ORIG_A][ORIG_A2];[ORIG_A2]pan=stereo|c0=c2|c1=${PHASE_VALUE_2}c3,asplit=${ASPLIT_N}[MAIN_STEREO_A1]$([[ "${MP4_CHOICE}" = "true" ]] && echo "[MP4_A1]")$([[ "${QCTOOLSXML_V2_CHOICE}" = "true" ]] && echo "[QC_A1]")" AUDIO_PLAY_MAP="pan=stereo|c0=c2|c1=${PHASE_VALUE_2}c3" AUDIO_PLAY_LABELS=",drawtext=fontfile=${DEFAULTFONT}:text='L(1)':fontcolor=white:fontsize=18:x=0:y=40,drawtext=fontfile=${DEFAULTFONT}:text='R(1)':fontcolor=white:fontsize=18:x=0:y=60" AUDIO_CHANNEL_MAP+=(-map "[ORIG_A]" -map "[MAIN_STEREO_A1]") [[ "${MP4_CHOICE}" = "true" ]] && AUDIO_MP4_MAP+=(-map "[MP4_A1]") AUDIO_CHANNEL_IDX=(1) ;; "Channel 1 -> 1st Track Mono, Channel 2 -> 2nd Track Mono") - ASPLIT_N=$([[ "${MP4_CHOICE}" = "true" ]] && echo 2 || echo 1) - AUDIOMAP="[0:a:0]asplit=2[ORIG_A][ORIG_A2];[ORIG_A2]pan=mono|c0=c0,asplit=${ASPLIT_N}[MAIN_MONO_A1]$([[ "${MP4_CHOICE}" = "true" ]] && echo "[MP4_A1]");[0:a:0]pan=mono| c0=${PHASE_VALUE_2}c1,asplit=${ASPLIT_N}[MAIN_MONO_A2]$([[ "${MP4_CHOICE}" = "true" ]] && echo "[MP4_A2]")" + AUDIOMAP="[0:a:0]asplit=2[ORIG_A][ORIG_A2];[ORIG_A2]pan=mono|c0=c0,asplit=${ASPLIT_N}[MAIN_MONO_A1]$([[ "${MP4_CHOICE}" = "true" ]] && echo "[MP4_A1]")$([[ "${QCTOOLSXML_V2_CHOICE}" = "true" ]] && echo "[QC_A1]");[0:a:0]pan=mono| c0=${PHASE_VALUE_2}c1,asplit=${ASPLIT_N}[MAIN_MONO_A2]$([[ "${MP4_CHOICE}" = "true" ]] && echo "[MP4_A2]")$([[ "${QCTOOLSXML_V2_CHOICE}" = "true" ]] && echo "[QC_A2]")" AUDIO_PLAY_MAP="pan=stereo|c0=c0|c1=${PHASE_VALUE_2}c1" AUDIO_PLAY_LABELS=",drawtext=fontfile=${DEFAULTFONT}:text='M(1)':fontcolor=white:fontsize=18:x=0:y=0,drawtext=fontfile=${DEFAULTFONT}:text='M(2)':fontcolor=white:fontsize=18:x=0:y=20" AUDIO_CHANNEL_MAP+=(-map "[ORIG_A]" -map "[MAIN_MONO_A1]" -map "[MAIN_MONO_A2]") [[ "${MP4_CHOICE}" = "true" ]] && AUDIO_MP4_MAP+=(-map "[MP4_A1]" -map "[MP4_A2]") AUDIO_CHANNEL_IDX=(1 2) ;; "Channel 2 -> 1st Track Mono, Channel 1 -> 2nd Track Mono") - ASPLIT_N=$([[ "${MP4_CHOICE}" = "true" ]] && echo 2 || echo 1) - AUDIOMAP="[0:a:0]asplit=2[ORIG_A][ORIG_A2];[ORIG_A2]pan=mono|c0=${PHASE_VALUE_2}c1,asplit=${ASPLIT_N}[MAIN_MONO_A1]$([[ "${MP4_CHOICE}" = "true" ]] && echo "[MP4_A1]");[0:a:0]pan=mono| c0=c0,asplit=${ASPLIT_N}[MAIN_MONO_A2]$([[ "${MP4_CHOICE}" = "true" ]] && echo "[MP4_A2]")" + AUDIOMAP="[0:a:0]asplit=2[ORIG_A][ORIG_A2];[ORIG_A2]pan=mono|c0=${PHASE_VALUE_2}c1,asplit=${ASPLIT_N}[MAIN_MONO_A1]$([[ "${MP4_CHOICE}" = "true" ]] && echo "[MP4_A1]")$([[ "${QCTOOLSXML_V2_CHOICE}" = "true" ]] && echo "[QC_A1]");[0:a:0]pan=mono| c0=c0,asplit=${ASPLIT_N}[MAIN_MONO_A2]$([[ "${MP4_CHOICE}" = "true" ]] && echo "[MP4_A2]")$([[ "${QCTOOLSXML_V2_CHOICE}" = "true" ]] && echo "[QC_A2]")" AUDIO_PLAY_MAP="pan=stereo|c0=${PHASE_VALUE_2}c1|c1=c0" AUDIO_PLAY_LABELS=",drawtext=fontfile=${DEFAULTFONT}:text='M(2)':fontcolor=white:fontsize=18:x=0:y=0,drawtext=fontfile=${DEFAULTFONT}:text='M(1)':fontcolor=white:fontsize=18:x=0:y=20" AUDIO_CHANNEL_MAP+=(-map "[ORIG_A]" -map "[MAIN_MONO_A1]" -map "[MAIN_MONO_A2]") [[ "${MP4_CHOICE}" = "true" ]] && AUDIO_MP4_MAP+=(-map "[MP4_A1]" -map "[MP4_A2]") AUDIO_CHANNEL_IDX=(1 2) ;; "Channel 1 -> Single Track Mono") - ASPLIT_N=$([[ "${MP4_CHOICE}" = "true" ]] && echo 2 || echo 1) - AUDIOMAP="[0:a:0]asplit=2[ORIG_A][ORIG_A2];[ORIG_A2]pan=mono|c0=c0,asplit=${ASPLIT_N}[MAIN_MONO_A1]$([[ "${MP4_CHOICE}" = "true" ]] && echo "[MP4_A1]")" + AUDIOMAP="[0:a:0]asplit=2[ORIG_A][ORIG_A2];[ORIG_A2]pan=mono|c0=c0,asplit=${ASPLIT_N}[MAIN_MONO_A1]$([[ "${MP4_CHOICE}" = "true" ]] && echo "[MP4_A1]")$([[ "${QCTOOLSXML_V2_CHOICE}" = "true" ]] && echo "[QC_A1]")" AUDIO_PLAY_MAP="pan=mono| c0=c0" AUDIO_PLAY_LABELS=",drawtext=fontfile=${DEFAULTFONT}:text='M(1)':fontcolor=white:fontsize=18:x=0:y=0" AUDIO_CHANNEL_MAP+=(-map "[ORIG_A]" -map "[MAIN_MONO_A1]") [[ "${MP4_CHOICE}" = "true" ]] && AUDIO_MP4_MAP+=(-map "[MP4_A1]") AUDIO_CHANNEL_IDX=(1) ;; "Channel 2 -> Single Track Mono") - ASPLIT_N=$([[ "${MP4_CHOICE}" = "true" ]] && echo 2 || echo 1) - AUDIOMAP="[0:a:0]asplit=2[ORIG_A][ORIG_A2];[ORIG_A2]pan=mono|c0=c1,asplit=${ASPLIT_N}[MAIN_MONO_A1]$([[ "${MP4_CHOICE}" = "true" ]] && echo "[MP4_A1]")" + AUDIOMAP="[0:a:0]asplit=2[ORIG_A][ORIG_A2];[ORIG_A2]pan=mono|c0=c1,asplit=${ASPLIT_N}[MAIN_MONO_A1]$([[ "${MP4_CHOICE}" = "true" ]] && echo "[MP4_A1]")$([[ "${QCTOOLSXML_V2_CHOICE}" = "true" ]] && echo "[QC_A1]")" AUDIO_PLAY_MAP="pan=mono| c0=c1" AUDIO_PLAY_LABELS=",drawtext=fontfile=${DEFAULTFONT}:text='M(1)':fontcolor=white:fontsize=18:x=0:y=20" AUDIO_CHANNEL_MAP+=(-map "[ORIG_A]" -map "[MAIN_MONO_A1]") @@ -2766,6 +2904,7 @@ _review_all_options(){ _review_option "VIDEO_INPUT_CHOICE" "${VIDEO_INPUT_OPTIONS[@]}" _review_option "AUDIO_INPUT_CHOICE" "${AUDIO_INPUT_OPTIONS[@]}" _review_option "VIDEO_BIT_DEPTH_CHOICE" "${VIDEO_BITDEPTH_OPTIONS[@]}" + ASPLIT_N=$((1 + ($([[ "${MP4_CHOICE}" = "true" ]] && echo 1 || echo 0)) + ($([[ "${QCTOOLSXML_V2_CHOICE}" = "true" ]] && echo 1 || echo 0)))) _review_option "AUDIO_MAPPING_CHOICE" "${CHANNEL_MAPPING_OPTIONS[@]}" _review_option "STANDARD_CHOICE" "${STANDARD_OPTIONS[@]}" _set_some_params @@ -3658,6 +3797,30 @@ if [[ "${DEVICE_INPUT_CHOICE}" = 0 ]] ; then fi _report -d "QCTools analysis is complete." fi + if [[ "${QCTOOLSXML_V2_CHOICE}" = "true" ]] ; then + _report -d "Post-processing vrecord-native QCTools report (v2)..." + QC_WIDTH="${SIG_GEN_SIZE%x*}" + QC_HEIGHT="${SIG_GEN_SIZE#*x}" + if [[ "${PIXEL_FORMAT}" = "yuv422p10" ]] ; then + QC_PIXFMT="yuv422p10le" + else + QC_PIXFMT="${PIXEL_FORMAT}" + fi + QCTOOLS_XML_GZ="$(_maketemp .qctools.xml.gz)" + QC_DUMP_ARGS=("${QC_VIDEO_DUMP}" video 0) + for I in "${!QC_AUDIO_DUMPS[@]}" ; do + QC_DUMP_ARGS+=("${QC_AUDIO_DUMPS[$I]}" audio "$(( I + 1 ))") + done + _qc_dump_to_xml "${QCTOOLS_XML_GZ}" "${QC_WIDTH}" "${QC_HEIGHT}" "${QC_PIXFMT}" "${LOGDIR}/${FULL_OUTPUT_ID}.${EXTENSION}" "${QC_DUMP_ARGS[@]}" + if [[ -s "${QCTOOLS_THUMB_MKV}" ]] && [[ -s "${QCTOOLS_XML_GZ}" ]] && \ + command -v mkvpropedit &> /dev/null ; then + mkvpropedit "${QCTOOLS_THUMB_MKV}" --edit info --set "title=QCTools Report for ${FULL_OUTPUT_ID}.${EXTENSION}" --attachment-name "${FULL_OUTPUT_ID}.${EXTENSION}.qctools.xml.gz" --attachment-mime-type application/x-gzip --add-attachment "${QCTOOLS_XML_GZ}" + mv "${QCTOOLS_THUMB_MKV}" "${LOGDIR}/${FULL_OUTPUT_ID}.${EXTENSION}.qctools-v2.mkv" + mv "${QCTOOLS_XML_GZ}" "${LOGDIR}/${FULL_OUTPUT_ID}.${EXTENSION}.qctools_v2.xml.gz" + else + _report -w "QCTools v2 report generation failed: missing thumbnail mkv, xml.gz, or mkvpropedit" + fi + fi # make a timecode vtt if [[ "${TIMECODEVTT_CHOICE}" = "true" ]] && [[ "${FORMAT}" = "matroska" ]] && [[ "${SIGNAL_INPUT}" != 'true' ]] && [[ -z "${ALT_INPUT}" ]] ; then From 6dcfa0090b7b35fd26d26e30e3c31435d189048e Mon Sep 17 00:00:00 2001 From: Dave Rice Date: Wed, 5 Aug 2026 11:41:02 -0400 Subject: [PATCH 24/31] remember the QCTools v2 setting --- Resources/vrecord_functions | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Resources/vrecord_functions b/Resources/vrecord_functions index beb2ac90..0b39d03f 100644 --- a/Resources/vrecord_functions +++ b/Resources/vrecord_functions @@ -64,6 +64,7 @@ _update_config_file(){ echo "MP4_CHOICE=\"${MP4_CHOICE}\"" echo "FRAMEMD5_CHOICE=\"${FRAMEMD5_CHOICE}\"" echo "TIMECODEVTT_CHOICE=\"${TIMECODEVTT_CHOICE}\"" + echo "QCTOOLSXML_V2_CHOICE=\"${QCTOOLSXML_V2_CHOICE}\"" echo "EMBED_LOGS_CHOICE=\"${EMBED_LOGS_CHOICE}\"" echo "PLAYBACKVIEW_CHOICE=\"${PLAYBACKVIEW_CHOICE}\"" echo "PLAYBACKVIEW_CHOICE_PASS=\"${PLAYBACKVIEW_CHOICE_PASS}\"" @@ -267,6 +268,7 @@ _get_summary(){ echo "Access MP4: ${MP4_CHOICE}" echo "Frame MD5s: ${FRAMEMD5_CHOICE}" echo "Timecode VTT: ${TIMECODEVTT_CHOICE}" + echo "Vrecord QCTools: ${QCTOOLSXML_V2_CHOICE}" echo "QCTools XML: ${QCTOOLSXML_CHOICE}" echo "Playback: ${PLAYBACKVIEW_CHOICE} view (for recording) and ${PLAYBACKVIEW_CHOICE_PASS} view (for passthrough)" elif [ "${DEVICE_INPUT_CHOICE}" = "1" ] ; then From e53c137e3f7d3884b465b29c102081aad8a8b6cc Mon Sep 17 00:00:00 2001 From: Dave Rice Date: Tue, 25 Aug 2026 13:29:38 -0400 Subject: [PATCH 25/31] add colorspace properties to the access mp4 --- vrecord | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vrecord b/vrecord index 87d0f994..52024362 100755 --- a/vrecord +++ b/vrecord @@ -724,7 +724,7 @@ _setup_vrecord_process(){ MP4NAME="${DIR}/${FULL_OUTPUT_ID}.mp4" MP4_V_PADNAME="MP4_V" SPLIT_PADS+=("${MP4_V_PADNAME}") - RECORD_COMMAND_MP4_FILTER=";[MP4_V]${RECORDINGFILTER_MP4#,},bwdif=send_frame,format=yuv420p[${MP4_V_PADNAME}]" + RECORD_COMMAND_MP4_FILTER=";[MP4_V]${RECORDINGFILTER_MP4#,},bwdif=send_frame,setparams=range=${SET_RANGE}:color_primaries=${SET_COLOR_PRIMARIES}:color_trc=${SET_COLOR_TRC}:colorspace=${SET_COLORSPACE},format=yuv420p[${MP4_V_PADNAME}]" SPLIT_MAPS+=(-map "[${MP4_V_PADNAME}]") (( VIDEO_STREAM_IDX++ )) EXTRAOPTIONS+=(-c:v:"${MP4_V_IDX}" h264 -pix_fmt:v:"${MP4_V_IDX}" yuv420p -g:v:"${MP4_V_IDX}" 12 -profile:v:"${MP4_V_IDX}" main -level:v:"${MP4_V_IDX}" "${ACCESS_MP4_LEVEL}" -b:v:"${MP4_V_IDX}" "${ACCESS_MP4_BITRATE}") From 120abc4020eebff65e8162b11e50788b11d5224b Mon Sep 17 00:00:00 2001 From: Dave Rice Date: Tue, 25 Aug 2026 14:10:33 -0400 Subject: [PATCH 26/31] set a blank output for truncation test as a No --- vrecord | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vrecord b/vrecord index 52024362..492f7076 100755 --- a/vrecord +++ b/vrecord @@ -3221,7 +3221,7 @@ _check_file_truncation() { if [[ "${IS_TRUNCATED}" = "Yes" ]] ; then cowsay "$(_report -w "WARNING: MediaInfo reports that the ${TEST_LABEL} file (${TEST_FILE}) may be truncated or incomplete.")" fi - _writeingestlog "${TEST_LABEL}_truncated" "${IS_TRUNCATED}" + _writeingestlog "${TEST_LABEL}_truncated" "${IS_TRUNCATED:-No}" fi } From 4833c2c7137cc453394b643055eec3d7aa64d553 Mon Sep 17 00:00:00 2001 From: Dave Rice Date: Tue, 25 Aug 2026 14:11:02 -0400 Subject: [PATCH 27/31] remove redundant pixel format setting, it's already in the filterchain --- vrecord | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vrecord b/vrecord index 492f7076..d823e7f1 100755 --- a/vrecord +++ b/vrecord @@ -727,7 +727,7 @@ _setup_vrecord_process(){ RECORD_COMMAND_MP4_FILTER=";[MP4_V]${RECORDINGFILTER_MP4#,},bwdif=send_frame,setparams=range=${SET_RANGE}:color_primaries=${SET_COLOR_PRIMARIES}:color_trc=${SET_COLOR_TRC}:colorspace=${SET_COLORSPACE},format=yuv420p[${MP4_V_PADNAME}]" SPLIT_MAPS+=(-map "[${MP4_V_PADNAME}]") (( VIDEO_STREAM_IDX++ )) - EXTRAOPTIONS+=(-c:v:"${MP4_V_IDX}" h264 -pix_fmt:v:"${MP4_V_IDX}" yuv420p -g:v:"${MP4_V_IDX}" 12 -profile:v:"${MP4_V_IDX}" main -level:v:"${MP4_V_IDX}" "${ACCESS_MP4_LEVEL}" -b:v:"${MP4_V_IDX}" "${ACCESS_MP4_BITRATE}") + EXTRAOPTIONS+=(-c:v:"${MP4_V_IDX}" h264 -g:v:"${MP4_V_IDX}" 12 -profile:v:"${MP4_V_IDX}" main -level:v:"${MP4_V_IDX}" "${ACCESS_MP4_LEVEL}" -b:v:"${MP4_V_IDX}" "${ACCESS_MP4_BITRATE}") fi RECORD_COMMAND_QC_FILTER="" if [[ "${QCTOOLSXML_V2_CHOICE}" = "true" ]] ; then From e5f8b1219ef5926d4103166247b2e38ac4f770f9 Mon Sep 17 00:00:00 2001 From: Dave Rice Date: Tue, 25 Aug 2026 14:12:37 -0400 Subject: [PATCH 28/31] use _mv_tmp_file function rather than mv --- vrecord | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vrecord b/vrecord index d823e7f1..64489a00 100755 --- a/vrecord +++ b/vrecord @@ -3253,7 +3253,7 @@ ${WEBVTT_EXTRA} FADGI_VTT_HEADER tail -n +2 "${VTT_FILE}" >> "${VTT_TMP}" -mv "${VTT_TMP}" "${VTT_FILE}" +_mv_tmp_file "${VTT_TMP}" "${VTT_FILE}" } # command-line options to set media id and original variables @@ -3702,7 +3702,7 @@ if [[ "${DEVICE_INPUT_CHOICE}" = 0 ]] ; then _report -dt "Post-processing benchmarking files..." grep " bench: " "${FULL_CAPTURE_LOG}" > "${BENCHMARK_DETAILS_NAME}" grep -v " bench: " "${FULL_CAPTURE_LOG}" > "${FULL_CAPTURE_LOG}.tmp" - mv "${FULL_CAPTURE_LOG}.tmp" "${FULL_CAPTURE_LOG}" + _mv_tmp_file "${FULL_CAPTURE_LOG}.tmp" "${FULL_CAPTURE_LOG}" echo "# vrecord benchmarking summary data" > "${BENCHMARK_SUMMARY_NAME}" echo "# capture_log_file: ${INGESTLOG}, cores=${CORE_COUNT}" >> "${BENCHMARK_SUMMARY_NAME}" awk -v labels="${OUTPUT_LABELS}" 'BEGIN { printf "%-8s %-6s %-8s %-16s %-5s %-10s %-10s %-10s %-10s %-10s %-8s %-10s\n", "action","media","stream","label","n","user","sys","real","real/user","sys/user","%user","avg_us" @@ -3755,7 +3755,7 @@ if [[ "${DEVICE_INPUT_CHOICE}" = 0 ]] ; then _report -dt "Wrapping the extracted captions into the Access MP4 file..." "${FFMPEG_BIN}" -nostdin -v 0 -y -i "${MP4NAME}" -i "${CAP2EMBED}" -map 0:v -map 0:a -map 1:s -c:v copy -c:a copy -c:s mov_text "${MP4NAME}.subs.mp4" if [[ $? -eq 0 ]]; then - mv -v "${MP4NAME}.subs.mp4" "${MP4NAME}" + _mv_tmp_file "${MP4NAME}.subs.mp4" "${MP4NAME}" fi fi fi @@ -3815,8 +3815,8 @@ if [[ "${DEVICE_INPUT_CHOICE}" = 0 ]] ; then if [[ -s "${QCTOOLS_THUMB_MKV}" ]] && [[ -s "${QCTOOLS_XML_GZ}" ]] && \ command -v mkvpropedit &> /dev/null ; then mkvpropedit "${QCTOOLS_THUMB_MKV}" --edit info --set "title=QCTools Report for ${FULL_OUTPUT_ID}.${EXTENSION}" --attachment-name "${FULL_OUTPUT_ID}.${EXTENSION}.qctools.xml.gz" --attachment-mime-type application/x-gzip --add-attachment "${QCTOOLS_XML_GZ}" - mv "${QCTOOLS_THUMB_MKV}" "${LOGDIR}/${FULL_OUTPUT_ID}.${EXTENSION}.qctools-v2.mkv" - mv "${QCTOOLS_XML_GZ}" "${LOGDIR}/${FULL_OUTPUT_ID}.${EXTENSION}.qctools_v2.xml.gz" + _mv_tmp_file "${QCTOOLS_THUMB_MKV}" "${LOGDIR}/${FULL_OUTPUT_ID}.${EXTENSION}.qctools-v2.mkv" + _mv_tmp_file "${QCTOOLS_XML_GZ}" "${LOGDIR}/${FULL_OUTPUT_ID}.${EXTENSION}.qctools_v2.xml.gz" else _report -w "QCTools v2 report generation failed: missing thumbnail mkv, xml.gz, or mkvpropedit" fi From 17a24ca707126b60d265b2cf131d6635b146877d Mon Sep 17 00:00:00 2001 From: Dave Rice Date: Tue, 25 Aug 2026 14:49:45 -0400 Subject: [PATCH 29/31] add a force option to _mv_tmp_file --- vrecord | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/vrecord b/vrecord index 64489a00..2d7322ce 100755 --- a/vrecord +++ b/vrecord @@ -3253,7 +3253,7 @@ ${WEBVTT_EXTRA} FADGI_VTT_HEADER tail -n +2 "${VTT_FILE}" >> "${VTT_TMP}" -_mv_tmp_file "${VTT_TMP}" "${VTT_FILE}" +_mv_tmp_file -f "${VTT_TMP}" "${VTT_FILE}" } # command-line options to set media id and original variables @@ -3328,10 +3328,15 @@ _upgrade_config_file(){ _mv_tmp_file(){ # this is to move a temp file into its final place without really worrying about preserving owner and group file attributes + local FORCE="false" + if [[ "${1}" = "-f" ]] ; then + FORCE="true" + shift + fi TMP_FROM="${1}" TMP_TO="${2}" TMP_DIR=$(dirname -- "${TMP_TO}") - if [[ -e "${TMP_TO}" ]]; then + if [[ -e "${TMP_TO}" ]] && [[ "${FORCE}" != "true" ]] ; then _report -w "ERROR: _mv_tmp_file, destination ${TMP_TO} already exists" exit 1 fi From 13049d6b3295edcbf06fe64b17b6a6091e98d713 Mon Sep 17 00:00:00 2001 From: Dave Rice Date: Tue, 25 Aug 2026 14:50:44 -0400 Subject: [PATCH 30/31] lets no try to preserve all these attributes on a temp file --- vrecord | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vrecord b/vrecord index 2d7322ce..bfb53c63 100755 --- a/vrecord +++ b/vrecord @@ -3348,7 +3348,7 @@ _mv_tmp_file(){ _report -w "WARNING: logging directory ${TMP_DIR} is not writable" fi if [[ -f "${TMP_FROM}" ]] ; then - cp -p "${TMP_FROM}" "${TMP_TO}" && rm "${TMP_FROM}" + cp "${TMP_FROM}" "${TMP_TO}" && rm "${TMP_FROM}" else _report -w "ERROR _mv_tmp_file is moving ${TMP_FROM} but it doesn't exist" exit 1 From e026cdaeea32c3dd0b745b233edc1cbd8452b601 Mon Sep 17 00:00:00 2001 From: Dave Rice Date: Tue, 25 Aug 2026 15:58:22 -0400 Subject: [PATCH 31/31] set pixel_format via -pix_fmt rather than filter else pixel-format can cross-pollute --- vrecord | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vrecord b/vrecord index bfb53c63..ae7c5a11 100755 --- a/vrecord +++ b/vrecord @@ -724,10 +724,10 @@ _setup_vrecord_process(){ MP4NAME="${DIR}/${FULL_OUTPUT_ID}.mp4" MP4_V_PADNAME="MP4_V" SPLIT_PADS+=("${MP4_V_PADNAME}") - RECORD_COMMAND_MP4_FILTER=";[MP4_V]${RECORDINGFILTER_MP4#,},bwdif=send_frame,setparams=range=${SET_RANGE}:color_primaries=${SET_COLOR_PRIMARIES}:color_trc=${SET_COLOR_TRC}:colorspace=${SET_COLORSPACE},format=yuv420p[${MP4_V_PADNAME}]" + RECORD_COMMAND_MP4_FILTER=";[MP4_V]${RECORDINGFILTER_MP4#,},bwdif=send_frame,setparams=range=${SET_RANGE}:color_primaries=${SET_COLOR_PRIMARIES}:color_trc=${SET_COLOR_TRC}:colorspace=${SET_COLORSPACE}[${MP4_V_PADNAME}]" SPLIT_MAPS+=(-map "[${MP4_V_PADNAME}]") (( VIDEO_STREAM_IDX++ )) - EXTRAOPTIONS+=(-c:v:"${MP4_V_IDX}" h264 -g:v:"${MP4_V_IDX}" 12 -profile:v:"${MP4_V_IDX}" main -level:v:"${MP4_V_IDX}" "${ACCESS_MP4_LEVEL}" -b:v:"${MP4_V_IDX}" "${ACCESS_MP4_BITRATE}") + EXTRAOPTIONS+=(-c:v:"${MP4_V_IDX}" h264 -pix_fmt:v:"${MP4_V_IDX}" yuv420p -g:v:"${MP4_V_IDX}" 12 -profile:v:"${MP4_V_IDX}" main -level:v:"${MP4_V_IDX}" "${ACCESS_MP4_LEVEL}" -b:v:"${MP4_V_IDX}" "${ACCESS_MP4_BITRATE}") fi RECORD_COMMAND_QC_FILTER="" if [[ "${QCTOOLSXML_V2_CHOICE}" = "true" ]] ; then