more id3 refactoring, support TAG+

db4
Doug Coleman 2009-04-09 15:03:34 -05:00
parent 5279bb0efc
commit cdc3d1b643
2 changed files with 121 additions and 73 deletions

View File

@ -7,7 +7,7 @@ IN: id3
HELP: mp3>id3 HELP: mp3>id3
{ $values { $values
{ "path" "a path string" } { "path" "a path string" }
{ "id3v2-info/f" "a tuple storing ID3v2 metadata or f" } } { "id3/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. Words to access the ID3v1 information are here:" { $description "Return a tuple containing the ID3 information parsed out of the MP3 file, or " { $link f } " if no metadata is present. Words to access the ID3v1 information are here:"
{ $list { $list
{ $link title } { $link title }
@ -22,49 +22,49 @@ HELP: mp3>id3
HELP: album HELP: album
{ $values { $values
{ "id3" id3v2-info } { "id3" id3 }
{ "album/f" "string or f" } { "string/f" "string or f" }
} }
{ $description "Returns the album, or " { $link f } " if this field is missing, from a parsed id3 tag." } ; { $description "Returns the album, or " { $link f } " if this field is missing, from a parsed id3 tag." } ;
HELP: artist HELP: artist
{ $values { $values
{ "id3" id3v2-info } { "id3" id3 }
{ "artist/f" "string or f" } { "string/f" "string or f" }
} }
{ $description "Returns the artist, or " { $link f } " if this field is missing, from a parsed id3 tag." } ; { $description "Returns the artist, or " { $link f } " if this field is missing, from a parsed id3 tag." } ;
HELP: comment HELP: comment
{ $values { $values
{ "id3" id3v2-info } { "id3" id3 }
{ "comment/f" "string or f" } { "string/f" "string or f" }
} }
{ $description "Returns the comment, or " { $link f } " if this field is missing, from a parsed id3 tag." } ; { $description "Returns the comment, or " { $link f } " if this field is missing, from a parsed id3 tag." } ;
HELP: genre HELP: genre
{ $values { $values
{ "id3" id3v2-info } { "id3" id3 }
{ "genre/f" "string or f" } { "string/f" "string or f" }
} }
{ $description "Returns the genre, or " { $link f } " if this field is missing, from a parsed id3 tag." } ; { $description "Returns the genre, or " { $link f } " if this field is missing, from a parsed id3 tag." } ;
HELP: title HELP: title
{ $values { $values
{ "id3" id3v2-info } { "id3" id3 }
{ "title/f" "string or f" } { "string/f" "string or f" }
} }
{ $description "Returns the title, or " { $link f } " if this field is missing, from a parsed id3 tag." } ; { $description "Returns the title, or " { $link f } " if this field is missing, from a parsed id3 tag." } ;
HELP: year HELP: year
{ $values { $values
{ "id3" id3v2-info } { "id3" id3 }
{ "year/f" "string or f" } { "string/f" "string or f" }
} }
{ $description "Returns the year, or " { $link f } " if this field is missing, from a parsed id3 tag." } ; { $description "Returns the year, or " { $link f } " if this field is missing, from a parsed id3 tag." } ;
HELP: find-id3-frame HELP: find-id3-frame
{ $values { $values
{ "id3" id3v2-info } { "name" string } { "id3" id3 } { "name" string }
{ "obj/f" "object or f" } { "obj/f" "object or f" }
} }
{ $description "Returns the " { $slot "data" } " slot of the ID3 frame with the given name, or " { $link f } "." } ; { $description "Returns the " { $slot "data" } " slot of the ID3 frame with the given name, or " { $link f } "." } ;

View File

@ -6,7 +6,7 @@ combinators math.ranges unicode.categories byte-arrays
io.encodings.string io.encodings.utf16 assocs math.parser io.encodings.string io.encodings.utf16 assocs math.parser
combinators.short-circuit fry namespaces combinators.smart combinators.short-circuit fry namespaces combinators.smart
splitting io.encodings.ascii arrays io.files.info unicode.case splitting io.encodings.ascii arrays io.files.info unicode.case
io.directories.search ; io.directories.search literals ;
IN: id3 IN: id3
<PRIVATE <PRIVATE
@ -37,47 +37,68 @@ CONSTANT: genres
"Primus" "Porn Groove" "Satire" "Slow Jam" "Club" "Tango" "Primus" "Porn Groove" "Satire" "Slow Jam" "Club" "Tango"
"Samba" "Folklore" "Ballad" "Power Ballad" "Rhythmic Soul" "Samba" "Folklore" "Ballad" "Power Ballad" "Rhythmic Soul"
"Freestyle" "Duet" "Punk Rock" "Drum Solo" "A capella" "Freestyle" "Duet" "Punk Rock" "Drum Solo" "A capella"
"Euro-House" "Dance Hall" "Euro-House" "Dance Hall" "Goa" "Drum & Bass" "Club-House"
"Hardcore" "Terror" "Indie" "BritPop" "Negerpunk"
"Polsk Punk" "Beat" "Christian Gangsta Rap" "Heavy Metal"
"Black Metal" "Crossover" "Contemporary Christian"
"Christian Rock"
} }
TUPLE: header version flags size ; TUPLE: header version flags size ;
TUPLE: frame frame-id flags size data ; TUPLE: frame tag flags size data ;
TUPLE: id3v2-info header frames ; TUPLE: id3 header frames
title artist album year comment genre
speed genre-name start-time end-time ;
TUPLE: id3v1-info title artist album year comment genre ; : <id3> ( -- id3 )
id3 new
: <id3v1-info> ( -- object ) id3v1-info new ; inline H{ } clone >>frames ; inline
: <id3v2-info> ( header frames -- object )
[ [ frame-id>> ] keep ] H{ } map>assoc id3v2-info boa ;
: <header> ( -- object ) header new ; inline : <header> ( -- object ) header new ; inline
: <frame> ( -- object ) frame new ; inline : <frame> ( -- object ) frame new ; inline
: id3v2? ( mmap -- ? ) "ID3" head? ; inline : id3v2? ( seq -- ? ) "ID3" head? ; inline
: id3v1? ( mmap -- ? ) CONSTANT: id3v1-length 128
{ [ length 128 >= ] [ 128 tail-slice* "TAG" head? ] } 1&& ; inline CONSTANT: id3v1-offset 128
CONSTANT: id3v1+-length 227
CONSTANT: id3v1+-offset $[ 128 227 + ]
: id3v1-frame ( string key -- frame ) : id3v1? ( seq -- ? )
<frame> {
swap >>frame-id [ length id3v1-offset >= ]
swap >>data ; inline [ id3v1-length tail-slice* "TAG" head? ]
} 1&& ; inline
: id3v1>id3v2 ( id3v1 -- id3v2 ) : id3v1+? ( seq -- ? )
{
[ length id3v1+-offset >= ]
[ id3v1+-length tail-slice* "TAG+" head? ]
} 1&& ; inline
: pair>frame ( string key -- frame/f )
over [
<frame>
swap >>tag
swap >>data
] [
2drop f
] if ; inline
: id3v1>frames ( id3v1 -- seq )
[ [
{ {
[ title>> "TIT2" id3v1-frame ] [ title>> "TIT2" pair>frame ]
[ artist>> "TPE1" id3v1-frame ] [ artist>> "TPE1" pair>frame ]
[ album>> "TALB" id3v1-frame ] [ album>> "TALB" pair>frame ]
[ year>> "TYER" id3v1-frame ] [ year>> "TYER" pair>frame ]
[ comment>> "COMM" id3v1-frame ] [ comment>> "COMM" pair>frame ]
[ genre>> "TCON" id3v1-frame ] [ genre>> "TCON" pair>frame ]
} cleave } cleave
] output>array f swap <id3v2-info> ; inline ] output>array sift ;
: >28bitword ( seq -- int ) : >28bitword ( seq -- int )
0 [ [ 7 shift ] dip bitor ] reduce ; inline 0 [ [ 7 shift ] dip bitor ] reduce ; inline
@ -85,10 +106,10 @@ TUPLE: id3v1-info title artist album year comment genre ;
: filter-text-data ( data -- filtered ) : filter-text-data ( data -- filtered )
[ printable? ] filter ; inline [ printable? ] filter ; inline
: valid-frame-id? ( id -- ? ) : valid-tag? ( id -- ? )
[ { [ digit? ] [ LETTER? ] } 1|| ] all? ; inline [ { [ digit? ] [ LETTER? ] } 1|| ] all? ; inline
: read-frame-data ( frame mmap -- frame data ) : read-frame-data ( frame seq -- frame data )
[ 10 over size>> 10 + ] dip <slice> filter-text-data ; inline [ 10 over size>> 10 + ] dip <slice> filter-text-data ; inline
: decode-text ( string -- string' ) : decode-text ( string -- string' )
@ -96,28 +117,29 @@ TUPLE: id3v1-info title artist album year comment genre ;
{ { HEX: ff HEX: fe } { HEX: fe HEX: ff } } member? { { HEX: ff HEX: fe } { HEX: fe HEX: ff } } member?
utf16 ascii ? decode ; inline utf16 ascii ? decode ; inline
: (read-frame) ( mmap -- frame ) : (read-frame) ( seq -- frame )
[ <frame> ] dip [ <frame> ] dip
{ {
[ 4 head-slice decode-text >>frame-id ] [ 4 head-slice decode-text >>tag ]
[ [ 4 8 ] dip subseq >28bitword >>size ] [ [ 4 8 ] dip subseq >28bitword >>size ]
[ [ 8 10 ] dip subseq >byte-array >>flags ] [ [ 8 10 ] dip subseq >byte-array >>flags ]
[ read-frame-data decode-text >>data ] [ read-frame-data decode-text >>data ]
} cleave ; inline } cleave ; inline
: read-frame ( mmap -- frame/f ) : read-frame ( seq -- frame/f )
dup 4 head-slice valid-frame-id? dup 4 head-slice valid-tag?
[ (read-frame) ] [ drop f ] if ; inline [ (read-frame) ] [ drop f ] if ; inline
: remove-frame ( mmap frame -- mmap ) : remove-frame ( seq frame -- seq )
size>> 10 + tail-slice ; inline size>> 10 + tail-slice ; inline
: read-frames ( mmap -- frames ) : frames>assoc ( seq -- assoc )
[ dup read-frame dup ] [ [ tag>> ] keep ] H{ } map>assoc ; inline
[ [ remove-frame ] keep ]
produce 2nip ; inline
: read-v2-header ( seq -- id3header ) : read-frames ( seq -- assoc )
[ dup read-frame dup ] [ [ remove-frame ] keep ] produce 2nip ; inline
: read-v2-header ( seq -- header )
[ <header> ] dip [ <header> ] dip
{ {
[ [ 3 5 ] dip <slice> >array >>version ] [ [ 3 5 ] dip <slice> >array >>version ]
@ -125,15 +147,18 @@ TUPLE: id3v1-info title artist album year comment genre ;
[ [ 6 10 ] dip <slice> >28bitword >>size ] [ [ 6 10 ] dip <slice> >28bitword >>size ]
} cleave ; inline } cleave ; inline
: read-v2-tag-data ( seq -- id3v2-info ) : merge-frames ( id3 assoc -- id3 )
[ dup frames>> ] dip update ; inline
: merge-id3v1 ( id3 -- id3 )
dup id3v1>frames frames>assoc merge-frames ; inline
: read-v2-tags ( id3 seq -- id3 )
10 cut-slice 10 cut-slice
[ read-v2-header ] [ read-v2-header >>header ]
[ read-frames ] bi* <id3v2-info> ; inline [ read-frames frames>assoc merge-frames ] bi* ; inline
: skip-to-v1-data ( seq -- seq ) 125 tail-slice* ; inline : extract-v1-tags ( id3 seq -- id3 )
: (read-v1-tag-data) ( seq -- mp3-file )
[ <id3v1-info> ] dip
{ {
[ 30 head-slice decode-text filter-text-data >>title ] [ 30 head-slice decode-text filter-text-data >>title ]
[ [ 30 60 ] dip subseq decode-text filter-text-data >>artist ] [ [ 30 60 ] dip subseq decode-text filter-text-data >>artist ]
@ -143,8 +168,30 @@ TUPLE: id3v1-info title artist album year comment genre ;
[ [ 124 ] dip nth number>string >>genre ] [ [ 124 ] dip nth number>string >>genre ]
} cleave ; inline } cleave ; inline
: read-v1-tag-data ( seq -- mp3-file ) : read-v1-tags ( id3 seq -- id3 )
skip-to-v1-data (read-v1-tag-data) ; inline id3v1-offset tail-slice* 3 tail-slice
extract-v1-tags ; inline
: extract-v1+-tags ( id3 seq -- id3 )
{
[ 60 head-slice decode-text filter-text-data [ append ] change-title ]
[
[ 60 120 ] dip subseq decode-text filter-text-data
[ append ] change-artist
]
[
[ 120 180 ] dip subseq decode-text filter-text-data
[ append ] change-album
]
[ [ 180 ] dip nth >>speed ]
[ [ 181 211 ] dip subseq decode-text >>genre-name ]
[ [ 211 217 ] dip subseq decode-text >>start-time ]
[ [ 217 223 ] dip subseq decode-text >>end-time ]
} cleave ; inline
: read-v1+-tags ( id3 seq -- id3 )
id3v1+-offset tail-slice* 4 tail-slice
extract-v1+-tags ; inline
: parse-genre ( string -- n/f ) : parse-genre ( string -- n/f )
dup "(" ?head-slice drop ")" ?tail-slice drop dup "(" ?head-slice drop ")" ?tail-slice drop
@ -154,34 +201,35 @@ TUPLE: id3v1-info title artist album year comment genre ;
drop drop
] if ; inline ] if ; inline
: (mp3>id3) ( path -- id3v2-info/f ) : (mp3>id3) ( path -- id3v2/f )
[ [
[ <id3> ] dip
{ {
{ [ dup id3v2? ] [ read-v2-tag-data ] } [ dup id3v1? [ read-v1-tags merge-id3v1 ] [ drop ] if ]
{ [ dup id3v1? ] [ read-v1-tag-data id3v1>id3v2 ] } [ dup id3v1+? [ read-v1+-tags merge-id3v1 ] [ drop ] if ]
[ drop f ] [ dup id3v2? [ read-v2-tags ] [ drop ] if ]
} cond } cleave
] with-mapped-uchar-file ; ] with-mapped-uchar-file ;
PRIVATE> PRIVATE>
: mp3>id3 ( path -- id3v2-info/f ) : mp3>id3 ( path -- id3/f )
dup file-info size>> 0 <= [ drop f ] [ (mp3>id3) ] if ; inline dup file-info size>> 0 <= [ drop f ] [ (mp3>id3) ] if ; inline
: find-id3-frame ( id3 name -- obj/f ) : find-id3-frame ( id3 name -- obj/f )
swap frames>> at* [ data>> ] when ; inline swap frames>> at* [ data>> ] when ; inline
: title ( id3 -- title/f ) "TIT2" find-id3-frame ; inline : title ( id3 -- string/f ) "TIT2" find-id3-frame ; inline
: artist ( id3 -- artist/f ) "TPE1" find-id3-frame ; inline : artist ( id3 -- string/f ) "TPE1" find-id3-frame ; inline
: album ( id3 -- album/f ) "TALB" find-id3-frame ; inline : album ( id3 -- string/f ) "TALB" find-id3-frame ; inline
: year ( id3 -- year/f ) "TYER" find-id3-frame ; inline : year ( id3 -- string/f ) "TYER" find-id3-frame ; inline
: comment ( id3 -- comment/f ) "COMM" find-id3-frame ; inline : comment ( id3 -- string/f ) "COMM" find-id3-frame ; inline
: genre ( id3 -- genre/f ) : genre ( id3 -- string/f )
"TCON" find-id3-frame parse-genre ; inline "TCON" find-id3-frame parse-genre ; inline
: find-mp3s ( path -- seq ) : find-mp3s ( path -- seq )