;; ================================================== ;; Script: mp3-stream-parser.r ;; downloaded from: www.REBOL.org ;; on: 29-Mar-2024 ;; at: 1:49:48.641708 UTC ;; owner: oldes [script library member who can update ;; this script] ;; ================================================== ;; ================================================== ;; email address(es) have been munged to protect them ;; from spam harvesters. ;; If you were logged on the email addresses ;; would not be munged ;; ================================================== REBOL [ Title: "MP3 stream parser" Name: "mp3-stream-parser" Date: 1-Apr-2009/21:10:30+2:00 File: %mp3-stream-parser.r Version: 1.0.0 Home: http://box.lebeda.ws/~hmm/rebol/mp3.html Author: "Oldes" Owner: none Rights: none Needs: none Tabs: none Usage: [ ;to get just a part of MP3 file: mp3/open http://box.lebeda.ws/~hmm/mp3/Electric_Bazar_Cie-America.mp3 write/binary %test.mp3 mp3/get-sample-of-length 0:0:4.5 ;you can continue to read other samples here... ;...and close the stream if you don't need it mp3/close ;to go thru complete file and parse it: mp3/parse/file %test.mp3 print [ "Frames total:" mp3/num_frames "^/Duration: " to-time mp3/duration ] ] Purpose: {MP3 parser with streaming input} Comment: none History: none Language: none Type: none Content: none Email: %oliva--david--seznam--cz library: [ level: [intermediate advanced] type: 'tool domain: 'file-handling platform: 'all tested-under: none support: none license: 'public-domain see-also: none ] ] ;### MP3 MP3: context [ inBuffer: outBuffer: MP3-port: ID3v2: ID3v1: frame: MP3FrameHeader: continue?: none num_frames: duration: 0 headers: make hash! [] ;used to cache parsed mp3 frame headers ;## Functions used to read data from input stream seekToBuffer: func[bytes /local new][ either any [ none? inBuffer tail? inBuffer ][ inBuffer: copy/part MP3-port bytes ;print "seekToBuffer <-complete" ][ inBuffer: remove/part head inBuffer (-1 + index? inBuffer) if all [ 0 < bytes: (bytes - length? inBuffer) ; bytes > 8192 ][ if new: copy/part MP3-port bytes [ insert tail inBuffer new ;print ["seekToBuffer <-only" bytes] ] ] ] inBuffer ] checkBufferSize: func[bytes][ if bytes > length? inBuffer [ ;print "Refilling inBuffer" seekToBuffer bytes ] bytes ] skipBytes: func[nbytes][inBuffer: skip inBuffer nbytes] readBytes: func[nbytes][ copy/part inBuffer inBuffer: skip inBuffer nbytes ] readBytesRev: func[nbytes][ head reverse copy/part inBuffer inBuffer: skip inBuffer nbytes ] readUI8: has[i][i: first inBuffer inBuffer: next inBuffer i] readUI16: does[to integer! readBytesRev 2] readUI32: does[to integer! readBytesRev 4] readSynchSafeInt: does [ ((readUI8 and 127) * 2097152)+ ((readUI8 and 127) * 16384)+ ((readUI8 and 127) * 128)+ ( readUI8 and 127) ] readID3v1: does [ checkBufferSize 128 ID3v1: context [ Type: 1 Title: as-string trim/with readBytes 30 #"^(00)" Artist: as-string trim/with readBytes 30 #"^(00)" Album: as-string trim/with readBytes 30 #"^(00)" Year: to-integer as-string readBytes 4 Comment: as-string trim/with readBytes 30 #"^(00)" Gendre: readUI8 ] ] readID3v2: does [ ;http://www.id3.org/id3v2.4.0-structure ID3v2: context [ Type: 2 Version: readUI16 Flags: readUI8 Data: readBytes checkBufferSize readSynchSafeInt ;<-not parsed yet ] ] readMP3FrameHeader: has[hdrb hdr ][ hdrb: next copy/part inBuffer 4 unless MP3FrameHeader: select headers hdrb [ hdr: to integer! hdrb if 14680064 = (14680064 and hdr) [ MP3FrameHeader: context [ MpegVersion: shift (1572864 and hdr) 19 Layer: shift (393216 and hdr) 17 ;Protected?: shift (65536 and hdr) 16 Bitrate: pick (switch layer either MpegVersion = 3 [[ 3 [[32 64 96 128 160 192 224 256 288 320 352 384 416 448]] 2 [[32 48 56 64 80 96 112 128 160 192 224 256 320 384]] 1 [[32 40 48 56 64 80 96 112 128 160 192 224 256 320]] ]][[ 3 [[32 48 56 64 80 96 112 128 144 160 176 192 224 256]] 2 [[ 8 16 24 32 40 48 56 64 80 96 112 128 144 160]] 1 [[ 8 16 24 32 40 48 56 64 80 96 112 128 144 160]] ]]) shift (61440 and hdr) 12 SamplingRate: pick switch MpegVersion [ 3 [[44100 48000 32000 none]] 2 [[22050 24000 16000 none]] 0 [[11025 12000 8000 none]] ] (1 + (shift (3072 and hdr) 10)) PaddingBit: shift (512 and hdr) 9 sdsize: to integer! either MpegVersion = 3 [ ;version 1 ((( either layer = 3 [48000][144000]) * Bitrate) / SamplingRate) + PaddingBit ][ ((( either layer = 3 [24000][72000]) * Bitrate) / SamplingRate) + PaddingBit ] ] repend headers [hdrb MP3FrameHeader] ] ] MP3FrameHeader ] ;## main functions open: func["Opens MP3 file for parsing" MP3-file [file! url!]][ close ;<- try to close still opened previous port MP3Header: ID3v2: ID3v1: frame: none num_frames: duration: 0 inBuffer: make binary! 100 * 1024 outBuffer: make binary! 100 * 1024 MP3-port: system/words/open/read/binary/direct MP3-file seekToBuffer 50480 ] close: does [ clear headers error? try [system/words/close MP3-port] ] parse: func[ "Parses MP3 file and prints info about it's content" /file MP3-file [file! url!] ][ continue?: true if file [open MP3-file] while [all [continue? inBuffer]][ either all [ 255 = first inBuffer 224 < second inBuffer ][ on-read-MP3Frame readMP3FrameHeader ][ switch as-string readBytes 3 [ "ID3" [ on-read-ID3 readID3v2] "TAG" [ on-read-ID3 readID3v1] ] ] if all [continue? 5048 > length? inBuffer] [ seekToBuffer 50480 ] ] close true ] ;## parse events on-read-MP3Frame: func[hdr][ num_frames: num_frames + 1 duration: duration + (1152 / hdr/SamplingRate) skipBytes hdr/sdsize ] on-read-ID3: func[id3][ ?? id3 ] ;## get-sample-of-length get-sample-of-length: func[timeLength [time!] /since startTime [time!] /local hdr endTime][ clear outBuffer startTime: either since [to decimal! startTime][duration] endTime: startTime + to-decimal timeLength while [inBuffer][ either all [ 255 = first inBuffer 224 < second inBuffer ][ hdr: readMP3FrameHeader either startTime <= duration [ either endTime >= duration: duration + (1152 / hdr/SamplingRate) [ insert tail outBuffer readBytes hdr/sdsize ][ break ] ][ duration: duration + (1152 / hdr/SamplingRate) skipBytes hdr/sdsize ] ][ switch/default as-string copy/part inBuffer 3 [ "ID3" [ skipBytes 3 readID3v2] "TAG" [ skipBytes 3 readID3v1] ][ inBuffer: next inBuffer] ] if 5048 > length? inBuffer [ seekToBuffer 50480 ] ] outBuffer ] ]