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