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>