Help me make my MP3 tag reader more idiomatic

Hello there :slight_smile:
My name is Raj and this is my first post here.

I’m mostly acquainted with Java and I’ve been learning Clojure for a few days now. I’m trying to write a simple app to read ID3v2 tags from an MP3 file using just the core libs. You can have a look here to see how the ID3v2 header is structured.
Here’s what I want to do:

  1. Read first 10 bytes from the MP3 file
  2. First three bytes should represent \I \D \3 … so it should always be 0x494433
  3. Store 4th and 5th bytes as version: 0x0300 means version 3.0
  4. Use 6th byte to set various flags
  5. Last 4 bytes can be used to calculate total size

Mostly bit operations there. I’m searching for an idiomatic way of reading the bytes from io/input-stream, performing the checks and storing valid results in a map. My initial attempt was something like:

(defn read-tags
  [path]
  (with-open [stream (io/input-stream path)]
    (-> {:stream stream :result {}}
        (read-id3)
        (read-version)
        (read-flags)
        (read-size)
        (:result))))

Where each of those read functions will read bytes from the stream, and update the results. And in the end, I can return that result map. For example, my read-id3 and read-version look like:

(defn- read-id3
  [stream-map]
  (let [head  (->> #(.read (:stream stream-map))
                   (repeatedly 3)
                   (map char)
                   (apply str))]
    (if (= head "ID3")
      stream-map
      (throw (ex-info "Invalid ID3v2 header" 
                      {:starts-with head})))))

(defn- read-version
  [stream-map]
  (let [stream    (:stream stream-map)
        major     (.read stream)
        revision  (.read stream)]
    (if (and (< major  0xFF) (< revision 0xFF))
      (assoc-in stream-map [:result :version] 
                (str "ID3v2." major "." revision))
      (throw (ex-info "Invalid ID3v2 version"
                      {:major major :revision revision})))))

How can this be improved and made more idiomatic?
How’d you have attempted the same problem?
Let me know your thoughts. :slight_smile:

Hi Raj,
Have a look at this incomplete example that uses a parser combinator library: you compose small parsing functions that know how to read and write binary values. The tricky bit is locating the tags in the file. If you are looking for a more complete implementation, check the ID3 project using the same parsing library.

1 Like