--- /dev/null
+USING: tools.test ;
+IN: cuesheet
+
+{
+ T{ cuesheet
+ { files
+ {
+ T{ file
+ { name "Faithless - Live in Berlin.mp3" }
+ { type "MP3" }
+ { tracks
+ {
+ T{ track
+ { number 1 }
+ { datatype "AUDIO" }
+ { title "Reverence" }
+ { performer "Faithless" }
+ { indices { T{ index f 1 "00:00:00" } } }
+ }
+ T{ track
+ { number 2 }
+ { datatype "AUDIO" }
+ { title "She's My Baby" }
+ { performer "Faithless" }
+ { indices { T{ index f 1 "06:42:00" } } }
+ }
+ T{ track
+ { number 3 }
+ { datatype "AUDIO" }
+ { title "Take the Long Way Home" }
+ { performer "Faithless" }
+ { indices { T{ index f 1 "10:54:00" } } }
+ }
+ T{ track
+ { number 4 }
+ { datatype "AUDIO" }
+ { title "Insomnia" }
+ { performer "Faithless" }
+ { indices { T{ index f 1 "17:04:00" } } }
+ }
+ T{ track
+ { number 5 }
+ { datatype "AUDIO" }
+ { title "Bring the Family Back" }
+ { performer "Faithless" }
+ { indices { T{ index f 1 "25:44:00" } } }
+ }
+ T{ track
+ { number 6 }
+ { datatype "AUDIO" }
+ { title "Salva Mea" }
+ { performer "Faithless" }
+ { indices { T{ index f 1 "30:50:00" } } }
+ }
+ T{ track
+ { number 7 }
+ { datatype "AUDIO" }
+ { title "Dirty Old Man" }
+ { performer "Faithless" }
+ { indices { T{ index f 1 "38:24:00" } } }
+ }
+ T{ track
+ { number 8 }
+ { datatype "AUDIO" }
+ { title "God Is a DJ" }
+ { performer "Faithless" }
+ { indices { T{ index f 1 "42:35:00" } } }
+ }
+ }
+ }
+ }
+ }
+ }
+ { remarks { "GENRE \"Electronica\"" "DATE \"1998\"" } }
+ { performer "Faithless" }
+ { title "Live in Berlin" }
+ }
+} [
+ """
+ REM GENRE "Electronica"
+ REM DATE "1998"
+ PERFORMER "Faithless"
+ TITLE "Live in Berlin"
+ FILE "Faithless - Live in Berlin.mp3" MP3
+ TRACK 01 AUDIO
+ TITLE "Reverence"
+ PERFORMER "Faithless"
+ INDEX 01 00:00:00
+ TRACK 02 AUDIO
+ TITLE "She's My Baby"
+ PERFORMER "Faithless"
+ INDEX 01 06:42:00
+ TRACK 03 AUDIO
+ TITLE "Take the Long Way Home"
+ PERFORMER "Faithless"
+ INDEX 01 10:54:00
+ TRACK 04 AUDIO
+ TITLE "Insomnia"
+ PERFORMER "Faithless"
+ INDEX 01 17:04:00
+ TRACK 05 AUDIO
+ TITLE "Bring the Family Back"
+ PERFORMER "Faithless"
+ INDEX 01 25:44:00
+ TRACK 06 AUDIO
+ TITLE "Salva Mea"
+ PERFORMER "Faithless"
+ INDEX 01 30:50:00
+ TRACK 07 AUDIO
+ TITLE "Dirty Old Man"
+ PERFORMER "Faithless"
+ INDEX 01 38:24:00
+ TRACK 08 AUDIO
+ TITLE "God Is a DJ"
+ PERFORMER "Faithless"
+ INDEX 01 42:35:00
+ """ string>cuesheet
+] unit-test
--- /dev/null
+! Copyright (C) 2013 John Benediktsson
+! See http://factorcode.org/license.txt for BSD license
+
+USING: accessors ascii combinators io io.encodings.utf8 io.files
+io.streams.string kernel math.parser sequences splitting ;
+
+IN: cuesheet
+
+TUPLE: cuesheet catalog cdtextfile files flags remarks performer
+songwriter title ;
+
+: <cuesheet> ( -- cuesheet )
+ f f f f f f f f cuesheet boa ;
+
+TUPLE: file name type tracks ;
+
+: <file> ( name type -- file )
+ f file boa ;
+
+TUPLE: track number datatype title performer songwriter pregap
+indices isrc postgap ;
+
+: <track> ( number datatype -- track )
+ f f f f f f f track boa ;
+
+TUPLE: index number duration ;
+
+C: <index> index
+
+ERROR: unknown-filetype filetype ;
+
+: check-filetype ( filetype -- filetype )
+ dup { "BINARY" "MOTOROLA" "AIFF" "WAVE" "MP3" } member?
+ [ unknown-filetype ] unless ;
+
+ERROR: unknown-flag flag ;
+
+: check-flag ( flag -- flag )
+ dup { "DCP" "4CH" "PRE" "SCMS" "DATA" } member?
+ [ unknown-flag ] unless ;
+
+: check-flags ( flags -- flags )
+ dup [ check-flag drop ] each ;
+
+ERROR: unknown-datatype datatype ;
+
+: check-datatype ( datatype -- datatype )
+ dup {
+ "AUDIO" "CDG" "MODE1/2048" "MODE1/2352" "MODE2/2336"
+ "MODE2/2352" "CDI/2336" "CDI/2352"
+ } member? [ unknown-datatype ] unless ;
+
+ERROR: unknown-syntax syntax ;
+
+<PRIVATE
+
+: trim-comments ( str -- str' )
+ dup [ CHAR: ; = ] find drop [ head ] when* ;
+
+: trim-quotes ( str -- str' )
+ [ CHAR: " = ] trim ;
+
+: last-track ( cuesheet -- cuesheet track )
+ dup files>> last tracks>> last ;
+
+: track-or-disc ( cuesheet -- cuesheet track/disc )
+ dup files>> [ dup ] [ last tracks>> last ] if-empty ;
+
+: parse-file ( cuesheet str -- cuesheet )
+ " " split1-last [ trim-quotes ] [ check-filetype ] bi*
+ <file> [ suffix ] curry change-files ;
+
+: parse-flags ( cuesheet str -- cuesheet )
+ check-flag [ suffix ] curry change-flags ;
+
+: parse-index ( cuesheet str -- cuesheet )
+ [ last-track ] [
+ " " split1 [ string>number ] dip <index>
+ [ suffix ] curry change-indices drop
+ ] bi* ;
+
+: parse-isrc ( cuesheet str -- cuesheet )
+ [ last-track ] [ >>isrc drop ] bi* ;
+
+: parse-performer ( cuesheet str -- cuesheet )
+ [ track-or-disc ] [ trim-quotes >>performer drop ] bi* ;
+
+: parse-postgap ( cuesheet str -- cuesheet )
+ [ last-track ] [ >>postgap drop ] bi* ;
+
+: parse-pregap ( cuesheet str -- cuesheet )
+ [ last-track ] [ >>pregap drop ] bi* ;
+
+: parse-remarks ( cuesheet str -- cuesheet )
+ [ suffix ] curry change-remarks ;
+
+: parse-songwriter ( cuesheet str -- cuesheet )
+ [ track-or-disc ] [ trim-quotes >>songwriter drop ] bi* ;
+
+: parse-title ( cuesheet str -- cuesheet )
+ [ track-or-disc ] [ trim-quotes >>title drop ] bi* ;
+
+: parse-track ( cuesheet str -- cuesheet )
+ [ dup files>> last ] [
+ " " split1 [ string>number ] [ check-datatype ] bi*
+ ] bi* <track> [ suffix ] curry change-tracks drop ;
+
+: parse-line ( cuesheet line -- cuesheet )
+ trim-comments [ blank? ] trim " " split1 swap {
+ { "CATALOG" [ >>catalog ] }
+ { "CDTEXTFILE" [ >>cdtextfile ] }
+ { "FILE" [ parse-file ] }
+ { "FLAGS" [ parse-flags ] }
+ { "INDEX" [ parse-index ] }
+ { "ISRC" [ parse-isrc ] }
+ { "PERFORMER" [ parse-performer ] }
+ { "POSTGAP" [ parse-postgap ] }
+ { "PREGAP" [ parse-pregap ] }
+ { "REM" [ parse-remarks ] }
+ { "SONGWRITER" [ parse-songwriter ] }
+ { "TITLE" [ parse-title ] }
+ { "TRACK" [ parse-track ] }
+ { "" [ drop ] }
+ [ unknown-syntax ]
+ } case ;
+
+PRIVATE>
+
+: read-cuesheet ( -- cuesheet )
+ <cuesheet> [ readln dup ] [ parse-line ] while drop ;
+
+: file>cuesheet ( path -- cuesheet )
+ utf8 [ read-cuesheet ] with-file-reader ;
+
+: string>cuesheet ( str -- cuesheet )
+ [ read-cuesheet ] with-string-reader ;