From 1dfba430a8c58250ec743f2edaec9ed6bcc5ef3e Mon Sep 17 00:00:00 2001 From: Steve Ayerhart Date: Tue, 25 Feb 2020 22:45:30 -0600 Subject: [PATCH] initial flac metadata read --- extra/flac/authors.txt | 1 + extra/flac/decoder/decoder.factor | 82 +++++++++ extra/flac/flac-docs.factor | 10 ++ extra/flac/flac-tests.factor | 4 + extra/flac/flac.factor | 5 + extra/flac/metadata/metadata.factor | 267 ++++++++++++++++++++++++++++ extra/flac/summary.txt | 1 + extra/flac/tags.txt | 1 + 8 files changed, 371 insertions(+) create mode 100644 extra/flac/authors.txt create mode 100644 extra/flac/decoder/decoder.factor create mode 100644 extra/flac/flac-docs.factor create mode 100644 extra/flac/flac-tests.factor create mode 100644 extra/flac/flac.factor create mode 100644 extra/flac/metadata/metadata.factor create mode 100644 extra/flac/summary.txt create mode 100644 extra/flac/tags.txt diff --git a/extra/flac/authors.txt b/extra/flac/authors.txt new file mode 100644 index 0000000000..140dca8942 --- /dev/null +++ b/extra/flac/authors.txt @@ -0,0 +1 @@ +Steve Ayerhart diff --git a/extra/flac/decoder/decoder.factor b/extra/flac/decoder/decoder.factor new file mode 100644 index 0000000000..d5bb18e2a2 --- /dev/null +++ b/extra/flac/decoder/decoder.factor @@ -0,0 +1,82 @@ +! Copyright (C) 2020 . +! See http://factorcode.org/license.txt for BSD license. +USING: alien.syntax math io.encodings.binary kernel io io.files locals endian bit-arrays ; +USING: prettyprint ; +USING: flac.metadata.private flac.metadata ; + +QUALIFIED: bitstreams + + +IN: flac.decoder + +ALIAS: read-bit bitstreams:read +CONSTANT: sync-code 16382 +ERROR: sync-code-error ; + +ENUM: flac-channel-assignment + channel-assignment-independent + channel-assignment-left-side + channel-assignment-right-side + channel-assignment-mid-side ; +ENUM: flac-frame-number-type + frame-number-type-frame + frame-number-type-sample ; + +ENUM: flac-subframe-type + subframe-type-constant + subframe-type-verbatim + subframe-type-fixed + subframe-type-lpc ; + +ENUM: flac-entropy-coding-method + entropy-coding-partioned-rice + entropy-coding-partioned-rice2 ; + +TUPLE: subframe + { type maybe{ subframe-type-constant + subframe-type-verbatim + subframe-type-fixed + subframe-type-lpc } } ; + +TUPLE: frame-header + { blocksize integer } + { sample-rate integer } + { channels integer } + { channel-assignment maybe{ channel-assignment-independent + channel-assignment-left-side + channel-assignment-right-side + channel-assignment-mid-side } } + { bits-per-sample integer } + { number-type maybe{ frame-number-type-frame frame-number-type-sample } } + { number integer } + { crc integer } ; + +TUPLE: frame-footer + { crc integer } ; + +:: read-sync-code ( bitstream -- ? ) + 14 bitstream read-bit sync-code = ; + +:: (decode-frame-header) ( bitstream -- ) + [ + bitstream read-sync-code [ sync-code-error ] unless + 1 bitstream read-bit drop + 1 bitstream read-bit drop + 4 bitstream read-bit integer>bit-array . + 4 bitstream read-bit integer>bit-array . + + ] with-big-endian ; + +: decode-frame-header ( -- ) + [ + 3 read bitstreams: (decode-frame-header) + ] with-big-endian ; + +: decode-file ( filename -- ) + binary + [ + read-flac-magic [ not-a-flac-file ] unless + read-stream-info drop + skip-metadata + decode-frame-header + ] with-file-reader ; diff --git a/extra/flac/flac-docs.factor b/extra/flac/flac-docs.factor new file mode 100644 index 0000000000..46db6aefc1 --- /dev/null +++ b/extra/flac/flac-docs.factor @@ -0,0 +1,10 @@ +! Copyright (C) 2020 . +! See http://factorcode.org/license.txt for BSD license. +USING: help.markup help.syntax ; +IN: flac + +ARTICLE: "flac" "flac" +{ $vocab-link "flac" } +; + +ABOUT: "flac" diff --git a/extra/flac/flac-tests.factor b/extra/flac/flac-tests.factor new file mode 100644 index 0000000000..aebf5be062 --- /dev/null +++ b/extra/flac/flac-tests.factor @@ -0,0 +1,4 @@ +! Copyright (C) 2020 . +! See http://factorcode.org/license.txt for BSD license. +USING: tools.test flac ; +IN: flac.tests diff --git a/extra/flac/flac.factor b/extra/flac/flac.factor new file mode 100644 index 0000000000..747fcba61b --- /dev/null +++ b/extra/flac/flac.factor @@ -0,0 +1,5 @@ +! Copyright (C) 2020 . +! See http://factorcode.org/license.txt for BSD license. +USING: ; + +IN: flac diff --git a/extra/flac/metadata/metadata.factor b/extra/flac/metadata/metadata.factor new file mode 100644 index 0000000000..efbada2432 --- /dev/null +++ b/extra/flac/metadata/metadata.factor @@ -0,0 +1,267 @@ +! Copyright (C) 2020 . +! See http://factorcode.org/license.txt for BSD license. +USING: endian sequences kernel classes.struct io io.binary io.files io.encodings io.encodings.string io.encodings.utf8 io.encodings.binary alien.c-types alien.endian math locals accessors prettyprint combinators pack math.parser strings arrays io.streams.byte-array sequences.generalizations assocs splitting byte-arrays alien.syntax alien.enums io.encodings.ascii ; +QUALIFIED: bitstreams + +IN: flac.metadata + +ALIAS: read-bit bitstreams:read + +CONSTANT: FLAC-MAGIC "fLaC" + +ENUM: metadata-type + metadata-stream-info + metadata-padding + metadata-application + metadata-seek-table + metadata-vorbis-comment + metadata-cuesheet + metadata-picture + { metadata-invalid 127 } ; + +ERROR: not-a-flac-file ; +ERROR: cuesheet-index-reserved-must-be-zero ; + +TUPLE: metadata-block-header + { last? boolean } + { type maybe{ metadata-stream-info + metadata-padding + metadata-application + metadata-seek-table + metadata-vorbis-comment + metadata-cuesheet + metadata-picture + metadata-invalid } } + { length integer } ; + +TUPLE: stream-info + { min-block-size integer } + { max-block-size integer } + { min-frame-size integer } + { max-frame-size integer } + { sample-rate integer } + { channels integer } + { bits-per-sample integer } + { samples integer } + { md5 string } ; + +TUPLE: seek-table + { seek-points array } ; +TUPLE: seek-point + { sample-number integer } + { offset integer } + { total-samples } ; + +TUPLE: vorbis-comment + { vendor-string string } + { comments assoc } ; + +TUPLE: padding + { length integer } ; + +TUPLE: application + { id string } + { data byte-array } ; + +ENUM: cuesheet-track-type audio non-audio ; + +TUPLE: cuesheet-track + { offset integer } + { number number } + { isrc string } + { type integer } + { pre-emphasis boolean } + { indices array } ; +TUPLE: cuesheet-index + { offset integer } + { number integer } ; +TUPLE: cuesheet + { catalog-number integer } + { lead-in integer } + { cd? boolean } + { tracks array } ; + +ENUM: picture-type + other + file-icon + other-file-icon + front-cover + back-cover + leaflet-page + media + lead-artist/performer/soloist + artist/performer + conductor + band/orchestra + composer + lyricist/text-writer + recording-location + during-recording + during-performance + movie/video-screen-capture + bright-coloured-fish + illustration + badn/artist-logotype + publisher/studio-logotype ; + +TUPLE: picture + type + { mime-type string } + { description string } + { width integer } + { height integer } + { depth integer } + { colors integer } + { data byte-array } ; + +TUPLE: metadata + { stream-info stream-info } + { padding maybe{ padding } } + { application maybe{ application } } + { seek-table maybe{ seek-table } } + { vorbis-comment maybe{ vorbis-comment } } + { cuesheet maybe{ cuesheet } } + { picture maybe{ picture } } ; + + + 24 bitstream read-bit + ] with-big-endian + metadata-block-header boa ; + +: parse-metadata-block-header ( byte-array -- header ) + bitstreams: (parse-metadata-block-header) ; + +: read-metadata-block-header ( -- header ) + 4 read parse-metadata-block-header dup . ; + +:: (parse-stream-info) ( bitstream -- stream-info ) + [ + 16 bitstream read-bit + 16 bitstream read-bit + 24 bitstream read-bit + 24 bitstream read-bit + 20 bitstream read-bit + 3 bitstream read-bit 1 + + 5 bitstream read-bit 1 + + 36 bitstream read-bit + 128 bitstream read-bit u128>byte-array bytes>hex-string + ] with-big-endian + stream-info boa ; + +: parse-stream-info ( byte-array -- stream-info ) + bitstreams: (parse-stream-info) ; + +: parse-seek-table ( byte-array -- seek-table ) + dup + binary + [ + length 18 / + [ drop 8 read be> 8 read be> 2 read be> seek-point boa ] map + ] with-byte-reader + seek-table boa ; + +: parse-vorbis-comment ( byte-array -- comments ) + binary + [ + 4 read le> read utf8 decode + 4 read le> [ + drop + 4 read le> read utf8 decode + "=" split + ] map + ] with-byte-reader >alist vorbis-comment boa ; + +: parse-padding ( byte-array -- padding ) + length padding boa ; + +: parse-application ( byte-array -- application ) + drop application new ; + +: parse-cuesheet ( byte-array -- cuesheet ) + binary + [ + 128 read ascii decode + 8 read be> + 259 read drop f + 1 read be> [ + drop + 8 read be> + 1 read be> + 12 read ascii decode + 21 read drop 0 t + 1 read [ + drop + 8 read be> + 1 read be> + 3 read be> = 0 [ cuesheet-index-reserved-must-be-zero ] unless + cuesheet-index boa + ] map + cuesheet-track boa + ] map + ] with-byte-reader cuesheet boa ; + +: parse-picture ( byte-array -- picture ) + binary + [ + 4 read be> + 4 read be> read utf8 decode + 4 read be> read utf8 decode + 4 read be> + 4 read be> + 4 read be> + 4 read be> + 4 read be> read + ] with-byte-reader picture boa ; + +: read-metadata-block ( metadata byte-array type -- metadata ) + { + { metadata-stream-info [ parse-stream-info >>stream-info ] } + { metadata-padding [ parse-padding >>padding ] } + { metadata-application [ parse-application >>application ] } + { metadata-seek-table [ parse-seek-table >>seek-table ] } + { metadata-vorbis-comment [ parse-vorbis-comment >>vorbis-comment ] } + { metadata-cuesheet [ parse-cuesheet >>cuesheet ] } + { metadata-picture [ parse-picture >>picture ] } + } case ; + +PRIVATE> + +: read-stream-info ( -- stream-info ) + read-metadata-block-header + length>> read bitstreams: parse-stream-info ; + +: skip-metadata ( -- ) + [ + read-metadata-block-header + [ length>> read drop ] [ last?>> not ] bi + ] loop ; + +! TODO: handle other formats gracefully such as ID3 +: read-metadata ( filename -- metadata ) + binary + [ + read-flac-magic [ not-a-flac-file ] unless + metadata new + [ + read-metadata-block-header + [ length>> read ] [ type>> ] [ last?>> not ] tri + [ read-metadata-block ] dip + ] loop + ] with-file-reader ; + +: ( filename -- metadata ) + read-metadata ; + +:: write-something ( bitstream -- header ) + 1 1 bitstream bitstreams:poke + 1 7 bitstream bitstreams:poke + 34 24 bitstream bitstreams:poke + bitstream bitstreams:bit-writer-bytes ; diff --git a/extra/flac/summary.txt b/extra/flac/summary.txt new file mode 100644 index 0000000000..984233f0c8 --- /dev/null +++ b/extra/flac/summary.txt @@ -0,0 +1 @@ +flac diff --git a/extra/flac/tags.txt b/extra/flac/tags.txt new file mode 100644 index 0000000000..7240e97133 --- /dev/null +++ b/extra/flac/tags.txt @@ -0,0 +1 @@ +flac audio