(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))