Annotation of plm/pl, revision 1.1
1.1 ! snw 1: #!/usr/bin/env bash
! 2:
! 3: # SPDX-FileCopyrightText: (C) 2024 Coherent Logic Development LLC
! 4: # SPDX-FileCopyrightText: (C) 2024 Serena Willis
! 5: #
! 6: # SPDX-License-Identifier: AGPL-3.0
! 7:
! 8: BLACK=$(tput setaf 0)
! 9: RED=$(tput setaf 1)
! 10: GREEN=$(tput setaf 2)
! 11: YELLOW=$(tput setaf 3)
! 12: LIME_YELLOW=$(tput setaf 190)
! 13: POWDER_BLUE=$(tput setaf 153)
! 14: BLUE=$(tput setaf 4)
! 15: MAGENTA=$(tput setaf 5)
! 16: CYAN=$(tput setaf 6)
! 17: WHITE=$(tput setaf 7)
! 18: BRIGHT=$(tput bold)
! 19: NORMAL=$(tput sgr0)
! 20: BLINK=$(tput blink)
! 21: REVERSE=$(tput smso)
! 22: UNDERLINE=$(tput smul)
! 23:
! 24: BASEDIR="/var/nas/media/music"
! 25: CHOSEN=""
! 26: REPL=""
! 27: SHUFFLE=0
! 28: REPEAT=0
! 29:
! 30: function usage {
! 31: echo "usage:"
! 32: echo " pl [[-c|--check]|[-m|--merge]] -i|--input=<playlist1,playlist2,...,playlistN> [[-o|--output]=<output-playlist>]"
! 33: exit 1
! 34: }
! 35:
! 36: function playsong {
! 37: local SONG="$1"
! 38:
! 39: echo "Playing ${BRIGHT}${SONG}${NORMAL}..."
! 40: ffplay -loglevel fatal -nodisp "${SONG}"
! 41: }
! 42:
! 43: function rdl {
! 44: local FILE=$1
! 45: local CHOICE=""
! 46:
! 47: echo "Would you like to:"
! 48: echo
! 49: echo " ${BRIGHT}r${NORMAL})eplace the missing file in the playlist"
! 50: echo " ${BRIGHT}d${NORMAL})elete the missing file from the playlist"
! 51: echo " ${BRIGHT}l${NORMAL})eave the missing file in the playlist"
! 52: echo
! 53: echo -n '> '
! 54: read -n1 CHOICE < /dev/tty
! 55:
! 56: if [[ ${CHOICE} != "r" ]] && [[ ${CHOICE} != "d" ]] && [[ ${CHOICE} != "l" ]]
! 57: then
! 58: echo "pl: ${RED}must choose r, d, or l"
! 59: CHOSEN=""
! 60: else
! 61: CHOSEN=$CHOICE
! 62: fi
! 63: }
! 64:
! 65: function replprompt {
! 66: local FILE=$1
! 67:
! 68: CHOSEN=""
! 69: REPL=""
! 70:
! 71: while [[ ${CHOSEN} == "" ]]
! 72: do
! 73: rdl "${FILE}"
! 74: done
! 75:
! 76: case "${CHOSEN}" in
! 77: r)
! 78: NEWFILE=$(zenity --file-selection --title="Replace ${FILE}" --filename="${BASEDIR}/" 2>/dev/null)
! 79: REPL="${NEWFILE:$BASEDIRLEN}"
! 80: ;;
! 81: d)
! 82: REPL=""
! 83: ;;
! 84: l)
! 85: REPL=$FILE
! 86: ;;
! 87: esac
! 88:
! 89: }
! 90:
! 91: VERBOSE=0
! 92: ACTION=""
! 93: INPUT=""
! 94: OUTPUT=""
! 95: OVERWRITE=0
! 96: COPY=0
! 97: SHUFFLE=0
! 98: REPEAT=0
! 99: SYNC=""
! 100: DELETE=""
! 101:
! 102: TEMP=$(getopt -o hvci:o:mb:rVpsRS:d: --long help,verbose,check,input:,output:,merge,basedir:,repair,overwrite,play,shuffle,repeat,sync:,delete: -n 'pl' -- "$@")
! 103: eval set -- "$TEMP"
! 104:
! 105: while true
! 106: do
! 107: case "$1" in
! 108: -h|--help)
! 109: usage
! 110: ;;
! 111: -V|--overwrite)
! 112: OVERWRITE=1
! 113: shift
! 114: ;;
! 115: -v|--verbose)
! 116: VERBOSE=1
! 117: shift
! 118: ;;
! 119: -c|--check)
! 120: ACTION="check"
! 121: COPY=0
! 122: shift
! 123: ;;
! 124: -r|--repair)
! 125: ACTION="repair"
! 126: COPY=1
! 127: shift
! 128: ;;
! 129: -b|--basedir)
! 130: BASEDIR=$2
! 131: shift 2
! 132: ;;
! 133: -i|--input)
! 134: INPUT="${INPUT} $(echo $2 | tr ',' ' ')"
! 135: shift 2
! 136: ;;
! 137: -o|--output)
! 138: OUTPUT="$2"
! 139: shift 2
! 140: ;;
! 141: -m|--merge)
! 142: ACTION="merge"
! 143: COPY=1
! 144: shift
! 145: ;;
! 146: -p|--play)
! 147: ACTION="play"
! 148: COPY=0
! 149: shift
! 150: ;;
! 151: -s|--shuffle)
! 152: SHUFFLE=1
! 153: shift
! 154: ;;
! 155: -R|--repeat)
! 156: REPEAT=1
! 157: shift
! 158: ;;
! 159: -S|--sync)
! 160: ACTION="sync"
! 161: SYNC=$2
! 162: shift 2
! 163: ;;
! 164: -d|--delete)
! 165: DELETE=$2
! 166: shift 2
! 167:
! 168: if [[ "${DELETE}" != "all" ]] && [[ "${DELETE}" != "update" ]]
! 169: then
! 170: echo "pl: must specify --delete=all or --delete=update"
! 171: exit 13
! 172: fi
! 173:
! 174: ;;
! 175: --)
! 176: shift
! 177: break
! 178: ;;
! 179: esac
! 180: done
! 181:
! 182: BASEDIRLEN=$(echo ${BASEDIR} | wc -c)
! 183:
! 184: if [[ ${ACTION} == "" ]]
! 185: then
! 186: usage
! 187: fi
! 188:
! 189: if [[ ${ACTION} != "check" ]] && [[ ${ACTION} != "merge" ]] && [[ ${ACTION} != "repair" ]] && [[ ${ACTION} != "play" ]] && [[ ${ACTION} != "sync" ]]
! 190: then
! 191: echo "pl: must supply --check, --repair, --play, --sync, or --merge"
! 192: usage
! 193: fi
! 194:
! 195: if [[ ${ACTION} != "sync" ]] && [[ ${DELETE} != "" ]]
! 196: then
! 197: echo "pl: --delete only allowed with --sync"
! 198: exit 14
! 199: fi
! 200:
! 201: if [[ "${INPUT}" == "" ]]
! 202: then
! 203: echo "pl: must supply --input"
! 204: exit 3
! 205: fi
! 206:
! 207: if [[ ${ACTION} == "merge" ]] && [[ "${OUTPUT}" == "" ]]
! 208: then
! 209: echo "pl: must supply --output with --merge"
! 210: exit 4
! 211: fi
! 212:
! 213: if [[ ${ACTION} == "repair" ]] && [[ "${OUTPUT}" == "" ]]
! 214: then
! 215: echo "pl: must supply --output with --repair"
! 216: exit 10
! 217: fi
! 218:
! 219: if [[ ${ACTION} == "check" ]] && [[ "${OUTPUT}" != "" ]]
! 220: then
! 221: echo "pl: --output not supported with --check"
! 222: exit 5
! 223: fi
! 224:
! 225: if [[ -f "${OUTPUT}" ]] && [[ ${OVERWRITE} != 1 ]]
! 226: then
! 227: echo "pl: output file exists; pass --overwrite to overwrite"
! 228: exit 11
! 229: fi
! 230:
! 231: if [[ ${ACTION} == "sync" ]] && [[ ! -d "${SYNC}" ]]
! 232: then
! 233: echo "pl: sync destination ${SYNC} does not exist or is not a directory"
! 234: exit 12
! 235: fi
! 236:
! 237: for FILE in ${INPUT}
! 238: do
! 239: if [[ ! -f "${FILE}" ]]
! 240: then
! 241: echo "pl: input file ${FILE} does not exist"
! 242: exit 6
! 243: fi
! 244:
! 245: FIRSTLINE=$(cat "${FILE}" | head -1)
! 246: if [[ "${FIRSTLINE}" != "#EXTM3U" ]]
! 247: then
! 248: echo "pl: input file ${FILE} is not a valid m3u playlist"
! 249: exit 7
! 250: fi
! 251: done
! 252:
! 253: TOTOK=0
! 254: TOTMISSING=0
! 255: TOTFILES=0
! 256: TOTLISTS=0
! 257:
! 258: [[ $COPY == 1 ]] && echo "#EXTM3U" > "${OUTPUT}"
! 259:
! 260: if [[ ${ACTION} == "play" ]]
! 261: then
! 262: PLAYFILE=$(mktemp)
! 263: fi
! 264:
! 265: if [[ ${ACTION} == "sync" ]]
! 266: then
! 267: echo "pl: initializing sync operation to ${SYNC}"
! 268:
! 269: # $SYNC == sync destination
! 270: # $DELETE == all means to delete everything in that location
! 271: # $DELETE == update means to only delete what was removed from the playlists
! 272:
! 273: SYNCFILE="${SYNC}/pl.sync"
! 274:
! 275: if [[ ${DELETE} == "update" ]]
! 276: then
! 277:
! 278: if [[ -f "${SYNCFILE}" ]]
! 279: then
! 280: echo "pl: sync file found at ${SYNCFILE}"
! 281: else
! 282: echo "pl: no sync file found at ${SYNCFILE} - cannot --delete=update"
! 283: exit 20
! 284: fi
! 285:
! 286: fi
! 287:
! 288: if [[ ${DELETE} == "all" ]]
! 289: then
! 290: echo "${RED}${BRIGHT}CAUTION: THIS WILL PERMANENTLY DELETE EVERYTHING IN ${SYNC}!!!!${NORMAL}"
! 291: read -p "Type CONFIRM to confirm: " CONFIRM
! 292:
! 293: if [[ ${CONFIRM} != "CONFIRM" ]]
! 294: then
! 295: echo "pl: sync cancelled on user request"
! 296: exit 21
! 297: fi
! 298:
! 299: read -n1 -p "Are you sure? (y/n) " CONFIRM
! 300: echo
! 301:
! 302: if [[ $CONFIRM != "y" ]] && [[ $CONFIRM != "Y" ]]
! 303: then
! 304: echo "pl: sync cancelled on user request"
! 305: exit 21
! 306: fi
! 307:
! 308: rm -rfv "${SYNC}/*"
! 309: echo "DATE: $(date +%Y%m%d%H%M%S)" > "${SYNCFILE}"
! 310: echo "PLAYLISTS: ${INPUT}" >> "${SYNCFILE}"
! 311: echo "DELETE: ${DELETE}" >> "${SYNCFILE}"
! 312: echo "SYNC: ${SYNC}" >> "${SYNCFILE}"
! 313: fi
! 314:
! 315:
! 316: fi
! 317:
! 318: for FILE in ${INPUT}
! 319: do
! 320: if [[ ${ACTION} == "sync" ]]
! 321: then
! 322: echo "pl: copying ${FILE} to ${SYNC}"
! 323: cp "${FILE}" "${SYNC}/"
! 324: fi
! 325:
! 326: if [[ ${VERBOSE} == 1 ]]
! 327: then
! 328: echo "${BRIGHT}Playlist:${NORMAL}${MAGENTA} ${FILE}${NORMAL}"
! 329: echo
! 330: printf "%-8s %-50s %-80s %-10s\n" "LENGTH" "TITLE" "PATH" "STATUS"
! 331: printf "%-8s %-50s %-80s %-10s\n" "======" "=====" "====" "======"
! 332: fi
! 333:
! 334: TOTLISTS=$(($TOTLISTS+1))
! 335: FILOK=0
! 336: FILMISSING=0
! 337: FILFILES=0
! 338: while read LINE
! 339: do
! 340: if [[ ${LINE:0:1} == "#" ]]
! 341: then
! 342:
! 343: if [[ ${LINE:0:7} == "#EXTINF" ]]
! 344: then
! 345: INF=${LINE}
! 346: TAGPART=$(echo ${LINE} | cut -d: -f2)
! 347: SECS=$(echo ${TAGPART} | cut -d, -f1)
! 348: NAME=$(echo ${TAGPART} | cut -d, -f2)
! 349: fi
! 350:
! 351: else
! 352:
! 353: FILFILES=$(($FILFILES+1))
! 354: TOTFILES=$(($TOTFILES+1))
! 355: AUPATH=$LINE
! 356:
! 357: if [[ ! -f "${AUPATH}" ]]
! 358: then
! 359:
! 360: LSTAT="[${BRIGHT}${RED}MISSING${NORMAL}]"
! 361: FILMISSING=$(($FILMISSING+1))
! 362: TOTMISSING=$(($TOTMISSING+1))
! 363: MISSING=1
! 364:
! 365: if [[ ${COPY} == 1 ]] && [[ ${ACTION} == "repair" ]]
! 366: then
! 367: replprompt "${AUPATH}"
! 368:
! 369: if [[ "${REPL}" != "" ]]
! 370: then
! 371: echo "${INF}" >> "${OUTPUT}"
! 372: echo "${REPL}" >> "${OUTPUT}"
! 373: fi
! 374: fi
! 375:
! 376: else
! 377:
! 378: if [[ ${COPY} == 1 ]]
! 379: then
! 380: echo "${INF}" >> "${OUTPUT}"
! 381: echo "${AUPATH}" >> "${OUTPUT}"
! 382: fi
! 383:
! 384: if [[ ${ACTION} == "play" ]]
! 385: then
! 386: echo "${BASEDIR}/${AUPATH}" >> "${PLAYFILE}"
! 387: fi
! 388:
! 389: if [[ ${ACTION} == "sync" ]]
! 390: then
! 391: CPSRC="${BASEDIR}/${AUPATH}"
! 392: CPDST="${SYNC}/${AUPATH}"
! 393: DSTDIR="${CPDST%/*}"
! 394: echo "${AUPATH}" >> "${SYNCFILE}"
! 395: echo "pl: creating ${DSTDIR}"
! 396: mkdir -p "${DSTDIR}"
! 397: echo "pl: copying ${CPSRC} to ${CPDST}"
! 398: cp -r "${CPSRC}" "${CPDST}"
! 399: fi
! 400:
! 401: FILOK=$(($FILOK+1))
! 402: TOTOK=$(($TOTOK+1))
! 403: LSTAT="[${BRIGHT}${GREEN}OK${NORMAL}]"
! 404: MISSING=0
! 405:
! 406: fi
! 407:
! 408: if [[ $VERBOSE == 1 ]]
! 409: then
! 410: printf "%-8s %-50s %-80s %-10s\n" "${SECS}" "${NAME:0:48}" "${AUPATH:0:78}" "${LSTAT}"
! 411: else
! 412: if [[ ${MISSING} == 1 ]]
! 413: then
! 414: [[ ${REPAIR} == 1 ]] && echo
! 415: echo "${GREEN}${NAME}${NORMAL} is ${RED}missing${NORMAL}"
! 416: fi
! 417: fi
! 418:
! 419: fi
! 420: done < "${FILE}"
! 421: echo "Missing ${RED}${FILMISSING}/${FILFILES}${NORMAL} files in ${BRIGHT}${FILE}${NORMAL}"
! 422: done
! 423:
! 424: case $ACTION in
! 425: check|repair)
! 426: echo "Processed ${GREEN}${TOTLISTS} playlists${NORMAL}, and found ${RED}${TOTMISSING} files missing${NORMAL} out of ${GREEN}${TOTFILES} referenced files${NORMAL}."
! 427: ;;
! 428: merge)
! 429: echo "Merged ${GREEN}${TOTLISTS} playlists (${INPUT:1})${NORMAL} into playlist ${GREEN}${OUTPUT}${NORMAL}."
! 430: ;;
! 431: play)
! 432: echo "Playing $(cat ${PLAYFILE} | wc -l) files in ${PLAYFILE}"
! 433: ;;
! 434: esac
! 435:
! 436: if [[ ${ACTION} == "play" ]]
! 437: then
! 438: declare -a PLAYLIST
! 439:
! 440: while read F
! 441: do
! 442: PLAYLIST+=("${F}")
! 443: done < "${PLAYFILE}"
! 444:
! 445: rm -f "${PLAYFILE}"
! 446:
! 447: if [[ ${SHUFFLE} == 0 ]]
! 448: then
! 449:
! 450: if [[ ${REPEAT} == 0 ]]
! 451: then
! 452: for SONG in "${PLAYLIST[@]}"
! 453: do
! 454: playsong "${SONG}"
! 455: done
! 456: else
! 457: while true
! 458: do
! 459: for SONG in "${PLAYLIST[@]}"
! 460: do
! 461: playsong "${SONG}"
! 462: done
! 463: done
! 464: fi
! 465:
! 466: else
! 467:
! 468: while true
! 469: do
! 470: SONG=${PLAYLIST[$RANDOM % ${#PLAYLIST[@]}]}
! 471: playsong "${SONG}"
! 472: done
! 473:
! 474: fi
! 475: fi
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>