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 :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 :repeat task-count
|
|
|
|
:do (format t "~A~%" (lparallel.queue:pop-queue queue))))
|
|
|
|
|
|
|
|
(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."
|
2024-10-31 21:41:34 -07:00
|
|
|
: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*))
|
|
|
|
(*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)))))
|