File: mp3.go 1 package mediainfo 2 3 /* 4 The MIT License (MIT) 5 6 Copyright © 2024 pacman64 7 8 Permission is hereby granted, free of charge, to any person obtaining a copy of 9 this software and associated documentation files (the “Software”), to deal 10 in the Software without restriction, including without limitation the rights to 11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 12 of the Software, and to permit persons to whom the Software is furnished to do 13 so, subject to the following conditions: 14 15 The above copyright notice and this permission notice shall be included in all 16 copies or substantial portions of the Software. 17 18 THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 SOFTWARE. 25 */ 26 27 import ( 28 "errors" 29 "io" 30 "math" 31 ) 32 33 // http://www.mp3-tech.org/programmer/frame_header.html 34 35 /* 36 aaaaaaaaaaa bb cc d eeee ff g h ii jj k l mm 37 11111111111 00 00 0 0000 00 0 0 00 00 0 0 00 frame-sync mask 38 00000000000 11 00 0 0000 00 0 0 00 00 0 0 00 audio version mask 39 00000000000 00 11 0 0000 00 0 0 00 00 0 0 00 audio layer mask 40 00000000000 00 00 1 0000 00 0 0 00 00 0 0 00 CRC check mask 41 00000000000 00 00 0 1111 00 0 0 00 00 0 0 00 bit-rate index mask 42 00000000000 00 00 0 0000 11 0 0 00 00 0 0 00 sample-rate index mask 43 00000000000 00 00 0 0000 00 1 0 00 00 0 0 00 frame-padding check mask 44 45 aaaaaaaa aaabbccd eeeeffgh iijjklmm 46 */ 47 48 // these errors are for the rarely-used reserved options in frame headers 49 var ( 50 ErrMP3ReservedLayer = errors.New("MP3 data use reserved format layer") 51 ErrMP3ReservedVersion = errors.New("MP3 data use reserved format versions") 52 ) 53 54 var mp3BitRates = []int{ 55 0, 0, 0, 0, 0, 0, // free bit-rates 56 32000, 32000, 32000, 32000, 8000, 8000, 57 64000, 48000, 40000, 48000, 16000, 16000, 58 96000, 56000, 48000, 56000, 24000, 24000, 59 128000, 64000, 56000, 64000, 32000, 32000, 60 160000, 80000, 64000, 80000, 40000, 40000, 61 192000, 96000, 80000, 96000, 48000, 48000, 62 224000, 112000, 96000, 112000, 56000, 56000, 63 256000, 128000, 112000, 128000, 64000, 64000, 64 288000, 160000, 128000, 144000, 80000, 80000, 65 320000, 192000, 160000, 160000, 96000, 96000, 66 352000, 224000, 192000, 176000, 112000, 112000, 67 384000, 256000, 224000, 192000, 128000, 128000, 68 416000, 320000, 256000, 224000, 144000, 144000, 69 448000, 384000, 320000, 256000, 160000, 160000, 70 0, 0, 0, 0, 0, 0, // reserved (invalid) space for bit-rates 71 } 72 73 var mp3SampleRates = []int{ 74 44100, 22050, 11025, 75 48000, 24000, 12000, 76 32000, 16000, 8000, 77 0, 0, 0, 78 } 79 80 // https://stackoverflow.com/questions/6220660/calculating-the-length-of-mp3-frames-in-milliseconds 81 var mp3SamplesPerFrame = []int{ 82 384, 1152, 1152, // MPEG 1 83 384, 1152, 576, // MPEG 2 84 384, 1152, 576, // MPEG 2.5 85 } 86 87 // mp3Duration tries to find the duration in seconds of the MP3 stream given 88 func mp3Duration(r io.ReadSeeker) (seconds float64, err error) { 89 // buffers larger than 32kb don't seem to speed things up further 90 var b [32 * 1024]byte 91 92 // how many leading bytes to skip/ignore from current chunk 93 skip := 0 94 95 for i := 0; true; i++ { 96 n, err := r.Read(b[:]) 97 if n <= 0 { 98 return seconds, nil 99 } 100 if err != nil && err != io.EOF { 101 return seconds, err 102 } 103 104 // only check for ID3v2 metadata on the first chunk read 105 if i == 0 && n >= 10 { 106 skip = calcSizeID3v2(b[:n]) 107 } 108 109 // done, if there aren't enough data for a frame intro 110 if n < 3 { 111 return seconds, nil 112 } 113 114 // check whether whole chunk needs skipping, as unlikely as that is 115 if n < skip { 116 skip -= n 117 continue 118 } 119 120 dt, skipnext, err := mp3SliceDuration(b[skip:n]) 121 if err != nil { 122 return seconds, err 123 } 124 125 skip = skipnext 126 seconds += dt 127 } 128 129 return seconds, nil 130 } 131 132 // mp3SliceDuration handles the slice logic for func mp3Duration 133 func mp3SliceDuration(b []byte) (sec float64, skip int, err error) { 134 // when there aren't enough data for a frame, the duration is 0 seconds 135 if len(b) < 3 { 136 return 0, 0, nil 137 } 138 139 // upper-limit for index is 2 less than length, since there's a 2-byte 140 // look-ahead in loop 141 for i := 0; i < len(b)-2; i++ { 142 // frames start with their first 11 bits all on 143 syn := b[i] 144 if syn != 255 { 145 // not all the first 8 bits are on: not a frame-sync 146 continue 147 } 148 h1 := b[i+1] 149 if h1 < 224 { 150 // not all the 3 extra bits are on: not a frame-sync 151 continue 152 } 153 h2 := b[i+2] 154 155 // check the audio layer number using the 3rd-last and 2nd-last bits 156 layer := 0 157 switch h1 & 0b00000110 { 158 case 0: 159 // return t, ErrMP3ReservedLayer 160 continue 161 case 2: 162 layer = 3 163 case 4: 164 layer = 2 165 case 6: 166 layer = 1 167 } 168 169 // check the MPEG version using the 5th-last and 4th-last bits: 170 // version 3 means MPEG 2.5 171 version := 0 172 switch h1 & 0b00011000 { 173 case 0: // MPEG 2.5 174 version = 3 175 case 8: // reserved (invalid) // 0b00001000 176 // return t, ErrMP3ReservedVersion 177 // ignore frames with a reserved-value version, instead of 178 // giving an error 179 continue 180 case 16: // MPEG 2 // 0b00010000 181 version = 2 182 case 24: // MPEG 1 // 0b00011000 183 version = 1 184 } 185 186 // check for frame padding using the 2nd-last bit 187 padding := 0 188 if h2&0b00000010 != 0 { 189 padding = 1 190 } 191 192 bitRateRow := int(h2 >> 4) 193 if bitRateRow == 0 || bitRateRow == 15 { 194 continue 195 } 196 197 sampleRateRow := int(h2 & 0b00001100 >> 2) 198 if sampleRateRow == 3 { 199 continue 200 } 201 202 bitRate := 0 203 switch version { 204 case 1: 205 bitRate = mp3BitRates[6*bitRateRow+layer-1] 206 case 2, 3: 207 bitRate = mp3BitRates[6*bitRateRow+2*layer-1] 208 } 209 sampleRate := mp3SampleRates[3*sampleRateRow+version-1] 210 211 // update time duration value 212 spf := mp3SamplesPerFrame[3*(version-1)+layer-1] 213 sec += float64(spf) / float64(sampleRate) 214 215 // calculate how many bytes to jump forward 216 // 217 // http://www.mp3-converter.com/mp3codec/frames.htm 218 // the formula suggested there seems wrong 219 // frame_size = 144 * bit_rate / (sample_rate + padding) 220 // and should instead be 221 // frame_size = floor(144 * bit_rate / sample_rate) + padding 222 n := int(math.Floor(float64(144*bitRate)/float64(sampleRate))) + padding 223 224 // handle skipping inside current slice 225 if i+n < len(b)-3 { 226 // jump ahead by n - 1 instead of n, since the loop already adds 1 227 i += n - 1 228 continue 229 } 230 231 // handle skipping beyond current slice 232 skip = n - (len(b) - i) 233 if skip < 0 { 234 skip = 0 235 } 236 return sec, skip, nil 237 } 238 239 return sec, 0, nil 240 }