diff --git a/extra/id3/authors.txt b/extra/id3/authors.txt index ece617b969..2bd5c6037e 100644 --- a/extra/id3/authors.txt +++ b/extra/id3/authors.txt @@ -1,2 +1,2 @@ Tim Wawrzynczak - +Doug Coleman diff --git a/extra/id3/id3-docs.factor b/extra/id3/id3-docs.factor index a54bba1629..d171d03798 100644 --- a/extra/id3/id3-docs.factor +++ b/extra/id3/id3-docs.factor @@ -6,7 +6,7 @@ IN: id3 HELP: file-id3-tags { $values { "path" "a path string" } - { "object/f" "a tuple storing ID3 metadata or f" } } + { "id3v2-info/f" "a tuple storing ID3v2 metadata or f" } } { $description "Return a tuple containing the ID3 information parsed out of the MP3 file, or " { $link f } " if no metadata is present. Currently, the parser supports the following tags: " $nl { $link title>> } $nl { $link artist>> } diff --git a/extra/id3/id3-tests.factor b/extra/id3/id3-tests.factor index bcdc312440..eabbf00ad7 100644 --- a/extra/id3/id3-tests.factor +++ b/extra/id3/id3-tests.factor @@ -1,35 +1,42 @@ ! Copyright (C) 2009 Tim Wawrzynczak ! See http://factorcode.org/license.txt for BSD license. -USING: tools.test id3 id3.private ; +USING: tools.test id3 combinators ; IN: id3.tests -[ - T{ id3-info - { title "BLAH" } - { artist "ARTIST" } - { album "ALBUM" } - { year "2009" } - { comment "COMMENT" } - { genre "Bluegrass" } - } -] [ "resource:extra/id3/tests/blah.mp3" file-id3-tags ] unit-test +: id3-params ( id3 -- title artist album year comment genre ) + { + [ id3-title ] + [ id3-artist ] + [ id3-album ] + [ id3-year ] + [ id3-comment ] + [ id3-genre ] + } cleave ; [ - T{ id3-info - { title "Anthem of the Trinity" } - { artist "Terry Riley" } - { album "Shri Camel" } - { genre "Classical" } - } -] [ "resource:extra/id3/tests/blah2.mp3" file-id3-tags ] unit-test + "BLAH" + "ARTIST" + "ALBUM" + "2009" + "COMMENT" + "Bluegrass" +] [ "resource:extra/id3/tests/blah.mp3" file-id3-tags id3-params ] unit-test + +[ + "Anthem of the Trinity" + "Terry Riley" + "Shri Camel" + f + f + "Classical" +] [ "resource:extra/id3/tests/blah2.mp3" file-id3-tags id3-params ] unit-test [ - T{ id3-info - { title "Stormy Weather" } - { artist "Frank Sinatra" } - { album "Night and Day Frank Sinatra" } - { comment "eng, AG# 08E1C12E" } - { genre "Big Band" } - } -] [ "resource:extra/id3/tests/blah3.mp3" file-id3-tags ] unit-test + "Stormy Weather" + "Frank Sinatra" + "Night and Day Frank Sinatra" + f + "eng, AG# 08E1C12E" + "Big Band" +] [ "resource:extra/id3/tests/blah3.mp3" file-id3-tags id3-params ] unit-test diff --git a/extra/id3/id3.factor b/extra/id3/id3.factor index abfe01c3e4..fba6188b1e 100644 --- a/extra/id3/id3.factor +++ b/extra/id3/id3.factor @@ -1,145 +1,144 @@ -! Copyright (C) 2009 Tim Wawrzynczak +! Copyright (C) 2009 Tim Wawrzynczak, Doug Coleman. ! See http://factorcode.org/license.txt for BSD license. USING: sequences io io.encodings.binary io.files io.pathnames strings kernel math io.mmap io.mmap.uchar accessors syntax combinators math.ranges unicode.categories byte-arrays io.encodings.string io.encodings.utf8 assocs math.parser -combinators.short-circuit fry ; +combinators.short-circuit fry namespaces multiline +combinators.smart splitting ; IN: id3 ( -- object ) id3-info new ; -: ( header frames -- object ) id3v2-info boa ; +: ( header frames -- object ) + [ [ frame-id>> ] keep ] H{ } map>assoc + id3v2-info boa ; :
( -- object ) header new ; : ( -- object ) frame new ; -! utility words - -: id3v2? ( mmap -- ? ) - "ID3" head? ; +: id3v2? ( mmap -- ? ) "ID3" head? ; inline : id3v1? ( mmap -- ? ) - { [ length 128 >= ] [ 128 tail-slice* "TAG" head? ] } 1&& ; + { [ length 128 >= ] [ 128 tail-slice* "TAG" head? ] } 1&& ; inline + +: id3v1-frame ( string key -- frame ) + + swap >>frame-id + swap >>data ; + +: id3v1>id3v2 ( id3v1 -- id3v2 ) + [ + { + [ title>> "TIT2" id3v1-frame ] + [ artist>> "TPE1" id3v1-frame ] + [ album>> "TALB" id3v1-frame ] + [ year>> "TYER" id3v1-frame ] + [ comment>> "COMM" id3v1-frame ] + [ genre>> "TCON" id3v1-frame ] + } cleave + ] output>array f swap ; : >28bitword ( seq -- int ) - 0 [ swap 7 shift bitor ] reduce ; + 0 [ [ 7 shift ] dip bitor ] reduce ; inline : filter-text-data ( data -- filtered ) - [ printable? ] filter ; + [ printable? ] filter ; inline ! frame details stuff : valid-frame-id? ( id -- ? ) - [ [ digit? ] [ LETTER? ] bi or ] all? ; + [ { [ digit? ] [ LETTER? ] } 1|| ] all? ; inline : read-frame-id ( mmap -- id ) - 4 head-slice ; + 4 head-slice ; inline : read-frame-size ( mmap -- size ) - [ 4 8 ] dip subseq ; + [ 4 8 ] dip subseq ; inline : read-frame-flags ( mmap -- flags ) - [ 8 10 ] dip subseq ; + [ 8 10 ] dip subseq ; inline : read-frame-data ( frame mmap -- frame data ) - [ 10 over size>> 10 + ] dip filter-text-data ; + [ 10 over size>> 10 + ] dip filter-text-data ; inline ! read whole frames @@ -200,10 +215,11 @@ TUPLE: id3-info title artist album year comment genre ; } cleave ; : read-frame ( mmap -- frame/f ) - dup read-frame-id valid-frame-id? [ (read-frame) ] [ drop f ] if ; + dup read-frame-id valid-frame-id? + [ (read-frame) ] [ drop f ] if ; : remove-frame ( mmap frame -- mmap ) - size>> 10 + tail-slice ; + size>> 10 + tail-slice ; inline : read-frames ( mmap -- frames ) [ dup read-frame dup ] @@ -213,13 +229,12 @@ TUPLE: id3-info title artist album year comment genre ; ! header stuff : read-header-supported-version? ( mmap -- ? ) - 3 tail-slice [ { 4 } head? ] [ { 3 } head? ] bi or ; + 3 tail-slice first { 3 4 } member? ; inline -: read-header-flags ( mmap -- flags ) - 5 swap nth ; +: read-header-flags ( mmap -- flags ) 5 swap nth ; inline : read-header-size ( mmap -- size ) - [ 6 10 ] dip >28bitword ; + [ 6 10 ] dip >28bitword ; inline : read-v2-header ( mmap -- id3header ) [
] dip @@ -227,51 +242,30 @@ TUPLE: id3-info title artist album year comment genre ; [ read-header-supported-version? >>version ] [ read-header-flags >>flags ] [ read-header-size >>size ] - } cleave ; + } cleave ; inline : drop-header ( mmap -- seq1 seq2 ) - dup 10 tail-slice swap ; + [ 10 tail-slice ] [ ] bi ; inline -: frame-tag ( frame string -- tag/f ) - '[ frame-id>> _ = ] find nip ; inline - -: parse-frames ( id3v2-info -- id3-info ) - [ ] dip frames>> - { - [ "TIT2" frame-tag [ data>> >>title ] when* ] - [ "TALB" frame-tag [ data>> >>album ] when* ] - [ "TPE1" frame-tag [ data>> >>artist ] when* ] - [ "TCON" frame-tag [ data>> [ [ digit? ] filter string>number ] keep swap [ genres at nip ] when* - >>genre ] when* ] - [ "COMM" frame-tag [ data>> >>comment ] when* ] - [ "TYER" frame-tag [ data>> >>year ] when* ] - } cleave ; - -: read-v2-tag-data ( seq -- id3-info ) - drop-header read-v2-header swap read-frames parse-frames ; +: read-v2-tag-data ( seq -- id3v2-info ) + drop-header read-v2-header + swap read-frames ; inline ! v1 information -: skip-to-v1-data ( seq -- seq ) - 125 tail-slice* ; +: skip-to-v1-data ( seq -- seq ) 125 tail-slice* ; inline -: read-title ( seq -- title ) - 30 head-slice ; +: read-title ( seq -- title ) 30 head-slice ; inline -: read-artist ( seq -- title ) - [ 30 60 ] dip subseq ; +: read-artist ( seq -- title ) [ 30 60 ] dip subseq ; inline -: read-album ( seq -- album ) - [ 60 90 ] dip subseq ; +: read-album ( seq -- album ) [ 60 90 ] dip subseq ; inline -: read-year ( seq -- year ) - [ 90 94 ] dip subseq ; +: read-year ( seq -- year ) [ 90 94 ] dip subseq ; inline -: read-comment ( seq -- comment ) - [ 94 124 ] dip subseq ; +: read-comment ( seq -- comment ) [ 94 124 ] dip subseq ; inline -: read-genre ( seq -- genre ) - [ 124 ] dip nth ; +: read-genre ( seq -- genre ) [ 124 ] dip nth ; inline : (read-v1-tag-data) ( seq -- mp3-file ) [ ] dip @@ -281,23 +275,46 @@ TUPLE: id3-info title artist album year comment genre ; [ read-album utf8 decode filter-text-data >>album ] [ read-year utf8 decode filter-text-data >>year ] [ read-comment utf8 decode filter-text-data >>comment ] - [ read-genre >fixnum genres at >>genre ] - } cleave ; + [ read-genre number>string >>genre ] + } cleave ; inline : read-v1-tag-data ( seq -- mp3-file ) - skip-to-v1-data (read-v1-tag-data) ; + skip-to-v1-data (read-v1-tag-data) ; inline + +: parse-genre ( string -- n/f ) + dup "(" ?head-slice drop ")" ?tail-slice drop + string>number dup number? [ + genres ?nth swap or + ] [ + drop + ] if ; inline PRIVATE> -! public interface +: frame-named ( id3 name quot -- obj ) + [ swap frames>> at* ] dip + [ data>> ] prepose [ drop f ] if ; inline -: file-id3-tags ( path -- object/f ) +: id3-title ( id3 -- title/f ) "TIT2" [ ] frame-named ; inline + +: id3-artist ( id3 -- artist/f ) "TPE1" [ ] frame-named ; inline + +: id3-album ( id3 -- album/f ) "TALB" [ ] frame-named ; inline + +: id3-year ( id3 -- year/f ) "TYER" [ ] frame-named ; inline + +: id3-comment ( id3 -- comment/f ) "COMM" [ ] frame-named ; inline + +: id3-genre ( id3 -- genre/f ) + "TCON" [ parse-genre ] frame-named ; inline + +: id3-frame ( id3 key -- value/f ) [ ] frame-named ; inline + +: file-id3-tags ( path -- id3v2-info/f ) [ { - { [ dup id3v2? ] [ read-v2-tag-data ] } ! ( ? -- id3v2 ) - { [ dup id3v1? ] [ read-v1-tag-data ] } ! ( ? -- id3-info ) - [ drop f ] ! ( mmap -- f ) + { [ dup id3v2? ] [ read-v2-tag-data ] } + { [ dup id3v1? ] [ read-v1-tag-data id3v1>id3v2 ] } + [ drop f ] } cond ] with-mapped-uchar-file ; - -! end