Annotation of plm/pl, revision 1.1.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>