Skip to content

Instantly share code, notes, and snippets.

@twocity
Created February 6, 2018 15:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save twocity/dce4b79884a8dfd73dd3fa4fffbb26ee to your computer and use it in GitHub Desktop.
Save twocity/dce4b79884a8dfd73dd3fa4fffbb26ee to your computer and use it in GitHub Desktop.
AVI format parser
import Header.RIFF
import okio.Buffer
import okio.BufferedSource
import okio.Okio
import java.io.File
class AVIFormat private constructor(val riff: ChunkGroup) {
val chunks by lazy { riff.spread() }
private fun ChunkGroup.spread(): List<Chunk> {
return chunks.fold(listOf()) { acc, chunk ->
acc.toMutableList().apply {
add(chunk)
if (chunk is ChunkGroup) {
addAll(chunk.spread())
}
}.toList()
}
}
fun find(type: String): Chunk = findAll(type).first()
fun findAll(type: String): List<Chunk> = chunks.filter { it.type == type }
companion object {
fun parse(file: File): AVIFormat {
val buffer = Okio.buffer(Okio.source(file))
val header = buffer.readFourCC()
require(header == RIFF.name, { "Not RIFF format file" })
val size = buffer.readIntLe()
val type = buffer.readFourCC()
require(type.trim() == "AVI", { "Not AVI container, found $type" })
val buf = Buffer()
buffer.readFully(buf, size.toLong() - 4)
val riff = ChunkGroup(RIFF, size = size - 4, type = type, buffer = buf)
return AVIFormat(riff)
}
}
override fun toString(): String = riff.toString()
}
fun BufferedSource.readFourCC(): String = readIntLe().toFourCC()
private fun Int.toFourCC(): String {
val chars = charArrayOf(and(0xff).toChar(),
shr(8).and(0xff).toChar(),
shr(16).and(0xff).toChar(),
shr(24).and(0xff).toChar())
return String(chars)
}
import Header.LIST
import okio.Buffer
import okio.BufferedSource
enum class Header {
RIFF, LIST
}
/**
* The basic type of Chunk:
* ```
* type, size, buffer
* ```
*
* @param type chunk's type name
* @param size of buffer
* @param buffer the raw data of chunk
* @property totalSize length of chunk
*/
open class Chunk(val type: String, val size: Int, val buffer: BufferedSource) {
open val totalSize = 4 + 4 + size // sizeOf(type) + sizeOf(size)
}
/**
* The basic type of Chunk Container:
* ```
* header, type, size, buffer
* ```
*
* @param header chunk container's type
* @param type chunk's type name
* @param size of buffer
* @param buffer the raw data of chunk
* @property totalSize length of chunk
* @property chunks container's sub chunks
**/
class ChunkGroup(val header: Header, type: String,
size: Int, buffer: BufferedSource) : Chunk(type, size, buffer) {
override val totalSize: Int = 4 + super.totalSize // sizeOf(header) + sizeOf(type) + sizeOf(size)
val chunks: List<Chunk> by lazy { scanChunks() }
private fun scanChunks(): List<Chunk> {
val chunks = mutableListOf<Chunk>()
var consumed = 4 + 4 + 4 // header + type + size
while (!buffer.exhausted()) {
val c = buffer.readChunk()
consumed += c.totalSize
if (consumed % 2 != 0) {
// skip padding
consumed += 1
buffer.skip(1)
}
chunks.add(c)
}
require(consumed == totalSize)
return chunks.toList()
}
private fun BufferedSource.readChunk(): Chunk {
val header = readFourCC()
return when (header) {
LIST.name -> {
val size = readIntLe()
val type = readFourCC()
val sink = Buffer()
readFully(sink, size.toLong() - 4)
ChunkGroup(header = LIST, type = type, size = size - 4, buffer = sink)
}
else -> {
val size = readIntLe()
val sink = Buffer()
readFully(sink, size.toLong())
Chunk(type = header, size = size, buffer = sink)
}
}
}
}
@twocity
Copy link
Author

twocity commented Feb 6, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment