Skip to content

Instantly share code, notes, and snippets.

@Yasushi
Created November 2, 2011 03:50
Show Gist options
  • Save Yasushi/1332813 to your computer and use it in GitHub Desktop.
Save Yasushi/1332813 to your computer and use it in GitHub Desktop.
package ya
import scala.util.parsing.combinator._
import scala.util.parsing.input._
import scalax.file.Path
import scala.io.Source
class BytePosition(index: Int) extends Position {
def line = 0
def column = index
def lineContents = ""
override def toString = index.toString
override def longString = toString
}
class ByteSeqReader(seq: Seq[Byte], index: Int = 0) extends Reader[Byte] {
def first = seq(index)
def rest =
if (index < seq.length)
new ByteSeqReader(seq, index + 1)
else
this
def pos = new BytePosition(index)
def atEnd = index >= seq.length
}
object ID3 extends Parsers {
type Elem = Byte
case class Header(version: Int, flags: Int, size: Int) {
lazy val unsync = (flags&0x80) == 1
lazy val extendedHeader = (flags&0x40) == 1
lazy val experimental = (flags&0x20) == 1
}
case class FrameHeader(id: String, flags: Int, size: Int) {
lazy val tagPreserved = (flags&0x8000)==0
lazy val filePreserved = (flags&0x4000)==0
lazy val readonly = (flags&0x2000)==1
lazy val compessed = (flags&0x0080)==1
lazy val encrypted = (flags&0x0040)==1
lazy val hasGroupInfo = (flags&0x0020)==1
lazy val experimental = (id(0) == 'X' || id(0) == 'Y' || id(0) == 'Z')
}
sealed abstract class Frame(header: FrameHeader)
case class TFrame(header: FrameHeader, enc: Int, body: String) extends Frame(header)
case class UnknownFrame(header: FrameHeader, body: String) extends Frame(header)
def byte = elem("", _ => true)
def char = elem("", _ => true) ^^ { case v => v.toChar }
def int = repN(4, byte) ^^ { case v => v.foldLeft(0){ (a,b) => (a << 8) | b&0xff}}
def literal(s: String) = acceptSeq(s.getBytes) ^^ {case v => v.map(_.toChar).mkString}
def id = literal("ID3")
def version = byte <~ elem(0)
def flags = byte
def size = repN(4, byte) ^^ { case v => v.foldLeft(0){ (a,b) => (a << 7) | (b & 0x7f)}}
def ascii(size: Int) = elem(0)~> repN(size-2, byte) <~ elem(0) ^^ { bs => (0, Source.fromBytes(bs.toArray, "8859_1").mkString) }
def utf16(size: Int) = elem(1)~> repN(size-3, byte) <~ elem(0) <~ elem(0) ^^ { bs => (1, Source.fromBytes(bs.toArray, "UTF-16").mkString) }
def utf16be(size: Int) = elem(2)~> repN(size-3, byte) <~ elem(0) <~ elem(0) ^^ { bs => (1, Source.fromBytes(bs.toArray, "UTF-16BE").mkString) }
def utf8(size: Int) = elem(3)~> repN(size-2, byte) <~ elem(0) ^^ { bs => (1, Source.fromBytes(bs.toArray, "UTF-8").mkString) }
def text(size: Int) = ascii(size) | utf16(size) | utf16be(size) | utf8(size) | failure("invalid encoding")
def header = id ~ version ~ flags ~ size >> {
case i~v~f~s =>
Header(v,f,s) match {
case h if h.extendedHeader => failure("not implemented")
case h => success(h)
}
}
def capAlpha = elem("", v => v >= 'A' && v <= 'Z')
def numChar = elem("", v => v >= '0' && v <= '9')
def idChar = (capAlpha | numChar) ^^ { case v => v.toChar }
def frameId = repN(4, idChar) ^^ { case v => v.mkString }
def frameSize = int
def frameFlags = byte ~ byte ^^ { case a ~ b => (a << 8) | b&0x80}
def frameHeader = frameId ~ frameSize ~ frameFlags ^^ { case id~s~f => FrameHeader(id, f, s) }
def frame = frameHeader >> {
case h @ FrameHeader("TXXX", _, _) => err("not implemented")
case h @ FrameHeader(id, _, _) if id.startsWith("T") =>
text(h.size) ^^ { case (enc, info) => TFrame(h, enc, info)}
case h =>
println(h)
repN(h.size, byte) ^^ {bs => UnknownFrame(h, bs.map("%02x".format(_)).mkString)}
}
def term = Parser { in =>
if(in.atEnd)
Success("term", in)
else
Failure("!term", in)
}
def x = header ~ (frame*)
}
object mp3 {
def main(args: Array[String]) {
val input = new ByteSeqReader(Path("test.mp3").bytes.take(10240).toSeq)
println(ID3.x(input))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment