Browse Source

Add script zik_validator

master
Xavier Gendre 1 year ago
parent
commit
d0337539a6
2 changed files with 427 additions and 0 deletions
  1. +4
    -0
      README.md
  2. +423
    -0
      zik_validator

+ 4
- 0
README.md View File

@@ -5,3 +5,7 @@ Quelques outils utiles pour gérer ma bibliothèque musicale.
## Convert to folder picture

Script à copier dans `~/.local/share/nautilus/scripts/` pour convertir une image en jaquette par un simple clic droit

## Zik Validator

Script pour vérifier la bonne organisation d'une bibliothèque sur le modèle `artist/album`.

+ 423
- 0
zik_validator View File

@@ -0,0 +1,423 @@
#!/bin/bash

set -e
set -u
set -o pipefail

# Include files whose name begin with a dot
shopt -s dotglob

# ${1} path for artist to check
check_artist() {
local -r artist_path="${1}"
local -r artist="$(basename -- "${artist_path}")"

zik_info "artist: ${artist}"

# Check directory permissions
if [[ "$(stat -L -c "%a" "${artist_path}")" != "755" ]]; then
zik_warn "uncommon permissions for directory \"${artist_path}\""
fi

if [[ "${artist:0:1}" == "." ]]; then
zik_error "artist name should not start with a dot (${artist})"
fi

if [[ -z "$(ls -A "${artist_path}")" ]]; then
zik_error "no album found for artist \"${artist}\""
else
for item in "${artist_path}"/*; do
if [[ -d "${item}" ]]; then
check_album "${artist}" "${item}"
else
zik_warn "\"${item}\" is not a directory," \
"artist path should contain only directories"
fi
done
fi
}

# ${1} artist name
# ${2} path for album to check
check_album() {
local -r artist="${1}"
local -r album_path="${2}"
local -r album="$(basename -- "${album_path}")"

zik_info "album: ${album}"

# Check directory permissions
if [[ "$(stat -L -c "%a" "${album_path}")" != "755" ]]; then
zik_warn "uncommon permissions for directory \"${album_path}\""
fi

if [[ "${album:0:1}" == "." ]]; then
zik_error "album name should not start with a dot (${artist} / ${album})"
fi

# Check for a cover picture
local -r cover_file="${album_path}/${COVER_PICTURE_FILE}"
if [[ -f "${cover_file}" ]]; then
zik_info "found a cover picture for ${artist} / ${album}"

# Check file permissions
if [[ "$(stat -L -c "%a" "${cover_file}")" != "644" ]]; then
zik_warn "uncommon permissions for file \"${cover_file}\""
fi

# Check cover picture size
local -r cover_size="$(exiftool -s3 -ImageSize "${cover_file}")"
if [[ "${cover_size}" != "${COVER_SIZE}" ]]; then
zik_warn "cover picture size is ${cover_size} for ${artist} / ${album}"
fi
else
zik_warn "no cover picture \"${COVER_PICTURE_FILE}\" found" \
"for ${artist} / ${album}"
fi

local file_name
if [[ -z "$(ls -A "${album_path}")" ]]; then
zik_error "no files found for ${artist} / ${album}"
else
# Total number of files (no cover)
# shellcheck disable=SC2012
local -r num="$(ls -1q -I "${COVER_PICTURE_FILE}" "${album_path}" | wc -l)"

for item in "${album_path}"/*; do
if [[ -f "${item}" ]]; then
file_name="$(basename -- "${item}")"

# Skip cover picture file
if [[ "${file_name}" = "${COVER_PICTURE_FILE}" ]]; then
continue
fi

# Check file type and name (must start with 3 digits and a dash)
if [[ "${file_name##*.}" =~ ^(flac|mp3|ogg)$ ]]; then
if [[ "${file_name%.*}" =~ ^[0-9]{3}\ -\ .*$ ]]; then
check_file "${artist}" "${album}" "${num}" "${item}"
else
zik_error "badly formatted file name \"${item}\""
fi
else
zik_error "unmanaged file \"${item}\""
fi
else
zik_warn "\"${item}\" is not a file," \
"album path should contain only files"
fi
done
fi
}

# ${1} artist name
# ${2} album name
# ${3} number of files in album path (no cover)
# ${4} path for file to check
check_file() {
local -r artist="${1}"
local -r album="${2}"
local -r track_total="$(printf "%03d" "${3}")"
local -r file_path="${4}"
local -r file_name="$(basename -- "${file_path}")"

# Check file permissions
if [[ "$(stat -L -c "%a" "${file_path}")" != "644" ]]; then
zik_warn "uncommon permissions for file \"${file_path}\""
fi

# Extract track info from file name
local -r track_num="$(echo "${file_name%.*}" | cut -d " " -f 1)"
local -r track_name="$(echo "${file_name%.*}" | cut -d " " -f 3-)"
local -r track_ext="${file_name##*.}"

# Reset exiftool error file size
truncate -s 0 "${EXIFTOOL_ERROR_FILE}"

# Gather audio file tags
local -a tags
# shellcheck disable=SC2016
readarray -t tags <<< "$(exiftool -f -p '$FileType' \
-p '$FileTypeExtension' \
-p '$Title' \
-p '$Artist' \
-p '$Band' \
-p '$Album' \
-p '$AlbumArtist' \
-p '$Comment' \
-p '$Description' \
-p '$Genre' \
-p '$DiscNumber' \
-p '$PartOfSet' \
-p '$Track' \
-p '$TrackNumber' \
-p '$TrackTotal' \
-p '$PictureDescription' \
-p '$PictureType' \
"${file_path}" \
2> "${EXIFTOOL_ERROR_FILE}")"

# Manage gathered tags
local -r exif_file_type="${tags[0]}"
local -r exif_file_type_extension="${tags[1]}"
local -r exif_title="${tags[2]}"
local -r exif_artist="${tags[3]}"
local -r exif_band="${tags[4]}"
local -r exif_album="${tags[5]}"
local -r exif_album_artist="${tags[6]}"
local -r exif_comment="${tags[7]}"
local -r exif_description="${tags[8]}"
local -r exif_genre="${tags[9]}"
local -r exif_disc_number="${tags[10]}"
local -r exif_part_of_set="${tags[11]}"
local -r exif_track="${tags[12]}"
local -r exif_track_number="${tags[13]}"
local -r exif_track_total="${tags[14]}"
local -r exif_picture_description="${tags[15]}"
local -r exif_picture_type="${tags[16]}"

# Manage exiftool error messages
if [[ -s "${EXIFTOOL_ERROR_FILE}" ]]; then
zik_warn "exiftool output error for file \"${file_path}\""
while read -r EXIFTOOL_MSG; do
echo " ${EXIFTOOL_MSG}"
done < "${EXIFTOOL_ERROR_FILE}"
fi

# Check type extension
if [[ "${track_ext}" != "${exif_file_type_extension}" ]]; then
zik_error "type extension mismatch for file \"${file_path}\""
fi

# Check artist
if [[ "${artist}" != "${exif_artist}" ]]; then
zik_warn "artist mismatch for file \"${file_path}\""
fi

# Check album
local -r album_clean="${album//["/"" ""-""?""'"":""\"""_""*""+"]/}"
local -r exif_album_clean="${exif_album//["/"" ""-""?""'"":""\"""_""*""+"]/}"
if [[ "${album_clean}" != "${exif_album_clean}" ]]; then
zik_warn "album mismatch for file \"${file_path}\""
echo " Album from file: \"${album}\""
echo " Album from tags: \"${exif_album}\""
fi

# Check title
local -r track_name_clean="${track_name//["/"" ""-""?""'"":""\"""_""*""+"]/}"
local -r exif_title_clean="${exif_title//["/"" ""-""?""'"":""\"""_""*""+"]/}"
if [[ "${track_name_clean}" != "${exif_title_clean}" ]]; then
zik_warn "track title mismatch for file \"${file_path}\""
echo " Title from file: \"${track_name}\""
echo " Title from tags: \"${exif_title}\""
fi

# Check genre
if [[ "${exif_genre}" != "-" ]]; then
zik_warn "genre is set to \"${exif_genre}\" for file \"${file_path}\""
fi

# Check picture file name
if [[ "${exif_picture_description}" != "${COVER_PICTURE_FILE}" ]]; then
zik_warn "bad embedded picture name for file \"${file_path}\""
fi

# Check picture type
if [[ "${exif_picture_type}" != "Front Cover" ]]; then
zik_warn "embedded picture is not front cover for file \"${file_path}\""
fi

# Specific file type checks
case "${exif_file_type}" in
FLAC)
# Check album artist
if [[ "${exif_album_artist}" != "${exif_artist}" ]]; then
zik_warn "artist and album artist are different for file \"${file_path}\""
fi
# Check track number
if [[ "${track_num}" != "${exif_track_number}" ]]; then
zik_warn "bad track number for file \"${file_path}\""
fi
# Check track total
if [[ "${track_total}" != "${exif_track_total}" ]]; then
zik_warn "bad track total for file \"${file_path}\""
fi
# Check for no description
if [[ "${exif_description}" != "-" ]]; then
zik_warn "description is set for file \"${file_path}\""
fi
# Check for no disc number
if [[ "${exif_disc_number}" != "-" ]]; then
zik_warn "disc number is set for file \"${file_path}\""
fi
;;
MP3)
# Check band name
if [[ "${exif_band}" != "${exif_artist}" ]]; then
zik_warn "artist and band are different for file \"${file_path}\""
fi
# Check track number
local -r mp3_track_number="$(echo "${exif_track}" | cut -d '/' -f 1)"
if [[ "${track_num}" != "${mp3_track_number}" ]]; then
zik_warn "bad track number for file \"${file_path}\""
fi
# Check track total
local -r mp3_track_total="$(echo "${exif_track}" | cut -d '/' -f 2)"
if [[ "${track_total}" != "${mp3_track_total}" ]]; then
zik_warn "bad track total for file \"${file_path}\""
fi
# Check for no comment
if [[ "${exif_comment}" != "-" ]]; then
zik_warn "comment is set for file \"${file_path}\""
fi
# Check for no part of set
if [[ "${exif_part_of_set}" != "-" ]]; then
zik_warn "partOfSet is set for file \"${file_path}\""
fi
;;
OGG)
# Check album artist
if [[ "${exif_album_artist}" != "${exif_artist}" ]]; then
zik_warn "artist and album artist are different for file \"${file_path}\""
fi
# Check track number
if [[ "${track_num}" != "${exif_track_number}" ]]; then
zik_warn "bad track number for file \"${file_path}\""
fi
# Check track total
if [[ "${track_total}" != "${exif_track_total}" ]]; then
zik_warn "bad track total for file \"${file_path}\""
fi
# Check for no description
if [[ "${exif_description}" != "-" ]]; then
zik_warn "description is set for file \"${file_path}\""
fi
# Check for no disc number
if [[ "${exif_disc_number}" != "-" ]]; then
zik_warn "disc number is set for file \"${file_path}\""
fi
;;
*)
zik_error "unmanaged file \"${file_path}\""
;;
esac
}

error() {
echo "${0}: ${*}" >&2
}

log() {
echo "${@}"
}

on_exit() {
# Remove temporary file
rm -f "${EXIFTOOL_ERROR_FILE}"

echo -n "Elapsed time: "
if [[ "${SECONDS}" -lt 60 ]]; then
echo "${SECONDS}s"
elif [[ "${SECONDS}" -lt 3600 ]]; then
local -r MINUTES=$(( SECONDS / 60 ))
echo "${MINUTES}m$(( SECONDS % 60 ))s"
else
local -r HOURS=$(( SECONDS / 3600 ))
local -r MINUTES=$(( SECONDS / 60 - HOURS * 60 ))
echo "${HOURS}h${MINUTES}m$(( SECONDS % 60 ))s"
fi
}

usage() {
echo "Usage: ${0} [OPTIONS] PATH"
echo "Validate hierarchy and tags of the music files included in PATH."
echo ""
echo "OPTIONS:"
echo " -c COVER set cover picture file name"
echo " -s SIZE set expected cover picture size"
echo " -v show more informations"
}

zik_error() {
printf "\\033[0;31m[CRIT]\\033[0m "
log "${@}"
}

zik_info() {
if [[ "${VERBOSE}" -ne 0 ]]; then
printf "\\033[0;32m[INFO]\\033[0m "
log "${@}"
fi
}

zik_warn() {
printf "\\033[0;33m[WARN]\\033[0m "
log "${@}"
}

main() {
# Temporary file to store exiftool error messages
readonly EXIFTOOL_ERROR_FILE=$(mktemp)

# Default settings
local COVER_PICTURE_FILE="folder.jpg"
local COVER_SIZE="500x500"
local VERBOSE=0

# Process command line options
while getopts ":c:s:v" flag; do
case "${flag}" in
c)
COVER_PICTURE_FILE="${OPTARG}"
;;
s)
COVER_SIZE="${OPTARG}"
;;
v)
VERBOSE=1
;;
\?)
error "unknown option -${OPTARG}"
usage
exit 1
;;
esac
done
shift "$((OPTIND-1))"

# Freeze settings
readonly COVER_PICTURE_FILE
readonly COVER_SIZE
readonly VERBOSE

# Gather path to browse
if [[ "${#}" -lt 1 ]]; then
usage
exit 1
fi
local -r PATH_TO_ZIK="${1}"

if [[ ! -d "${PATH_TO_ZIK}" ]]; then
error "${PATH_TO_ZIK} is not a valid directory"
exit 1
fi

echo "--- Zik Validator ---"
echo "Exiftool version: $(exiftool -ver)"

for item in "${PATH_TO_ZIK}"/*; do
# Remove unnecessary slashes
item="$(readlink -m "${item}")"

if [[ -d "${item}" ]]; then
check_artist "${item}"
else
zik_warn "\"${item}\" is not a directory," \
"root path should contain only directories"
fi
done
}

trap on_exit EXIT
SECONDS=0
main "$@"

Loading…
Cancel
Save