cl-taglib / cl-taglib.lisp

;;;; cl-taglib.lisp

(in-package #:cl-taglib)

;;; "cl-taglib" goes here. Hacks and glory await!
(define-foreign-library taglib
  (t (:default "libtag_c")))

(use-foreign-library taglib)

(defcfun (set-strings-unicode "taglib_set_strings_unicode") :void
  (unicode :boolean))

(defcfun (set-string-management "taglib_set_string_management_enabled") :void
  (mangement :boolean))

(defcfun (free "taglib_free") :void
  (str :pointer))

(defcfun (file-free "taglib_file_free") :void
  (file :pointer))

(defcfun (file-is-valid "taglib_file_is_valid") :boolean
  (file :pointer))

(defun file-new (file)
  (let ((ptr (foreign-funcall "taglib_file_new" :string (namestring file) :pointer)))
    (cond ((null-pointer-p ptr) (error (format nil "Invalid tagfile: ~s" file)))
	  ((not (file-is-valid ptr)) (error (format nil "Invalid tagfile: ~s" file)))
	  (t ptr))))

(defcfun (file-tag "taglib_file_tag") :pointer
  (file :pointer))

(defcfun (file-audioproperties "taglib_file_audioproperties") :pointer
  (file :pointer))

(defcfun (file-save "taglib_file_save") :boolean
  (file :pointer))

(defmacro with-tag-file ((var path) &body body)
  `(let ((,var (file-new ,path)))
     (unwind-protect (progn
		       ,@body)
       (file-free ,var))))

(defmacro tag-accessor-for (slot type)
  (let ((function-name (intern (concatenate 'string "TAG-" (string slot))))
	(foreign-name (string-downcase (concatenate 'string "taglib_tag_" (string slot))))
	(foreign-set-name (string-downcase (concatenate 'string "taglib_tag_set_" (string slot)))))
    (list 'progn
	  (if (eq type :string)
	      `(defun ,function-name (tag)
		(let ((str-ptr (foreign-funcall ,foreign-name :pointer tag :string+ptr)))
		  (foreign-funcall "taglib_free" :pointer (cadr str-ptr) :void)
		  (car str-ptr)))
	      `(defun ,function-name (tag)
		(foreign-funcall ,foreign-name :pointer tag ,type)))
	  `(defun (setf ,function-name) (value tag)
	    (foreign-funcall ,foreign-set-name :pointer tag ,type value :void)))))

(tag-accessor-for :title :string)
(tag-accessor-for :artist :string)
(tag-accessor-for :album :string)
(tag-accessor-for :comment :string)
(tag-accessor-for :genre :string)
(tag-accessor-for :year :uint)
(tag-accessor-for :track :uint)

(defmacro audio-reader-for (slot)
  (let ((function-name (intern (concatenate 'string "AUDIO-" (string slot))))
	(foreign-name (string-downcase (concatenate 'string "taglib_audioproperties_" (string slot)))))
    `(defcfun (,function-name ,foreign-name) :int
       (audio :pointer))))

(audio-reader-for :length)
(audio-reader-for :bitrate)
(audio-reader-for :samplerate)
(audio-reader-for :channels)
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.