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>