Quelques outils utiles pour gérer ma bibliothèque musicale
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

424 lines
12 KiB

  1. #!/bin/bash
  2. set -e
  3. set -u
  4. set -o pipefail
  5. # Include files whose name begin with a dot
  6. shopt -s dotglob
  7. # ${1} path for artist to check
  8. check_artist() {
  9. local -r artist_path="${1}"
  10. local -r artist="$(basename -- "${artist_path}")"
  11. zik_info "artist: ${artist}"
  12. # Check directory permissions
  13. if [[ "$(stat -L -c "%a" "${artist_path}")" != "755" ]]; then
  14. zik_warn "uncommon permissions for directory \"${artist_path}\""
  15. fi
  16. if [[ "${artist:0:1}" == "." ]]; then
  17. zik_error "artist name should not start with a dot (${artist})"
  18. fi
  19. if [[ -z "$(ls -A "${artist_path}")" ]]; then
  20. zik_error "no album found for artist \"${artist}\""
  21. else
  22. for item in "${artist_path}"/*; do
  23. if [[ -d "${item}" ]]; then
  24. check_album "${artist}" "${item}"
  25. else
  26. zik_warn "\"${item}\" is not a directory," \
  27. "artist path should contain only directories"
  28. fi
  29. done
  30. fi
  31. }
  32. # ${1} artist name
  33. # ${2} path for album to check
  34. check_album() {
  35. local -r artist="${1}"
  36. local -r album_path="${2}"
  37. local -r album="$(basename -- "${album_path}")"
  38. zik_info "album: ${album}"
  39. # Check directory permissions
  40. if [[ "$(stat -L -c "%a" "${album_path}")" != "755" ]]; then
  41. zik_warn "uncommon permissions for directory \"${album_path}\""
  42. fi
  43. if [[ "${album:0:1}" == "." ]]; then
  44. zik_error "album name should not start with a dot (${artist} / ${album})"
  45. fi
  46. # Check for a cover picture
  47. local -r cover_file="${album_path}/${COVER_PICTURE_FILE}"
  48. if [[ -f "${cover_file}" ]]; then
  49. zik_info "found a cover picture for ${artist} / ${album}"
  50. # Check file permissions
  51. if [[ "$(stat -L -c "%a" "${cover_file}")" != "644" ]]; then
  52. zik_warn "uncommon permissions for file \"${cover_file}\""
  53. fi
  54. # Check cover picture size
  55. local -r cover_size="$(exiftool -s3 -ImageSize "${cover_file}")"
  56. if [[ "${cover_size}" != "${COVER_SIZE}" ]]; then
  57. zik_warn "cover picture size is ${cover_size} for ${artist} / ${album}"
  58. fi
  59. else
  60. zik_warn "no cover picture \"${COVER_PICTURE_FILE}\" found" \
  61. "for ${artist} / ${album}"
  62. fi
  63. local file_name
  64. if [[ -z "$(ls -A "${album_path}")" ]]; then
  65. zik_error "no files found for ${artist} / ${album}"
  66. else
  67. # Total number of files (no cover)
  68. # shellcheck disable=SC2012
  69. local -r num="$(ls -1q -I "${COVER_PICTURE_FILE}" "${album_path}" | wc -l)"
  70. for item in "${album_path}"/*; do
  71. if [[ -f "${item}" ]]; then
  72. file_name="$(basename -- "${item}")"
  73. # Skip cover picture file
  74. if [[ "${file_name}" = "${COVER_PICTURE_FILE}" ]]; then
  75. continue
  76. fi
  77. # Check file type and name (must start with 3 digits and a dash)
  78. if [[ "${file_name##*.}" =~ ^(flac|mp3|ogg)$ ]]; then
  79. if [[ "${file_name%.*}" =~ ^[0-9]{3}\ -\ .*$ ]]; then
  80. check_file "${artist}" "${album}" "${num}" "${item}"
  81. else
  82. zik_error "badly formatted file name \"${item}\""
  83. fi
  84. else
  85. zik_error "unmanaged file \"${item}\""
  86. fi
  87. else
  88. zik_warn "\"${item}\" is not a file," \
  89. "album path should contain only files"
  90. fi
  91. done
  92. fi
  93. }
  94. # ${1} artist name
  95. # ${2} album name
  96. # ${3} number of files in album path (no cover)
  97. # ${4} path for file to check
  98. check_file() {
  99. local -r artist="${1}"
  100. local -r album="${2}"
  101. local -r track_total="$(printf "%03d" "${3}")"
  102. local -r file_path="${4}"
  103. local -r file_name="$(basename -- "${file_path}")"
  104. # Check file permissions
  105. if [[ "$(stat -L -c "%a" "${file_path}")" != "644" ]]; then
  106. zik_warn "uncommon permissions for file \"${file_path}\""
  107. fi
  108. # Extract track info from file name
  109. local -r track_num="$(echo "${file_name%.*}" | cut -d " " -f 1)"
  110. local -r track_name="$(echo "${file_name%.*}" | cut -d " " -f 3-)"
  111. local -r track_ext="${file_name##*.}"
  112. # Reset exiftool error file size
  113. truncate -s 0 "${EXIFTOOL_ERROR_FILE}"
  114. # Gather audio file tags
  115. local -a tags
  116. # shellcheck disable=SC2016
  117. readarray -t tags <<< "$(exiftool -f -p '$FileType' \
  118. -p '$FileTypeExtension' \
  119. -p '$Title' \
  120. -p '$Artist' \
  121. -p '$Band' \
  122. -p '$Album' \
  123. -p '$AlbumArtist' \
  124. -p '$Comment' \
  125. -p '$Description' \
  126. -p '$Genre' \
  127. -p '$DiscNumber' \
  128. -p '$PartOfSet' \
  129. -p '$Track' \
  130. -p '$TrackNumber' \
  131. -p '$TrackTotal' \
  132. -p '$PictureDescription' \
  133. -p '$PictureType' \
  134. "${file_path}" \
  135. 2> "${EXIFTOOL_ERROR_FILE}")"
  136. # Manage gathered tags
  137. local -r exif_file_type="${tags[0]}"
  138. local -r exif_file_type_extension="${tags[1]}"
  139. local -r exif_title="${tags[2]}"
  140. local -r exif_artist="${tags[3]}"
  141. local -r exif_band="${tags[4]}"
  142. local -r exif_album="${tags[5]}"
  143. local -r exif_album_artist="${tags[6]}"
  144. local -r exif_comment="${tags[7]}"
  145. local -r exif_description="${tags[8]}"
  146. local -r exif_genre="${tags[9]}"
  147. local -r exif_disc_number="${tags[10]}"
  148. local -r exif_part_of_set="${tags[11]}"
  149. local -r exif_track="${tags[12]}"
  150. local -r exif_track_number="${tags[13]}"
  151. local -r exif_track_total="${tags[14]}"
  152. local -r exif_picture_description="${tags[15]}"
  153. local -r exif_picture_type="${tags[16]}"
  154. # Manage exiftool error messages
  155. if [[ -s "${EXIFTOOL_ERROR_FILE}" ]]; then
  156. zik_warn "exiftool output error for file \"${file_path}\""
  157. while read -r EXIFTOOL_MSG; do
  158. echo " ${EXIFTOOL_MSG}"
  159. done < "${EXIFTOOL_ERROR_FILE}"
  160. fi
  161. # Check type extension
  162. if [[ "${track_ext}" != "${exif_file_type_extension}" ]]; then
  163. zik_error "type extension mismatch for file \"${file_path}\""
  164. fi
  165. # Check artist
  166. if [[ "${artist}" != "${exif_artist}" ]]; then
  167. zik_warn "artist mismatch for file \"${file_path}\""
  168. fi
  169. # Check album
  170. local -r album_clean="${album//["/"" ""-""?""'"":""\"""_""*""+"]/}"
  171. local -r exif_album_clean="${exif_album//["/"" ""-""?""'"":""\"""_""*""+"]/}"
  172. if [[ "${album_clean}" != "${exif_album_clean}" ]]; then
  173. zik_warn "album mismatch for file \"${file_path}\""
  174. echo " Album from file: \"${album}\""
  175. echo " Album from tags: \"${exif_album}\""
  176. fi
  177. # Check title
  178. local -r track_name_clean="${track_name//["/"" ""-""?""'"":""\"""_""*""+"]/}"
  179. local -r exif_title_clean="${exif_title//["/"" ""-""?""'"":""\"""_""*""+"]/}"
  180. if [[ "${track_name_clean}" != "${exif_title_clean}" ]]; then
  181. zik_warn "track title mismatch for file \"${file_path}\""
  182. echo " Title from file: \"${track_name}\""
  183. echo " Title from tags: \"${exif_title}\""
  184. fi
  185. # Check genre
  186. if [[ "${exif_genre}" != "-" ]]; then
  187. zik_warn "genre is set to \"${exif_genre}\" for file \"${file_path}\""
  188. fi
  189. # Check picture file name
  190. if [[ "${exif_picture_description}" != "${COVER_PICTURE_FILE}" ]]; then
  191. zik_warn "bad embedded picture name for file \"${file_path}\""
  192. fi
  193. # Check picture type
  194. if [[ "${exif_picture_type}" != "Front Cover" ]]; then
  195. zik_warn "embedded picture is not front cover for file \"${file_path}\""
  196. fi
  197. # Specific file type checks
  198. case "${exif_file_type}" in
  199. FLAC)
  200. # Check album artist
  201. if [[ "${exif_album_artist}" != "${exif_artist}" ]]; then
  202. zik_warn "artist and album artist are different for file \"${file_path}\""
  203. fi
  204. # Check track number
  205. if [[ "${track_num}" != "${exif_track_number}" ]]; then
  206. zik_warn "bad track number for file \"${file_path}\""
  207. fi
  208. # Check track total
  209. if [[ "${track_total}" != "${exif_track_total}" ]]; then
  210. zik_warn "bad track total for file \"${file_path}\""
  211. fi
  212. # Check for no description
  213. if [[ "${exif_description}" != "-" ]]; then
  214. zik_warn "description is set for file \"${file_path}\""
  215. fi
  216. # Check for no disc number
  217. if [[ "${exif_disc_number}" != "-" ]]; then
  218. zik_warn "disc number is set for file \"${file_path}\""
  219. fi
  220. ;;
  221. MP3)
  222. # Check band name
  223. if [[ "${exif_band}" != "${exif_artist}" ]]; then
  224. zik_warn "artist and band are different for file \"${file_path}\""
  225. fi
  226. # Check track number
  227. local -r mp3_track_number="$(echo "${exif_track}" | cut -d '/' -f 1)"
  228. if [[ "${track_num}" != "${mp3_track_number}" ]]; then
  229. zik_warn "bad track number for file \"${file_path}\""
  230. fi
  231. # Check track total
  232. local -r mp3_track_total="$(echo "${exif_track}" | cut -d '/' -f 2)"
  233. if [[ "${track_total}" != "${mp3_track_total}" ]]; then
  234. zik_warn "bad track total for file \"${file_path}\""
  235. fi
  236. # Check for no comment
  237. if [[ "${exif_comment}" != "-" ]]; then
  238. zik_warn "comment is set for file \"${file_path}\""
  239. fi
  240. # Check for no part of set
  241. if [[ "${exif_part_of_set}" != "-" ]]; then
  242. zik_warn "partOfSet is set for file \"${file_path}\""
  243. fi
  244. ;;
  245. OGG)
  246. # Check album artist
  247. if [[ "${exif_album_artist}" != "${exif_artist}" ]]; then
  248. zik_warn "artist and album artist are different for file \"${file_path}\""
  249. fi
  250. # Check track number
  251. if [[ "${track_num}" != "${exif_track_number}" ]]; then
  252. zik_warn "bad track number for file \"${file_path}\""
  253. fi
  254. # Check track total
  255. if [[ "${track_total}" != "${exif_track_total}" ]]; then
  256. zik_warn "bad track total for file \"${file_path}\""
  257. fi
  258. # Check for no description
  259. if [[ "${exif_description}" != "-" ]]; then
  260. zik_warn "description is set for file \"${file_path}\""
  261. fi
  262. # Check for no disc number
  263. if [[ "${exif_disc_number}" != "-" ]]; then
  264. zik_warn "disc number is set for file \"${file_path}\""
  265. fi
  266. ;;
  267. *)
  268. zik_error "unmanaged file \"${file_path}\""
  269. ;;
  270. esac
  271. }
  272. error() {
  273. echo "${0}: ${*}" >&2
  274. }
  275. log() {
  276. echo "${@}"
  277. }
  278. on_exit() {
  279. # Remove temporary file
  280. rm -f "${EXIFTOOL_ERROR_FILE}"
  281. echo -n "Elapsed time: "
  282. if [[ "${SECONDS}" -lt 60 ]]; then
  283. echo "${SECONDS}s"
  284. elif [[ "${SECONDS}" -lt 3600 ]]; then
  285. local -r MINUTES=$(( SECONDS / 60 ))
  286. echo "${MINUTES}m$(( SECONDS % 60 ))s"
  287. else
  288. local -r HOURS=$(( SECONDS / 3600 ))
  289. local -r MINUTES=$(( SECONDS / 60 - HOURS * 60 ))
  290. echo "${HOURS}h${MINUTES}m$(( SECONDS % 60 ))s"
  291. fi
  292. }
  293. usage() {
  294. echo "Usage: ${0} [OPTIONS] PATH"
  295. echo "Validate hierarchy and tags of the music files included in PATH."
  296. echo ""
  297. echo "OPTIONS:"
  298. echo " -c COVER set cover picture file name"
  299. echo " -s SIZE set expected cover picture size"
  300. echo " -v show more informations"
  301. }
  302. zik_error() {
  303. printf "\\033[0;31m[CRIT]\\033[0m "
  304. log "${@}"
  305. }
  306. zik_info() {
  307. if [[ "${VERBOSE}" -ne 0 ]]; then
  308. printf "\\033[0;32m[INFO]\\033[0m "
  309. log "${@}"
  310. fi
  311. }
  312. zik_warn() {
  313. printf "\\033[0;33m[WARN]\\033[0m "
  314. log "${@}"
  315. }
  316. main() {
  317. # Temporary file to store exiftool error messages
  318. readonly EXIFTOOL_ERROR_FILE=$(mktemp)
  319. # Default settings
  320. local COVER_PICTURE_FILE="folder.jpg"
  321. local COVER_SIZE="500x500"
  322. local VERBOSE=0
  323. # Process command line options
  324. while getopts ":c:s:v" flag; do
  325. case "${flag}" in
  326. c)
  327. COVER_PICTURE_FILE="${OPTARG}"
  328. ;;
  329. s)
  330. COVER_SIZE="${OPTARG}"
  331. ;;
  332. v)
  333. VERBOSE=1
  334. ;;
  335. \?)
  336. error "unknown option -${OPTARG}"
  337. usage
  338. exit 1
  339. ;;
  340. esac
  341. done
  342. shift "$((OPTIND-1))"
  343. # Freeze settings
  344. readonly COVER_PICTURE_FILE
  345. readonly COVER_SIZE
  346. readonly VERBOSE
  347. # Gather path to browse
  348. if [[ "${#}" -lt 1 ]]; then
  349. usage
  350. exit 1
  351. fi
  352. local -r PATH_TO_ZIK="${1}"
  353. if [[ ! -d "${PATH_TO_ZIK}" ]]; then
  354. error "${PATH_TO_ZIK} is not a valid directory"
  355. exit 1
  356. fi
  357. echo "--- Zik Validator ---"
  358. echo "Exiftool version: $(exiftool -ver)"
  359. for item in "${PATH_TO_ZIK}"/*; do
  360. # Remove unnecessary slashes
  361. item="$(readlink -m "${item}")"
  362. if [[ -d "${item}" ]]; then
  363. check_artist "${item}"
  364. else
  365. zik_warn "\"${item}\" is not a directory," \
  366. "root path should contain only directories"
  367. fi
  368. done
  369. }
  370. trap on_exit EXIT
  371. SECONDS=0
  372. main "$@"