Skip to content
Permalink
main
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
executable file 1063 lines (1016 sloc) 52.9 KB
#!/bin/bash
# dependencies: dvrescue, ffmpeg, xmlstarlet
_usage(){
cat <<EOF
dvpackager
Rewrap a DV stream. This script is part of the dvrescue project.
Usage:
dvpackager [options] file.dv
Options:
By default, dvpackager will split the output files so that each time
significant technical characteristics of the dv stream change (such as aspect
ratio, frame rate, audio channel count, or audio sample rate) a new output file
will be written. The following flags adjust the way dvpackager will split the
output.
-f (forces dvpackager to ignore changes in significant technical
characteristics of the dv stream when splitting the output)
-s (split the output file at recording start markers)
-d (split the output file at non-consecutive recording timestamps)
-t (split the output file at non-consecutive timecode values)
-o <dir> (provide a custom output directory)
-e <ext> (specify the extension of the container to use. Tested with:
mkv, mov, dv. Defaults to mkv. Note that using the 'dv' option
shall simply extract the dv from the file while using the
selected options to split the output.)
-l <code>(specify the language code to use for the audio tracks.
If multiple languages are provided with a comma delimiter such as
'eng,spa,fra,nor' then they are used for each audio track in order.
Note that usually could be one or two stereo tracks, but if -m is
enabled then it is possible for two or four mono tracks, each
with it's own language code.)
-L <code>(specify the language code to use for the caption tracks (if
any).
-m (By default, audio will be handled in stereo pairs, enabling this
option will arrange the audio into tracks with mono channels.)
-S (embeds a caption track if there are captions to represent in the
source DV)
-n (do not repackage, simply generate a dvrescue xml if one doesn't
already exist, and report on what the output files would be)
-v (shows ffmpeg stderr output, otherwise this is hidden)
-F <path> (provide a custom ffmpeg path)
-M <path> (provide a custom mediainfo path)
-D <path> (provide a custom dvrescue path)
-X <path> (provide a custom xmlstarlet path)
-x <path> (provide a custom path to a DVRescue XML that corresponds to the
input file)
-V <path> (provide a custom path to a DVRescue technical subtitle file that
corresponds to the input file, ignored if -S is not set)
-C <path> (provide a custom path to a DVRescue SCC that corresponds to the
input file, ignored if -S is not set)
-3 (by default, dvpacakager will resample audio to 48000 Hz sampling
rate when muxing to a container. Enable this option to keep the
sampling rate in its native format. Setting this may result in
more output files as each change in sampling rate will require a
new output file. Automatically enabled when '-e dv'.)
-a <opt> (Choose a strategy for handling mid-stream changes in aspect ratio.
Options:
- n: Split segments for aspect ratio changes.
- 4: Do not split segments for aspect ratio changes alone,
and if packaging a mix of 4/3 and 16/9 content together,
then label it as 4/3 in the container.
- 9: Do not split segments for aspect ratio changes alone,
and if packaging a mix of 4/3 and 16/9 content together,
then label it as 16/9 in the container.
-c: Do not split segments for aspect ratio changes alone,
and if packaging a mix of 4/3 and 16/9 content together,
then label it according the more common aspect ratio in
that segment.
)
For example, the following command:
dvpackager -s INPUT.dv
will read INPUT.dv and rewrap those dv frames into an output file while making
one new output file whenever there is a change in significant technical
characteristics or frames with recording-start flags.
dvpackager also has an 'unpackage' mode
-u (export the dv stream from each provided file into a single dv stream)
For example, the following command:
dvpackager -u INPUT_1.mkv INPUT_2.mkv INPUT_3.mkv
will create one dv stream that contains all the DV of each input file.
dvpackager also has a method to express a table of how the file would be
repackaged given an XML input.
-T (provide only a table of information about the output files without
making any)
For example, the following command:
dvpackager -T -s -a c file.dv.dvrescue.xml
will show a table of the outputs for file.dv with the provided options (-s -a c).
EOF
}
if [ "${#}" = 0 ] ; then
_usage
exit 0
fi
_maketemp(){
mktemp -q -t "$(basename "${0}").XXXXXX"
if [ "${?}" -ne 0 ]; then
echo "${0}: Can't create temp file, exiting..."
exit 1
fi
}
_get_ranges(){
OPTIND=1
unset IN_FRAME OUT_FRAME
while getopts "ai:o:q" opt ; do
case "${opt}" in
a) NO_AUDIO_BREAK="Y" ;;
i) IN_FRAME="${OPTARG}" ;;
o) OUT_FRAME="${OPTARG}" ;;
q) QUICK="Y" ;;
esac
done
shift "$((OPTIND-1))"
FRAME_MATCH="d:dvrescue/d:media/d:frames"
if [[ "${FORCE_FRAMES}" = "Y" ]] ; then
FRAME_MATCH="${FRAME_MATCH}[1]"
RANGE_MATCH="d:frame[1]"
RELATIVE_FRAMES="../../d:frames[last()]"
elif [[ -n "${1}" ]] ; then
RANGE_MATCH="${1}"
RELATIVE_FRAMES="parent::d:frames"
else
RANGE_MATCH="${MATCH_FRAMES}"
RELATIVE_FRAMES="parent::d:frames"
fi
if [[ -n "${IN_FRAME}" && "${IN_FRAME}" = "${OUT_FRAME}" ]] ; then
RANGE_MATCH="$(echo "${RANGE_MATCH}" | sed "s|d:frame|d:frame[@n=${IN_FRAME}]|g")"
else
if [[ -n "${OUT_FRAME}" ]] ; then
RANGE_MATCH="$(echo "${RANGE_MATCH}" | sed "s|d:frame|d:frame[@n<=${OUT_FRAME}]|g")"
fi
if [[ -n "${IN_FRAME}" ]] ; then
RANGE_MATCH="$(echo "${RANGE_MATCH}" | sed "s|d:frame|d:frame[@n>=${IN_FRAME}]|g")"
fi
fi
RANGE_MATCH_FS="$(echo "${RANGE_MATCH}" | cut -s -d "|" -f2- | sed 's|d:|following-sibling::d:|g')"
if [[ "$QUICK" == "Y" ]] ;then
"${XMLSTARLET_PATH}" sel -N "d=https://mediaarea.net/dvrescue" -t -m "${FRAME_MATCH}" -m "${RANGE_MATCH}" \
--var "p=number((${RANGE_MATCH_FS:-x})[1]/@n)-1" \
--var "b=(${RANGE_MATCH_FS:-x}|parent::d:frames/following-sibling::d:frames[1]/d:frame[1])[1]/@pos" \
-v "@pts" -o "|" \
--if "\$p" -v "(${RANGE_MATCH_FS:-x})[1]/@pts" --else -v "${RELATIVE_FRAMES}/@end_pts" -b -o "|" \
-v "@n" -o "|" \
--if "\$p" -v "\$p" --else -v "${RELATIVE_FRAMES}/d:frame[last()]/@n" -b -o "|" \
-v "@tc" -o "|" \
-v "@pos" -o "|" \
-v "@rdt" -o "|" \
-v "${RELATIVE_FRAMES}/@size" -o "|" \
--if "${RELATIVE_FRAMES}/@video_rate" -v "${RELATIVE_FRAMES}/@video_rate" --else \
--if "${RELATIVE_FRAMES}/@size='720x480'" -o "30000/1001" --else \
--if "${RELATIVE_FRAMES}/@size='720x576'" -o "25" --else -o "30000/1001" -b -b -b -o "|" \
-v "${RELATIVE_FRAMES}/@chroma_subsampling" -o "|" \
-v "${RELATIVE_FRAMES}/@aspect_ratio" -o "|" \
--if "${RELATIVE_FRAMES}[@audio_rate]" -v "${RELATIVE_FRAMES}/@audio_rate" --else -o "0" -b -o "|" \
--if "${RELATIVE_FRAMES}[@channels]" -v "${RELATIVE_FRAMES}/@channels" --else -o "0" -b -o "|" \
-v "@rec_start" -o "|" \
-v "@rdt_nc" -o "|" \
-v "@tc_nc" -o "|" \
-o "-|" \
--if "\$b" -v "\$b" --else -v "ancestor::d:media/@size" -break -o "|" \
-o "-|" \
-n "${DVRESCUE_XML}"
# output is pipe-delimited and in this order:
# 1-2 start pts, end pts
# 3-4 start frame, end frame
# 5. timecode
# 6. offset start
# 7. recording timestamp
# 8-11 frame size, frame rate, chroma subsampling, aspect ratio
# 12-13 sampling rate, channel count
# 14-16 rec start, recording time jump, timecode jump
# 17 if ends with missing audio metadata DISABLED
# 18 offset at end of the range
# 19 offset of last frame of range DISABLED
else
"${XMLSTARLET_PATH}" sel -N "d=https://mediaarea.net/dvrescue" -t -m "${FRAME_MATCH}" -m "${RANGE_MATCH}" \
--var "p=number((${RANGE_MATCH_FS:-x})[1]/@n)-1" \
--var "b=(${RANGE_MATCH_FS:-x}|parent::d:frames/following-sibling::d:frames[1]/d:frame[1])[1]/@pos" \
-v "@pts" -o "|" \
--if "\$p" -v "(${RANGE_MATCH_FS:-x})[1]/@pts" --else -v "${RELATIVE_FRAMES}/@end_pts" -b -o "|" \
-v "@n" -o "|" \
--if "\$p" -v "\$p" --else -v "${RELATIVE_FRAMES}/d:frame[last()]/@n" -b -o "|" \
-v "@tc" -o "|" \
-v "@pos" -o "|" \
-v "@rdt" -o "|" \
-v "${RELATIVE_FRAMES}/@size" -o "|" \
--if "${RELATIVE_FRAMES}/@video_rate" -v "${RELATIVE_FRAMES}/@video_rate" --else \
--if "${RELATIVE_FRAMES}/@size='720x480'" -o "30000/1001" --else \
--if "${RELATIVE_FRAMES}/@size='720x576'" -o "25" --else -o "30000/1001" -b -b -b -o "|" \
-v "${RELATIVE_FRAMES}/@chroma_subsampling" -o "|" \
-v "${RELATIVE_FRAMES}/@aspect_ratio" -o "|" \
--if "${RELATIVE_FRAMES}[@audio_rate]" -v "${RELATIVE_FRAMES}/@audio_rate" --else -o "0" -b -o "|" \
--if "${RELATIVE_FRAMES}[@channels]" -v "${RELATIVE_FRAMES}/@channels" --else -o "0" -b -o "|" \
-v "@rec_start" -o "|" \
-v "@rdt_nc" -o "|" \
-v "@tc_nc" -o "|" \
--if "\$p" \
-v "count(//d:frame[@n=\$p]/@no_pack|//d:frame[@n=\$p]/@no_pack_aud|//d:frame[@n=\$p]/@no_sourceorcontrol_aud)" \
--else \
-v "count(${RELATIVE_FRAMES}/d:frame[last()]/@no_pack|${RELATIVE_FRAMES}/d:frame[last()]/@no_pack_aud|${RELATIVE_FRAMES}/d:frame[last()]/@no_sourceorcontrol_aud)" \
--break -o "|" \
--if "\$b" -v "\$b" --else -v "ancestor::d:media/@size" -break -o "|" \
--if "\$p" \
-v "//d:frame[@n=\$p]/@pos" \
--else \
-v "${RELATIVE_FRAMES}/d:frame[last()]/@pos" -break \
-n "${DVRESCUE_XML}"
# output is pipe-delimited and in this order:
# 1-2 start pts, end pts
# 3-4 start frame, end frame
# 5. timecode
# 6. offset start
# 7. recording timestamp
# 8-11 frame size, frame rate, chroma subsampling, aspect ratio
# 12-13 sampling rate, channel count
# 14-16 rec start, recording time jump, timecode jump
# 17 if ends with missing audio metadata
# 18 offset at end of the range
# 19 offset of last frame of range
fi
}
_ranges_2_table(){
RANGES="${1}"
echo
echo "# St='Flagged Start of a recording', ncTC='non-continuous timecode value', ncR='non-continuous recording timestamp value'"
echo "| PTS Range | Duration | Frame Range | Byte Range | Timecode | Recording Timestamp | Size | Frame Rate | DAR | ChSub | Audio | St | ncTC | ncR |"
while IFS="|" read PTS END_PTS FRAME_START FRAME_END TC PKT_POS RDT SIZE VIDEO_RATE CH_SUB AR AUDIO_RATE CH REC_ST RDT_NC TC_NC LAST_FRAME_NO_AUDIO PKT_POS_END PKT_POS_LAST ; do
DURATION="$(_duration_from_pts_range "${PTS}" "${END_PTS}")"
awk -v PTS="${PTS}" -v END_PTS="${END_PTS}" -v FRAME_START="${FRAME_START}" -v FRAME_END="${FRAME_END}" -v TC="${TC}" -v PKT_POS="${PKT_POS}" -v RDT="${RDT}" -v SIZE="${SIZE}" -v VIDEO_RATE="${VIDEO_RATE}" -v CH_SUB="${CH_SUB}" -v AR="${AR}" -v AUDIO_RATE="${AUDIO_RATE}" -v CH="${CH}" -v REC_ST="${REC_ST}" -v RDT_NC="${RDT_NC}" -v TC_NC="${TC_NC}" -v LAST_FRAME_NO_AUDIO="${LAST_FRAME_NO_AUDIO}" -v PKT_POS_END="${PKT_POS_END}" -v PKT_POS_LAST="${PKT_POS_LAST}" -v DURATION="${DURATION}" \
'END {printf "| %15s - %15s | %8.3f | %8i - %8i | %11i - %11i | %11s | %-22s | %9s | %10s | %5s | %5s | %1sch %5i | %2s | %4s | %3s |\n", \
PTS, END_PTS, DURATION, FRAME_START, FRAME_END, PKT_POS, PKT_POS_END, TC, RDT, SIZE, VIDEO_RATE, AR, CH_SUB, CH, AUDIO_RATE, REC_ST, TC_NC, RDT_NC}' < /dev/null
done < <(echo "${RANGES}")
echo
}
_condense_ranges(){
# the inputs is pipe-delimited and in this order:
# 1-2 start pts, end pts
# 3-4 start frame, end frame
# 5. timecode
# 6. offset start
# 7. recording timestamp
# 8-11 frame size, frame rate, chroma subsampling, aspect ratio
# 12-13 sampling rate, channel count
# 14-16 rec start, recording time jump, timecode jump
# 17 if ends with missing audio metadata
# 18 offset at end of the range
# 19 offset of last frame of range
OPTIND=1
unset SAMPLING_FREQ_MERGE ASPECT_MERGE
while getopts "sa:" opt ; do
case "${opt}" in
s) SAMPLING_FREQ_MERGE="Y" ;;
a) ASPECT_MERGE="${OPTARG}" ;;
esac
done
shift "$((OPTIND-1))"
_set_first(){
# store values from beginning of range
PTS_FIRST="${PTS}" #1
FRAME_START_FIRST="${FRAME_START}" #3
TC_FIRST="${TC}" #5
PKT_POS_FIRST="${PKT_POS}" #6
RDT_FIRST="${RDT}" #7
SIZE_FIRST="${SIZE}" #8
VIDEO_RATE_FIRST="${VIDEO_RATE}" #9
CH_SUB_FIRST="${CH_SUB}" #10
AR_FIRST="${AR}" #11
AUDIO_RATE_FIRST="${AUDIO_RATE}" #12
CH_FIRST="${CH}" #13
REC_ST_FIRST="${REC_ST}" #14
RDT_NC_FIRST="${RDT_NC}" #15
TC_NC_FIRST="${TC_NC}" #16
LAST_FRAME_NO_AUDIO_FIRST="${LAST_FRAME_NO_AUDIO}" #17
PKT_POS_END_FIRST="${PKT_POS_END}" #18
PKT_POS_LAST_FIRST="${PKT_POS_LAST}" #19
}
_set_prev(){
# store values from previous range
PTS_PREV="${PTS}" #1
END_PTS_PREV="${END_PTS}" #2
FRAME_START_PREV="${FRAME_START}" #3
FRAME_END_PREV="${FRAME_END}" #4
TC_PREV="${TC}" #5
PKT_POS_PREV="${PKT_POS}" #6
RDT_PREV="${RDT}" #7
SIZE_PREV="${SIZE}" #8
VIDEO_RATE_PREV="${VIDEO_RATE}" #9
CH_SUB_PREV="${CH_SUB}" #10
AR_PREV="${AR}" #11
AUDIO_RATE_PREV="${AUDIO_RATE}" #12
CH_PREV="${CH}" #13
REC_ST_PREV="${REC_ST}" #14
RDT_NC_PREV="${RDT_NC}" #15
TC_NC_PREV="${TC_NC}" #16
LAST_FRAME_NO_AUDIO_PREV="${LAST_FRAME_NO_AUDIO}" #17
PKT_POS_END_PREV="${PKT_POS_END}" #18
PKT_POS_LAST_PREV="${PKT_POS_LAST}" #19
}
_report_condensed_line(){
if [[ -n "${ASPECT_MERGE}" && "${ASPECT_FLIP}" = "Y" ]] ; then
if [[ "${COUNT_43}" -ge "${COUNT_169}" ]] ; then
AR_FORCE="4/3"
else
AR_FORCE="16/9"
fi
else
AR_FORCE="${AR_FIRST}"
fi
if [[ "${SAMPLING_FREQ_MERGE}" = "Y" ]] ; then
if [[ "${AUDIO_RATE_FIRST}" = "48000" || "${AUDIO_RATE_PREV}" = "48000" ]] ; then
SR_FORCE="48000"
else
SR_FORCE="${AUDIO_RATE_FIRST}"
fi
else
SR_FORCE="${AUDIO_RATE_FIRST}"
fi
if [[ "${CH_FIRST}" = "4" || "${CH_PREV}" = "4" ]] ; then
CH_FORCE="4"
else
CH_FORCE="2"
fi
echo "${PTS_FIRST}|${END_PTS_PREV}|${FRAME_START_FIRST}|${FRAME_END_PREV}|${TC_FIRST}|${PKT_POS_FIRST}|${RDT_FIRST}|${SIZE_FIRST}|${VIDEO_RATE_FIRST}|${CH_SUB_FIRST}|${AR_FORCE}|${SR_FORCE}|${CH_FORCE}|${REC_ST_FIRST}|${RDT_NC_FIRST}|${TC_NC_FIRST}|${LAST_FRAME_NO_AUDIO_FIRST}|${PKT_POS_END_PREV}|${PKT_POS_LAST_PREV}"
}
RANGES="${1}"
unset ASPECT_FLIP
COUNT_43="0"
COUNT_169="0"
while IFS="|" read PTS END_PTS FRAME_START FRAME_END TC PKT_POS RDT SIZE VIDEO_RATE CH_SUB AR AUDIO_RATE CH REC_ST RDT_NC TC_NC LAST_FRAME_NO_AUDIO PKT_POS_END PKT_POS_LAST ; do
if [[ -z "${PTS_FIRST}" ]] ; then
_set_first
_set_prev
else
if [[ "${AR_PREV}" = "4/3" ]] ; then
COUNT_43="$((${COUNT_43} + ${FRAME_END_PREV} - ${FRAME_START_PREV}))"
elif [[ "${AR_PREV}" = "16/9" ]] ; then
COUNT_169="$((${COUNT_169} + ${FRAME_END_PREV} - ${FRAME_START_PREV}))"
fi
if [[ \
"${SAMPLING_FREQ_MERGE}" = "Y" && -z "${ASPECT_MERGE}" && \
"${SIZE_FIRST}|${VIDEO_RATE_FIRST}|${CH_SUB_FIRST}|${AR_FIRST}" == "${SIZE}|${VIDEO_RATE}|${CH_SUB}|${AR}" && \
"${REC_START_OPT}" != "${REC_ST}" && \
"${RDT_NC_OPT}" != "${RDT_NC}" && \
"${TC_NC_OPT}" != "${TC_NC}" \
]] ; then
_set_prev
continue
elif [[ \
"${SAMPLING_FREQ_MERGE}" = "Y" && -n "${ASPECT_MERGE}" && \
"${SIZE_FIRST}|${VIDEO_RATE_FIRST}|${CH_SUB_FIRST}" == "${SIZE}|${VIDEO_RATE}|${CH_SUB}" && \
"${REC_START_OPT}" != "${REC_ST}" && \
"${RDT_NC_OPT}" != "${RDT_NC}" && \
"${TC_NC_OPT}" != "${TC_NC}" \
]] ; then
if [[ "${AR_FIRST}" != "${AR}" ]] ; then
ASPECT_FLIP="Y"
fi
_set_prev
continue
elif [[ \
-z "${SAMPLING_FREQ_MERGE}" && -n "${ASPECT_MERGE}" && \
"${SIZE_FIRST}|${VIDEO_RATE_FIRST}|${CH_SUB_FIRST}" == "${SIZE}|${VIDEO_RATE}|${CH_SUB}" && \
"${AUDIO_RATE_FIRST}|${CH_FIRST}" == "${AUDIO_RATE}|${CH}" && \
"${REC_START_OPT}" != "${REC_ST}" && \
"${RDT_NC_OPT}" != "${RDT_NC}" && \
"${TC_NC_OPT}" != "${TC_NC}" \
]] ; then
if [[ "${AR_FIRST}" != "${AR}" ]] ; then
ASPECT_FLIP="Y"
fi
_set_prev
continue
else
_report_condensed_line
_set_first
_set_prev
unset ASPECT_FLIP
COUNT_43="0"
COUNT_169="0"
fi
fi
done < <(echo "${RANGES}")
_report_condensed_line
}
_convert_hhmmssmmm2s(){
TS="${1}"
echo "${TS}" | awk -F ":" '{ printf "%8.6f\n", ($1 * 3600) + ($2 * 60) + $3 }'
}
_duration_from_pts_range(){
START="${1}"
END="${2}"
START_SEC="$(_convert_hhmmssmmm2s "${START}")"
END_SEC="$(_convert_hhmmssmmm2s "${END}")"
awk -v S="${START_SEC}" -v E="${END_SEC}" 'END { printf "%8.6f\n", E - S }' < /dev/null
}
_count_dv_frames(){
if [[ -f "${MEDIAINFO_PATH}" ]] ; then
INPUT_FILE="${1}"
"${MEDIAINFO_PATH}" -f --inform="Video;%FrameCount%" "${INPUT_FILE}" | head -n 1
else
_report -w "Warning: Missing mediainfo, dvpackager can still package but some verification checks can not run."
fi
}
_get_track2_maxdiff(){
# 1: input dv 2: byte offset 3: duration to read
"${FFMPEG_PATH}" -nostdin -skip_initial_bytes "${2}" -t "${3}" -i "${1}" -filter_complex "[0:a:1]astats" -f null -vn - 2>&1 | sed '1,/Overall$/d' | grep "Max difference:" | cut -d ":" -f2- | sed 's/ //g'
}
_check_audio_by_ffmpeg_subfile(){
# The input should be a subfile input statement
# The output will be a list of the audio track sample rates, likely "48000" or "32000 32000"
SUBFILE="${1}"
QUERY="$(ffmpeg -i "${SUBFILE}" 2>&1 | grep Audio: | grep -o "[0-9]* Hz" | awk '{print $1}' | xargs)"
if [[ "${QUERY}" = "48000" ]] ; then
echo "2-48000"
elif [[ "${QUERY}" = "44100" ]] ; then
echo "2-44100"
elif [[ "${QUERY}" = "32000" ]] ; then
echo "2-32000"
elif [[ "${QUERY}" = "32000 32000" ]] ; then
echo "4-32000"
else
echo "0"
fi
}
_make_chapter_metadata_file(){
OPTIND=1
while getopts "i:o:s:e:r" opt ; do
case "${opt}" in
i) CHAPTER_FRAME_IN="${OPTARG}" ;;
o) CHAPTER_FRAME_OUT="${OPTARG}" ;;
s) CHAPTER_PTS_IN="${OPTARG}" ;;
e) CHAPTER_PTS_OUT="${OPTARG}" ;;
r) REPORT=true ;;
esac
done
shift "$((OPTIND-1))"
CHAP_RANGE_START="$(_convert_hhmmssmmm2s "${CHAPTER_PTS_IN}" | awk '{printf "%i", $1 * 100000}')"
CHAP_RANGE_END="$(_convert_hhmmssmmm2s "${CHAPTER_PTS_OUT}" | awk '{printf "%i", $1 * 100000}')"
echo ";FFMETADATA1"
_get_ranges -i "${CHAPTER_FRAME_IN}" -o "${CHAPTER_FRAME_OUT}" "d:frame[1]|d:frame[@rec_start='1']|d:frame[@rdt_nc='1']" | \
while IFS="|" read PTS END_PTS FRAME_START FRAME_END TC PKT_POS RDT SIZE VIDEO_RATE CH_SUB AR AUDIO_RATE CH REC_ST RDT_NC TC_NC ; do
START_CHAPTER="$(_convert_hhmmssmmm2s "${PTS}" | awk '{printf "%i", $1 * 100000}')"
END_CHAPTER="$(_convert_hhmmssmmm2s "${END_PTS}" | awk '{printf "%i", $1 * 100000}')"
START_CHAPTER_OFFSET="$((START_CHAPTER-CHAP_RANGE_START))"
END_CHAPTER_OFFSET="$((END_CHAPTER-CHAP_RANGE_START))"
if [[ "${START_CHAPTER}" -ge "${CHAP_RANGE_START}" ]] && [[ "${END_CHAPTER}" -le "${CHAP_RANGE_END}" ]] ; then
unset CHAP_TITLE
if [[ -n "${TC}" ]] && [[ -n ${RDT} ]] ; then
CHAP_TITLE+="${TC} - ${RDT}"
elif [[ -n ${TC} ]] ; then
CHAP_TITLE+="${TC}"
elif [[ -n ${RDT} ]] ; then
CHAP_TITLE+="--:--:--:-- - ${RDT}"
fi
_report -v "Chapter: ${PTS}-${END_PTS} (${START_CHAPTER_OFFSET}-${END_CHAPTER_OFFSET}): ${CHAP_TITLE}"
echo "[CHAPTER]"
echo "TIMEBASE=1/100000"
echo "START=${START_CHAPTER_OFFSET}"
echo "END=${END_CHAPTER_OFFSET}"
echo "title=${CHAP_TITLE}"
fi
done
}
_get_iso8601(){
date +%FT%T
}
_report(){
local RED="$(tput setaf 1)" # Red - For Warnings
local GREEN="$(tput setaf 2)" # Green - For Declarations
local BLUE="$(tput setaf 4)" # Blue - For Questions
local MAGENTA="$(tput setaf 5)" # Magenta - For Verbose Statements
local NC="$(tput sgr0)" # No Color
local COLOR=""
local STARTMESSAGE=""
local ECHOOPT=""
local VERBOSE_CHECK=false
OPTIND=1
while getopts "vqdwstn" opt ; do
case "${opt}" in
v) VERBOSE_CHECK=true ;
COLOR="${MAGENTA}" ;
STARTMESSAGE+="#" ;; # only output the message if DV_VERBOSE=true
q) COLOR="${BLUE}" ;; # question mode, use color blue
d) COLOR="${GREEN}" ;; # declaration mode, use color green
w) COLOR="${RED}" ;; # warning mode, use color red
s) STARTMESSAGE+=([${SCRIPTNAME}] ) ;; # prepend scriptname to the message
t) STARTMESSAGE+=($(_get_iso8601) '- ' ) ;; # prepend timestamp to the message
n) ECHOOPT="-n" ;; # to avoid line breaks after echo
esac
done
shift "$((OPTIND-1))"
MESSAGE="${1}"
if ! "${VERBOSE_CHECK}" || ( "${DV_VERBOSE}" && "${VERBOSE_CHECK}" ) ; then
>&2 echo ${ECHOOPT} "${COLOR}${STARTMESSAGE[@]}${MESSAGE}${NC}"
fi
}
MATCH_FRAMES="d:frame[1]|"
FFMPEG_VERBOSE=(-v quiet -stats)
DV_VERBOSE=false
REPORT_ONLY="N"
EXT="mov"
SUBS="N"
REC_START_OPT="0"
RDT_NC_OPT="0"
TC_NC_OPT="0"
CONDENSE_OPTS=(-s)
# command-line options to set media id and original variables
OPTIND=1
while getopts ":fsdto:e:l:L:mSnvuhF:M:D:X:x:V:C:3a:T" opt ; do
case "${opt}" in
f) FORCE_FRAMES="Y" ;;
s) REC_START_OPT="1" ; MATCH_FRAMES+="d:frame[@rec_start='1']|" ;;
d) RDT_NC_OPT="1" ; MATCH_FRAMES+="d:frame[@rdt_nc='1']|" ;;
t) TC_NC_OPT="1" ; MATCH_FRAMES+="d:frame[@tc_nc='1']|" ;;
o) OUTPUTDIR="${OPTARG}" ;;
e) EXT="${OPTARG}" ;;
l) LANGUAGES="${OPTARG}" ;;
L) CAPTION_LANG="${OPTARG}" ;;
m) MONO_CHANNELS="Y" ;;
n) REPORT_ONLY="Y" ;;
S) SUBS="Y" ;;
v) unset FFMPEG_VERBOSE ; DV_VERBOSE=true ;;
u) UNPACKAGER="Y" ;;
F) FFMPEG_PATH="${OPTARG}" ;;
M) MEDIAINFO_PATH="${OPTARG}" ;;
D) DVRESCUE_PATH="${OPTARG}" ;;
X) XMLSTARLET_PATH="${OPTARG}" ;;
x) DVRESCUE_XML="${OPTARG}" ;;
V) DVRESCUE_TECHSUBS="${OPTARG}" ;;
C) DVRESCUE_SCC="${OPTARG}" ;;
3) unset CONDENSE_OPTS ;;
a) ASPECT_RATIO_STRATEGY="${OPTARG}" ;;
T) SHOW_TABLE="Y" ;;
h) _usage ; exit 0 ;;
:) echo "Option -${OPTARG} requires an argument" ; _usage ; exit 1 ;;
*) echo "bad option -${OPTARG}" ; _usage ; exit 1 ;;
esac
done
shift "$((OPTIND-1))"
DVFILE="${1}"
shift
if [[ "${UNPACKAGER}" != "Y" && "${@}" != "" ]] ; then
_report -w "Error: there are additional unused arguments (${*}). Please reference the DV file last."
_usage
exit 0
fi
if [[ "${ASPECT_RATIO_STRATEGY}" == "4" ]] ; then
CONDENSE_OPTS+=(-a 4)
elif [[ "${ASPECT_RATIO_STRATEGY}" == "9" ]] ; then
CONDENSE_OPTS+=(-a 9)
elif [[ "${ASPECT_RATIO_STRATEGY}" == "c" ]] ; then
CONDENSE_OPTS+=(-a c)
fi
_report -v "CONDENSE_OPTS is set to ${CONDENSE_OPTS[*]}"
MATCH_FRAMES="${MATCH_FRAMES%?}"
if [[ -z "${MEDIAINFO_PATH}" ]] ; then
MEDIAINFO_PATH="$(which mediainfo)"
fi
if [[ ! -f "${MEDIAINFO_PATH}" ]] ; then
_report -d "The mediainfo command-line tool is not found, but is used to verify that the output is well synchronized."
_report -d "Please install a mediainfo CLI from https://mediaarea.net/en/MediaInfo/Download or provide a path to mediainfo with the -M argument."
fi
if [[ -z "${DVRESCUE_PATH}" ]] ; then
DVRESCUE_PATH="$(which dvrescue)"
fi
if [[ ! -f "${DVRESCUE_PATH}" ]] ; then
_report -w "The dvrescue command-line tool is not found."
exit 1
fi
if [[ -z "${XMLSTARLET_PATH}" ]] ; then
XMLSTARLET_PATH="$(which xmlstarlet)"
fi
if [[ ! -f "${XMLSTARLET_PATH}" ]] ; then
_report -w "The xmlstarlet command-line tool is not found."
exit 1
fi
if [[ "${SHOW_TABLE}" = "Y" ]] ; then
DVRESCUE_XML="${DVFILE}"
if [[ "${DVRESCUE_XML##*.}" != "xml" ]] ; then
_report -w "In show-table mode, the input should be an xml file."
exit 1
fi
if [[ "${#CONDENSE_OPTS[@]}" = "0" ]] ; then
_get_ranges -q
else
_condense_ranges "${CONDENSE_OPTS[@]}" "$(_get_ranges -q)"
fi
exit
fi
# if FFMPEG_PATH wasn't provided as an argument then see if one is installed
if [[ -z "${FFMPEG_PATH}" ]] ; then
FFMPEG_DL_GUESS="$(brew --prefix ffmpegdecklink)/bin/ffmpeg-dl"
if [[ -x "${FFMPEG_DL_GUESS}" ]] ; then
FFMPEG_PATH="${FFMPEG_DL_GUESS}"
else
FFMPEG_PATH="$(which ffmpeg)"
fi
fi
# verify that ffmpeg is installed and qualifies
if [[ -z "${FFMPEG_PATH}" ]] ; then
_report -v "ffmpeg is not found. Please install ffmpeg. Version 4.4 or higher is recommended."
exit 1
else
_report -v "Using ${FFMPEG_PATH} for ffmpeg operations."
fi
FFversion="$("${FFMPEG_PATH}" -version)"
LAVFversion="$(echo "${FFversion}" | grep "libavformat" | head -n 1 | sed 's/[^0-9./]//g' | cut -d "/" -f 1)"
LAVFversion_maj="$(echo "${LAVFversion}" | cut -d. -f1)"
LAVFversion_min="$(echo "${LAVFversion}" | cut -d. -f2)"
LAVFversion_mic="$(echo "${LAVFversion}" | cut -d. -f3)"
if ! ( \
[[ "${LAVFversion_maj}" -gt 58 ]] || \
( [[ "${LAVFversion_maj}" -eq 58 ]] && [[ "${LAVFversion_min}" -gt 65 ]] ) || \
( [[ "${LAVFversion_maj}" -eq 58 ]] && [[ "${LAVFversion_min}" -eq 65 ]] && [[ "${LAVFversion_min}" -ge 101 ]] ) \
) ; then
_report -w "FFmpeg's libavformat version 58.65.101 or greater is recommend and ${FFMPEG_PATH} is running ${LAVFversion}."
_report -w "Please update ffmpeg."
_report -w "Using this current install of ffmpeg may result in out of sync audio."
fi
OPT_INPUT=(-y)
OPT_INPUT+=(-nostdin)
OPT_INPUT+=(-hide_banner)
OPT_OUTPUT+=(-c:v:0 copy)
LANG1="$(echo "${LANGUAGES}" | cut -d "," -f1 )"
LANG2="$(echo "${LANGUAGES}" | cut -d "," -f2 )"
LANG3="$(echo "${LANGUAGES}" | cut -d "," -f3 )"
LANG4="$(echo "${LANGUAGES}" | cut -d "," -f4 )"
if [[ -n "${LANG1}" ]] ; then
OPT_OUTPUT+=(-metadata:s:a:0 language="${LANG1}")
fi
if [[ -n "${LANG2}" ]] ; then
OPT_OUTPUT+=(-metadata:s:a:1 language="${LANG2}")
fi
if [[ -n "${LANG3}" ]] ; then
OPT_OUTPUT+=(-metadata:s:a:2 language="${LANG3}")
fi
if [[ -n "${LANG4}" ]] ; then
OPT_OUTPUT+=(-metadata:s:a:3 language="${LANG4}")
fi
if [[ ! -f "${FFMPEG_PATH}" ]] ; then
_report -w "Error: ffmpeg is needed but not found."
exit 1
elif [[ ! -x "${FFMPEG_PATH}" ]] ; then
_report -w "Error: The ffmpeg needed at ${FFMPEG_PATH} is not executable."
exit 1
fi
if [[ "${UNPACKAGER}" == "Y" ]] ; then
OUTPUTNAME="unpackaged_$(uuidgen).dv"
echo "Unpackaging mode. Unpackaging the input files into ${OUTPUTNAME}."
for i in "${DVFILE}" "${@}" ; do
"${FFMPEG_PATH}" -nostdin "${FFMPEG_VERBOSE[@]}" -i "$i" -map 0:v:0 -c copy -f rawvideo - >> "${OUTPUTNAME}"
done
exit
fi
case "${EXT}" in
"mov")
FORMAT="mov"
EXTENSION="mov"
MAP_OUTPUT+=(-map 0:v:0)
OPT_OUTPUT+=(-c:a pcm_s16le)
;;
"mkv")
FORMAT="matroska"
EXTENSION="mkv"
MAP_OUTPUT+=(-map 0:v:0)
OPT_OUTPUT+=(-c:a pcm_s16le)
;;
"dv")
FORMAT="rawvideo"
EXTENSION="dv"
SUBS="N"
MAP_OUTPUT+=(-map 0:v:0)
;;
*)
_report -w "Error: ${EXT} is an invalid extension option."
_usage
exit 1
;;
esac
OPT_OUTPUT+=(-f "$FORMAT")
unset OUTPUTOPTS
BASENAME="$(basename "${DVFILE}")"
if [[ -d "${OUTPUTDIR}" ]] ; then
SIDECAR_DIR="${OUTPUTDIR}"
elif [[ -n "${OUTPUTDIR}" ]] ; then
_report -w "ERROR: ${OUTPUTDIR} is not a directory."
exit
else
SIDECAR_DIR="$(dirname "${DVFILE}")"
fi
if [[ -z "${DVRESCUE_XML}" ]] ; then
DVRESCUE_XML="${SIDECAR_DIR}/${BASENAME}.dvrescue.xml"
if [[ ! -f "${DVRESCUE_XML}" ]] ; then
DVRESCUE_XML_TEMP="$(_maketemp)"
OUTPUTOPTS+=(--xml-output "${DVRESCUE_XML_TEMP}")
XML_TO_CHECK="${DVRESCUE_XML_TEMP}"
else
XML_TO_CHECK="${DVRESCUE_XML}"
fi
else
XML_TO_CHECK="${DVRESCUE_XML}"
fi
if [[ "${SUBS}" = "Y" ]]; then
if [[ -z "${DVRESCUE_TECHSUBS}" ]] ; then
DVRESCUE_TECHSUBS="${SIDECAR_DIR}/${BASENAME}.dvrescue.techsubs.vtt"
if [[ ! -f "${DVRESCUE_TECHSUBS}" ]] ; then
DVRESCUE_TECHSUBS_TEMP="$(_maketemp).vtt"
OUTPUTOPTS+=(--webvtt-output "${DVRESCUE_TECHSUBS_TEMP}")
fi
fi
if [[ -z "${DVRESCUE_SCC}" ]] ; then
DVRESCUE_SCC="${SIDECAR_DIR}/${BASENAME}.dvrescue.scc"
if [[ ! -f "${DVRESCUE_SCC}" ]] ; then
DVRESCUE_SCC_TEMP="$(_maketemp).scc"
OUTPUTOPTS+=(--cc-format scc)
OUTPUTOPTS+=(--cc-output "${DVRESCUE_SCC_TEMP}")
fi
fi
fi
TOTAL_DVFRAMES_PACKAGED="0"
if [[ -f "${DVFILE}" ]] ; then
if [[ "${REPORT_ONLY}" = "Y" ]] ; then
echo -n "Analyzing ${BASENAME}"
else
echo -n "Packaging ${BASENAME}"
fi
if [[ "${#OUTPUTOPTS[@]}" != 0 ]]; then
# if dvrescue outputs are needed then let's make them
echo ", making dvrescue outputs"
"${DVRESCUE_PATH}" "${DVFILE}" "${OUTPUTOPTS[@]}"
fi
ERROR="$("${XMLSTARLET_PATH}" sel -N "d=https://mediaarea.net/dvrescue" -t -m "/d:dvrescue/d:media" -v "@error" "${XML_TO_CHECK}")"
if [[ -n "${ERROR}" ]] ; then
echo ". Error: ${BASENAME} (${ERROR})."
exit 1
else
_report -v "No xmlstarlet error"
# check if the sidecar directory is there
if [[ ! -d "${SIDECAR_DIR}" ]] ; then
_report -v "Trying to make a directory at ${SIDECAR_DIR}"
mkdir -p "${SIDECAR_DIR}"
fi
if [[ -s "${DVRESCUE_XML_TEMP}" ]] ; then
_report -v "Trying to move the temporary xml from ${DVRESCUE_XML_TEMP} to ${DVRESCUE_XML}"
mv "${DVRESCUE_XML_TEMP}" "${DVRESCUE_XML}"
fi
if [[ -s "${DVRESCUE_TECHSUBS_TEMP}" ]] ; then
_report -v "Trying to move the temporary vtt tfrom ${DVRESCUE_TECHSUBS_TEMP} ${DVRESCUE_TECHSUBS}"
mv "${DVRESCUE_TECHSUBS_TEMP}" "${DVRESCUE_TECHSUBS}"
fi
if [[ -s "${DVRESCUE_SCC_TEMP}" ]] ; then
_report -v "Trying to move the temporary scc from ${DVRESCUE_SCC_TEMP} to ${DVRESCUE_SCC}"
mv "${DVRESCUE_SCC_TEMP}" "${DVRESCUE_SCC}"
fi
fi
DV_FRAME_COUNT="$("${XMLSTARLET_PATH}" sel -N "d=https://mediaarea.net/dvrescue" -t -v "sum(/d:dvrescue/d:media[1]/d:frames/@count)" "${DVRESCUE_XML}" 2>/dev/null)"
_report -v "DV_FRAME_COUNT=(${DV_FRAME_COUNT})"
SOURCE_FORMAT="$("${XMLSTARLET_PATH}" sel -N "d=https://mediaarea.net/dvrescue" -t -m "/d:dvrescue/d:media[1]" -v "@format" "${DVRESCUE_XML}" 2>/dev/null)"
_report -v "SOURCE_FORMAT=(${SOURCE_FORMAT})"
if [[ "${SOURCE_FORMAT}" == "QuickTime" || "${SOURCE_FORMAT}" == "MPEG-4" ]] ; then
_report -v "Performing test of QuickTime PTS coherency."
MOV_INPUT_OPT=(-ignore_editlist true)
MOV_SHOWINFO="$("${FFMPEG_PATH}" "${MOV_INPUT_OPT[@]}" -i "${DVFILE}" -vframes 2 -vf showinfo -f null -map 0:v - 2>&1)"
MOV_PTS_0="$(echo "${MOV_SHOWINFO}" | grep "showinfo.*n:[ ]*0" | grep -o "pts:[ ]*[0-9.]*" | cut -d : -f2 | sed 's/ //g')"
MOV_PTS_1="$(echo "${MOV_SHOWINFO}" | grep "showinfo.*n:[ ]*1" | grep -o "pts:[ ]*[0-9.]*" | cut -d : -f2 | sed 's/ //g')"
MOV_PTS_2="$(echo "${MOV_SHOWINFO}" | grep "showinfo.*n:[ ]*2" | grep -o "pts:[ ]*[0-9.]*" | cut -d : -f2 | sed 's/ //g')"
MOV_PTS_TIME_0="$(echo "${MOV_SHOWINFO}" | grep "showinfo.*n:[ ]*0" | grep -o "pts_time:[ ]*[0-9.]*" | cut -d : -f2 | sed 's/ //g')"
MOV_PTS_TIME_1="$(echo "${MOV_SHOWINFO}" | grep "showinfo.*n:[ ]*1" | grep -o "pts_time:[ ]*[0-9.]*" | cut -d : -f2 | sed 's/ //g')"
MOV_PTS_TIME_2="$(echo "${MOV_SHOWINFO}" | grep "showinfo.*n:[ ]*2" | grep -o "pts_time:[ ]*[0-9.]*" | cut -d : -f2 | sed 's/ //g')"
MOV_FIRST_FRAME_CHECK="$(echo "if ((${MOV_PTS_2} - ${MOV_PTS_0})/2 == ${MOV_PTS_1}) 1 else 0" | bc -l)"
_report -v "MOV_FIRST_FRAME_CHECK=(${MOV_FIRST_FRAME_CHECK}), MOV_SHOWINFO=(${MOV_SHOWINFO})"
fi
_report -v "Trying to present a table of ranges"
if [[ "${#CONDENSE_OPTS[@]}" = "0" ]] ; then
_ranges_2_table "$(_get_ranges)"
else
_ranges_2_table "$(_condense_ranges "${CONDENSE_OPTS[@]}" "$(_get_ranges)")"
fi
#DVRESCUE_VERSION="$("${XMLSTARLET_PATH}" sel -N "d=https://mediaarea.net/dvrescue" -t -v "d:dvrescue/d:creator/d:program" -o "-" -v "d:dvrescue/d:creator/d:version" -n "${DVRESCUE_XML}")"
#MUXER="${LAVF_VERSION} + ${DVRESCUE_VERSION}"
_report -d "The results will be written to ${SIDECAR_DIR}"
while IFS="|" read PTS END_PTS FRAME_START FRAME_END TC PKT_POS RDT SIZE VIDEO_RATE CH_SUB AR AUDIO_RATE CH REC_ST RDT_NC TC_NC LAST_FRAME_NO_AUDIO PKT_POS_END PKT_POS_LAST ; do
unset START_TIME METADATA FRAME_COUNT DURATION_SECS DURATION_ARG AUDIO_FILTER APAD SUB_INPUT MOV_PTS_OFFSET MOV_INPUT_RATE_OPT
unset DV_INPUT AUDIO_INPUTS AUDIO_RESAMPLE_FILTER AUDIO_CONCAT_FILTER AUDIO_RESAMPLE_FILTER_T2 AUDIO_CONCAT_FILTER_T2
PTS_START_FILENAME_SAFE="${PTS//:/-}"
OUTPUT_FILE="${SIDECAR_DIR}/${BASENAME%.*}_${PTS_START_FILENAME_SAFE}.${EXTENSION}"
OUTPUT_FILE_BASENAME="$(basename "${OUTPUT_FILE}")"
_report -v "Starting work on ${OUTPUT_FILE_BASENAME}."
if [[ "${SOURCE_FORMAT}" == "QuickTime" || "${SOURCE_FORMAT}" == "MPEG-4" ]] ; then
if [[ "${MOV_FIRST_FRAME_CHECK}" == "0" ]] ; then
_report -d "Noting that the first few frames use uneven timestamps [$MOV_PTS_TIME_0,$MOV_PTS_TIME_1,$MOV_PTS_TIME_2]"
MOV_PTS_OFFSET="$(echo "(${MOV_PTS_TIME_1} - (1/(${VIDEO_RATE})) )" | bc -l | awk '{printf "%f", $0}')"
_report -d "Will apply a pts offset of ${MOV_PTS_OFFSET} to correlate between the timestamps of the encoding and the timestamps of the container."
fi
MOV_INPUT_RATE_OPT=(-r "${VIDEO_RATE}") # apply input framerate after pts offset coherency check
fi
if [[ "${SOURCE_FORMAT}" == "DV" ]] ; then
_report -v "Reading from DV stream input at byte range ${PKT_POS}-${PKT_POS_END}"
DV_INPUT=(-i "subfile,,start,${PKT_POS},end,${PKT_POS_END},,:${DVFILE}")
else
if [[ "${PTS}" = "00:00:00.000000" ]] ; then
:
elif [[ -n "${MOV_PTS_OFFSET}" ]] ; then
PTS_OFFED="$(echo "$(_convert_hhmmssmmm2s "${PTS}") + ${MOV_PTS_OFFSET}" | bc -l | awk '{printf "%.9f\n", $0}')"
START_TIME=(-ss "${PTS_OFFED}")
else
START_TIME=(-ss "${PTS}")
fi
DV_INPUT=("${MOV_INPUT_OPT[@]}" "${MOV_INPUT_RATE_OPT[@]}" "${START_TIME[@]}" -i "${DVFILE}")
fi
FRAME_COUNT="$(echo "${FRAME_END} - ${FRAME_START}" + 1 | bc)"
DURATION_SECS="$(echo "${FRAME_COUNT}/(${VIDEO_RATE})" | bc -l | awk '{printf "%.9f\n", $0}')"
DURATION_ARG=(-t "${DURATION_SECS}")
INPUT_COUNT="1"
if [[ -n "${TC}" ]] && [[ "${EXTENSION}" != "dv" ]] ; then
METADATA+=(-metadata "timecode=${TC}")
fi
if [[ "${EXTENSION}" != "dv" ]] ; then
AUDIO_RANGE_SUBLIST="$(_get_ranges -i "${FRAME_START}" -o "${FRAME_END}")"
_report -v "The audio range sublist (from frame ${FRAME_START} to ${FRAME_END}) is"
_report -v "${AUDIO_RANGE_SUBLIST}"
AUDIO_SUBLIST_COUNT="$(echo -n "${AUDIO_RANGE_SUBLIST}" | grep -c '^')"
if [[ "${AUDIO_SUBLIST_COUNT}" = "1" ]] ; then
AUDIO_MAP=(-map 0:a?)
if [[ "${LAST_FRAME_NO_AUDIO}" != "0" ]] ; then
APAD=",apad=whole_dur=${DURATION_SECS}"
_report -d "FYI: Padding a little silence at the end of ${OUTPUT_FILE_BASENAME} since the source ends with frames missing audio."
fi
_report -v "Prepping a mapping for ${CH} channel input (${MONO_CHANNELS})."
if [[ "${CH}" = "2" ]] ; then
if [[ "${MONO_CHANNELS}" == "Y" ]] ; then
AUDIO_FILTER=(-filter_complex "[0:a:0]aresample=async=1:min_hard_comp=0.01${APAD},channelsplit[l1][r1];[l1]aformat=channel_layouts=mono[c1];[r1]aformat=channel_layouts=mono[c2]")
AUDIO_MAP=(-map "[c1]" -map "[c2]")
else
AUDIO_FILTER=(-filter_complex "[0:a:0]aresample=async=1:min_hard_comp=0.01${APAD}[aud]")
AUDIO_MAP=(-map "[aud]")
fi
elif [[ "${CH}" = "4" ]] ; then
if [[ "${MONO_CHANNELS}" == "Y" ]] ; then
AUDIO_FILTER=(-filter_complex "[0:a:0]aresample=async=1:min_hard_comp=0.01${APAD},channelsplit[l1][r1];[l1]aformat=channel_layouts=mono[c1];[r1]aformat=channel_layouts=mono[c2];[0:a:1]aresample=async=1:min_hard_comp=0.01${APAD},channelsplit[l2][r2];[l2]aformat=channel_layouts=mono[c3];[r2]aformat=channel_layouts=mono[c4]")
AUDIO_MAP=(-map "[c1]" -map "[c2]" -map "[c3]" -map "[c4]")
else
AUDIO_FILTER=(-filter_complex "[0:a:0]aresample=async=1:min_hard_comp=0.01${APAD}[aud1];[0:a:0]aresample=async=1:min_hard_comp=0.01${APAD}[aud2]")
AUDIO_MAP=(-map "[aud1]" -map "[aud2]")
fi
fi
else
AUDIO_INPUT_COUNT="0"
TRACK_2_AUDIO="0"
while IFS="|" read PTSa END_PTSa FRAME_STARTa FRAME_ENDa TCa PKT_POSa RDTa SIZEa VIDEO_RATEa CH_SUBa ARa AUDIO_RATEa CHa REC_STa RDT_NCa TC_NCa LAST_FRAME_NO_AUDIOa PKT_POS_ENDa PKT_POS_LASTa ; do
((AUDIO_INPUT_COUNT++))
unset SRa APADa
FRAME_COUNTa="$(echo "${FRAME_ENDa} - ${FRAME_STARTa}" + 1 | bc)"
DURATION_SECSa="$(echo "${FRAME_COUNTa}/(${VIDEO_RATEa})" | bc -l | awk '{printf "%.9f\n", $0}')"
AUDIO_SUBFILE_INPUT="subfile,,start,${PKT_POSa},end,${PKT_POS_ENDa},,:${DVFILE}"
AUDIO_INPUTS+=(-i "${AUDIO_SUBFILE_INPUT}")
LAST_FRAME_SUBFILE=(-i "subfile,,start,${PKT_POS_LASTa},end,${PKT_POS_ENDa},,:${DVFILE}")
FFMPEG_AUDIO_QUERY_START="$(_check_audio_by_ffmpeg_subfile "${AUDIO_SUBFILE_INPUT[@]}")"
if [[ "${CHa}" = "0" ]] ; then
DVRESCUE_AUDIO_QUERY="0"
else
DVRESCUE_AUDIO_QUERY="${CHa}-${AUDIO_RATEa}"
fi
if [[ "${FFMPEG_AUDIO_QUERY_START}" != "${DVRESCUE_AUDIO_QUERY}" ]] ; then
_report -w "ERROR: ffmpeg and dvrescue disagree about the audio characterstics at offset ${PKT_POSa} at ${PTSa}."
_report -w "We shall use ffmpeg's analysis on this. (ff:${FFMPEG_AUDIO_QUERY_START} != dvr:${DVRESCUE_AUDIO_QUERY})"
_report -w "Consider updating to the latest version of dvrescue and this error recurs,"
_report -w "then consider mentioning this at https://github.com/mipops/dvrescue/issues."
CHa="$(echo "${FFMPEG_AUDIO_QUERY_START}" | cut -d "-" -f1)"
fi
AUDIO_STREAM_IN="[${AUDIO_INPUT_COUNT}:a:0]"
AUDIO_STREAM_OUT="[${AUDIO_INPUT_COUNT}at]"
AUDIO_STREAM_IN_T2="[${AUDIO_INPUT_COUNT}:a:1]"
AUDIO_STREAM_OUT_T2="[${AUDIO_INPUT_COUNT}at2]"
if [[ "${AUDIO_RATEa}" != "48000" ]] ; then
SRa="48000:"
fi
if [[ "${LAST_FRAME_NO_AUDIOa}" != "0" ]] ; then
APADa=",apad=whole_dur=${DURATION_SECSa}"
_report -d "FYI: Padding a little silence near ${END_PTSa} for ${OUTPUT_FILE_BASENAME} since a source portion ends with frames missing audio (via dvrescue)."
else
FFMPEG_AUDIO_QUERY_END="$(_check_audio_by_ffmpeg_subfile "${LAST_FRAME_SUBFILE[@]}")"
if [[ "${FFMPEG_AUDIO_QUERY_END}" = "0" ]] ; then
APADa=",apad=whole_dur=${DURATION_SECSa}"
_report -d "FYI: Padding a little silence near ${END_PTSa} for ${OUTPUT_FILE_BASENAME} since a source portion ends with frames missing audio (via ffmpeg)."
fi
fi
AUDIO_CONCAT_FILTER+="${AUDIO_STREAM_OUT}"
if [[ "${CHa}" = "4" ]] ; then
MAX_DIFF="$(_get_track2_maxdiff "${DVFILE}" "${PKT_POSa}" "${DURATION_SECSa}")"
_report -v "Checked track 2 audio levels in ${DVFILE} for ${DURATION_SECSa} from byte ${PKT_POSa}. Maximum sample difference is ${MAX_DIFF}."
if [[ "${MAX_DIFF}" != "0.000000" ]] ; then
((TRACK_2_AUDIO++))
fi
AUDIO_RESAMPLE_FILTER+="${AUDIO_STREAM_IN}aresample=${SRa}async=1:min_hard_comp=0.01${APADa}${AUDIO_STREAM_OUT};"
AUDIO_RESAMPLE_FILTER_T2+="${AUDIO_STREAM_IN_T2}aresample=${SRa}async=1:min_hard_comp=0.01${APADa}${AUDIO_STREAM_OUT_T2};"
elif [[ "${CHa}" = "0" ]] ; then
# since there is no audio, create some
AUDIO_RESAMPLE_FILTER+="aevalsrc=channel_layout=stereo:sample_rate=48000:exprs=0|0:duration=${DURATION_SECSa}${AUDIO_STREAM_OUT};"
AUDIO_RESAMPLE_FILTER_T2+="aevalsrc=channel_layout=stereo:sample_rate=48000:exprs=0|0:duration=${DURATION_SECSa}${AUDIO_STREAM_OUT_T2};"
else
AUDIO_RESAMPLE_FILTER+="${AUDIO_STREAM_IN}aresample=${SRa}async=1:min_hard_comp=0.01${APADa}${AUDIO_STREAM_OUT};"
AUDIO_RESAMPLE_FILTER_T2+="aevalsrc=channel_layout=stereo:sample_rate=48000:exprs=0|0:duration=${DURATION_SECSa}${AUDIO_STREAM_OUT_T2};"
fi
AUDIO_CONCAT_FILTER_T2+="${AUDIO_STREAM_OUT_T2}"
done < <(echo "${AUDIO_RANGE_SUBLIST}")
if [[ "${TRACK_2_AUDIO}" = "0" ]] ; then
if [[ "${MONO_CHANNELS}" == "Y" ]] ; then
AUDIO_FILTER=(-filter_complex "${AUDIO_RESAMPLE_FILTER}${AUDIO_CONCAT_FILTER}concat=n=${AUDIO_INPUT_COUNT}:v=0:a=1,channelsplit[l1][r1];[l1]aformat=channel_layouts=mono[c1];[r1]aformat=channel_layouts=mono[c2]")
AUDIO_MAP=(-map "[c1]" -map "[c2]")
else
AUDIO_FILTER=(-filter_complex "${AUDIO_RESAMPLE_FILTER}${AUDIO_CONCAT_FILTER}concat=n=${AUDIO_INPUT_COUNT}:v=0:a=1[aud]")
AUDIO_MAP=(-map "[aud]")
fi
else
_report -v "Adding a second stereo track, as audio was detect in the 3rd or 4th channels of the input dv."
if [[ "${MONO_CHANNELS}" == "Y" ]] ; then
AUDIO_FILTER=(-filter_complex "${AUDIO_RESAMPLE_FILTER}${AUDIO_RESAMPLE_FILTER_T2}${AUDIO_CONCAT_FILTER}concat=n=${AUDIO_INPUT_COUNT}:v=0:a=1,channelsplit[l1][r1];[l1]aformat=channel_layouts=mono[c1];[r1]aformat=channel_layouts=mono[c2];${AUDIO_CONCAT_FILTER_T2}concat=n=${AUDIO_INPUT_COUNT}:v=0:a=1,channelsplit[l2][r2];[l2]aformat=channel_layouts=mono[c3];[r2]aformat=channel_layouts=mono[c4]")
AUDIO_MAP=(-map "[c1]" -map "[c2]" -map "[c3]" -map "[c4]")
else
AUDIO_FILTER=(-filter_complex "${AUDIO_RESAMPLE_FILTER}${AUDIO_RESAMPLE_FILTER_T2}${AUDIO_CONCAT_FILTER}concat=n=${AUDIO_INPUT_COUNT}:v=0:a=1[aud];${AUDIO_CONCAT_FILTER_T2}concat=n=${AUDIO_INPUT_COUNT}:v=0:a=1[aud_t2]")
AUDIO_MAP=(-map "[aud]" -map "[aud_t2]")
fi
fi
INPUT_COUNT=$((${INPUT_COUNT} + ${AUDIO_INPUT_COUNT}))
fi
MAP_OUTPUT+=("${AUDIO_MAP[@]}")
if [[ "${RDT:0:4}" =~ "^[0-9]{4}" ]] && [[ "${RDT:0:4}" -ge 1995 ]] ; then
if [[ "${EXTENSION}" = "mkv" ]] ; then
METADATA+=(-metadata "DATE_RECORDED=${RDT}")
elif [[ "${EXTENSION}" = "mov" ]] ; then
METADATA+=(-metadata "date=${RDT}")
fi
fi
SUBTITLE_INPUT_COUNT="0"
if [[ "${SUBS}" = "Y" ]] ; then
if [[ -s "${DVRESCUE_SCC}" ]]; then
if [[ "${PTS}" != "00:00:00.000000" ]] ; then
SUB_INPUT+=(-ss "${PTS}")
fi
SUB_INPUT+=(-i "${DVRESCUE_SCC}")
MAP_OUTPUT+=(-map "${INPUT_COUNT}:s:0")
if [[ "${EXT}" = "mov" ]] ; then
OPT_OUTPUT+=(-c:s mov_text)
OPT_OUTPUT+=(-tag:s:s:"${SUBTITLE_INPUT_COUNT}" tx3g)
elif [[ "${EXT}" = "mkv" ]] ; then
OPT_OUTPUT+=(-metadata:s:s:"${SUBTITLE_INPUT_COUNT}" "title=Captions")
OPT_OUTPUT+=(-c:s ass)
fi
if [[ -n "${CAPTION_LANG}" ]] ; then
OPT_OUTPUT+=(-metadata:s:s:"${SUBTITLE_INPUT_COUNT}" "language=${CAPTION_LANG}")
else
OPT_OUTPUT+=(-metadata:s:s:"${SUBTITLE_INPUT_COUNT}" "language=und")
fi
((INPUT_COUNT++))
((SUBTITLE_INPUT_COUNT++))
fi
fi
CHAPTER_FFMETADATA="$(_maketemp).ffmetadata"
_report -v "Writing a chapter track at ${CHAPTER_FFMETADATA}."
_make_chapter_metadata_file -i "${FRAME_START}" -o "${FRAME_END}" -s "${PTS}" -e "${END_PTS}" > "${CHAPTER_FFMETADATA}"
CHAPTER_PROCESS=(-i "${CHAPTER_FFMETADATA}")
CHAPTER_PROCESS+=(-map_metadata "${INPUT_COUNT}")
if [[ -n "${AR}" ]] ; then
METADATA+=(-aspect "${AR}")
fi
fi
((INPUT_COUNT++))
if [[ "$REPORT_ONLY" != "Y" ]] ; then
if [[ "${SOURCE_FORMAT}" == "DV" ]] ; then
_report -v "Trying to run: ${FFMPEG_PATH} ${FFMPEG_VERBOSE[@]} ${OPT_INPUT[@]} ${DV_INPUT[@]} ${AUDIO_INPUTS[@]} ${SUB_INPUT[@]} ${CHAPTER_PROCESS[@]} ${AUDIO_FILTER[@]} ${DURATION_ARG[@]} ${OPT_OUTPUT[@]} ${MAP_OUTPUT[@]} ${METADATA[@]} ${OUTPUT_FILE}"
"${FFMPEG_PATH}" "${FFMPEG_VERBOSE[@]}" "${OPT_INPUT[@]}" "${DV_INPUT[@]}" "${AUDIO_INPUTS[@]}" \
"${SUB_INPUT[@]}" "${CHAPTER_PROCESS[@]}" "${AUDIO_FILTER[@]}" "${DURATION_ARG[@]}" "${OPT_OUTPUT[@]}" "${MAP_OUTPUT[@]}" "${METADATA[@]}" "${OUTPUT_FILE}"
else
_report -v "Trying to run: ${FFMPEG_PATH} ${FFMPEG_VERBOSE[@]} ${OPT_INPUT[@]} ${DV_INPUT[@]} -map 0:v:0 -c:v:0 copy -f rawvideo - | ffmpeg ${FFMPEG_VERBOSE[@]} ${OPT_INPUT[@]} -i - ${SUB_INPUT[@]} ${CHAPTER_PROCESS[@]} ${AUDIO_FILTER[@]} ${DURATION_ARG[@]} ${OPT_OUTPUT[@]} ${MAP_OUTPUT[@]} ${METADATA[@]} ${OUTPUT_FILE}"
"${FFMPEG_PATH}" "${FFMPEG_VERBOSE[@]}" "${OPT_INPUT[@]}" "${DV_INPUT[@]}" \
-map 0:v:0 -c:v:0 copy -f rawvideo - | ffmpeg "${FFMPEG_VERBOSE[@]}" "${OPT_INPUT[@]}" -i - \
"${SUB_INPUT[@]}" "${CHAPTER_PROCESS[@]}" "${AUDIO_FILTER[@]}" "${DURATION_ARG[@]}" "${OPT_OUTPUT[@]}" "${MAP_OUTPUT[@]}" "${METADATA[@]}" "${OUTPUT_FILE}"
fi
if [[ ! -s "${OUTPUT_FILE}" ]] ; then
_report -w "ERROR: ${OUTPUT_FILE_BASENAME} is expected but missing. Please retry with the -v option and report to https://github.com/mipops/dvrescue/issues."
else
if [[ -f "${MEDIAINFO_PATH}" ]] ; then
# quick track duration check on the result
PACKAGED_DVFRAMES="$(_count_dv_frames "${OUTPUT_FILE}")"
TOTAL_DVFRAMES_PACKAGED="$(echo "${TOTAL_DVFRAMES_PACKAGED} + ${PACKAGED_DVFRAMES}" | bc -l)"
AUD_STREAM_COUNT="$("${MEDIAINFO_PATH}" -f --Output="General;%AudioCount%" "${OUTPUT_FILE}" | head -n 1 | awk '{printf "%s", $0}')"
_report -v "AUD_STREAM_COUNT=(${AUD_STREAM_COUNT})"
if [[ -n "${AUD_STREAM_COUNT}" ]] ; then
VID_DUR="$("${MEDIAINFO_PATH}" -f --Output="Video;%Duration%\n" "${OUTPUT_FILE}" | head -n 1 | awk '{printf "%s", $0}')"
AUD_DUR="$("${MEDIAINFO_PATH}" -f --Output="Audio;%Duration%\n" "${OUTPUT_FILE}" | head -n 1 | awk '{printf "%s", $0}')"
DUR_DIFF="$(echo "${VID_DUR}" - "${AUD_DUR}" | bc | awk '{printf "%f", $0}')"
_report -v "VID_DUR=(${VID_DUR}), AUD_DIR=(${AUD_DUR}), DUR_DIFF=(${DUR_DIFF})"
if (( $( echo "${DUR_DIFF} >= -033" |bc -l) )) && (( $(echo "${DUR_DIFF} <= 033" |bc -l) )); then
_report -v "DUR_DIFF is within an acceptable range."
else
_report -w "ERROR: In ${OUTPUT_FILE_BASENAME} the video track is ${VID_DUR} milliseconds and the audio track is ${AUD_DUR}. These tracks may lose sync."
fi
else
_report -d "FYI: ${OUTPUT_FILE_BASENAME} has no audio track."
fi
fi
_report -d "FYI: Packaged to ${OUTPUT_FILE_BASENAME}."
if [[ -f "${MEDIAINFO_PATH}" ]] && [[ "${PACKAGED_DVFRAMES}" != "${FRAME_COUNT}" ]] ; then
_report -w "ERROR: ${BASENAME} contained ${FRAME_COUNT} DV frames at ${PTS}-${END_PTS} but ${OUTPUT_FILE_BASENAME} contains ${PACKAGED_DVFRAMES} frames of DV."
fi
fi
fi
done < <(if [[ "${#CONDENSE_OPTS[@]}" = "0" ]] ; then
_get_ranges
else
_condense_ranges "${CONDENSE_OPTS[@]}" "$(_get_ranges)"
fi)
if [[ "$REPORT_ONLY" != "Y" ]] && [[ -f "${MEDIAINFO_PATH}" ]] && [[ "${TOTAL_DVFRAMES_PACKAGED}" != "${DV_FRAME_COUNT}" ]] ; then
_report -w "ERROR: ${BASENAME} contained ${DV_FRAME_COUNT} DV frames, but the outputs contain ${TOTAL_DVFRAMES_PACKAGED} frames of DV."
fi
else
echo "${BASENAME} is not a file, skipping."
fi