From d3216c5c4fd94cc4a354b3a4f296775376c9d327 Mon Sep 17 00:00:00 2001 From: Petteri Hintsanen Date: Thu, 18 Feb 2021 23:05:50 +0200 Subject: Read and parse id3v2 frames individually Instead of reading the whole tag and then parsing it, do reading and parsing one frame at a time, and skip uninteresting frames. This may conserve memory and give better performance in some cases. --- emms-info-native.el | 134 ++++++++++++++++++++++++++++------------------------ 1 file changed, 72 insertions(+), 62 deletions(-) diff --git a/emms-info-native.el b/emms-info-native.el index f31475f..ab5b6c0 100644 --- a/emms-info-native.el +++ b/emms-info-native.el @@ -519,16 +519,15 @@ outside itself.") (revision u8) (flags bits 1) (size-bytes vec 4) - (size eval (emms-info-native--checked-id3v2-size 'tag last))) + (size eval (emms-info-native--decode-id3v2-size last t))) "id3v2 header specification.") -(defconst emms-info-native--id3v2-frame-bindat-spec +(defconst emms-info-native--id3v2-frame-header-bindat-spec '((id str (eval (if (= emms-info-native--id3v2-version 2) 3 4))) (size-bytes vec (eval (if (= emms-info-native--id3v2-version 2) 3 4))) (size eval (emms-info-native--checked-id3v2-size 'frame last)) - (flags bits (eval (if (= emms-info-native--id3v2-version 2) 0 2))) - (payload vec (size))) - "id3v2 frame specification.") + (flags bits (eval (if (= emms-info-native--id3v2-version 2) 0 2)))) + "id3v2 frame header specification.") (defconst emms-info-native--id3v2-frame-to-info '(("TP1" . "artist") @@ -565,8 +564,7 @@ outside itself.") (defun emms-info-native--decode-id3v2 (filename) "Read and decode id3v2 metadata from FILENAME. -Return metadata in a list of (FIELD . VALUE) cons cells. See -‘emms-info-native--decode-id3v2-text-frame’ for details." +Return metadata in a list of (FIELD . VALUE) cons cells." (let* (emms-info-native--id3v2-version (header (emms-info-native--decode-id3v2-header filename)) (tag-size (bindat-get-field header 'size)) @@ -624,10 +622,7 @@ Return the decoded size." (* (expt 2 (* num-bits idx)) elt)) (reverse bytes))))) -(defun emms-info-native--decode-id3v2-frames (filename - begin - end - unsync) +(defun emms-info-native--decode-id3v2-frames (filename begin end unsync) "Read and decode id3v2 text frames from FILENAME. BEGIN should be the offset of first byte after id3v2 header and extended header (if any), and END should be the offset after the @@ -636,61 +631,76 @@ complete id3v2 tag. If UNSYNC is t, the frames are assumed to have gone through unsynchronization and decoded as such. -Return metadata in a list of (FIELD . VALUE) cons cells. See -‘emms-info-native--decode-id3v2-text-frame’ for details." +Return metadata in a list of (FIELD . VALUE) cons cells." + (let ((offset begin) + comments) + (condition-case nil + (while (< offset end) + (let* ((header (emms-info-native--decode-id3v2-frame-header filename + offset)) + (info-id (emms-info-native--id3v2-frame-info-id header)) + (decoded-size (bindat-get-field (cdr header) 'size))) + (setq offset (car header)) ;advance to frame data begin + (if (or unsync info-id) + ;; Note that if unsync is t, we have to always read a + ;; frame to gets its true size so that we can adjust + ;; offset correctly. + (let ((data (emms-info-native--read-id3v2-frame-data filename + offset + decoded-size + unsync))) + (setq offset (car data)) + (when info-id + (let ((value (emms-info-native--decode-id3v2-string (cdr data)))) + (push (cons info-id value) comments)))) + ;; Skip the frame. + (cl-incf offset decoded-size)))) + (error nil)) + comments)) + +(defun emms-info-native--decode-id3v2-frame-header (filename begin) + "Read and decode id3v2 frame header from FILENAME. +Start reading from offset BEGIN. + +Return a cons cell (OFFSET . FRAME), where OFFSET is the byte +offset after the frame header, and FRAME is the decoded frame." (with-temp-buffer (set-buffer-multibyte nil) - (insert (emms-info-native--read-id3v2-frames filename - begin - end - unsync)) - (emms-info-native--decode-id3v2-text-frames (buffer-string)))) - -(defun emms-info-native--read-id3v2-frames (filename begin end unsync) - "Read id3v2 frames from FILE. -Start at offset BEGIN and end before offset END. If UNSYNC is t, -reverse unsynchronization. - -Return the frames." + (let ((end (+ begin (if (= emms-info-native--id3v2-version 2) 6 10)))) + (insert-file-contents-literally filename nil begin end) + (cons end (bindat-unpack emms-info-native--id3v2-frame-header-bindat-spec + (buffer-string)))))) + +(defun emms-info-native--read-id3v2-frame-data (filename + begin + num-bytes + unsync) + "Read NUM-BYTES of raw id3v2 frame data from FILENAME. +Start reading from offset BEGIN. If UNSYNC is t, all ’FF 00’ +byte combinations are replaced by ’FF’. Replaced byte pairs are +counted as one, instead of two, towards NUM-BYTES. + +Return a cons cell (OFFSET . DATA), where OFFSET is the byte +offset after NUM-BYTES bytes have been read, and DATA is the raw +data." (with-temp-buffer (set-buffer-multibyte nil) - (insert-file-contents-literally filename nil begin end) - (when unsync - (while (re-search-forward (string 255 0) nil t) - (replace-match (string 255)))) - (buffer-string))) - -(defun emms-info-native--decode-id3v2-text-frames (frames) - "Decode id3v2 text frames from FRAMES. - -Return metadata in a list of (FIELD . VALUE) cons cells. See -‘emms-info-native--decode-id3v2-text-frame’ for details." - (let (comments - (offset 0)) - (condition-case nil - (while (< offset (length frames)) - (let* ((frame (bindat-unpack emms-info-native--id3v2-frame-bindat-spec - frames - offset)) - (comment (emms-info-native--decode-id3v2-text-frame - frame))) - (when comment (push comment comments)) - (cl-incf offset - (+ (bindat-get-field frame 'size) - (if (= emms-info-native--id3v2-version 2) 6 10))))) - (error nil)) - comments)) - -(defun emms-info-native--decode-id3v2-text-frame (frame) - "Identify and decode id3v2 text frame FRAME. -If FRAME’s identifier matches a key in -‘emms-info-native--id3v2-frame-to-info’, return a cons cell -(FIELD . VALUE), where FIELD is the corresponding info field -identifier and VALUE is the decoded text. Otherwise return nil." - (let ((info-id (emms-info-native--id3v2-frame-info-id frame)) - (payload (bindat-get-field frame 'payload))) - (when info-id - (cons info-id (emms-info-native--decode-id3v2-string payload))))) + (if unsync + ;; Reverse unsynchronization. + (let ((peek-end (+ begin (* 2 num-bytes))) + (end num-bytes)) + (insert-file-contents-literally filename nil begin peek-end) + (beginning-of-buffer) + (while (and (re-search-forward (string 255 0) nil t) + (< (point) end)) + (replace-match (string 255)) + (cl-incf end 1)) + (delete-region (1+ num-bytes) (point-max)) + (cons (+ begin end) (buffer-string))) + ;; No unsynchronization: read the data as-is. + (let ((end (+ begin num-bytes))) + (insert-file-contents-literally filename nil begin end) + (cons end (buffer-string)))))) (defun emms-info-native--id3v2-frame-info-id (frame) "Return the emms-info identifier for FRAME. -- cgit v1.2.3 From 0fe61009c5e00790a180e13021050f92b0916cd0 Mon Sep 17 00:00:00 2001 From: Petteri Hintsanen Date: Fri, 19 Feb 2021 19:13:50 +0200 Subject: Improve id3v2 error handling - Allow zero-sized frames; in practice they mean start of padding. - Do not try to read frame headers going over tag boundary. These cases also mean start of padding in practice. - Return nil from emms-info-native--decode-id3v2 in case of errors *or* if there were no useful metadata. --- emms-info-native.el | 92 +++++++++++++++++++++++++++++------------------------ 1 file changed, 51 insertions(+), 41 deletions(-) diff --git a/emms-info-native.el b/emms-info-native.el index ab5b6c0..039738c 100644 --- a/emms-info-native.el +++ b/emms-info-native.el @@ -564,20 +564,26 @@ outside itself.") (defun emms-info-native--decode-id3v2 (filename) "Read and decode id3v2 metadata from FILENAME. -Return metadata in a list of (FIELD . VALUE) cons cells." - (let* (emms-info-native--id3v2-version - (header (emms-info-native--decode-id3v2-header filename)) - (tag-size (bindat-get-field header 'size)) - (unsync (memq 7 (bindat-get-field header 'flags))) - (offset 10)) - (when (memq 6 (bindat-get-field header 'flags)) - ;; Skip the extended header. - (cl-incf offset - (emms-info-native--decode-id3v2-ext-header-size filename))) - (emms-info-native--decode-id3v2-frames filename - offset - (+ tag-size 10) - unsync))) +Return metadata in a list of (FIELD . VALUE) cons cells, or nil +in case of errors or if there were no known fields in FILENAME. + +See ‘emms-info-native--id3v2-frame-to-info’ for recognized +fields." + (condition-case-unless-debug nil + (let* (emms-info-native--id3v2-version + (header (emms-info-native--decode-id3v2-header filename)) + (tag-size (bindat-get-field header 'size)) + (unsync (memq 7 (bindat-get-field header 'flags))) + (offset 10)) + (when (memq 6 (bindat-get-field header 'flags)) + ;; Skip the extended header. + (cl-incf offset + (emms-info-native--decode-id3v2-ext-header-size filename))) + (emms-info-native--decode-id3v2-frames filename + offset + (+ tag-size 10) + unsync)) + (error nil))) (defun emms-info-native--decode-id3v2-header (filename) "Read and decode id3v2 header from FILENAME." @@ -594,7 +600,7 @@ Return the size. Signal an error if the size exceeds (with-temp-buffer (set-buffer-multibyte nil) (insert-file-contents-literally filename nil 10 14) - (emms-info-native--checked-id3v2-size (buffer-string)))) + (emms-info-native--checked-id3v2-size 'frame (buffer-string)))) (defun emms-info-native--checked-id3v2-size (elt bytes) "Calculate id3v2 element ELT size from BYTES. @@ -607,7 +613,7 @@ Return the size." (if (= emms-info-native--id3v2-version 4) (setq size (emms-info-native--decode-id3v2-size bytes t)) (setq size (emms-info-native--decode-id3v2-size bytes nil))))) - (when (or (= size 0) (> size emms-info-native--max-peek-size)) + (when (> size emms-info-native--max-peek-size) (error "id3v2 tag or frame size %s is invalid" size)) size)) @@ -633,30 +639,34 @@ unsynchronization and decoded as such. Return metadata in a list of (FIELD . VALUE) cons cells." (let ((offset begin) + (limit (- end (emms-info-native--id3v2-frame-header-size))) comments) - (condition-case nil - (while (< offset end) - (let* ((header (emms-info-native--decode-id3v2-frame-header filename - offset)) - (info-id (emms-info-native--id3v2-frame-info-id header)) - (decoded-size (bindat-get-field (cdr header) 'size))) - (setq offset (car header)) ;advance to frame data begin - (if (or unsync info-id) - ;; Note that if unsync is t, we have to always read a - ;; frame to gets its true size so that we can adjust - ;; offset correctly. - (let ((data (emms-info-native--read-id3v2-frame-data filename - offset - decoded-size - unsync))) - (setq offset (car data)) - (when info-id - (let ((value (emms-info-native--decode-id3v2-string (cdr data)))) - (push (cons info-id value) comments)))) - ;; Skip the frame. - (cl-incf offset decoded-size)))) - (error nil)) - comments)) + (catch 'eof + (while (< offset limit) + (let* ((header (emms-info-native--decode-id3v2-frame-header filename + offset)) + (info-id (emms-info-native--id3v2-frame-info-id header)) + (decoded-size (bindat-get-field (cdr header) 'size))) + (when (= decoded-size 0) (throw 'eof t)) + (setq offset (car header)) ;advance to frame data begin + (if (or unsync info-id) + ;; Note that if unsync is t, we have to always read a + ;; frame to gets its true size so that we can adjust + ;; offset correctly. + (let ((data (emms-info-native--read-id3v2-frame-data filename + offset + decoded-size + unsync))) + (setq offset (car data)) + (when info-id + (let ((value (emms-info-native--decode-id3v2-string (cdr data)))) + (push (cons info-id value) comments)))) + ;; Skip the frame. + (cl-incf offset decoded-size))))) + comments)) + +(defun emms-info-native--id3v2-frame-header-size () + (if (= emms-info-native--id3v2-version 2) 6 10)) (defun emms-info-native--decode-id3v2-frame-header (filename begin) "Read and decode id3v2 frame header from FILENAME. @@ -666,7 +676,7 @@ Return a cons cell (OFFSET . FRAME), where OFFSET is the byte offset after the frame header, and FRAME is the decoded frame." (with-temp-buffer (set-buffer-multibyte nil) - (let ((end (+ begin (if (= emms-info-native--id3v2-version 2) 6 10)))) + (let ((end (+ begin (emms-info-native--id3v2-frame-header-size)))) (insert-file-contents-literally filename nil begin end) (cons end (bindat-unpack emms-info-native--id3v2-frame-header-bindat-spec (buffer-string)))))) @@ -690,7 +700,7 @@ data." (let ((peek-end (+ begin (* 2 num-bytes))) (end num-bytes)) (insert-file-contents-literally filename nil begin peek-end) - (beginning-of-buffer) + (goto-char (point-min)) (while (and (re-search-forward (string 255 0) nil t) (< (point) end)) (replace-match (string 255)) -- cgit v1.2.3 From 7bb5f7fde2f9917ea3753b09d86cb181ef71cf7a Mon Sep 17 00:00:00 2001 From: Petteri Hintsanen Date: Sun, 21 Feb 2021 18:52:55 +0200 Subject: Improve id3v2 validity checks - Remove id3v2 size checks agains emms-info-native--max-peek-size. Decoded sizes do not guide memory reservation anymore so checks are in that sense redundant. Trigger errors on zero-sized elements though, because they are always wrong. - Verify frame identifiers. --- emms-info-native.el | 88 +++++++++++++++++++++++++++++------------------------ 1 file changed, 48 insertions(+), 40 deletions(-) diff --git a/emms-info-native.el b/emms-info-native.el index 039738c..bca21b1 100644 --- a/emms-info-native.el +++ b/emms-info-native.el @@ -519,11 +519,13 @@ outside itself.") (revision u8) (flags bits 1) (size-bytes vec 4) - (size eval (emms-info-native--decode-id3v2-size last t))) + (size eval (emms-info-native--checked-id3v2-size 'tag last))) "id3v2 header specification.") (defconst emms-info-native--id3v2-frame-header-bindat-spec '((id str (eval (if (= emms-info-native--id3v2-version 2) 3 4))) + (eval (unless (emms-info-native--valid-id3v2-frame-id-p last) + (error "id3v2 frame id ‘%s’ is invalid" last))) (size-bytes vec (eval (if (= emms-info-native--id3v2-version 2) 3 4))) (size eval (emms-info-native--checked-id3v2-size 'frame last)) (flags bits (eval (if (= emms-info-native--id3v2-version 2) 0 2)))) @@ -569,7 +571,7 @@ in case of errors or if there were no known fields in FILENAME. See ‘emms-info-native--id3v2-frame-to-info’ for recognized fields." - (condition-case-unless-debug nil + (condition-case nil (let* (emms-info-native--id3v2-version (header (emms-info-native--decode-id3v2-header filename)) (tag-size (bindat-get-field header 'size)) @@ -578,7 +580,7 @@ fields." (when (memq 6 (bindat-get-field header 'flags)) ;; Skip the extended header. (cl-incf offset - (emms-info-native--decode-id3v2-ext-header-size filename))) + (emms-info-native--checked-id3v2-ext-header-size filename))) (emms-info-native--decode-id3v2-frames filename offset (+ tag-size 10) @@ -593,10 +595,9 @@ fields." (bindat-unpack emms-info-native--id3v2-header-bindat-spec (buffer-string)))) -(defun emms-info-native--decode-id3v2-ext-header-size (filename) +(defun emms-info-native--checked-id3v2-ext-header-size (filename) "Read and decode id3v2 extended header size from FILENAME. -Return the size. Signal an error if the size exceeds -‘emms-info-native--max-peek-size’." +Return the size. Signal an error if the size is zero." (with-temp-buffer (set-buffer-multibyte nil) (insert-file-contents-literally filename nil 10 14) @@ -604,18 +605,18 @@ Return the size. Signal an error if the size exceeds (defun emms-info-native--checked-id3v2-size (elt bytes) "Calculate id3v2 element ELT size from BYTES. -ELT must be either 'tag or 'frame. Check the validity of size. -Return the size." - (let (size) - (cond ((eq elt 'tag) - (setq size (emms-info-native--decode-id3v2-size bytes t))) - ((eq elt 'frame) - (if (= emms-info-native--id3v2-version 4) - (setq size (emms-info-native--decode-id3v2-size bytes t)) - (setq size (emms-info-native--decode-id3v2-size bytes nil))))) - (when (> size emms-info-native--max-peek-size) - (error "id3v2 tag or frame size %s is invalid" size)) - size)) +ELT must be either 'tag or 'frame. + +Return the size. Signal an error if the size is zero." + (let ((size (cond ((eq elt 'tag) + (emms-info-native--decode-id3v2-size bytes t)) + ((eq elt 'frame) + (if (= emms-info-native--id3v2-version 4) + (emms-info-native--decode-id3v2-size bytes t) + (emms-info-native--decode-id3v2-size bytes nil)))))) + (if (zerop size) + (error "id3v2 tag/frame size is zero") + size))) (defun emms-info-native--decode-id3v2-size (bytes syncsafe) "Decode id3v2 element size from BYTES. @@ -641,33 +642,40 @@ Return metadata in a list of (FIELD . VALUE) cons cells." (let ((offset begin) (limit (- end (emms-info-native--id3v2-frame-header-size))) comments) - (catch 'eof - (while (< offset limit) - (let* ((header (emms-info-native--decode-id3v2-frame-header filename - offset)) - (info-id (emms-info-native--id3v2-frame-info-id header)) - (decoded-size (bindat-get-field (cdr header) 'size))) - (when (= decoded-size 0) (throw 'eof t)) - (setq offset (car header)) ;advance to frame data begin - (if (or unsync info-id) - ;; Note that if unsync is t, we have to always read a - ;; frame to gets its true size so that we can adjust - ;; offset correctly. - (let ((data (emms-info-native--read-id3v2-frame-data filename - offset - decoded-size - unsync))) - (setq offset (car data)) - (when info-id - (let ((value (emms-info-native--decode-id3v2-string (cdr data)))) - (push (cons info-id value) comments)))) - ;; Skip the frame. - (cl-incf offset decoded-size))))) + (condition-case nil + (while (< offset limit) + (let* ((header (emms-info-native--decode-id3v2-frame-header filename + offset)) + (info-id (emms-info-native--id3v2-frame-info-id header)) + (decoded-size (bindat-get-field (cdr header) 'size))) + (setq offset (car header)) ;advance to frame data begin + (if (or unsync info-id) + ;; Note that if unsync is t, we have to always read a + ;; frame to gets its true size so that we can adjust + ;; offset correctly. + (let ((data (emms-info-native--read-id3v2-frame-data filename + offset + decoded-size + unsync))) + (setq offset (car data)) + (when info-id + (let ((value (emms-info-native--decode-id3v2-string (cdr data)))) + (push (cons info-id value) comments)))) + ;; Skip the frame. + (cl-incf offset decoded-size)))) + (error nil)) comments)) (defun emms-info-native--id3v2-frame-header-size () + "Return the last decoded header size in bytes." (if (= emms-info-native--id3v2-version 2) 6 10)) +(defun emms-info-native--valid-id3v2-frame-id-p (id) + "Return t if ID is a proper id3v2 frame identifier, nil otherwise." + (if (= emms-info-native--id3v2-version 2) + (string-match "[A-Z0-9]\\{3\\}" id) + (string-match "[A-Z0-9]\\{4\\}" id))) + (defun emms-info-native--decode-id3v2-frame-header (filename begin) "Read and decode id3v2 frame header from FILENAME. Start reading from offset BEGIN. -- cgit v1.2.3 From f9f2bab6acc2b078f1b0031f2c626b8f3dfbdbd0 Mon Sep 17 00:00:00 2001 From: Petteri Hintsanen Date: Sun, 21 Feb 2021 19:14:24 +0200 Subject: Trim trailing whitespace from id3v2 strings --- emms-info-native.el | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/emms-info-native.el b/emms-info-native.el index bca21b1..4aaafed 100644 --- a/emms-info-native.el +++ b/emms-info-native.el @@ -728,15 +728,17 @@ If there is no such identifier, return nil." (defun emms-info-native--decode-id3v2-string (bytes) "Decode id3v2 text information from BYTES. -Remove the terminating null byte, if any. Return the text as -string." +Remove the terminating null byte, if any, and trim trailing +whitespace. + +Return the text as string." (let* ((encoding (emms-info-native--id3v2-text-encoding bytes)) (string (mapconcat #'byte-to-string (seq-rest bytes) "")) (decoded (decode-coding-string string encoding))) (when (> (length decoded) 0) - (if (equal (substring decoded -1) "\0") - (substring decoded 0 -1) - decoded)))) + (string-trim-right (if (equal (substring decoded -1) "\0") + (substring decoded 0 -1) + decoded))))) (defun emms-info-native--id3v2-text-encoding (bytes) "Return the encoding for text information BYTES." -- cgit v1.2.3 From c8f198dd1e8f01ff754d250e8d20d02a196e3828 Mon Sep 17 00:00:00 2001 From: Petteri Hintsanen Date: Sun, 21 Feb 2021 19:22:50 +0200 Subject: Put some id3v2.4 frame data to info-date instead of info-year MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TDRC (‘Recording time’), TDRL (‘Release time’) and TDOR (‘Original release time’) frames in id3v2.4 are time stamps, not years, so they should go to info-date and info-originaldate fields instead of info-year and info-originalyear. --- emms-info-native.el | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/emms-info-native.el b/emms-info-native.el index 4aaafed..76e6c0e 100644 --- a/emms-info-native.el +++ b/emms-info-native.el @@ -548,11 +548,11 @@ outside itself.") ("TRK" . "tracknumber") ("TPOS" . "discnumber") ("TPA" . "discnumber") - ("TDRC" . "year") - ("TDRL" . "year") + ("TDRC" . "date") + ("TDRL" . "date") ("TYER" . "year") ("TYE" . "year") - ("TDOR" . "originalyear") + ("TDOR" . "originaldate") ("TORY" . "originalyear") ("TOR" . "originalyear")) "Mapping from id3v2 frame identifiers to info fields.") -- cgit v1.2.3 From 60c93048ee2acd557f4c102323b779b5092f6662 Mon Sep 17 00:00:00 2001 From: Petteri Hintsanen Date: Sun, 21 Feb 2021 19:39:10 +0200 Subject: Trim trailing whitespace from all info-fields --- emms-info-native.el | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/emms-info-native.el b/emms-info-native.el index 76e6c0e..d0c9777 100644 --- a/emms-info-native.el +++ b/emms-info-native.el @@ -728,17 +728,16 @@ If there is no such identifier, return nil." (defun emms-info-native--decode-id3v2-string (bytes) "Decode id3v2 text information from BYTES. -Remove the terminating null byte, if any, and trim trailing -whitespace. +Remove the terminating null byte, if any. Return the text as string." (let* ((encoding (emms-info-native--id3v2-text-encoding bytes)) (string (mapconcat #'byte-to-string (seq-rest bytes) "")) (decoded (decode-coding-string string encoding))) (when (> (length decoded) 0) - (string-trim-right (if (equal (substring decoded -1) "\0") - (substring decoded 0 -1) - decoded))))) + (if (equal (substring decoded -1) "\0") + (substring decoded 0 -1) + decoded)))) (defun emms-info-native--id3v2-text-encoding (bytes) "Return the encoding for text information BYTES." @@ -754,7 +753,7 @@ Supports Ogg Vorbis/Opus, FLAC, and MP3 files." (info-fields (emms-info-native--decode-info-fields filename))) (dolist (field info-fields) (let ((name (intern (concat "info-" (car field)))) - (value (cdr field))) + (value (string-trim-right (cdr field)))) (emms-track-set track name (if (eq name 'info-playing-time) -- cgit v1.2.3 From fa8f64e6a98373922c88af01d87046af2497d03d Mon Sep 17 00:00:00 2001 From: Petteri Hintsanen Date: Mon, 22 Feb 2021 22:05:26 +0200 Subject: Fix whitespace trimming Don't try to trim nil. --- emms-info-native.el | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/emms-info-native.el b/emms-info-native.el index d0c9777..0c7a220 100644 --- a/emms-info-native.el +++ b/emms-info-native.el @@ -753,12 +753,13 @@ Supports Ogg Vorbis/Opus, FLAC, and MP3 files." (info-fields (emms-info-native--decode-info-fields filename))) (dolist (field info-fields) (let ((name (intern (concat "info-" (car field)))) - (value (string-trim-right (cdr field)))) - (emms-track-set track - name - (if (eq name 'info-playing-time) - (string-to-number value) - value)))))) + (value (cdr field))) + (unless (zerop (length value)) + (emms-track-set track + name + (if (eq name 'info-playing-time) + (string-to-number value) + (string-trim-right value)))))))) (defun emms-info-native--decode-info-fields (filename) "Decode info fields from FILENAME. -- cgit v1.2.3 From bfae6b2ac78e85ee90a15cc0bbe45e436018a8bc Mon Sep 17 00:00:00 2001 From: Petteri Hintsanen Date: Mon, 22 Feb 2021 22:26:50 +0200 Subject: Replace curved quotes with ASCII quotes --- emms-info-native.el | 92 ++++++++++++++++++++++++++--------------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/emms-info-native.el b/emms-info-native.el index 0c7a220..9517155 100644 --- a/emms-info-native.el +++ b/emms-info-native.el @@ -25,31 +25,31 @@ ;; This file provides a native emms-info-method for EMMS. Here ;; "native" means a pure Emacs Lisp implementation instead of one -;; relying on external tools or libraries like ‘emms-info-ogginfo’ or -;; ‘emms-info-libtag’. +;; relying on external tools or libraries like `emms-info-ogginfo' or +;; `emms-info-libtag'. ;; -;; To use this method, add ‘emms-info-native’ to -;; ‘emms-info-functions’. +;; To use this method, add `emms-info-native' to +;; `emms-info-functions'. ;; ;; The following file formats are supported: ;; -;; - Vorbis: Ogg Vorbis I Profile, filename extension ‘.ogg’, -;; elementary streams only. Based on xiph.org’s Vorbis I +;; - Vorbis: Ogg Vorbis I Profile, filename extension `.ogg', +;; elementary streams only. Based on xiph.org's Vorbis I ;; specification, see URL -;; ‘https://xiph.org/vorbis/doc/Vorbis_I_spec.html’. +;; `https://xiph.org/vorbis/doc/Vorbis_I_spec.html'. ;; -;; - Opus: Ogg Opus profile, filename extension ‘.opus’, elementary +;; - Opus: Ogg Opus profile, filename extension `.opus', elementary ;; streams only. Based on RFC 7845, see URL -;; ‘https://tools.ietf.org/html/rfc7845.html’. +;; `https://tools.ietf.org/html/rfc7845.html'. ;; ;; - FLAC streams in native encapsulation format, filename extension -;; ‘.flac’. Based on xiph.org’s FLAC format specification, see URL -;; ‘https://xiph.org/flac/format.html’. +;; `.flac'. Based on xiph.org's FLAC format specification, see URL +;; `https://xiph.org/flac/format.html'. ;; -;; - MP3 files with extension ‘.mp3’ and id3v2 tags. All id3v2 +;; - MP3 files with extension `.mp3' and id3v2 tags. All id3v2 ;; versions should work, but many features like CRC, compression and ;; encryption are not supported. Based on id3v2 Informal Standards, -;; see URL ‘https://id3.org’. +;; see URL `https://id3.org'. ;; ;; Format detection is based solely on filename extension, which is ;; matched case-insensitively. @@ -62,10 +62,10 @@ (defconst emms-info-native--max-peek-size (* 2048 1024) "Maximum buffer size for metadata decoding. -Functions called by ‘emms-info-native’ read certain amounts of +Functions called by `emms-info-native' read certain amounts of data into a temporary buffer while decoding metadata. This variable controls the maximum size of that buffer: if more than -‘emms-info-native--max-peek-size’ bytes are needed, an error is +`emms-info-native--max-peek-size' bytes are needed, an error is signaled. Technically metadata blocks can have almost arbitrary lengths, @@ -76,7 +76,7 @@ exhaustion in case of garbled or malicious inputs.") (defconst emms-info-native--ogg-magic-array [79 103 103 83] - "Ogg format magic capture pattern ‘OggS’.") + "Ogg format magic capture pattern `OggS'.") (defconst emms-info-native--ogg-page-size 65307 "Maximum size for a single Ogg container page.") @@ -84,7 +84,7 @@ exhaustion in case of garbled or malicious inputs.") (defconst emms-info-native--ogg-page-bindat-spec '((capture-pattern vec 4) (eval (unless (equal last emms-info-native--ogg-magic-array) - (error "Ogg framing mismatch: expected ‘%s’, got ‘%s’" + (error "Ogg framing mismatch: expected `%s', got `%s'" emms-info-native--ogg-magic-array last))) (stream-structure-version u8) @@ -104,10 +104,10 @@ exhaustion in case of garbled or malicious inputs.") (defun emms-info-native--decode-ogg-comments (filename stream-type) "Read and decode comments from Ogg file FILENAME. The file is assumed to contain a single stream of type -STREAM-TYPE, which must either ‘vorbis’ or ‘opus’. +STREAM-TYPE, which must either `vorbis' or `opus'. Return comments in a list of (FIELD . VALUE) cons cells. See -‘emms-info-native--split-vorbis-comment’ for details." +`emms-info-native--split-vorbis-comment' for details." (let* ((packets (emms-info-native--decode-ogg-packets filename 2)) (headers (emms-info-native--decode-ogg-headers packets stream-type)) @@ -123,9 +123,9 @@ frames, and concatenate payloads until at least PACKETS number of packets have been decoded. Return the decoded packets in a vector, concatenated. -Data is read in ‘emms-info-native--ogg-page-size’ chunks. If the +Data is read in `emms-info-native--ogg-page-size' chunks. If the total length of concatenated packets becomes greater than -‘emms-info-native--max-peek-size’, an error is signaled. +`emms-info-native--max-peek-size', an error is signaled. Only elementary streams are supported, that is, FILENAME should contain only a single logical stream. Note that this assumption @@ -172,7 +172,7 @@ Note that N can be zero." (defun emms-info-native--num-of-packets (page) "Return the number of packets in Ogg page PAGE. PAGE must correspond to -‘emms-info-native--ogg-page-bindat-spec’." +`emms-info-native--ogg-page-bindat-spec'." ;; Every element that is less than 255 in the segment table ;; represents a packet boundary. (length (seq-filter (lambda (elt) (< elt 255)) @@ -180,11 +180,11 @@ PAGE must correspond to (defun emms-info-native--decode-ogg-headers (packets stream-type) "Decode first two stream headers from PACKETS for STREAM-TYPE. -STREAM-TYPE must be either ‘vorbis’ or ‘opus’. +STREAM-TYPE must be either `vorbis' or `opus'. Return a structure that corresponds to either -‘emms-info-native--opus-headers-bindat-spec’ or -‘emms-info-native--vorbis-headers-bindat-spec’." +`emms-info-native--opus-headers-bindat-spec' or +`emms-info-native--vorbis-headers-bindat-spec'." (cond ((eq stream-type 'vorbis) (bindat-unpack emms-info-native--vorbis-headers-bindat-spec packets)) @@ -247,7 +247,7 @@ their comments have almost the same format as Vorbis.") (defconst emms-info-native--vorbis-magic-array [118 111 114 98 105 115] - "Header packet magic pattern ‘vorbis’.") + "Header packet magic pattern `vorbis'.") (defconst emms-info-native--vorbis-headers-bindat-spec '((identification-header struct emms-info-native--vorbis-identification-header-bindat-spec) @@ -263,7 +263,7 @@ header.") last))) (vorbis vec 6) (eval (unless (equal last emms-info-native--vorbis-magic-array) - (error "Vorbis framing mismatch: expected ‘%s’, got ‘%s’" + (error "Vorbis framing mismatch: expected `%s', got `%s'" emms-info-native--vorbis-magic-array last))) (vorbis-version u32r) @@ -289,7 +289,7 @@ header.") last))) (vorbis vec 6) (eval (unless (equal last emms-info-native--vorbis-magic-array) - (error "Vorbis framing mismatch: expected ‘%s’, got ‘%s’" + (error "Vorbis framing mismatch: expected `%s', got `%s'" emms-info-native--vorbis-magic-array last))) (vendor-length u32r) @@ -319,14 +319,14 @@ header.") (defun emms-info-native--extract-vorbis-comments (user-comments) "Return a decoded list of comments from USER-COMMENTS. USER-COMMENTS should be a list of Vorbis comments according to -‘user-comments’ field in -‘emms-info-native--vorbis-comment-header-bindat-spec’, -‘emms-info-native--opus-comment-header-bindat-spec’ or -‘emms-info-native--flac-comment-block-bindat-spec’. +`user-comments' field in +`emms-info-native--vorbis-comment-header-bindat-spec', +`emms-info-native--opus-comment-header-bindat-spec' or +`emms-info-native--flac-comment-block-bindat-spec'. Return comments in a list of (FIELD . VALUE) cons cells. Only FIELDs that are listed in -‘emms-info-native--accepted-vorbis-fields’ are returned." +`emms-info-native--accepted-vorbis-fields' are returned." (let (comments) (dolist (user-comment user-comments) (let* ((comment (cdr (assoc 'user-comment user-comment))) @@ -339,7 +339,7 @@ FIELDs that are listed in (defun emms-info-native--split-vorbis-comment (comment) "Split Vorbis comment to a field-value pair. -Vorbis comments are of form ‘FIELD=VALUE’. FIELD is a +Vorbis comments are of form `FIELD=VALUE'. FIELD is a case-insensitive field name with a restricted set of ASCII characters. VALUE is an arbitrary UTF-8 encoded octet stream. @@ -363,11 +363,11 @@ outside itself.") (defconst emms-info-native--opus-head-magic-array [79 112 117 115 72 101 97 100] - "Opus identification header magic pattern ‘OpusHead’.") + "Opus identification header magic pattern `OpusHead'.") (defconst emms-info-native--opus-tags-magic-array [79 112 117 115 84 97 103 115] - "Opus comment header magic pattern ‘OpusTags’.") + "Opus comment header magic pattern `OpusTags'.") (defconst emms-info-native--opus-headers-bindat-spec '((identification-header struct emms-info-native--opus-identification-header-bindat-spec) @@ -379,7 +379,7 @@ header.") (defconst emms-info-native--opus-identification-header-bindat-spec '((opus-head vec 8) (eval (unless (equal last emms-info-native--opus-head-magic-array) - (error "Opus framing mismatch: expected ‘%s’, got ‘%s’" + (error "Opus framing mismatch: expected `%s', got `%s'" emms-info-native--opus-head-magic-array last))) (opus-version u8) @@ -406,7 +406,7 @@ header.") (defconst emms-info-native--opus-comment-header-bindat-spec '((opus-tags vec 8) (eval (unless (equal last emms-info-native--opus-tags-magic-array) - (error "Opus framing mismatch: expected ‘%s’, got ‘%s’" + (error "Opus framing mismatch: expected `%s', got `%s'" emms-info-native--opus-tags-magic-array last))) (vendor-length u32r) @@ -450,7 +450,7 @@ header.") "Read and decode comments from FLAC file FILENAME. Return comments in a list of (FIELD . VALUE) cons cells. Only FIELDs that are listed in -‘emms-info-native--accepted-vorbis-fields’ are returned." +`emms-info-native--accepted-vorbis-fields' are returned." (unless (emms-info-native--has-flac-signature filename) (error "Invalid FLAC stream")) (let* ((block (emms-info-native--decode-flac-comment-block @@ -506,12 +506,12 @@ outside itself.") (defconst emms-info-native--id3v2-magic-array [#x49 #x44 #x33] - "id3v2 header magic pattern ‘ID3’.") + "id3v2 header magic pattern `ID3'.") (defconst emms-info-native--id3v2-header-bindat-spec '((file-identifier vec 3) (eval (unless (equal last emms-info-native--id3v2-magic-array) - (error "id3v2 framing mismatch: expected ‘%s’, got ‘%s’" + (error "id3v2 framing mismatch: expected `%s', got `%s'" emms-info-native--id3v2-magic-array last))) (version u8) @@ -525,7 +525,7 @@ outside itself.") (defconst emms-info-native--id3v2-frame-header-bindat-spec '((id str (eval (if (= emms-info-native--id3v2-version 2) 3 4))) (eval (unless (emms-info-native--valid-id3v2-frame-id-p last) - (error "id3v2 frame id ‘%s’ is invalid" last))) + (error "id3v2 frame id `%s' is invalid" last))) (size-bytes vec (eval (if (= emms-info-native--id3v2-version 2) 3 4))) (size eval (emms-info-native--checked-id3v2-size 'frame last)) (flags bits (eval (if (= emms-info-native--id3v2-version 2) 0 2)))) @@ -569,7 +569,7 @@ outside itself.") Return metadata in a list of (FIELD . VALUE) cons cells, or nil in case of errors or if there were no known fields in FILENAME. -See ‘emms-info-native--id3v2-frame-to-info’ for recognized +See `emms-info-native--id3v2-frame-to-info' for recognized fields." (condition-case nil (let* (emms-info-native--id3v2-version @@ -694,8 +694,8 @@ offset after the frame header, and FRAME is the decoded frame." num-bytes unsync) "Read NUM-BYTES of raw id3v2 frame data from FILENAME. -Start reading from offset BEGIN. If UNSYNC is t, all ’FF 00’ -byte combinations are replaced by ’FF’. Replaced byte pairs are +Start reading from offset BEGIN. If UNSYNC is t, all 'FF 00' +byte combinations are replaced by 'FF'. Replaced byte pairs are counted as one, instead of two, towards NUM-BYTES. Return a cons cell (OFFSET . DATA), where OFFSET is the byte @@ -780,7 +780,7 @@ strings." This is a naive implementation that relies solely on filename extension. -Return one of symbols ‘vorbis’, ‘opus’, ‘flac’, or ‘mp3’." +Return one of symbols `vorbis', `opus', `flac', or `mp3'." (let ((case-fold-search t)) (cond ((string-match ".ogg$" filename) 'vorbis) ((string-match ".opus$" filename) 'opus) -- cgit v1.2.3 From 5aea8e7be655d7cb1a6b557cea4334d73d820e33 Mon Sep 17 00:00:00 2001 From: Petteri Hintsanen Date: Tue, 23 Feb 2021 00:04:29 +0200 Subject: Fix id3v2 frame id to info-field mapping Use TDRC for info-date. Also add some more well-known tags so that id3v2 can provide the same info-fields than Vorbis. --- emms-info-native.el | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/emms-info-native.el b/emms-info-native.el index 9517155..9f0ed2d 100644 --- a/emms-info-native.el +++ b/emms-info-native.el @@ -243,7 +243,7 @@ their comments have almost the same format as Vorbis.") "titlesort" "tracknumber" "year") - "Emms info fields that are extracted from Vorbis comments.") + "EMMS info fields that are extracted from Vorbis comments.") (defconst emms-info-native--vorbis-magic-array [118 111 114 98 105 115] @@ -532,30 +532,38 @@ outside itself.") "id3v2 frame header specification.") (defconst emms-info-native--id3v2-frame-to-info - '(("TP1" . "artist") - ("TPE1" . "artist") + '(("TAL" . "album") + ("TALB" . "album") ("TPE2" . "albumartist") + ("TSO2" . "albumartistsort") + ("TSOA" . "albumsort") + ("TP1" . "artist") + ("TPE1" . "artist") ("TSOP" . "artistsort") ("TCM" . "composer") ("TCOM" . "composer") + ("TSOC" . "composersort") + ("TDRC" . "date") + ("TPA" . "discnumber") + ("TPOS" . "discnumber") + ("TCON" . "genre") + ("TPUB" . "label") + ("TDOR" . "originaldate") + ("TOR" . "originalyear") + ("TORY" . "originalyear") ("TIT2" . "title") ("TT2" . "title") ("TSOT" . "titlesort") - ("TALB" . "album") - ("TAL" . "album") - ("TSOA" . "albumsort") - ("TRCK" . "tracknumber") ("TRK" . "tracknumber") - ("TPOS" . "discnumber") - ("TPA" . "discnumber") - ("TDRC" . "date") - ("TDRL" . "date") - ("TYER" . "year") + ("TRCK" . "tracknumber") ("TYE" . "year") - ("TDOR" . "originaldate") - ("TORY" . "originalyear") - ("TOR" . "originalyear")) - "Mapping from id3v2 frame identifiers to info fields.") + ("TYER" . "year")) + "Mapping from id3v2 frame identifiers to EMMS info fields. + +Sources: + +- URL `https://picard-docs.musicbrainz.org/en/appendices/tag_mapping.html' +- URL `http://wiki.hydrogenaud.io/index.php?title=Foobar2000:ID3_Tag_Mapping'") (defconst emms-info-native--id3v2-text-encodings '((0 . latin-1) -- cgit v1.2.3