lisp-scripts/sync-music/main.lisp

100 lines
3.4 KiB
Common Lisp
Raw Normal View History

2024-10-27 23:44:59 -07:00
(uiop:define-package #:sync-music/cli
(:use #:cl #:alexandria :serapeum #:sync-music)
(:export #:main))
(in-package #:sync-music/cli)
(defvar *dry-run-p*)
(opts:define-opts
(:name :help
:description "print this help message"
:short #\h
:long "help")
(:name :jobs
:description "number of worker threads to spawn"
:short #\j
:long "jobs"
:arg-parser #'parse-integer
:meta-var "JOBS")
(:name :bitrate
:description "the bitrate for opus conversions (ffmpeg -b:a)"
:short #\b
:long "bitrate"
:arg-parser #'identity
:meta-var "BITRATE")
(:name :max-depth
:description "max depth to search directories before signaling an error"
:short #\d
:long "depth"
:arg-parser #'parse-integer
:meta-var "MAX-DEPTH")
(:name :dumb-cue-copy-p
:description "blindly copy cue files instead of parsing them"
:short #\c
:long "dumb-cue-copy")
2024-10-27 23:44:59 -07:00
(:name :dry-run-p
:description "print out actions and exit"
:short #\n
:long "dry-run")
(:name :no-cleanup-p
:description "don't sync deletions"
:short #\k
:long "no-cleanup")
(:name :no-ignore-toplevel-p
:description "don't ignore files in the top level of the directories"
:long "no-ignore-toplevel"))
(defun confirm-handler (copy-actions delete-actions directory-actions)
(let ((all-actions (list directory-actions copy-actions delete-actions)))
(when (every #'zerop (mapcar #'fset:size all-actions))
(format t "No actions to perform!~%")
(return-from confirm-handler))
(format t "Actions:~%")
(dolist (actions all-actions)
(fset:do-set (action actions)
(action-describe action t)))
(unless *dry-run-p*
(format t
"~2&Perform ~A copies and ~A deletions? [y/N] "
(fset:size copy-actions)
(fset:size delete-actions))
(finish-output)
(char-equal (read-char) #\y))))
(defun progress-handler (queue task-count)
(loop :for n :from 1 :upto task-count
:do (format t
"(~A/~A) ~A~%"
n
task-count
(lparallel.queue:pop-queue queue))))
2024-10-27 23:44:59 -07:00
(defun main ()
(multiple-value-bind (options free-args)
(opts:get-opts)
(let ((free-argc (length free-args)))
(cond
((= free-argc 1)
(push (uiop:native-namestring #P"~/Music/") free-args))
((or (getf options :help) (/= free-argc 2))
(opts:describe
:prefix "Sync music files between directories, converting flac to opus."
:suffix "ORIGIN is assumed to be `~/Music/` if not provided."
2024-10-27 23:44:59 -07:00
:usage-of "sync-music"
:args "[ORIGIN] TARGET")
(return-from main)))
(let ((*worker-threads* (getf options :jobs))
(*opus-bitrate* (or (getf options :bitrate)
*opus-bitrate*))
(*max-depth* (or (getf options :max-depth)
*max-depth*))
(*dumb-cue-copy-p* (getf options :dumb-cue-copy-p))
2024-10-27 23:44:59 -07:00
(*dry-run-p* (getf options :dry-run-p))
(*cleanupp* (not (getf options :no-cleanup-p)))
(*ignore-toplevel-p* (not (getf options :no-ignore-toplevel-p))))
(sync-music (uiop:parse-native-namestring (first free-args))
(uiop:parse-native-namestring (second free-args))
:confirmp #'confirm-handler
:progress-handler #'progress-handler)))))