(defpackage #:autotag
(:use #:cl
#:alexandria
#:split-sequence
#:just-getopt-parser)
(:export #:main))
(in-package #:autotag)
(cl-interpol:enable-interpol-syntax)
(defparameter *quiet-p* nil
"Should autotag be quiet?")
(defparameter *order-p* nil
"Apply tags in the same order as in release? (ignore name matching
or keywords)")
(defparameter *rename-p* t
"Should files be renamed by default?")
(defparameter *use-id-p* nil
"")
(defparameter *print-tags-p* nil
"")
(defvar *album-name* nil)
(defvar *album-artist* nil)
(defvar *npathnames* nil)
(defvar *database* "https://musicbrainz.org/ws/2/")
(defparameter *valid-cmd-line-args*
'(;;
(:artist "artist" :required)
;;
(:album #\A :required)
(:album "album" :required)
;;
(:order #\o)
(:order "order")
;;
(:id #\i)
(:id "id")
;;
(:use-id #\u :required)
(:use-id "use-id" :required)
;;
(:print-tags #\p)
(:print-tags "print-tags")
;;
(:dont-rename #\n)
(:dont-rename "dont-rename")
;;
(:quiet #\q)
(:quiet "quiet")
;;
(:help #\h)
(:help "help")))
(defun chomp (string)
(if (and (string/= string "") (eql (last-elt string) #\Newline))
(subseq string 0 (1- (length string)))
string))
(defun dmenu (options &key (printer #'princ-to-string) (lines 10) prompt text-output)
(let ((text-options (mapcar printer options)))
(multiple-value-bind (output error-output exit-status)
(trivial-shell:shell-command
(format nil "dmenu~@[ -l '~A'~]~@[ -p \"~A\"~]"
lines
(str:replace-using '("\"" "\\\"" "$" "\\$") prompt))
:input (format nil "~{~A~%~}" text-options))
(declare (ignore error-output exit-status))
(if text-output
(chomp output)
(let ((position (position (chomp output) text-options :test #'string=)))
(and position (elt options position)))))))
(defun info (control-string &rest format-arguments)
(unless *quiet-p*
(apply #'format t control-string format-arguments)))
;; Tags used by this autotagger, each track has one instance of this
;; struct
(defstruct tag
(title "")
(album "")
(artist "")
(date "")
(track "")
(keywords nil))
(defparameter *ogg-tags*
`((:title "TITLE" ,#'tag-title ,#'(setf tag-title))
(:artist "ARTIST" ,#'tag-artist ,#'(setf tag-artist))
(:album "ALBUM" ,#'tag-album ,#'(setf tag-album))
(:date "DATE" ,#'tag-date ,#'(setf tag-date))
(:track "TRACKNUMBER" ,#'tag-track ,#'(setf tag-track))))
;; (defun read-ogg-tags (pathname)
;; (flet ((ogg-field-reader (line)
;; (unless (string= line "")
;; (let* ((fields (split-sequence #\= line))
;; (name (car (rassoc (car fields) *ogg-tags*
;; :test #'string= :key #'first)))
;; (value (apply #'concatenate 'string (cdr fields))))
;; (list name value)))))
;; (read-tags (format nil "vorbiscomment -l ~A" pathname) #'ogg-field-reader)))
(defun print-tag (tag)
(dolist (field *ogg-tags*)
(format t "~A=~A" (elt field 1) (funcall (elt field 2) tag))))
(defun write-ogg-tags (pathname tag)
(trivial-shell:shell-command
(with-open-stream (stream (make-string-output-stream))
(format stream "vorbiscomment -w \"~A\"" pathname)
(loop :for (tag-name ogg-name reader writer) :in *ogg-tags*
:for value = (funcall reader tag)
:do
(format stream " -t \"~A=~A\"" ogg-name value))
(get-output-stream-string stream))))
(defun help ()
(format t #?|~&Usage: autotag [OPTION...] [DIRECTORY... \| FILE...]
autotag - Uses metadata from musicbrainz.org to fill song files with
the tags of a given album.
Examples:
Assume that the directory working on has the same name as <ALBUM>
autotag --artist=<ARTIST> -A <ALBUM>
Options:
--artist=ARTIST improve search results by providing an artist
-A, --album=ALBUM name of the album the files belong to
-i, --id get album id
-u, --use-id=ID use ID as release id
-p, --print-tags write tags to stdout instead of file
-o, --order ignore file names and assume that files are in order
(useful for ripped CDs)
-q, --quiet do not output operations being done
-h, --help show help options
autotag
Copyright (C) 2020 Thomas Albers Raviola <thomas@thomaslabs.org>
This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under the terms of the GNU General Public License.
|)
(uiop:quit))