fordon_greeman / voidfiles

Branch: 2079ec5
Switch branches/tags
main
theidioticdev add emacs 2079ec5 21h ago
757 lines · 27.2 KB Raw Blame History
1 ;;; emms-info-native-mp3.el --- EMMS info functions for MP3 files -*- lexical-binding: t; -*-
2
3 ;; Copyright (C) 2020-2024 Free Software Foundation, Inc.
4
5 ;; Author: Petteri Hintsanen <petterih@iki.fi>
6
7 ;; This file is part of EMMS.
8
9 ;; EMMS is free software; you can redistribute it and/or modify it
10 ;; under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 3, or (at your option)
12 ;; any later version.
13
14 ;; EMMS is distributed in the hope that it will be useful, but WITHOUT
15 ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
17 ;; License for more details.
18
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with EMMS; see the file COPYING. If not, write to the Free
21 ;; Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22 ;; MA 02110-1301, USA.
23
24 ;;; Commentary:
25
26 ;; This file contains functions for extracting metadata from MP3 files
27 ;; with ID3v2 tags. The code is based on ID3v2 Informal Standards,
28 ;; see https://id3lib.sourceforge.net/id3/
29
30 ;; All ID3v2 versions should be recognized, but many features like
31 ;; CRC, compression and encryption are not supported. Since MP3 does
32 ;; not have a generally agreed-upon format for specifying the stream
33 ;; length, the reported playing time is only an estimate. For
34 ;; constant bit rate streams the estimate is usually accurate, but for
35 ;; variable bit rate streams it may be completely wrong, especially if
36 ;; there are no Xing/VBRI headers embedded in the file.
37
38 ;; For technical details on MP3 duration estimation, see
39 ;; https://www.codeproject.com/Articles/8295/MPEG-Audio-Frame-Header.
40
41 ;;; Code:
42
43 (require 'emms)
44 (require 'bindat)
45 (eval-when-compile (require 'subr-x))
46
47
48 ;;; ID3 code
49
50 (defvar emms-info-native-id3v2--version 0
51 "Last decoded ID3v2 version.")
52
53 (defconst emms-info-native-id3v2--magic-pattern "ID3"
54 "ID3v2 header magic pattern.")
55
56 (defconst emms-info-native-id3v2--header-bindat-spec
57 (if (eval-when-compile (fboundp 'bindat-type))
58 (bindat-type
59 (file-identifier str 3)
60 (_ unit (unless (equal file-identifier emms-info-native-id3v2--magic-pattern)
61 (error "ID3v2 framing mismatch: expected `%s', got `%s'"
62 emms-info-native-id3v2--magic-pattern
63 file-identifier)))
64 (version u8)
65 (_ unit (progn (setq emms-info-native-id3v2--version version) nil))
66 (revision u8)
67 (flags bits 1)
68 (size-bytes vec 4)
69 (size unit (emms-info-native-id3v2--checked-size 'tag size-bytes)))
70 '((file-identifier str 3)
71 (eval (unless (equal last emms-info-native-id3v2--magic-pattern)
72 (error "ID3v2 framing mismatch: expected `%s', got `%s'"
73 emms-info-native-id3v2--magic-pattern
74 last)))
75 (version u8)
76 (eval (setq emms-info-native-id3v2--version last))
77 (revision u8)
78 (flags bits 1)
79 (size-bytes vec 4)
80 (size eval (emms-info-native-id3v2--checked-size 'tag last))))
81 "ID3v2 header specification.")
82
83 (defconst emms-info-native-id3v2--frame-header-bindat-spec
84 (if (eval-when-compile (fboundp 'bindat-type))
85 (bindat-type
86 (id str (if (= emms-info-native-id3v2--version 2) 3 4))
87 (_ unit (unless (emms-info-native-id3v2--valid-frame-id-p id)
88 (error "ID3v2 frame id `%s' is invalid" id)))
89 (size-bytes vec (if (= emms-info-native-id3v2--version 2) 3 4))
90 (size unit (emms-info-native-id3v2--checked-size 'frame size-bytes))
91 (flags bits (if (= emms-info-native-id3v2--version 2) 0 2)))
92 '((id str (eval (if (= emms-info-native-id3v2--version 2) 3 4)))
93 (eval (unless (emms-info-native-id3v2--valid-frame-id-p last)
94 (error "ID3v2 frame id `%s' is invalid" last)))
95 (size-bytes vec (eval (if (= emms-info-native-id3v2--version 2) 3 4)))
96 (size eval (emms-info-native-id3v2--checked-size 'frame last))
97 (flags bits (eval (if (= emms-info-native-id3v2--version 2) 0 2)))))
98 "ID3v2 frame header specification.")
99
100 (defconst emms-info-native-id3v2--frame-to-info
101 '(("TAL" . "album")
102 ("TALB" . "album")
103 ("TPE2" . "albumartist")
104 ("TSO2" . "albumartistsort")
105 ("TSOA" . "albumsort")
106 ("TP1" . "artist")
107 ("TPE1" . "artist")
108 ("TSOP" . "artistsort")
109 ("TCM" . "composer")
110 ("TCOM" . "composer")
111 ("TSOC" . "composersort")
112 ("TDRC" . "date")
113 ("TPA" . "discnumber")
114 ("TPOS" . "discnumber")
115 ("TCON" . genre)
116 ("TPUB" . "label")
117 ("TDOR" . "originaldate")
118 ("TOR" . "originalyear")
119 ("TORY" . "originalyear")
120 ("TIT2" . "title")
121 ("TT2" . "title")
122 ("TSOT" . "titlesort")
123 ("TRK" . "tracknumber")
124 ("TRCK" . "tracknumber")
125 ("TYE" . "year")
126 ("TYER" . "year")
127 ("TXXX" . user-defined))
128 "Mapping from ID3v2 frame identifiers to EMMS info fields.
129
130 Sources:
131
132 - URL `https://picard-docs.musicbrainz.org/en/appendices/tag_mapping.html'
133 - URL `http://wiki.hydrogenaud.io/index.php?title=Foobar2000:ID3_Tag_Mapping'")
134
135 (defconst emms-info-id3v1--genres
136 '((0 . "Blues")
137 (1 . "Classic Rock")
138 (2 . "Country")
139 (3 . "Dance")
140 (4 . "Disco")
141 (5 . "Funk")
142 (6 . "Grunge")
143 (7 . "Hip-Hop")
144 (8 . "Jazz")
145 (9 . "Metal")
146 (10 . "New Age")
147 (11 . "Oldies")
148 (12 . "Other")
149 (13 . "Pop")
150 (14 . "R&B")
151 (15 . "Rap")
152 (16 . "Reggae")
153 (17 . "Rock")
154 (18 . "Techno")
155 (19 . "Industrial")
156 (20 . "Alternative")
157 (21 . "Ska")
158 (22 . "Death Metal")
159 (23 . "Pranks")
160 (24 . "Soundtrack")
161 (25 . "Euro-Techno")
162 (26 . "Ambient")
163 (27 . "Trip-Hop")
164 (28 . "Vocal")
165 (29 . "Jazz+Funk")
166 (30 . "Fusion")
167 (31 . "Trance")
168 (32 . "Classical")
169 (33 . "Instrumental")
170 (34 . "Acid")
171 (35 . "House")
172 (36 . "Game")
173 (37 . "Sound Clip")
174 (38 . "Gospel")
175 (39 . "Noise")
176 (40 . "AlternRock")
177 (41 . "Bass")
178 (42 . "Soul")
179 (43 . "Punk")
180 (44 . "Space")
181 (45 . "Meditative")
182 (46 . "Instrumental Pop")
183 (47 . "Instrumental Rock")
184 (48 . "Ethnic")
185 (49 . "Gothic")
186 (50 . "Darkwave")
187 (51 . "Techno-Industrial")
188 (52 . "Electronic")
189 (53 . "Pop-Folk")
190 (54 . "Eurodance")
191 (55 . "Dream")
192 (56 . "Southern Rock")
193 (57 . "Comedy")
194 (58 . "Cult")
195 (59 . "Gangsta")
196 (60 . "Top 40")
197 (61 . "Christian Rap")
198 (62 . "Pop/Funk")
199 (63 . "Jungle")
200 (64 . "Native American")
201 (65 . "Cabaret")
202 (66 . "New Wave")
203 (67 . "Psychadelic")
204 (68 . "Rave")
205 (69 . "Showtunes")
206 (70 . "Trailer")
207 (71 . "Lo-Fi")
208 (72 . "Tribal")
209 (73 . "Acid Punk")
210 (74 . "Acid Jazz")
211 (75 . "Polka")
212 (76 . "Retro")
213 (77 . "Musical")
214 (78 . "Rock & Roll")
215 (79 . "Hard Rock")
216 (80 . "Folk")
217 (81 . "Folk-Rock")
218 (82 . "National Folk")
219 (83 . "Swing")
220 (84 . "Fast Fusion")
221 (85 . "Bebob")
222 (86 . "Latin")
223 (87 . "Revival")
224 (88 . "Celtic")
225 (89 . "Bluegrass")
226 (90 . "Avantgarde")
227 (91 . "Gothic Rock")
228 (92 . "Progressive Rock")
229 (93 . "Psychedelic Rock")
230 (94 . "Symphonic Rock")
231 (95 . "Slow Rock")
232 (96 . "Big Band")
233 (97 . "Chorus")
234 (98 . "Easy Listening")
235 (99 . "Acoustic")
236 (100 . "Humour")
237 (101 . "Speech")
238 (102 . "Chanson")
239 (103 . "Opera")
240 (104 . "Chamber Music")
241 (105 . "Sonata")
242 (106 . "Symphony")
243 (107 . "Booty Bass")
244 (108 . "Primus")
245 (109 . "Porn Groove")
246 (110 . "Satire")
247 (111 . "Slow Jam")
248 (112 . "Club")
249 (113 . "Tango")
250 (114 . "Samba")
251 (115 . "Folklore")
252 (116 . "Ballad")
253 (117 . "Power Ballad")
254 (118 . "Rhythmic Soul")
255 (119 . "Freestyle")
256 (120 . "Duet")
257 (121 . "Punk Rock")
258 (122 . "Drum Solo")
259 (123 . "A cappella")
260 (124 . "Euro-House")
261 (125 . "Dance Hall"))
262 "ID3v1 genres.")
263
264 (defconst emms-info-native-id3v2--text-encodings
265 '((0 . latin-1)
266 (1 . utf-16)
267 (2 . uft-16be)
268 (3 . utf-8))
269 "ID3v2 text encodings.")
270
271 (defun emms-info-native-id3v2--valid-frame-id-p (id)
272 "Return t if ID is a proper ID3v2 frame identifier, nil otherwise."
273 (if (= emms-info-native-id3v2--version 2)
274 (string-match "^[A-Z0-9]\\{3\\}$" id)
275 (string-match "^[A-Z0-9]\\{4\\}$" id)))
276
277 (defun emms-info-native-id3v2--checked-size (elt bytes)
278 "Calculate ID3v2 element ELT size from BYTES.
279 ELT must be either `tag' or `frame'.
280
281 Return the size. Signal an error if the size is zero."
282 (let ((size (cond ((eq elt 'tag)
283 (emms-info-native-id3v2--decode-size bytes t))
284 ((eq elt 'frame)
285 (if (= emms-info-native-id3v2--version 4)
286 (emms-info-native-id3v2--decode-size bytes t)
287 (emms-info-native-id3v2--decode-size bytes nil))))))
288 (if (zerop size)
289 (error "ID3v2 tag/frame size is zero")
290 size)))
291
292 (defun emms-info-native-id3v2--decode-size (bytes syncsafe)
293 "Decode ID3v2 element size from BYTES.
294 Depending on SYNCSAFE, BYTES are interpreted as 7- or 8-bit
295 bytes, MSB first.
296
297 Return the decoded size."
298 (let ((num-bits (if syncsafe 7 8))
299 (mask (if syncsafe #x7f #xff)))
300 (apply '+ (seq-map-indexed (lambda (elt idx)
301 (* (expt 2 (* num-bits idx))
302 (logand elt mask)))
303 (reverse bytes)))))
304
305 (defun emms-info-native-id3v2--decode-header (filename)
306 "Read and decode ID3v2 header from FILENAME."
307 (with-temp-buffer
308 (set-buffer-multibyte nil)
309 (insert-file-contents-literally filename nil 0 10)
310 (bindat-unpack emms-info-native-id3v2--header-bindat-spec
311 (buffer-string))))
312
313 (defun emms-info-native-id3v2--checked-ext-header-size (filename)
314 "Read and decode ID3v2 extended header size from FILENAME.
315 Return the size. Signal an error if the size is zero."
316 (with-temp-buffer
317 (set-buffer-multibyte nil)
318 (insert-file-contents-literally filename nil 10 14)
319 (emms-info-native-id3v2--checked-size 'frame (buffer-string))))
320
321 (defun emms-info-native-id3v2--decode-frames (filename begin end unsync)
322 "Read and decode ID3v2 text frames from FILENAME.
323 BEGIN should be the offset of first byte of the first frame, and
324 END should be the offset after the complete ID3v2 tag.
325
326 If UNSYNC is non-nil, the frames are assumed to have gone through
327 unsynchronization and decoded as such.
328
329 Return metadata in a list of (FIELD . VALUE) cons cells."
330 (let ((offset begin)
331 (limit (- end (emms-info-native-id3v2--frame-header-size)))
332 comments)
333 (ignore-errors
334 (while (< offset limit)
335 (let* ((frame-data (emms-info-native-id3v2--decode-frame
336 filename offset unsync))
337 (next-frame-offset (car frame-data))
338 (comment (cdr frame-data)))
339 (when comment (push comment comments))
340 (setq offset next-frame-offset))))
341 comments))
342
343 (defun emms-info-native-id3v2--frame-header-size ()
344 "Return the last decoded header size in bytes."
345 (if (= emms-info-native-id3v2--version 2) 6 10))
346
347 (defun emms-info-native-id3v2--decode-frame (filename offset unsync)
348 "Read and decode a single ID3v2 frame from FILENAME.
349 Start reading the frame from byte offset OFFSET. See
350 `emms-info-native-id3v2--read-frame-data' for details on UNSYNC.
351
352 Skip frames that do not map to any info-id in
353 `emms-info-native-id3v2--frame-to-info'.
354
355 Return cons cell (NEXT . FRAME), where NEXT is the offset of the
356 next frame (if any) and FRAME is the decoded frame. See
357 `emms-info-native-id3v2--decode-frame-data'."
358 (let* ((decoded (emms-info-native-id3v2--decode-frame-header
359 filename offset))
360 (data-offset (car decoded))
361 (header (cdr decoded))
362 (frame-id (bindat-get-field header 'id))
363 (info-id (cdr (assoc frame-id emms-info-native-id3v2--frame-to-info)))
364 (size (bindat-get-field header 'size)))
365 (if (or info-id unsync)
366 ;; Note that if unsync is non-nil, we have to always read the
367 ;; frame to determine next-frame-offset.
368 (let* ((data (emms-info-native-id3v2--read-frame-data
369 filename data-offset size unsync))
370 (next-frame-offset (car data))
371 (value (emms-info-native-id3v2--decode-frame-data
372 (cdr data) info-id)))
373 (cons next-frame-offset value))
374 ;; Skip the frame.
375 (cons (+ data-offset size) nil))))
376
377 (defun emms-info-native-id3v2--decode-frame-header (filename begin)
378 "Read and decode ID3v2 frame header from FILENAME.
379 Start reading from byte offset BEGIN.
380
381 Return a cons cell (OFFSET . HEADER), where OFFSET is the byte
382 offset after the frame header, and HEADER is the decoded frame
383 header."
384 (with-temp-buffer
385 (set-buffer-multibyte nil)
386 (let ((end (+ begin (emms-info-native-id3v2--frame-header-size))))
387 (insert-file-contents-literally filename nil begin end)
388 (cons end
389 (bindat-unpack emms-info-native-id3v2--frame-header-bindat-spec
390 (buffer-string))))))
391
392 (defun emms-info-native-id3v2--read-frame-data (filename begin num-bytes unsync)
393 "Read NUM-BYTES of raw ID3v2 frame data from FILENAME.
394 Start reading from offset BEGIN. If UNSYNC is non-nil, all \"FF
395 00\" byte combinations are replaced by \"FF\". Replaced byte
396 pairs are counted as one, instead of two, towards NUM-BYTES.
397
398 Return a cons cell (OFFSET . DATA), where OFFSET is the byte
399 offset after NUM-BYTES bytes have been read, and DATA is the raw
400 data."
401 (with-temp-buffer
402 (set-buffer-multibyte nil)
403 (if unsync
404 ;; Reverse unsynchronization.
405 (let ((peek-end (+ begin (* 2 num-bytes)))
406 (end num-bytes))
407 (insert-file-contents-literally filename nil begin peek-end)
408 (goto-char (point-min))
409 (while (and (re-search-forward (string 255 0) nil t)
410 (< (point) end))
411 (replace-match (string 255))
412 (setq end (1+ end)))
413 (delete-region (1+ num-bytes) (point-max))
414 (cons (+ begin end) (buffer-string)))
415 ;; No unsynchronization: read the data as-is.
416 (let ((end (+ begin num-bytes)))
417 (insert-file-contents-literally filename nil begin end)
418 (cons end (buffer-string))))))
419
420 (defun emms-info-native-id3v2--decode-frame-data (data info-id)
421 "Decode ID3v2 text frame data DATA.
422 If INFO-ID is `user-defined', assume that DATA is a TXXX frame
423 with key/value-pair. Extract the key and, if it is a mapped
424 element in `emms-info-native-id3v2--frame-to-info', use it as INFO-ID.
425
426 If INFO-ID is `genre', assume that DATA is either an integral
427 ID3v1 genre reference or a plain genre string. In the former
428 case map the reference to a string via `emms-info-id3v1--genres';
429 in the latter case use the genre string verbatim.
430
431 Return a cons cell (INFO-ID . VALUE) where VALUE is the decoded
432 string, or nil if the decoding failed."
433 (when info-id
434 (let ((str (emms-info-native-id3v2--decode-string data)))
435 (cond ((string-empty-p str) nil)
436 ((stringp info-id) (cons info-id str))
437 ((eq info-id 'genre)
438 (if (string-match "^(?\\([0-9]+\\))?" str)
439 (let ((v1-genre
440 (assoc (string-to-number (match-string 1 str))
441 emms-info-id3v1--genres)))
442 (when v1-genre (cons "genre" (cdr v1-genre))))
443 (cons "genre" str)))
444 ((eq info-id 'user-defined)
445 (let* ((key-val (split-string str (string 0)))
446 (key (downcase (car key-val)))
447 (val (cadr key-val)))
448 (when (and (rassoc key emms-info-native-id3v2--frame-to-info)
449 (not (string-empty-p val)))
450 (cons key val))))))))
451
452 (defun emms-info-native-id3v2--decode-string (bytes)
453 "Decode ID3v2 text information from BYTES.
454 Remove the terminating null byte, if any.
455
456 Return the text as string."
457 (let* ((encoding (emms-info-native-id3v2--text-encoding bytes))
458 (decoded (decode-coding-string (seq-rest bytes) encoding)))
459 (if (and (> (length decoded) 0)
460 (equal (substring decoded -1) "\0"))
461 (substring decoded 0 -1)
462 decoded)))
463
464 (defun emms-info-native-id3v2--text-encoding (bytes)
465 "Return the encoding for text information BYTES."
466 (alist-get (seq-first bytes)
467 emms-info-native-id3v2--text-encodings))
468
469
470 ;;; MP3 code
471
472 (defconst emms-info-native-mp3--versions
473 '((0 . mpeg25)
474 (1 . reserved)
475 (2 . mpeg2)
476 (3 . mpeg1))
477 "MPEG versions.")
478
479 (defconst emms-info-native-mp3--layers
480 '((0 . reserved)
481 (1 . layerIII)
482 (2 . layerII)
483 (3 . layerI))
484 "MPEG Audio Layers.")
485
486 (defconst emms-info-native-mp3--channel-modes
487 '((0 . stereo)
488 (1 . joint-stereo)
489 (2 . dual-channel)
490 (3 . single-channel))
491 "MPEG channel modes.")
492
493 (defconst emms-info-native-mp3--bit-rates
494 '((mpeg1-layerI free 32 64 96 128 160 192 224 256 288 320 352 384 416 448 reserved)
495 (mpeg1-layerII free 32 48 56 64 80 96 112 128 160 192 224 256 320 384 reserved)
496 (mpeg1-layerIII free 32 40 48 56 64 80 96 112 128 160 192 224 256 320 reserved)
497 (mpeg2x-layerI free 32 48 56 64 80 96 112 128 144 160 176 192 224 256 reserved)
498 (mpeg2x-layerII-III free 8 16 24 32 40 48 56 64 80 96 112 128 144 160 reserved))
499 "Bit rates for each MPEG version/layer combination.")
500
501 (defconst emms-info-native-mp3--samples-per-frame
502 '((layerI . 384)
503 (layerII . 1152)
504 (layerIII-mpeg1 . 1152)
505 (layerIII-mpeg2x . 576))
506 "Samples per frame for each MPEG version/layer combination.")
507
508 (defconst emms-info-native-mp3--sample-rates
509 '((mpeg1 44100 48000 32000 reserved)
510 (mpeg2 22050 24000 16000 reserved)
511 (mpeg25 11025 12000 8000 reserved))
512 "Sample rate for each MPEG version/layer combination.")
513
514 (defconst emms-info-native-mp3--vbri-header-bindat-spec
515 (if (eval-when-compile (fboundp 'bindat-type))
516 (bindat-type
517 (id str 4)
518 (version uint 16)
519 (delay uint 16)
520 (quality uint 16)
521 (bytes uint 32)
522 (frames uint 32))
523 '((id str 4)
524 (version u16)
525 (delay u16)
526 (quality u16)
527 (bytes u32)
528 (frames u32)))
529 "VBR header, VBRI format.
530 This specification is purposefully incomplete, as we are
531 interested only in the frame count.")
532
533 (defconst emms-info-native-mp3--xing-header-bindat-spec
534 (if (eval-when-compile (fboundp 'bindat-type))
535 (bindat-type
536 (id vec 4)
537 (flags bits 4)
538 (frames uint 32))
539 '((id vec 4)
540 (flags bits 4)
541 (frames u32)))
542 "VBR header, Xing/Info format.
543 This specification is purposefully incomplete, as we are
544 interested only in the frame count.")
545
546 (defun emms-info-native-mp3-decode-metadata (filename)
547 "Read and decode metadata from MP3 file FILENAME.
548 Return metadata in a list of (FIELD . VALUE) cons cells, or nil
549 in case of errors or if there were no known fields in FILENAME.
550 Also try to estimate the stream duration, and return it in
551 `playing-time' field if successful.
552
553 See `emms-info-native-id3v2--frame-to-info' for recognized fields."
554 (let* (emms-info-native-id3v2--version
555 (header (emms-info-native-id3v2--decode-header filename))
556 (tag-size (bindat-get-field header 'size))
557 (unsync (memq 7 (bindat-get-field header 'flags)))
558 (offset 10))
559 (when (memq 6 (bindat-get-field header 'flags))
560 ;; Skip the extended header.
561 (setq offset (+ offset
562 (emms-info-native-id3v2--checked-ext-header-size
563 filename))))
564 (let ((tags
565 (emms-info-native-id3v2--decode-frames
566 filename offset (+ tag-size 10) unsync))
567 (playtime
568 (emms-info-native-mp3--decode-duration filename (+ tag-size 10))))
569 (nconc tags (when playtime
570 (list (cons "playing-time" playtime)))))))
571
572 (defun emms-info-native-mp3--decode-duration (filename offset)
573 "Decode or estimate stream duration for MP3 file FILENAME.
574 Start looking for necessary headers from byte offset OFFSET.
575
576 Return the duration in seconds, or nil in case of errors."
577 (with-temp-buffer
578 (set-buffer-multibyte nil)
579 (insert-file-contents-literally filename nil offset (+ offset 1024))
580 (let* ((header
581 (emms-info-native-mp3--find-and-decode-frame-header))
582 (samples-per-frame
583 (alist-get 'samples-per-frame header))
584 (sample-rate
585 (alist-get 'sample-rate header))
586 (bit-rate
587 (alist-get 'bit-rate header))
588 (frames
589 (or (emms-info-native-mp3--find-and-decode-xing-header)
590 (emms-info-native-mp3--find-and-decode-vbri-header))))
591 (cond ((and frames samples-per-frame (numberp sample-rate))
592 ;; The file has a usable VBR (Xing, Info or VBRI) header.
593 (/ (* frames samples-per-frame) sample-rate))
594 (bit-rate
595 ;; The file does not have a usable VBR header, therefore
596 ;; estimate the duration.
597 (emms-info-native-mp3--estimate-duration filename bit-rate))))))
598
599 (defun emms-info-native-mp3--find-and-decode-frame-header ()
600 "Find and decode MP3 audio frame header from the current buffer.
601 Return the decoded header in an alist, or nil if a header cannot
602 be found or decoded.
603
604 See `emms-info-native-mp3--decode-frame-header' for details."
605 (let (header)
606 (goto-char (point-min))
607 (ignore-errors
608 (while (and (not header)
609 (search-forward (string 255) nil t))
610 (let ((bytes
611 (emms-be-to-int
612 (buffer-substring-no-properties (- (point) 1)
613 (+ (point) 3)))))
614 (setq header
615 (emms-info-native-mp3--decode-frame-header bytes)))))
616 header))
617
618 (defun emms-info-native-mp3--decode-frame-header (header)
619 "Decode 32-bit numeric HEADER data.
620 Pack its elements to an alist and return the list. Return nil if
621 HEADER does not have MP3 sync bits set."
622 (when (= (logand header #xffe00000) #xffe00000)
623 (let* ((version-bits
624 (emms-extract-bits header 19 20))
625 (layer-bits
626 (emms-extract-bits header 17 18))
627 (crc-bit
628 (emms-extract-bits header 16))
629 (bit-rate-bits
630 (emms-extract-bits header 12 15))
631 (sample-rate-bits
632 (emms-extract-bits header 10 11))
633 (padding-bit
634 (emms-extract-bits header 9))
635 (private-bit
636 (emms-extract-bits header 8))
637 (channel-mode-bits
638 (emms-extract-bits header 6 7))
639 (mode-extension-bits
640 (emms-extract-bits header 4 5))
641 (copyright-bit
642 (emms-extract-bits header 3))
643 (original-bit
644 (emms-extract-bits header 2))
645 (emphasis-bits
646 (emms-extract-bits header 0 1))
647 (version
648 (alist-get version-bits
649 emms-info-native-mp3--versions))
650 (layer
651 (alist-get layer-bits
652 emms-info-native-mp3--layers))
653 (channel-mode
654 (alist-get channel-mode-bits
655 emms-info-native-mp3--channel-modes))
656 (sample-rate
657 (nth sample-rate-bits
658 (alist-get version
659 emms-info-native-mp3--sample-rates)))
660 (samples-per-frame
661 (emms-info-native-mp3--samples-per-frame
662 version layer))
663 (bit-rate
664 (emms-info-native-mp3--decode-bit-rate
665 version layer bit-rate-bits)))
666 (list (cons 'version version)
667 (cons 'layer layer)
668 (cons 'crc crc-bit)
669 (cons 'bit-rate bit-rate)
670 (cons 'sample-rate sample-rate)
671 (cons 'samples-per-frame samples-per-frame)
672 (cons 'padding padding-bit)
673 (cons 'private private-bit)
674 (cons 'channel-mode channel-mode)
675 (cons 'mode-extension mode-extension-bits)
676 (cons 'copyright copyright-bit)
677 (cons 'emphasis emphasis-bits)
678 (cons 'original original-bit)))))
679
680 (defun emms-info-native-mp3--find-and-decode-xing-header ()
681 "Find and decode Xing VBR header from the current buffer.
682 Return the number of frames in the stream, or nil if a header
683 cannot be found or decoded."
684 (goto-char (point-min))
685 (when (re-search-forward "Xing\\|Info" (point-max) t)
686 (let ((header
687 (bindat-unpack emms-info-native-mp3--xing-header-bindat-spec
688 (buffer-string)
689 (1- (match-beginning 0)))))
690 (when (memq 0 (bindat-get-field header 'flags))
691 (bindat-get-field header 'frames)))))
692
693 (defun emms-info-native-mp3--find-and-decode-vbri-header ()
694 "Find and decode VBRI header from the current buffer.
695 Return the number of frames in the stream, or nil if a header
696 cannot be found or decoded."
697 (goto-char (point-min))
698 (when (re-search-forward "VBRI" (point-max) t)
699 (let ((header
700 (bindat-unpack emms-info-native-mp3--vbri-header-bindat-spec
701 (buffer-string)
702 (1- (match-beginning 0)))))
703 (bindat-get-field header 'frames))))
704
705 (defun emms-info-native-mp3--estimate-duration (filename bitrate)
706 "Estimate stream duration for FILENAME.
707 Assume constant encoding bit rate of BITRATE kilobits per second.
708 Return the estimated stream duration in seconds, or nil in case
709 of errors."
710 (let ((size
711 (file-attribute-size
712 (file-attributes (file-chase-links filename)))))
713 (when (and size (numberp bitrate))
714 (/ (* 8 size) (* 1000 bitrate)))))
715
716 (defun emms-info-native-mp3--decode-bit-rate (version layer bits)
717 "Return the bit rate for MPEG VERSION/LAYER combination.
718 BITS is the bitrate index from MP3 header."
719 (cond ((eq version 'mpeg1)
720 (cond ((eq layer 'layerI)
721 (nth bits
722 (alist-get 'mpeg1-layerI
723 emms-info-native-mp3--bit-rates)))
724 ((eq layer 'layerII)
725 (nth bits
726 (alist-get 'mpeg1-layerII
727 emms-info-native-mp3--bit-rates)))
728 ((eq layer 'layerIII)
729 (nth bits
730 (alist-get 'mpeg1-layerIII
731 emms-info-native-mp3--bit-rates)))))
732 (t (cond ((eq layer 'layerI)
733 (nth bits
734 (alist-get 'mpeg2x-layerI
735 emms-info-native-mp3--bit-rates)))
736 (t (nth bits
737 (alist-get 'mpeg2x-layerII-III
738 emms-info-native-mp3--bit-rates)))))))
739
740 (defun emms-info-native-mp3--samples-per-frame (version layer)
741 "Return the samples per frame for MPEG VERSION/LAYER combination."
742 (cond ((eq layer 'layerIII)
743 (cond ((eq version 'mpeg1)
744 (alist-get 'layerIII-mpeg1
745 emms-info-native-mp3--samples-per-frame))
746 (t (alist-get 'layerIII-mpeg2x
747 emms-info-native-mp3--samples-per-frame))))
748 ((eq layer 'layerII)
749 (alist-get 'layerII
750 emms-info-native-mp3--samples-per-frame))
751 ((eq layer 'layerI)
752 (alist-get 'layerI
753 emms-info-native-mp3--samples-per-frame))))
754
755 (provide 'emms-info-native-mp3)
756
757 ;;; emms-info-native-mp3.el ends here