factor/extra/id3/id3.factor

249 lines
7.7 KiB
Factor
Raw Normal View History

! Copyright (C) 2009 Tim Wawrzynczak, Doug Coleman.
2009-02-09 22:50:04 -05:00
! See http://factorcode.org/license.txt for BSD license.
USING: sequences io io.encodings.binary io.files io.pathnames
strings kernel math io.mmap accessors combinators math.ranges
unicode byte-arrays io.encodings.string
io.encodings.utf16 assocs math.parser combinators.short-circuit
fry namespaces combinators.smart splitting io.encodings.ascii
arrays io.files.info io.directories.search literals
math.functions continuations ;
FROM: alien.c-types => uchar ;
2009-02-09 22:50:04 -05:00
IN: id3
<PRIVATE
CONSTANT: genres
{
"Blues" "Classic Rock" "Country" "Dance" "Disco" "Funk"
"Grunge" "Hip-Hop" "Jazz" "Metal" "New Age" "Oldies" "Other"
"Pop" "R&B" "Rap" "Reggae" "Rock" "Techno" "Industrial"
"Alternative" "Ska" "Death Metal" "Pranks" "Soundtrack"
"Euro-Techno" "Ambient" "Trip-Hop" "Vocal" "Jazz+Funk"
"Fusion" "Trance" "Classical" "Instrumental" "Acid" "House"
"Game" "Sound Clip" "Gospel" "Noise" "AlternRock" "Bass"
"Soul" "Punk" "Space" "Meditative" "Instrumental Pop"
"Instrumental Rock" "Ethnic" "Gothic" "Darkwave"
"Techno-Industrial" "Electronic" "Pop-Folk" "Eurodance"
"Dream" "Southern Rock" "Comedy" "Cult" "Gangsta" "Top 40"
"Christian Rap" "Pop/Funk" "Jungle" "Native American"
"Cabaret" "New Wave" "Psychedelic" "Rave" "Showtunes"
"Trailer" "Lo-Fi" "Tribal" "Acid Punk" "Acid Jazz" "Polka"
"Retro" "Musical" "Rock & Roll" "Hard Rock" "Folk"
"Folk-Rock" "National Folk" "Swing" "Fast Fusion" "Bebop"
"Latin" "Revival" "Celtic" "Bluegrass" "Avantgarde"
"Gothic Rock" "Progressive Rock" "Psychedelic Rock"
"Symphonic Rock" "Slow Rock" "Big Band" "Chorus"
"Easy Listening" "Acoustic" "Humour" "Speech" "Chanson"
"Opera" "Chamber Music" "Sonata" "Symphony" "Booty Bass"
"Primus" "Porn Groove" "Satire" "Slow Jam" "Club" "Tango"
"Samba" "Folklore" "Ballad" "Power Ballad" "Rhythmic Soul"
"Freestyle" "Duet" "Punk Rock" "Drum Solo" "A capella"
2009-04-09 16:03:34 -04:00
"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"
}
2009-02-09 22:50:04 -05:00
TUPLE: header version flags size ;
2009-04-09 16:03:34 -04:00
TUPLE: frame tag flags size data ;
2009-02-09 22:50:04 -05:00
2009-04-09 16:03:34 -04:00
TUPLE: id3 header frames
title artist album year comment genre
speed genre-name start-time end-time ;
2009-02-09 22:50:04 -05:00
2009-04-09 16:03:34 -04:00
: <id3> ( -- id3 )
id3 new
H{ } clone >>frames ; inline
2009-02-09 22:50:04 -05:00
: <header> ( -- object ) header new ; inline
2009-02-09 22:50:04 -05:00
: <frame> ( -- object ) frame new ; inline
2009-02-09 22:50:04 -05:00
2009-04-09 16:03:34 -04:00
: id3v2? ( seq -- ? ) "ID3" head? ; inline
2009-02-09 22:50:04 -05:00
2009-04-09 16:03:34 -04:00
CONSTANT: id3v1-length 128
CONSTANT: id3v1-offset 128
CONSTANT: id3v1+-length 227
: id3v1+-offset ( -- n ) id3v1-length id3v1+-length + ; inline
2009-04-09 16:03:34 -04:00
: id3v1? ( seq -- ? )
{
[ length id3v1-offset >= ]
[ id3v1-length tail-slice* "TAG" head? ]
2009-04-12 17:32:39 -04:00
} 1&& ;
2009-04-09 16:03:34 -04:00
: id3v1+? ( seq -- ? )
{
[ length id3v1+-offset >= ]
[ id3v1+-length tail-slice* "TAG+" head? ]
2009-04-12 17:32:39 -04:00
} 1&& ;
2009-04-09 16:03:34 -04:00
: pair>frame ( string key -- frame/f )
over [
<frame>
swap >>tag
swap >>data
] [
2drop f
2009-04-12 17:32:39 -04:00
] if ;
2009-04-09 16:03:34 -04:00
: id3v1>frames ( id3v1 -- seq )
[
{
2009-04-09 16:03:34 -04:00
[ title>> "TIT2" pair>frame ]
[ artist>> "TPE1" pair>frame ]
[ album>> "TALB" pair>frame ]
[ year>> "TYER" pair>frame ]
[ comment>> "COMM" pair>frame ]
[ genre>> "TCON" pair>frame ]
} cleave
2009-04-09 16:03:34 -04:00
] output>array sift ;
2009-02-09 22:50:04 -05:00
: sequence>synchsafe ( seq -- n )
2009-04-12 17:32:39 -04:00
0 [ [ 7 shift ] dip bitor ] reduce ;
2009-02-09 22:50:04 -05:00
: synchsafe>sequence ( n -- seq )
dup 1 + log2 1 + 7 / ceiling
2011-11-23 21:49:33 -05:00
[ [ -7 shift ] keep 0x7f bitand ] replicate nip reverse ;
2009-04-09 19:23:05 -04:00
2009-02-09 22:50:04 -05:00
: filter-text-data ( data -- filtered )
2009-04-12 17:32:39 -04:00
[ printable? ] filter ;
2009-02-09 22:50:04 -05:00
2009-04-09 16:03:34 -04:00
: valid-tag? ( id -- ? )
2009-04-12 17:32:39 -04:00
[ { [ digit? ] [ LETTER? ] } 1|| ] all? ;
2009-02-09 22:50:04 -05:00
2009-04-09 16:03:34 -04:00
: read-frame-data ( frame seq -- frame data )
2009-04-12 17:32:39 -04:00
[ 10 over size>> 10 + ] dip <slice> filter-text-data ;
2009-02-09 22:50:04 -05:00
: decode-text ( string -- string' )
dup 2 short head
2011-11-23 21:49:33 -05:00
{ { 0xff 0xfe } { 0xfe 0xff } } member?
2009-04-12 17:32:39 -04:00
utf16 ascii ? decode ;
2009-04-09 16:03:34 -04:00
: (read-frame) ( seq -- frame )
2009-02-09 22:50:04 -05:00
[ <frame> ] dip
{
2009-04-09 16:03:34 -04:00
[ 4 head-slice decode-text >>tag ]
[ [ 4 8 ] dip subseq sequence>synchsafe >>size ]
[ [ 8 10 ] dip subseq >byte-array >>flags ]
[ read-frame-data decode-text >>data ]
2009-04-12 17:32:39 -04:00
} cleave ;
2009-02-09 22:50:04 -05:00
2009-04-09 16:03:34 -04:00
: read-frame ( seq -- frame/f )
dup 4 head-slice valid-tag?
2009-04-12 17:32:39 -04:00
[ (read-frame) ] [ drop f ] if ;
2009-02-09 22:50:04 -05:00
2009-04-09 16:03:34 -04:00
: remove-frame ( seq frame -- seq )
2009-04-12 17:32:39 -04:00
size>> 10 + tail-slice ;
2009-02-09 22:50:04 -05:00
2009-04-09 16:03:34 -04:00
: frames>assoc ( seq -- assoc )
2009-04-12 17:32:39 -04:00
[ [ tag>> ] keep ] H{ } map>assoc ;
2009-04-09 16:03:34 -04:00
: read-frames ( seq -- assoc )
2009-04-12 17:32:39 -04:00
[ dup read-frame dup ] [ [ remove-frame ] keep ] produce 2nip ;
2009-04-09 16:03:34 -04:00
: read-v2-header ( seq -- header )
2009-02-09 22:50:04 -05:00
[ <header> ] dip
{
[ [ 3 5 ] dip <slice> >array >>version ]
[ [ 5 ] dip nth >>flags ]
[ [ 6 10 ] dip <slice> sequence>synchsafe >>size ]
2009-04-12 17:32:39 -04:00
} cleave ;
2009-02-09 22:50:04 -05:00
2009-04-09 16:03:34 -04:00
: merge-frames ( id3 assoc -- id3 )
[ dup frames>> ] dip assoc-union! drop ;
2009-04-09 16:03:34 -04:00
: merge-id3v1 ( id3 -- id3 )
2009-04-12 17:32:39 -04:00
dup id3v1>frames frames>assoc merge-frames ;
2009-04-09 16:03:34 -04:00
: read-v2-tags ( id3 seq -- id3 )
10 cut-slice
2009-04-09 16:03:34 -04:00
[ read-v2-header >>header ]
2009-04-12 17:32:39 -04:00
[ read-frames frames>assoc merge-frames ] bi* ;
2009-04-09 16:03:34 -04:00
: extract-v1-tags ( id3 seq -- id3 )
2009-02-09 22:50:04 -05:00
{
[ 30 head-slice decode-text filter-text-data >>title ]
[ [ 30 60 ] dip subseq decode-text filter-text-data >>artist ]
[ [ 60 90 ] dip subseq decode-text filter-text-data >>album ]
[ [ 90 94 ] dip subseq decode-text filter-text-data >>year ]
[ [ 94 124 ] dip subseq decode-text filter-text-data >>comment ]
[ [ 124 ] dip nth number>string >>genre ]
2009-04-12 17:32:39 -04:00
} cleave ;
2009-02-09 22:50:04 -05:00
2009-04-09 16:03:34 -04:00
: read-v1-tags ( id3 seq -- id3 )
id3v1-offset tail-slice* 3 tail-slice
2009-04-12 17:32:39 -04:00
extract-v1-tags ;
2009-04-09 16:03:34 -04:00
: 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 ]
2009-04-12 17:32:39 -04:00
} cleave ;
2009-04-09 16:03:34 -04:00
: read-v1+-tags ( id3 seq -- id3 )
id3v1+-offset tail-slice* 4 tail-slice
2009-04-12 17:32:39 -04:00
extract-v1+-tags ;
: parse-genre ( string -- n/f )
dup "(" ?head-slice drop ")" ?tail-slice drop
string>number dup number? [
genres ?nth swap or
] [
drop
2009-04-12 17:32:39 -04:00
] if ;
2009-02-09 22:50:04 -05:00
PRIVATE>
2009-04-25 22:03:12 -04:00
: mp3>id3 ( path -- id3/f )
[
[ <id3> ] dip uchar <mapped-array>
[ dup id3v1? [ read-v1-tags merge-id3v1 ] [ drop ] if ]
[ dup id3v1+? [ read-v1+-tags merge-id3v1 ] [ drop ] if ]
[ dup id3v2? [ read-v2-tags ] [ drop ] if ]
tri
] with-mapped-file-reader ;
: find-id3-frame ( id3 name -- obj/f )
2009-04-12 17:32:39 -04:00
swap frames>> at* [ data>> ] when ;
2009-02-09 22:50:04 -05:00
2009-04-12 17:32:39 -04:00
: title ( id3 -- string/f ) "TIT2" find-id3-frame ;
2009-04-12 17:32:39 -04:00
: artist ( id3 -- string/f ) "TPE1" find-id3-frame ;
2009-04-12 17:32:39 -04:00
: album ( id3 -- string/f ) "TALB" find-id3-frame ;
2009-04-12 17:32:39 -04:00
: year ( id3 -- string/f ) "TYER" find-id3-frame ;
2009-04-12 17:32:39 -04:00
: comment ( id3 -- string/f ) "COMM" find-id3-frame ;
2009-04-09 16:03:34 -04:00
: genre ( id3 -- string/f )
2009-04-12 17:32:39 -04:00
"TCON" find-id3-frame parse-genre ;
: find-mp3s ( path -- seq ) ".mp3" find-files-by-extension ;
2009-03-02 13:05:36 -05:00
ERROR: id3-parse-error path error ;
: (mp3-paths>id3s) ( seq -- seq' )
[ dup [ mp3>id3 ] [ \ id3-parse-error boa ] recover ] { } map>assoc ;
: mp3-paths>id3s ( seq -- seq' )
(mp3-paths>id3s)
[ dup second id3-parse-error? [ f over set-second ] when ] map ;
2009-03-02 20:41:58 -05:00
: parse-mp3-directory ( path -- seq )
find-mp3s mp3-paths>id3s ;