fix/feat: validation improvements and EXIT trap for all bad-signature scripts
fix: treat qpdf exit code 3 (warnings only) as VALID in PDF check
- exit 0 and exit 3 both map to VALID; only exit 2 is a structural error
- error detail now includes first matching error line from qpdf output
fix: add EXIT trap so encryption flag and temp files are always cleaned up
- changed signal trap from to
- EXIT fires for any bash exit including syntax errors and unexpected crashes
- clean_up() now runs first to prevent re-entry / infinite loop
- applies to recover_bad_signature.sh (where the flag matters most),
restore_bad_signature.sh and recreate_bad_signature.sh
feat: check optional validation tools at startup and offer apt install
- new check_optional_validation_tools() prompts [j/N] in interactive mode
- covers: imagemagick (identify), ffmpeg (ffprobe), mp3val, flac, vorbis-tools (ogginfo)
feat: use optional tools for deeper file validation when available
- PNG / GIF / BMP / TIFF: identify -regard-warnings (full decode) with magic-byte fallback
- MP4 / MOV / M4V: ffprobe -show_streams (container parse) with ftyp-box fallback
- MP3: new dedicated case — mp3val frame check with ID3/sync-word fallback
- FLAC: new dedicated case — flac --silent --test with fLaC-signature fallback
- OGG / OGA / OGV / OPUS: new dedicated case — ogginfo with OggS-signature fallback
Affects: recover_bad_signature.sh, restore_bad_signature.sh, recreate_bad_signature.sh
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GfXh5sRbEaXiEX6KjAPivc
This commit is contained in:
+223
-7
@@ -157,6 +157,82 @@ clean_up() {
|
|||||||
exit $1
|
exit $1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
check_optional_validation_tools() {
|
||||||
|
|
||||||
|
# Checks whether optional tools that improve file-validation quality
|
||||||
|
# are installed. In interactive mode the user is offered to install
|
||||||
|
# any that are missing via apt. In non-interactive mode the function
|
||||||
|
# is a quiet no-op (missing tools degrade quality but do not abort).
|
||||||
|
|
||||||
|
local _missing_tools=()
|
||||||
|
local _missing_pkgs=()
|
||||||
|
local _desc=()
|
||||||
|
|
||||||
|
command -v identify > /dev/null 2>&1 || {
|
||||||
|
_missing_tools+=("identify")
|
||||||
|
_missing_pkgs+=("imagemagick")
|
||||||
|
_desc+=("identify (imagemagick) — PNG / TIFF / BMP / GIF: vollst. Dekodierung statt nur Magic-Bytes")
|
||||||
|
}
|
||||||
|
command -v ffprobe > /dev/null 2>&1 || {
|
||||||
|
_missing_tools+=("ffprobe")
|
||||||
|
_missing_pkgs+=("ffmpeg")
|
||||||
|
_desc+=("ffprobe (ffmpeg) — MP4 / MOV / M4V: Container-Parsing statt nur ftyp-Box-Suche")
|
||||||
|
}
|
||||||
|
command -v mp3val > /dev/null 2>&1 || {
|
||||||
|
_missing_tools+=("mp3val")
|
||||||
|
_missing_pkgs+=("mp3val")
|
||||||
|
_desc+=("mp3val (mp3val) — MP3: Frame-Struktur-Check statt nur ID3-Signatur")
|
||||||
|
}
|
||||||
|
command -v flac > /dev/null 2>&1 || {
|
||||||
|
_missing_tools+=("flac")
|
||||||
|
_missing_pkgs+=("flac")
|
||||||
|
_desc+=("flac (flac) — FLAC: Decode-Test statt nur fLaC-Signatur")
|
||||||
|
}
|
||||||
|
command -v ogginfo > /dev/null 2>&1 || {
|
||||||
|
_missing_tools+=("ogginfo")
|
||||||
|
_missing_pkgs+=("vorbis-tools")
|
||||||
|
_desc+=("ogginfo (vorbis-tools) — OGG / OGA / OPUS: Container-Check statt nur OggS-Signatur")
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ ${#_missing_tools[@]} -eq 0 ]] && return 0
|
||||||
|
|
||||||
|
if ! $terminal ; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local _pkg_list="${_missing_pkgs[*]}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e " \033[33mOptionale Validierungs-Tools fehlen – Checks laufen mit reduzierter Genauigkeit:\033[m"
|
||||||
|
echo ""
|
||||||
|
local _d
|
||||||
|
for _d in "${_desc[@]}" ; do
|
||||||
|
echo " ${_d}"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
echo -n " Jetzt installieren? apt install ${_pkg_list} [j/N]: "
|
||||||
|
read -r _yn
|
||||||
|
echo ""
|
||||||
|
if [[ "$_yn" =~ ^[jJyY]$ ]] ; then
|
||||||
|
echononl " Installiere Pakete: ${_pkg_list}.."
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
if apt-get install -y ${_missing_pkgs[*]} > /dev/null 2>&1 ; then
|
||||||
|
echo_ok
|
||||||
|
else
|
||||||
|
echo_failed
|
||||||
|
echo ""
|
||||||
|
echo " Bitte manuell installieren:"
|
||||||
|
echo " apt install ${_pkg_list}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " Übersprungen — Validierung läuft mit reduzierter Genauigkeit."
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
is_number() {
|
is_number() {
|
||||||
|
|
||||||
return $(test ! -z "${1##*[!0-9]*}" > /dev/null 2>&1);
|
return $(test ! -z "${1##*[!0-9]*}" > /dev/null 2>&1);
|
||||||
@@ -305,38 +381,71 @@ validate_recovered_file() {
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
png)
|
png)
|
||||||
|
if command -v identify > /dev/null 2>&1 ; then
|
||||||
|
# Full decode: catches truncated data, corrupt IDAT chunks, bad CRCs
|
||||||
|
if identify -regard-warnings "$_f" > /dev/null 2>&1 ; then
|
||||||
|
echo "VALID|ImageMagick 'identify' decoded PNG ok"
|
||||||
|
else
|
||||||
|
echo "INVALID|ImageMagick 'identify' failed to decode PNG"
|
||||||
|
fi
|
||||||
|
else
|
||||||
local _head
|
local _head
|
||||||
_head="$(head -c8 "$_f" 2> /dev/null | od -An -tx1 | tr -d ' \n')"
|
_head="$(head -c8 "$_f" 2> /dev/null | od -An -tx1 | tr -d ' \n')"
|
||||||
if [[ "$_head" = "89504e470d0a1a0a" ]] ; then
|
if [[ "$_head" = "89504e470d0a1a0a" ]] ; then
|
||||||
echo "VALID|PNG signature ok"
|
echo "VALID|PNG signature ok (identify not installed — no deep check)"
|
||||||
else
|
else
|
||||||
echo "INVALID|PNG signature missing"
|
echo "INVALID|PNG signature missing"
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
gif)
|
gif)
|
||||||
|
if command -v identify > /dev/null 2>&1 ; then
|
||||||
|
if identify -regard-warnings "$_f" > /dev/null 2>&1 ; then
|
||||||
|
echo "VALID|ImageMagick 'identify' decoded GIF ok"
|
||||||
|
else
|
||||||
|
echo "INVALID|ImageMagick 'identify' failed to decode GIF"
|
||||||
|
fi
|
||||||
|
else
|
||||||
local _head6
|
local _head6
|
||||||
_head6="$(head -c6 "$_f" 2> /dev/null)"
|
_head6="$(head -c6 "$_f" 2> /dev/null)"
|
||||||
if [[ "$_head6" = "GIF87a" || "$_head6" = "GIF89a" ]] ; then
|
if [[ "$_head6" = "GIF87a" || "$_head6" = "GIF89a" ]] ; then
|
||||||
echo "VALID|GIF signature ok"
|
echo "VALID|GIF signature ok (identify not installed — no deep check)"
|
||||||
else
|
else
|
||||||
echo "INVALID|GIF signature missing"
|
echo "INVALID|GIF signature missing"
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
bmp)
|
bmp)
|
||||||
|
if command -v identify > /dev/null 2>&1 ; then
|
||||||
|
if identify -regard-warnings "$_f" > /dev/null 2>&1 ; then
|
||||||
|
echo "VALID|ImageMagick 'identify' decoded BMP ok"
|
||||||
|
else
|
||||||
|
echo "INVALID|ImageMagick 'identify' failed to decode BMP"
|
||||||
|
fi
|
||||||
|
else
|
||||||
if [[ "$(head -c2 "$_f" 2> /dev/null)" = "BM" ]] ; then
|
if [[ "$(head -c2 "$_f" 2> /dev/null)" = "BM" ]] ; then
|
||||||
echo "VALID|BMP signature ok"
|
echo "VALID|BMP signature ok (identify not installed — no deep check)"
|
||||||
else
|
else
|
||||||
echo "INVALID|BMP signature missing"
|
echo "INVALID|BMP signature missing"
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
tif|tiff)
|
tif|tiff)
|
||||||
|
if command -v identify > /dev/null 2>&1 ; then
|
||||||
|
if identify -regard-warnings "$_f" > /dev/null 2>&1 ; then
|
||||||
|
echo "VALID|ImageMagick 'identify' decoded TIFF ok"
|
||||||
|
else
|
||||||
|
echo "INVALID|ImageMagick 'identify' failed to decode TIFF"
|
||||||
|
fi
|
||||||
|
else
|
||||||
local _head4
|
local _head4
|
||||||
_head4="$(head -c4 "$_f" 2> /dev/null | od -An -tx1 | tr -d ' \n')"
|
_head4="$(head -c4 "$_f" 2> /dev/null | od -An -tx1 | tr -d ' \n')"
|
||||||
if [[ "$_head4" = "49492a00" || "$_head4" = "4d4d002a" ]] ; then
|
if [[ "$_head4" = "49492a00" || "$_head4" = "4d4d002a" ]] ; then
|
||||||
echo "VALID|TIFF signature ok"
|
echo "VALID|TIFF signature ok (identify not installed — no deep check)"
|
||||||
else
|
else
|
||||||
echo "INVALID|TIFF signature missing"
|
echo "INVALID|TIFF signature missing"
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
pnm|pgm|ppm|pbm)
|
pnm|pgm|ppm|pbm)
|
||||||
local _head2
|
local _head2
|
||||||
@@ -394,11 +503,27 @@ validate_recovered_file() {
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
mp4|mov|m4v)
|
mp4|mov|m4v)
|
||||||
|
if command -v ffprobe > /dev/null 2>&1 ; then
|
||||||
|
# Full container parse: detects truncated/corrupt streams
|
||||||
|
local _ffprobe_out _ffprobe_exit
|
||||||
|
_ffprobe_out="$(ffprobe -v error -show_streams "$_f" 2>&1)"
|
||||||
|
_ffprobe_exit=$?
|
||||||
|
if [[ $_ffprobe_exit -eq 0 ]] ; then
|
||||||
|
local _streams
|
||||||
|
_streams="$(echo "$_ffprobe_out" | grep -c '\[STREAM\]' || true)"
|
||||||
|
echo "VALID|ffprobe parsed container ok (${_streams} stream(s) found)"
|
||||||
|
else
|
||||||
|
local _ffprobe_err
|
||||||
|
_ffprobe_err="$(echo "$_ffprobe_out" | head -1)"
|
||||||
|
echo "INVALID|ffprobe failed to parse container${_ffprobe_err:+ (${_ffprobe_err})}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
if head -c 64 "$_f" 2> /dev/null | grep -aq "ftyp" ; then
|
if head -c 64 "$_f" 2> /dev/null | grep -aq "ftyp" ; then
|
||||||
echo "VALID|mp4 ftyp box found"
|
echo "VALID|mp4 ftyp box found (ffprobe not installed — no deep check)"
|
||||||
else
|
else
|
||||||
echo "INVALID|mp4 ftyp box not found"
|
echo "INVALID|mp4 ftyp box not found"
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
doc|xls|ppt|ole|msi)
|
doc|xls|ppt|ole|msi)
|
||||||
local _head8
|
local _head8
|
||||||
@@ -497,6 +622,75 @@ validate_recovered_file() {
|
|||||||
echo "INVALID|GIMP (gimp xcf) signature missing"
|
echo "INVALID|GIMP (gimp xcf) signature missing"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
|
mp3)
|
||||||
|
if command -v mp3val > /dev/null 2>&1 ; then
|
||||||
|
# mp3val always exits 0 but prints "No errors found" on success
|
||||||
|
local _mp3val_out
|
||||||
|
_mp3val_out="$(mp3val "$_f" 2>&1)"
|
||||||
|
if echo "$_mp3val_out" | grep -q 'No errors found' ; then
|
||||||
|
echo "VALID|mp3val: no errors found"
|
||||||
|
else
|
||||||
|
local _mp3_err
|
||||||
|
_mp3_err="$(echo "$_mp3val_out" | grep -iv '^mp3val\|^$' | head -2 | tr '\n' ' ' | sed 's/ $//')"
|
||||||
|
echo "INVALID|mp3val reported errors${_mp3_err:+ (${_mp3_err})}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
local _head3 _head2hex
|
||||||
|
_head3="$(head -c3 "$_f" 2>/dev/null)"
|
||||||
|
_head2hex="$(head -c2 "$_f" 2>/dev/null | od -An -tx1 | tr -d ' \n')"
|
||||||
|
if [[ "$_head3" = "ID3" ]] || echo "$_head2hex" | grep -qE '^fff[bef2]' ; then
|
||||||
|
echo "VALID|MP3 ID3 tag / MPEG sync ok (mp3val not installed — no deep check)"
|
||||||
|
else
|
||||||
|
echo "INVALID|MP3: no ID3 header or MPEG frame sync found"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
flac)
|
||||||
|
if command -v flac > /dev/null 2>&1 ; then
|
||||||
|
# --test decodes without writing output; exit 0 = file is intact
|
||||||
|
local _flac_out _flac_exit
|
||||||
|
_flac_out="$(flac --silent --test "$_f" 2>&1)"
|
||||||
|
_flac_exit=$?
|
||||||
|
if [[ $_flac_exit -eq 0 ]] ; then
|
||||||
|
echo "VALID|flac --test: ok"
|
||||||
|
else
|
||||||
|
local _flac_err
|
||||||
|
_flac_err="$(echo "$_flac_out" | grep -v '^$' | tail -1)"
|
||||||
|
echo "INVALID|flac --test failed${_flac_err:+ (${_flac_err})}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
local _head4hex
|
||||||
|
_head4hex="$(head -c4 "$_f" 2>/dev/null | od -An -tx1 | tr -d ' \n')"
|
||||||
|
if [[ "$_head4hex" = "664c6143" ]] ; then # "fLaC"
|
||||||
|
echo "VALID|FLAC 'fLaC' signature ok (flac not installed — no deep check)"
|
||||||
|
else
|
||||||
|
echo "INVALID|FLAC 'fLaC' signature missing"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
ogg|oga|ogv|opus)
|
||||||
|
if command -v ogginfo > /dev/null 2>&1 ; then
|
||||||
|
# ogginfo exits 0 if the Ogg container is intact
|
||||||
|
local _ogg_out _ogg_exit
|
||||||
|
_ogg_out="$(ogginfo "$_f" 2>&1)"
|
||||||
|
_ogg_exit=$?
|
||||||
|
if [[ $_ogg_exit -eq 0 ]] ; then
|
||||||
|
echo "VALID|ogginfo: container parsed ok"
|
||||||
|
else
|
||||||
|
local _ogg_err
|
||||||
|
_ogg_err="$(echo "$_ogg_out" | grep -iv '^Processing\|^$' | head -2 | tr '\n' ' ' | sed 's/ $//')"
|
||||||
|
echo "INVALID|ogginfo failed${_ogg_err:+ (${_ogg_err})}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
local _head4ogg
|
||||||
|
_head4ogg="$(head -c4 "$_f" 2>/dev/null)"
|
||||||
|
if [[ "$_head4ogg" = "OggS" ]] ; then
|
||||||
|
echo "VALID|OGG 'OggS' signature ok (ogginfo not installed — no deep check)"
|
||||||
|
else
|
||||||
|
echo "INVALID|OGG 'OggS' signature missing"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
wav)
|
wav)
|
||||||
local _riff _wave
|
local _riff _wave
|
||||||
_riff="$(head -c4 "$_f" 2> /dev/null)"
|
_riff="$(head -c4 "$_f" 2> /dev/null)"
|
||||||
@@ -584,6 +778,15 @@ validate_recovered_file() {
|
|||||||
## - PDF structural check shared by the 'pdf' case and the 'ai'
|
## - PDF structural check shared by the 'pdf' case and the 'ai'
|
||||||
## - (PDF-compatible) case. Sets $_pdf_check_detail, returns 0/1.
|
## - (PDF-compatible) case. Sets $_pdf_check_detail, returns 0/1.
|
||||||
## -
|
## -
|
||||||
|
## - qpdf exit codes:
|
||||||
|
## - 0 no problems
|
||||||
|
## - 2 structural errors → file is genuinely corrupt/unreadable
|
||||||
|
## - 3 warnings only → minor spec non-conformances; every real
|
||||||
|
## - viewer opens the file without issues
|
||||||
|
## - Only exit code 2 is treated as INVALID here. Exit code 3 (warnings)
|
||||||
|
## - is reported as VALID with a note, preventing false positives for
|
||||||
|
## - real-world PDFs that have trivial non-conformances.
|
||||||
|
## -
|
||||||
_pdf_check() {
|
_pdf_check() {
|
||||||
local _f="$1"
|
local _f="$1"
|
||||||
if [[ "$(head -c5 "$_f" 2> /dev/null)" != "%PDF-" ]] ; then
|
if [[ "$(head -c5 "$_f" 2> /dev/null)" != "%PDF-" ]] ; then
|
||||||
@@ -591,11 +794,22 @@ _pdf_check() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
if command -v qpdf > /dev/null 2>&1 ; then
|
if command -v qpdf > /dev/null 2>&1 ; then
|
||||||
if qpdf --check "$_f" > /dev/null 2>&1 ; then
|
local _qpdf_out _qpdf_exit
|
||||||
|
_qpdf_out="$(qpdf --check "$_f" 2>&1)"
|
||||||
|
_qpdf_exit=$?
|
||||||
|
if [[ $_qpdf_exit -eq 0 ]] ; then
|
||||||
_pdf_check_detail="qpdf --check ok"
|
_pdf_check_detail="qpdf --check ok"
|
||||||
return 0
|
return 0
|
||||||
|
elif [[ $_qpdf_exit -eq 3 ]] ; then
|
||||||
|
# Warnings only — no structural errors. File is readable by all
|
||||||
|
# standard PDF viewers; non-conformances are minor/cosmetic.
|
||||||
|
_pdf_check_detail="qpdf --check ok (warnings only — file is readable)"
|
||||||
|
return 0
|
||||||
else
|
else
|
||||||
_pdf_check_detail="qpdf --check failed"
|
# Exit code 2 (or unexpected): genuine structural errors.
|
||||||
|
local _first_err
|
||||||
|
_first_err="$(echo "$_qpdf_out" | grep -i 'error' | head -1 | sed 's/^[[:space:]]*//')"
|
||||||
|
_pdf_check_detail="qpdf --check failed (exit ${_qpdf_exit}${_first_err:+: ${_first_err}})"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -1099,6 +1313,8 @@ done
|
|||||||
mkdir -p "$report_dir" 2> /dev/null
|
mkdir -p "$report_dir" 2> /dev/null
|
||||||
recovery_report_file="${report_dir}/recovery_${WEBSITE}_${run_date}.tsv"
|
recovery_report_file="${report_dir}/recovery_${WEBSITE}_${run_date}.tsv"
|
||||||
|
|
||||||
|
check_optional_validation_tools
|
||||||
|
|
||||||
if $terminal ; then
|
if $terminal ; then
|
||||||
echo ""
|
echo ""
|
||||||
if $REVALIDATE_ONLY ; then
|
if $REVALIDATE_ONLY ; then
|
||||||
|
|||||||
+213
-7
@@ -192,6 +192,77 @@ clean_up() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
check_optional_validation_tools() {
|
||||||
|
|
||||||
|
local _missing_tools=()
|
||||||
|
local _missing_pkgs=()
|
||||||
|
local _desc=()
|
||||||
|
|
||||||
|
command -v identify > /dev/null 2>&1 || {
|
||||||
|
_missing_tools+=("identify")
|
||||||
|
_missing_pkgs+=("imagemagick")
|
||||||
|
_desc+=("identify (imagemagick) — PNG / TIFF / BMP / GIF: vollst. Dekodierung statt nur Magic-Bytes")
|
||||||
|
}
|
||||||
|
command -v ffprobe > /dev/null 2>&1 || {
|
||||||
|
_missing_tools+=("ffprobe")
|
||||||
|
_missing_pkgs+=("ffmpeg")
|
||||||
|
_desc+=("ffprobe (ffmpeg) — MP4 / MOV / M4V: Container-Parsing statt nur ftyp-Box-Suche")
|
||||||
|
}
|
||||||
|
command -v mp3val > /dev/null 2>&1 || {
|
||||||
|
_missing_tools+=("mp3val")
|
||||||
|
_missing_pkgs+=("mp3val")
|
||||||
|
_desc+=("mp3val (mp3val) — MP3: Frame-Struktur-Check statt nur ID3-Signatur")
|
||||||
|
}
|
||||||
|
command -v flac > /dev/null 2>&1 || {
|
||||||
|
_missing_tools+=("flac")
|
||||||
|
_missing_pkgs+=("flac")
|
||||||
|
_desc+=("flac (flac) — FLAC: Decode-Test statt nur fLaC-Signatur")
|
||||||
|
}
|
||||||
|
command -v ogginfo > /dev/null 2>&1 || {
|
||||||
|
_missing_tools+=("ogginfo")
|
||||||
|
_missing_pkgs+=("vorbis-tools")
|
||||||
|
_desc+=("ogginfo (vorbis-tools) — OGG / OGA / OPUS: Container-Check statt nur OggS-Signatur")
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ ${#_missing_tools[@]} -eq 0 ]] && return 0
|
||||||
|
|
||||||
|
if ! $terminal ; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local _pkg_list="${_missing_pkgs[*]}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e " \033[33mOptionale Validierungs-Tools fehlen – Checks laufen mit reduzierter Genauigkeit:\033[m"
|
||||||
|
echo ""
|
||||||
|
local _d
|
||||||
|
for _d in "${_desc[@]}" ; do
|
||||||
|
echo " ${_d}"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
echo -n " Jetzt installieren? apt install ${_pkg_list} [j/N]: "
|
||||||
|
read -r _yn
|
||||||
|
echo ""
|
||||||
|
if [[ "$_yn" =~ ^[jJyY]$ ]] ; then
|
||||||
|
echononl " Installiere Pakete: ${_pkg_list}.."
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
if apt-get install -y ${_missing_pkgs[*]} > /dev/null 2>&1 ; then
|
||||||
|
echo_ok
|
||||||
|
else
|
||||||
|
echo_failed
|
||||||
|
echo ""
|
||||||
|
echo " Bitte manuell installieren:"
|
||||||
|
echo " apt install ${_pkg_list}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " Übersprungen — Validierung läuft mit reduzierter Genauigkeit."
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
is_number() {
|
is_number() {
|
||||||
|
|
||||||
return $(test ! -z "${1##*[!0-9]*}" > /dev/null 2>&1);
|
return $(test ! -z "${1##*[!0-9]*}" > /dev/null 2>&1);
|
||||||
@@ -340,38 +411,70 @@ validate_recovered_file() {
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
png)
|
png)
|
||||||
|
if command -v identify > /dev/null 2>&1 ; then
|
||||||
|
if identify -regard-warnings "$_f" > /dev/null 2>&1 ; then
|
||||||
|
echo "VALID|ImageMagick 'identify' decoded PNG ok"
|
||||||
|
else
|
||||||
|
echo "INVALID|ImageMagick 'identify' failed to decode PNG"
|
||||||
|
fi
|
||||||
|
else
|
||||||
local _head
|
local _head
|
||||||
_head="$(head -c8 "$_f" 2> /dev/null | od -An -tx1 | tr -d ' \n')"
|
_head="$(head -c8 "$_f" 2> /dev/null | od -An -tx1 | tr -d ' \n')"
|
||||||
if [[ "$_head" = "89504e470d0a1a0a" ]] ; then
|
if [[ "$_head" = "89504e470d0a1a0a" ]] ; then
|
||||||
echo "VALID|PNG signature ok"
|
echo "VALID|PNG signature ok (identify not installed — no deep check)"
|
||||||
else
|
else
|
||||||
echo "INVALID|PNG signature missing"
|
echo "INVALID|PNG signature missing"
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
gif)
|
gif)
|
||||||
|
if command -v identify > /dev/null 2>&1 ; then
|
||||||
|
if identify -regard-warnings "$_f" > /dev/null 2>&1 ; then
|
||||||
|
echo "VALID|ImageMagick 'identify' decoded GIF ok"
|
||||||
|
else
|
||||||
|
echo "INVALID|ImageMagick 'identify' failed to decode GIF"
|
||||||
|
fi
|
||||||
|
else
|
||||||
local _head6
|
local _head6
|
||||||
_head6="$(head -c6 "$_f" 2> /dev/null)"
|
_head6="$(head -c6 "$_f" 2> /dev/null)"
|
||||||
if [[ "$_head6" = "GIF87a" || "$_head6" = "GIF89a" ]] ; then
|
if [[ "$_head6" = "GIF87a" || "$_head6" = "GIF89a" ]] ; then
|
||||||
echo "VALID|GIF signature ok"
|
echo "VALID|GIF signature ok (identify not installed — no deep check)"
|
||||||
else
|
else
|
||||||
echo "INVALID|GIF signature missing"
|
echo "INVALID|GIF signature missing"
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
bmp)
|
bmp)
|
||||||
|
if command -v identify > /dev/null 2>&1 ; then
|
||||||
|
if identify -regard-warnings "$_f" > /dev/null 2>&1 ; then
|
||||||
|
echo "VALID|ImageMagick 'identify' decoded BMP ok"
|
||||||
|
else
|
||||||
|
echo "INVALID|ImageMagick 'identify' failed to decode BMP"
|
||||||
|
fi
|
||||||
|
else
|
||||||
if [[ "$(head -c2 "$_f" 2> /dev/null)" = "BM" ]] ; then
|
if [[ "$(head -c2 "$_f" 2> /dev/null)" = "BM" ]] ; then
|
||||||
echo "VALID|BMP signature ok"
|
echo "VALID|BMP signature ok (identify not installed — no deep check)"
|
||||||
else
|
else
|
||||||
echo "INVALID|BMP signature missing"
|
echo "INVALID|BMP signature missing"
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
tif|tiff)
|
tif|tiff)
|
||||||
|
if command -v identify > /dev/null 2>&1 ; then
|
||||||
|
if identify -regard-warnings "$_f" > /dev/null 2>&1 ; then
|
||||||
|
echo "VALID|ImageMagick 'identify' decoded TIFF ok"
|
||||||
|
else
|
||||||
|
echo "INVALID|ImageMagick 'identify' failed to decode TIFF"
|
||||||
|
fi
|
||||||
|
else
|
||||||
local _head4
|
local _head4
|
||||||
_head4="$(head -c4 "$_f" 2> /dev/null | od -An -tx1 | tr -d ' \n')"
|
_head4="$(head -c4 "$_f" 2> /dev/null | od -An -tx1 | tr -d ' \n')"
|
||||||
if [[ "$_head4" = "49492a00" || "$_head4" = "4d4d002a" ]] ; then
|
if [[ "$_head4" = "49492a00" || "$_head4" = "4d4d002a" ]] ; then
|
||||||
echo "VALID|TIFF signature ok"
|
echo "VALID|TIFF signature ok (identify not installed — no deep check)"
|
||||||
else
|
else
|
||||||
echo "INVALID|TIFF signature missing"
|
echo "INVALID|TIFF signature missing"
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
pnm|pgm|ppm|pbm)
|
pnm|pgm|ppm|pbm)
|
||||||
local _head2
|
local _head2
|
||||||
@@ -420,11 +523,26 @@ validate_recovered_file() {
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
mp4|mov|m4v)
|
mp4|mov|m4v)
|
||||||
|
if command -v ffprobe > /dev/null 2>&1 ; then
|
||||||
|
local _ffprobe_out _ffprobe_exit
|
||||||
|
_ffprobe_out="$(ffprobe -v error -show_streams "$_f" 2>&1)"
|
||||||
|
_ffprobe_exit=$?
|
||||||
|
if [[ $_ffprobe_exit -eq 0 ]] ; then
|
||||||
|
local _streams
|
||||||
|
_streams="$(echo "$_ffprobe_out" | grep -c '\[STREAM\]' || true)"
|
||||||
|
echo "VALID|ffprobe parsed container ok (${_streams} stream(s) found)"
|
||||||
|
else
|
||||||
|
local _ffprobe_err
|
||||||
|
_ffprobe_err="$(echo "$_ffprobe_out" | head -1)"
|
||||||
|
echo "INVALID|ffprobe failed to parse container${_ffprobe_err:+ (${_ffprobe_err})}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
if head -c 64 "$_f" 2> /dev/null | grep -aq "ftyp" ; then
|
if head -c 64 "$_f" 2> /dev/null | grep -aq "ftyp" ; then
|
||||||
echo "VALID|mp4 ftyp box found"
|
echo "VALID|mp4 ftyp box found (ffprobe not installed — no deep check)"
|
||||||
else
|
else
|
||||||
echo "INVALID|mp4 ftyp box not found"
|
echo "INVALID|mp4 ftyp box not found"
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
doc|xls|ppt|ole|msi)
|
doc|xls|ppt|ole|msi)
|
||||||
local _head8
|
local _head8
|
||||||
@@ -515,6 +633,72 @@ validate_recovered_file() {
|
|||||||
echo "INVALID|GIMP (gimp xcf) signature missing"
|
echo "INVALID|GIMP (gimp xcf) signature missing"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
|
mp3)
|
||||||
|
if command -v mp3val > /dev/null 2>&1 ; then
|
||||||
|
local _mp3val_out
|
||||||
|
_mp3val_out="$(mp3val "$_f" 2>&1)"
|
||||||
|
if echo "$_mp3val_out" | grep -q 'No errors found' ; then
|
||||||
|
echo "VALID|mp3val: no errors found"
|
||||||
|
else
|
||||||
|
local _mp3_err
|
||||||
|
_mp3_err="$(echo "$_mp3val_out" | grep -iv '^mp3val\|^$' | head -2 | tr '\n' ' ' | sed 's/ $//')"
|
||||||
|
echo "INVALID|mp3val reported errors${_mp3_err:+ (${_mp3_err})}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
local _head3 _head2hex
|
||||||
|
_head3="$(head -c3 "$_f" 2>/dev/null)"
|
||||||
|
_head2hex="$(head -c2 "$_f" 2>/dev/null | od -An -tx1 | tr -d ' \n')"
|
||||||
|
if [[ "$_head3" = "ID3" ]] || echo "$_head2hex" | grep -qE '^fff[bef2]' ; then
|
||||||
|
echo "VALID|MP3 ID3 tag / MPEG sync ok (mp3val not installed — no deep check)"
|
||||||
|
else
|
||||||
|
echo "INVALID|MP3: no ID3 header or MPEG frame sync found"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
flac)
|
||||||
|
if command -v flac > /dev/null 2>&1 ; then
|
||||||
|
local _flac_out _flac_exit
|
||||||
|
_flac_out="$(flac --silent --test "$_f" 2>&1)"
|
||||||
|
_flac_exit=$?
|
||||||
|
if [[ $_flac_exit -eq 0 ]] ; then
|
||||||
|
echo "VALID|flac --test: ok"
|
||||||
|
else
|
||||||
|
local _flac_err
|
||||||
|
_flac_err="$(echo "$_flac_out" | grep -v '^$' | tail -1)"
|
||||||
|
echo "INVALID|flac --test failed${_flac_err:+ (${_flac_err})}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
local _head4hex
|
||||||
|
_head4hex="$(head -c4 "$_f" 2>/dev/null | od -An -tx1 | tr -d ' \n')"
|
||||||
|
if [[ "$_head4hex" = "664c6143" ]] ; then
|
||||||
|
echo "VALID|FLAC 'fLaC' signature ok (flac not installed — no deep check)"
|
||||||
|
else
|
||||||
|
echo "INVALID|FLAC 'fLaC' signature missing"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
ogg|oga|ogv|opus)
|
||||||
|
if command -v ogginfo > /dev/null 2>&1 ; then
|
||||||
|
local _ogg_out _ogg_exit
|
||||||
|
_ogg_out="$(ogginfo "$_f" 2>&1)"
|
||||||
|
_ogg_exit=$?
|
||||||
|
if [[ $_ogg_exit -eq 0 ]] ; then
|
||||||
|
echo "VALID|ogginfo: container parsed ok"
|
||||||
|
else
|
||||||
|
local _ogg_err
|
||||||
|
_ogg_err="$(echo "$_ogg_out" | grep -iv '^Processing\|^$' | head -2 | tr '\n' ' ' | sed 's/ $//')"
|
||||||
|
echo "INVALID|ogginfo failed${_ogg_err:+ (${_ogg_err})}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
local _head4ogg
|
||||||
|
_head4ogg="$(head -c4 "$_f" 2>/dev/null)"
|
||||||
|
if [[ "$_head4ogg" = "OggS" ]] ; then
|
||||||
|
echo "VALID|OGG 'OggS' signature ok (ogginfo not installed — no deep check)"
|
||||||
|
else
|
||||||
|
echo "INVALID|OGG 'OggS' signature missing"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
wav)
|
wav)
|
||||||
local _riff _wave
|
local _riff _wave
|
||||||
_riff="$(head -c4 "$_f" 2> /dev/null)"
|
_riff="$(head -c4 "$_f" 2> /dev/null)"
|
||||||
@@ -586,6 +770,15 @@ validate_recovered_file() {
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
## - qpdf exit codes:
|
||||||
|
## - 0 no problems
|
||||||
|
## - 2 structural errors → file is genuinely corrupt/unreadable
|
||||||
|
## - 3 warnings only → minor spec non-conformances; every real
|
||||||
|
## - viewer opens the file without issues
|
||||||
|
## - Only exit code 2 is treated as INVALID here. Exit code 3 (warnings)
|
||||||
|
## - is reported as VALID with a note, preventing false positives for
|
||||||
|
## - real-world PDFs that have trivial non-conformances.
|
||||||
|
## -
|
||||||
_pdf_check() {
|
_pdf_check() {
|
||||||
local _f="$1"
|
local _f="$1"
|
||||||
if [[ "$(head -c5 "$_f" 2> /dev/null)" != "%PDF-" ]] ; then
|
if [[ "$(head -c5 "$_f" 2> /dev/null)" != "%PDF-" ]] ; then
|
||||||
@@ -593,11 +786,22 @@ _pdf_check() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
if command -v qpdf > /dev/null 2>&1 ; then
|
if command -v qpdf > /dev/null 2>&1 ; then
|
||||||
if qpdf --check "$_f" > /dev/null 2>&1 ; then
|
local _qpdf_out _qpdf_exit
|
||||||
|
_qpdf_out="$(qpdf --check "$_f" 2>&1)"
|
||||||
|
_qpdf_exit=$?
|
||||||
|
if [[ $_qpdf_exit -eq 0 ]] ; then
|
||||||
_pdf_check_detail="qpdf --check ok"
|
_pdf_check_detail="qpdf --check ok"
|
||||||
return 0
|
return 0
|
||||||
|
elif [[ $_qpdf_exit -eq 3 ]] ; then
|
||||||
|
# Warnings only — no structural errors. File is readable by all
|
||||||
|
# standard PDF viewers; non-conformances are minor/cosmetic.
|
||||||
|
_pdf_check_detail="qpdf --check ok (warnings only — file is readable)"
|
||||||
|
return 0
|
||||||
else
|
else
|
||||||
_pdf_check_detail="qpdf --check failed"
|
# Exit code 2 (or unexpected): genuine structural errors.
|
||||||
|
local _first_err
|
||||||
|
_first_err="$(echo "$_qpdf_out" | grep -i 'error' | head -1 | sed 's/^[[:space:]]*//')"
|
||||||
|
_pdf_check_detail="qpdf --check failed (exit ${_qpdf_exit}${_first_err:+: ${_first_err}})"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -1061,6 +1265,8 @@ done
|
|||||||
mkdir -p "$report_dir" 2> /dev/null
|
mkdir -p "$report_dir" 2> /dev/null
|
||||||
recreate_report_file="${report_dir}/recreate_${WEBSITE}_${run_date}.tsv"
|
recreate_report_file="${report_dir}/recreate_${WEBSITE}_${run_date}.tsv"
|
||||||
|
|
||||||
|
check_optional_validation_tools
|
||||||
|
|
||||||
if $terminal ; then
|
if $terminal ; then
|
||||||
echo ""
|
echo ""
|
||||||
if $DRY_RUN ; then
|
if $DRY_RUN ; then
|
||||||
|
|||||||
+213
-7
@@ -176,6 +176,77 @@ clean_up() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
check_optional_validation_tools() {
|
||||||
|
|
||||||
|
local _missing_tools=()
|
||||||
|
local _missing_pkgs=()
|
||||||
|
local _desc=()
|
||||||
|
|
||||||
|
command -v identify > /dev/null 2>&1 || {
|
||||||
|
_missing_tools+=("identify")
|
||||||
|
_missing_pkgs+=("imagemagick")
|
||||||
|
_desc+=("identify (imagemagick) — PNG / TIFF / BMP / GIF: vollst. Dekodierung statt nur Magic-Bytes")
|
||||||
|
}
|
||||||
|
command -v ffprobe > /dev/null 2>&1 || {
|
||||||
|
_missing_tools+=("ffprobe")
|
||||||
|
_missing_pkgs+=("ffmpeg")
|
||||||
|
_desc+=("ffprobe (ffmpeg) — MP4 / MOV / M4V: Container-Parsing statt nur ftyp-Box-Suche")
|
||||||
|
}
|
||||||
|
command -v mp3val > /dev/null 2>&1 || {
|
||||||
|
_missing_tools+=("mp3val")
|
||||||
|
_missing_pkgs+=("mp3val")
|
||||||
|
_desc+=("mp3val (mp3val) — MP3: Frame-Struktur-Check statt nur ID3-Signatur")
|
||||||
|
}
|
||||||
|
command -v flac > /dev/null 2>&1 || {
|
||||||
|
_missing_tools+=("flac")
|
||||||
|
_missing_pkgs+=("flac")
|
||||||
|
_desc+=("flac (flac) — FLAC: Decode-Test statt nur fLaC-Signatur")
|
||||||
|
}
|
||||||
|
command -v ogginfo > /dev/null 2>&1 || {
|
||||||
|
_missing_tools+=("ogginfo")
|
||||||
|
_missing_pkgs+=("vorbis-tools")
|
||||||
|
_desc+=("ogginfo (vorbis-tools) — OGG / OGA / OPUS: Container-Check statt nur OggS-Signatur")
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ ${#_missing_tools[@]} -eq 0 ]] && return 0
|
||||||
|
|
||||||
|
if ! $terminal ; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local _pkg_list="${_missing_pkgs[*]}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e " \033[33mOptionale Validierungs-Tools fehlen – Checks laufen mit reduzierter Genauigkeit:\033[m"
|
||||||
|
echo ""
|
||||||
|
local _d
|
||||||
|
for _d in "${_desc[@]}" ; do
|
||||||
|
echo " ${_d}"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
echo -n " Jetzt installieren? apt install ${_pkg_list} [j/N]: "
|
||||||
|
read -r _yn
|
||||||
|
echo ""
|
||||||
|
if [[ "$_yn" =~ ^[jJyY]$ ]] ; then
|
||||||
|
echononl " Installiere Pakete: ${_pkg_list}.."
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
if apt-get install -y ${_missing_pkgs[*]} > /dev/null 2>&1 ; then
|
||||||
|
echo_ok
|
||||||
|
else
|
||||||
|
echo_failed
|
||||||
|
echo ""
|
||||||
|
echo " Bitte manuell installieren:"
|
||||||
|
echo " apt install ${_pkg_list}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " Übersprungen — Validierung läuft mit reduzierter Genauigkeit."
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
is_number() {
|
is_number() {
|
||||||
|
|
||||||
return $(test ! -z "${1##*[!0-9]*}" > /dev/null 2>&1);
|
return $(test ! -z "${1##*[!0-9]*}" > /dev/null 2>&1);
|
||||||
@@ -325,38 +396,70 @@ validate_recovered_file() {
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
png)
|
png)
|
||||||
|
if command -v identify > /dev/null 2>&1 ; then
|
||||||
|
if identify -regard-warnings "$_f" > /dev/null 2>&1 ; then
|
||||||
|
echo "VALID|ImageMagick 'identify' decoded PNG ok"
|
||||||
|
else
|
||||||
|
echo "INVALID|ImageMagick 'identify' failed to decode PNG"
|
||||||
|
fi
|
||||||
|
else
|
||||||
local _head
|
local _head
|
||||||
_head="$(head -c8 "$_f" 2> /dev/null | od -An -tx1 | tr -d ' \n')"
|
_head="$(head -c8 "$_f" 2> /dev/null | od -An -tx1 | tr -d ' \n')"
|
||||||
if [[ "$_head" = "89504e470d0a1a0a" ]] ; then
|
if [[ "$_head" = "89504e470d0a1a0a" ]] ; then
|
||||||
echo "VALID|PNG signature ok"
|
echo "VALID|PNG signature ok (identify not installed — no deep check)"
|
||||||
else
|
else
|
||||||
echo "INVALID|PNG signature missing"
|
echo "INVALID|PNG signature missing"
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
gif)
|
gif)
|
||||||
|
if command -v identify > /dev/null 2>&1 ; then
|
||||||
|
if identify -regard-warnings "$_f" > /dev/null 2>&1 ; then
|
||||||
|
echo "VALID|ImageMagick 'identify' decoded GIF ok"
|
||||||
|
else
|
||||||
|
echo "INVALID|ImageMagick 'identify' failed to decode GIF"
|
||||||
|
fi
|
||||||
|
else
|
||||||
local _head6
|
local _head6
|
||||||
_head6="$(head -c6 "$_f" 2> /dev/null)"
|
_head6="$(head -c6 "$_f" 2> /dev/null)"
|
||||||
if [[ "$_head6" = "GIF87a" || "$_head6" = "GIF89a" ]] ; then
|
if [[ "$_head6" = "GIF87a" || "$_head6" = "GIF89a" ]] ; then
|
||||||
echo "VALID|GIF signature ok"
|
echo "VALID|GIF signature ok (identify not installed — no deep check)"
|
||||||
else
|
else
|
||||||
echo "INVALID|GIF signature missing"
|
echo "INVALID|GIF signature missing"
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
bmp)
|
bmp)
|
||||||
|
if command -v identify > /dev/null 2>&1 ; then
|
||||||
|
if identify -regard-warnings "$_f" > /dev/null 2>&1 ; then
|
||||||
|
echo "VALID|ImageMagick 'identify' decoded BMP ok"
|
||||||
|
else
|
||||||
|
echo "INVALID|ImageMagick 'identify' failed to decode BMP"
|
||||||
|
fi
|
||||||
|
else
|
||||||
if [[ "$(head -c2 "$_f" 2> /dev/null)" = "BM" ]] ; then
|
if [[ "$(head -c2 "$_f" 2> /dev/null)" = "BM" ]] ; then
|
||||||
echo "VALID|BMP signature ok"
|
echo "VALID|BMP signature ok (identify not installed — no deep check)"
|
||||||
else
|
else
|
||||||
echo "INVALID|BMP signature missing"
|
echo "INVALID|BMP signature missing"
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
tif|tiff)
|
tif|tiff)
|
||||||
|
if command -v identify > /dev/null 2>&1 ; then
|
||||||
|
if identify -regard-warnings "$_f" > /dev/null 2>&1 ; then
|
||||||
|
echo "VALID|ImageMagick 'identify' decoded TIFF ok"
|
||||||
|
else
|
||||||
|
echo "INVALID|ImageMagick 'identify' failed to decode TIFF"
|
||||||
|
fi
|
||||||
|
else
|
||||||
local _head4
|
local _head4
|
||||||
_head4="$(head -c4 "$_f" 2> /dev/null | od -An -tx1 | tr -d ' \n')"
|
_head4="$(head -c4 "$_f" 2> /dev/null | od -An -tx1 | tr -d ' \n')"
|
||||||
if [[ "$_head4" = "49492a00" || "$_head4" = "4d4d002a" ]] ; then
|
if [[ "$_head4" = "49492a00" || "$_head4" = "4d4d002a" ]] ; then
|
||||||
echo "VALID|TIFF signature ok"
|
echo "VALID|TIFF signature ok (identify not installed — no deep check)"
|
||||||
else
|
else
|
||||||
echo "INVALID|TIFF signature missing"
|
echo "INVALID|TIFF signature missing"
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
pnm|pgm|ppm|pbm)
|
pnm|pgm|ppm|pbm)
|
||||||
local _head2
|
local _head2
|
||||||
@@ -414,11 +517,26 @@ validate_recovered_file() {
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
mp4|mov|m4v)
|
mp4|mov|m4v)
|
||||||
|
if command -v ffprobe > /dev/null 2>&1 ; then
|
||||||
|
local _ffprobe_out _ffprobe_exit
|
||||||
|
_ffprobe_out="$(ffprobe -v error -show_streams "$_f" 2>&1)"
|
||||||
|
_ffprobe_exit=$?
|
||||||
|
if [[ $_ffprobe_exit -eq 0 ]] ; then
|
||||||
|
local _streams
|
||||||
|
_streams="$(echo "$_ffprobe_out" | grep -c '\[STREAM\]' || true)"
|
||||||
|
echo "VALID|ffprobe parsed container ok (${_streams} stream(s) found)"
|
||||||
|
else
|
||||||
|
local _ffprobe_err
|
||||||
|
_ffprobe_err="$(echo "$_ffprobe_out" | head -1)"
|
||||||
|
echo "INVALID|ffprobe failed to parse container${_ffprobe_err:+ (${_ffprobe_err})}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
if head -c 64 "$_f" 2> /dev/null | grep -aq "ftyp" ; then
|
if head -c 64 "$_f" 2> /dev/null | grep -aq "ftyp" ; then
|
||||||
echo "VALID|mp4 ftyp box found"
|
echo "VALID|mp4 ftyp box found (ffprobe not installed — no deep check)"
|
||||||
else
|
else
|
||||||
echo "INVALID|mp4 ftyp box not found"
|
echo "INVALID|mp4 ftyp box not found"
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
doc|xls|ppt|ole|msi)
|
doc|xls|ppt|ole|msi)
|
||||||
local _head8
|
local _head8
|
||||||
@@ -517,6 +635,72 @@ validate_recovered_file() {
|
|||||||
echo "INVALID|GIMP (gimp xcf) signature missing"
|
echo "INVALID|GIMP (gimp xcf) signature missing"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
|
mp3)
|
||||||
|
if command -v mp3val > /dev/null 2>&1 ; then
|
||||||
|
local _mp3val_out
|
||||||
|
_mp3val_out="$(mp3val "$_f" 2>&1)"
|
||||||
|
if echo "$_mp3val_out" | grep -q 'No errors found' ; then
|
||||||
|
echo "VALID|mp3val: no errors found"
|
||||||
|
else
|
||||||
|
local _mp3_err
|
||||||
|
_mp3_err="$(echo "$_mp3val_out" | grep -iv '^mp3val\|^$' | head -2 | tr '\n' ' ' | sed 's/ $//')"
|
||||||
|
echo "INVALID|mp3val reported errors${_mp3_err:+ (${_mp3_err})}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
local _head3 _head2hex
|
||||||
|
_head3="$(head -c3 "$_f" 2>/dev/null)"
|
||||||
|
_head2hex="$(head -c2 "$_f" 2>/dev/null | od -An -tx1 | tr -d ' \n')"
|
||||||
|
if [[ "$_head3" = "ID3" ]] || echo "$_head2hex" | grep -qE '^fff[bef2]' ; then
|
||||||
|
echo "VALID|MP3 ID3 tag / MPEG sync ok (mp3val not installed — no deep check)"
|
||||||
|
else
|
||||||
|
echo "INVALID|MP3: no ID3 header or MPEG frame sync found"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
flac)
|
||||||
|
if command -v flac > /dev/null 2>&1 ; then
|
||||||
|
local _flac_out _flac_exit
|
||||||
|
_flac_out="$(flac --silent --test "$_f" 2>&1)"
|
||||||
|
_flac_exit=$?
|
||||||
|
if [[ $_flac_exit -eq 0 ]] ; then
|
||||||
|
echo "VALID|flac --test: ok"
|
||||||
|
else
|
||||||
|
local _flac_err
|
||||||
|
_flac_err="$(echo "$_flac_out" | grep -v '^$' | tail -1)"
|
||||||
|
echo "INVALID|flac --test failed${_flac_err:+ (${_flac_err})}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
local _head4hex
|
||||||
|
_head4hex="$(head -c4 "$_f" 2>/dev/null | od -An -tx1 | tr -d ' \n')"
|
||||||
|
if [[ "$_head4hex" = "664c6143" ]] ; then
|
||||||
|
echo "VALID|FLAC 'fLaC' signature ok (flac not installed — no deep check)"
|
||||||
|
else
|
||||||
|
echo "INVALID|FLAC 'fLaC' signature missing"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
ogg|oga|ogv|opus)
|
||||||
|
if command -v ogginfo > /dev/null 2>&1 ; then
|
||||||
|
local _ogg_out _ogg_exit
|
||||||
|
_ogg_out="$(ogginfo "$_f" 2>&1)"
|
||||||
|
_ogg_exit=$?
|
||||||
|
if [[ $_ogg_exit -eq 0 ]] ; then
|
||||||
|
echo "VALID|ogginfo: container parsed ok"
|
||||||
|
else
|
||||||
|
local _ogg_err
|
||||||
|
_ogg_err="$(echo "$_ogg_out" | grep -iv '^Processing\|^$' | head -2 | tr '\n' ' ' | sed 's/ $//')"
|
||||||
|
echo "INVALID|ogginfo failed${_ogg_err:+ (${_ogg_err})}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
local _head4ogg
|
||||||
|
_head4ogg="$(head -c4 "$_f" 2>/dev/null)"
|
||||||
|
if [[ "$_head4ogg" = "OggS" ]] ; then
|
||||||
|
echo "VALID|OGG 'OggS' signature ok (ogginfo not installed — no deep check)"
|
||||||
|
else
|
||||||
|
echo "INVALID|OGG 'OggS' signature missing"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
wav)
|
wav)
|
||||||
local _riff _wave
|
local _riff _wave
|
||||||
_riff="$(head -c4 "$_f" 2> /dev/null)"
|
_riff="$(head -c4 "$_f" 2> /dev/null)"
|
||||||
@@ -604,6 +788,15 @@ validate_recovered_file() {
|
|||||||
## - PDF structural check shared by the 'pdf' case and the 'ai'
|
## - PDF structural check shared by the 'pdf' case and the 'ai'
|
||||||
## - (PDF-compatible) case. Sets $_pdf_check_detail, returns 0/1.
|
## - (PDF-compatible) case. Sets $_pdf_check_detail, returns 0/1.
|
||||||
## -
|
## -
|
||||||
|
## - qpdf exit codes:
|
||||||
|
## - 0 no problems
|
||||||
|
## - 2 structural errors → file is genuinely corrupt/unreadable
|
||||||
|
## - 3 warnings only → minor spec non-conformances; every real
|
||||||
|
## - viewer opens the file without issues
|
||||||
|
## - Only exit code 2 is treated as INVALID here. Exit code 3 (warnings)
|
||||||
|
## - is reported as VALID with a note, preventing false positives for
|
||||||
|
## - real-world PDFs that have trivial non-conformances.
|
||||||
|
## -
|
||||||
_pdf_check() {
|
_pdf_check() {
|
||||||
local _f="$1"
|
local _f="$1"
|
||||||
if [[ "$(head -c5 "$_f" 2> /dev/null)" != "%PDF-" ]] ; then
|
if [[ "$(head -c5 "$_f" 2> /dev/null)" != "%PDF-" ]] ; then
|
||||||
@@ -611,11 +804,22 @@ _pdf_check() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
if command -v qpdf > /dev/null 2>&1 ; then
|
if command -v qpdf > /dev/null 2>&1 ; then
|
||||||
if qpdf --check "$_f" > /dev/null 2>&1 ; then
|
local _qpdf_out _qpdf_exit
|
||||||
|
_qpdf_out="$(qpdf --check "$_f" 2>&1)"
|
||||||
|
_qpdf_exit=$?
|
||||||
|
if [[ $_qpdf_exit -eq 0 ]] ; then
|
||||||
_pdf_check_detail="qpdf --check ok"
|
_pdf_check_detail="qpdf --check ok"
|
||||||
return 0
|
return 0
|
||||||
|
elif [[ $_qpdf_exit -eq 3 ]] ; then
|
||||||
|
# Warnings only — no structural errors. File is readable by all
|
||||||
|
# standard PDF viewers; non-conformances are minor/cosmetic.
|
||||||
|
_pdf_check_detail="qpdf --check ok (warnings only — file is readable)"
|
||||||
|
return 0
|
||||||
else
|
else
|
||||||
_pdf_check_detail="qpdf --check failed"
|
# Exit code 2 (or unexpected): genuine structural errors.
|
||||||
|
local _first_err
|
||||||
|
_first_err="$(echo "$_qpdf_out" | grep -i 'error' | head -1 | sed 's/^[[:space:]]*//')"
|
||||||
|
_pdf_check_detail="qpdf --check failed (exit ${_qpdf_exit}${_first_err:+: ${_first_err}})"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -1144,6 +1348,8 @@ done
|
|||||||
mkdir -p "$report_dir" 2> /dev/null
|
mkdir -p "$report_dir" 2> /dev/null
|
||||||
restore_report_file="${report_dir}/restore_${WEBSITE}_${run_date}.tsv"
|
restore_report_file="${report_dir}/restore_${WEBSITE}_${run_date}.tsv"
|
||||||
|
|
||||||
|
check_optional_validation_tools
|
||||||
|
|
||||||
if $terminal ; then
|
if $terminal ; then
|
||||||
echo ""
|
echo ""
|
||||||
if $DRY_RUN ; then
|
if $DRY_RUN ; then
|
||||||
|
|||||||
Reference in New Issue
Block a user