Created
November 2, 2011 03:50
-
-
Save Yasushi/1332813 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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