From 67f5263943276faee0de53d947b6191205ae7a43 Mon Sep 17 00:00:00 2001 From: William Xu Date: Tue, 17 Jun 2008 14:43:45 +0900 Subject: *.el -> lisp/*.el: Move lisp files into "lisp/" subdirectory. --- lisp/ogg-comment.el | 270 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 270 insertions(+) create mode 100644 lisp/ogg-comment.el (limited to 'lisp/ogg-comment.el') diff --git a/lisp/ogg-comment.el b/lisp/ogg-comment.el new file mode 100644 index 0000000..46dd50a --- /dev/null +++ b/lisp/ogg-comment.el @@ -0,0 +1,270 @@ +;;; ogg-comment.el --- Read Ogg-Vorbis file headers. + +;; Copyright (C) 2003, 2004, 2005, 2006, 2007 Free Software Foundation, Inc. + +;; Filename: ogg-comment.el +;; Version: $Revision: 1.5 $ +;; Author: lawrence mitchell +;; Maintainer: lawrence mitchell +;; Created: 2003-09-26 +;; Keywords: music + +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the GNU General Public License as +;; published by the Free Software Foundation; either version 3 of the +;; License, or (at your option) any later version. +;; +;; This program is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more +;; details. http://www.gnu.org/copyleft/gpl.html +;; +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If you did not, write to the Free Software +;; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +;; 02110-1301 USA + +;;; Commentary: +;; This file provides a minimal interface to reading the "comment" +;; section from an Ogg-Vorbis stream as defined in +;; It relies on all the comments being in the first 28kilobytes of +;; the file, thus removing the need to read the whole ogg file into +;; an Emacs buffer. + +;; The implementation is rather "byte-oriented", due to the way the +;; Ogg-Vorbis file headers are specified. Any improvements in making +;; the implementation more emacsy would be welcomed. + +;;; Installation: +;; To use, put this file somewhere in your `load-path' and do +;; (require 'ogg-comment). +;; You can then read ogg comments from a file by doing: +;; M-x oggc-show-header RET. + +;;; History: +;; + +;;; TODO: +;; o Read setup header, to get bitrate and such like. +;; o Make writing comments possible. + +;;; Code: +(eval-when-compile + (defvar it) + (require 'cl)) + +(defconst oggc-ogg-header "OggS" + "The string indicating the start of an Ogg stream.") + +(defconst oggc-identification-header "\001vorbis" + "The string indicating the start of the Ogg identification header.") + +(defconst oggc-comment-header "\003vorbis" + "The string indicating the start of the Ogg comment header.") + +(defconst oggc-setup-header "\005vorbis" + "The string indicating the start of the Ogg setup header.") + +(defconst oggc-code-book-pattern "BCV" + "The string indicating the start of an Ogg code book.") + +(defconst oggc-version "$Revision: 1.5 $" + "Ogg-comment's version number.") + +(defmacro with-part-of-file (file-spec &rest body) + "Execute BODY in a buffer containing part of FILE. + +BEG and END are as `insert-file-contents' (q.v.). + +\(fn (FILE &optional BEG END) &rest BODY)" + (let (file beg end) + (setq file (pop file-spec)) + (and file-spec (setq beg (pop file-spec))) + (and file-spec (setq end (pop file-spec))) + `(with-temp-buffer + (insert-file-contents-literally ,file nil ,beg ,end) + (goto-char (point-min)) + ,@body))) + +(defmacro aif (test-form then &rest else) + "Like `if', but with `it' bound to the result of TEST-FORM. +`it' is accessible in the THEN and ELSE clauses. + +Warning, non-hygienic by design. + +\(fn TEST-FORM THEN &rest ELSE)" + `(let ((it ,test-form)) + (if it + ,then + ,@else))) + +(defun oggc-split-comment (comment) + "Split Ogg COMMENT into a (name, value) pair. + +If possible (`ccl-execute-on-string' and `ccl-decode-mule-utf-8' +available), COMMENT is decoded into utf-8. + +The name-part is converted to lowercase, to make sure case-differences +are ignored." + (setq comment (split-string comment "=")) + (list (downcase (car comment)) + (oggc-decode-utf-8 (or (cadr comment) + "")))) + +(defun oggc-encode-utf-8 (string) + "Encode STRING into utf-8." + (if (and (fboundp 'ccl-execute-on-string) + (boundp 'ccl-encode-mule-utf-8)) + (ccl-execute-on-string ccl-encode-mule-utf-8 + (make-vector 9 nil) + string) + string)) + +(defun oggc-decode-utf-8 (string) + "Decode STRING from utf-8." + (if (and (fboundp 'ccl-execute-on-string) + (boundp 'ccl-decode-mule-utf-8)) + (ccl-execute-on-string ccl-decode-mule-utf-8 + (make-vector 9 nil) + string) + string)) + +(defun oggc-read-string (length) + "Read a string from `point' of LENGTH characters. + +Advances to (+ LENGTH (point))." + (buffer-substring-no-properties + (point) (goto-char (+ length (point))))) + +(defun oggc-valid-ogg-stream-p () + "Return non-nil if the current buffer contains a valid Ogg-Vorbis stream." + (or (search-forward oggc-ogg-header (min 100 (point-max)) t) + (error "File does not appear to be a valid ogg stream")) + (or (search-forward oggc-identification-header (min 300 (point-max)) t) + (error "Not a valid ogg stream"))) + +(defun oggc-comment-exists-p () + "Return the value of `point' where comments are found in the current buffer." + (let ((max (save-excursion + (search-forward oggc-setup-header nil t) + (point)))) + (and (search-forward oggc-comment-header max t) + (point)))) + +(defun oggc-bytes-to-lsb-int (n) + "Read N bytes as a LSB integer." + (loop for i from 0 below n + sum (* (expt 256 i) + (prog1 (char-after) + (forward-char 1))))) + +(defun oggc-int-to-lsb-bytes (int n) + "Return a list of N bytes encoding INT as a LSB integer." + (nreverse (loop for i downfrom (1- n) to 0 + for exp = (expt 256 i) + collect (floor int exp) + when (<= exp int) + do (setq int (/ int exp))))) + +(defun oggc-construct-comment-field (comment-list) + "Construct an Ogg-Vorbis comment header from COMMENT-LIST. + +COMMENT-LIST should be of the form (TITLE VALUE). +VALUE is encoded into UTF-8 if possible (`ccl-execute-on-string' and +`ccl-decode-mule-utf-8' available). The length of the thus ensuing +comment header is prepended to the string as a 4-byte lsb int." + (let* ((title (pop comment-list)) + (value (pop comment-list))) + (setq title (concat title "=" + (oggc-encode-utf-8 value))) + (concat (oggc-int-to-lsb-bytes (length title) 4) + title))) + +(defun oggc-construct-vendor (vendor) + "Construct a vendor string from VENDOR." + (concat (oggc-int-to-lsb-bytes (length vendor) 4) + vendor)) + +;;; FIXME: This doesn't work!! +;;; Somehow, we need to modify one of the code-book headers to make +;;; note of the fact that the comment has changed. I can't see in +;;; the spec what needs to be done. +;;; This doesn't work even for the case where we don't change the +;;; length of the comment, just one character, e.g. tracknumber=1 to +;;; tracknumber=2. +(defun oggc-write-comments (file comments) + "Write COMMENTS to FILE. + +COMMENTS should be as for `oggc-construct-comment-string' (q.v.)." + (with-temp-buffer + ;; dog slow for large files. + ;; an alternative would be to use head/tail/cut as needed to + ;; split the file up and put it back together again. + (insert-file-contents-literally file) + (when (oggc-valid-ogg-stream-p) + (when (oggc-comment-exists-p) + (let ((vendor (save-excursion (oggc-read-vendor)))) + (delete-region (point) (progn (oggc-read-comments (point)) + (point))) + (insert (oggc-construct-vendor vendor) + (oggc-construct-comment-string comments)))) + (write-region nil nil file)))) + +(defun oggc-construct-comment-string (comments) + "Construct a string off Ogg-Vorbis comment headers from COMMENTS. + +COMMENTS should be an alist of the form: + ((TITLE-1 VALUE-1) + (TITLE-2 VALUE-2))" + (concat (oggc-int-to-lsb-bytes (length comments) 4) + (mapconcat #'oggc-construct-comment-field comments ""))) + +(defun oggc-read-vendor () + "Read an Ogg-Vorbis vendor string from the current buffer." + (let ((length (oggc-bytes-to-lsb-int 4))) + (oggc-read-string length))) + +(defun oggc-read-comments (pos) + "Read Ogg-Vorbis comments, starting POS bytes from `point-min'." + (goto-char pos) + (let ((vendor (oggc-read-vendor)) + (length (oggc-bytes-to-lsb-int 4)) + comments) + (loop repeat length + for this-length = (oggc-bytes-to-lsb-int 4) + for c = (oggc-read-string this-length) do + (push (oggc-split-comment c) comments)) + (list vendor (nreverse comments)))) + +(defun oggc-read-header (file) + "Read an Ogg-Vorbis header from FILE." + (with-part-of-file (file 0 + ;; Lets hope that the comments + ;; aren't more than 28KB long. + (* 1024 28)) + (when (oggc-valid-ogg-stream-p) + (aif (oggc-comment-exists-p) + (oggc-read-comments it))))) + +(defun oggc-pretty-print-header (header) + "Print Ogg HEADER readably in a temporary buffer." + (let ((vendor (car header)) + (comments (cadr header))) + (switch-to-buffer (get-buffer-create "*comments*")) + (erase-buffer) + (insert "Vendor: "vendor "\n") + (mapc #'(lambda (s) + (insert (car s) ": " (cadr s) "\n")) + comments))) + +;;;###autoload +(defun oggc-show-header (file) + "Show a pretty printed representation of the Ogg Comments in FILE." + (interactive "FFile: ") + (oggc-pretty-print-header (oggc-read-header file))) + +(provide 'ogg-comment) + +;;; ogg-comment.el ends here -- cgit v1.2.3