Last active
August 29, 2015 14:06
-
-
Save gwik/e87162fdfb8449748afd to your computer and use it in GitHub Desktop.
ogg decoder for sniffing content type.
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 main | |
import ( | |
"bytes" | |
"encoding/binary" | |
"errors" | |
"fmt" | |
"io" | |
"os" | |
"strings" | |
) | |
const ( | |
flagContinued = 0x01 | |
flagFirst = 0x02 | |
flagLast = 0x04 | |
) | |
var capturePattern = []byte("OggS") | |
// pageHeader starts with OggS | |
type pageHeader struct { | |
version uint8 // stream_structure_version (4) | |
flags uint8 // header_type_flag (5) | |
pos uint64 // absolute granule position (6-13) | |
sn uint32 // stream serial number (14-17) | |
seqNo uint32 // page sequence no (18-21) | |
checksum uint32 // page checksum (22-25) | |
count uint8 // page_segments (26) | |
segments [256]uint8 // segment_table (27-n, n=page_segments+26) | |
} | |
func (ph *pageHeader) Len() int { | |
return int(27 + ph.count) | |
} | |
func (ph *pageHeader) PayloadLen() int { | |
l := 0 | |
for i := uint8(0); i < ph.count; i++ { | |
l += int(ph.segments[i]) | |
} | |
return l | |
} | |
// caller should ensure buf should be a least 27 bytes | |
func (ph *pageHeader) ReadHeader(p []byte) error { | |
if len(p) < 27 { | |
return errors.New("Invalid buffer size.") | |
} | |
if !bytes.Equal(p[:4], capturePattern) { | |
return errors.New("Capture pattern not found.") | |
} | |
ph.version = p[4] | |
if ph.version != 0 { | |
return fmt.Errorf("Unsupported version: %d", ph.version) | |
} | |
ph.flags = p[5] | |
ph.pos = binary.LittleEndian.Uint64(p[6:]) | |
ph.sn = binary.LittleEndian.Uint32(p[14:]) | |
ph.seqNo = binary.LittleEndian.Uint32(p[18:]) | |
ph.checksum = binary.LittleEndian.Uint32(p[22:]) | |
ph.count = p[26] | |
if ph.count > 255 { | |
return fmt.Errorf("Invalid packet count %d.", ph.count) | |
} | |
return nil | |
} | |
func (ph *pageHeader) ReadSegments(p []byte) error { | |
if len(p) < int(ph.count) { | |
return errors.New("Invalid buffer size.") | |
} | |
for i := uint8(0); i < ph.count; i++ { | |
ph.segments[i] = p[i] | |
} | |
return nil | |
} | |
const ( | |
flagCodecVideo = 1 << iota | |
flagCodecAudio = 1 << iota | |
flagCodecText = 1 << iota | |
) | |
type codecInfo struct { | |
prefix []byte | |
name string | |
flag int | |
} | |
// http://wiki.xiph.org/index.php/MIMETypesCodecs | |
var codecTable = []codecInfo{ | |
{[]byte("CELT "), "celt", flagCodecAudio}, | |
{[]byte("CMML\x00\x00\x00\x00"), "cmml", flagCodecText}, | |
{[]byte("BBCD\x00"), "dirac", flagCodecVideo}, | |
{[]byte("\177FLAC"), "flac", flagCodecAudio}, | |
{[]byte("\213JNG\r\n\032\n"), "jng", flagCodecVideo}, | |
{[]byte("\x80kate\x00\x00\x00"), "kate", flagCodecText}, | |
{[]byte("OggMIDI\x00"), "midi", flagCodecText}, | |
{[]byte("\212MNG\r\n\032\n"), "mng", flagCodecVideo}, | |
{[]byte("OpusHead"), "opus", flagCodecAudio}, | |
{[]byte("PCM "), "pcm", flagCodecAudio}, | |
{[]byte("\211PNG\r\n\032\n"), "png", flagCodecVideo}, | |
{[]byte("Speex "), "speex", flagCodecAudio}, | |
{[]byte("\x80theora"), "theora", flagCodecVideo}, | |
{[]byte("\x01vorbis"), "vorbis", flagCodecAudio}, | |
{[]byte("YUV4MPEG"), "yuv4mpeg", flagCodecVideo}, | |
} | |
type streamInfo struct { | |
sn uint32 | |
codec *codecInfo | |
} | |
func findCodecInfo(hdr []byte) *codecInfo { | |
hlen := len(hdr) | |
for _, pte := range codecTable { | |
plen := len(pte.prefix) | |
if hlen > plen && bytes.Equal(hdr[:plen], pte.prefix) { | |
return &pte | |
} | |
} | |
return nil | |
} | |
// peek looks at the first page for every streams and extract codec info. | |
func peek(in io.ReadSeeker) ([]streamInfo, error) { | |
buf := make([]byte, 256) | |
hbuf := buf[:27] | |
streams := make([]streamInfo, 0, 3) | |
ph := new(pageHeader) | |
for { | |
offset := 0 | |
_, err := io.ReadFull(in, hbuf) | |
if err != nil { | |
return streams, err | |
} | |
err = ph.ReadHeader(hbuf) | |
if err != nil { | |
return streams, err | |
} | |
segbuf := buf[:ph.count] | |
_, err = io.ReadFull(in, segbuf) | |
if err != nil { | |
return streams, err | |
} | |
ph.ReadSegments(segbuf) | |
if ph.flags&flagFirst < 1 { | |
return streams, nil | |
} | |
offset += ph.PayloadLen() | |
if ph.count > 0 { | |
size := int(ph.segments[0]) | |
_, err := io.ReadFull(in, buf[:size]) | |
if err != nil { | |
return streams, err | |
} | |
codec := findCodecInfo(buf) | |
if codec != nil { | |
streams = append(streams, streamInfo{ph.sn, codec}) | |
} | |
offset -= size | |
} | |
_, err = in.Seek(int64(offset), 1) | |
if err != nil { | |
return streams, err | |
} | |
} | |
} | |
// returns mimeType as defined in RFC5334 | |
func mimeType(streams []streamInfo) string { | |
flags := 0 | |
for _, stream := range streams { | |
flags |= stream.codec.flag | |
} | |
if flags&flagCodecVideo > 0 { | |
return "video/ogg" | |
} | |
if flags&flagCodecAudio > 0 { | |
return "audio/ogg" | |
} | |
return "application/ogg" | |
} | |
// returns mimeType + codecs attribute as defined in RFC5334 | |
func contentType(streams []streamInfo) string { | |
flags := 0 | |
mime := "application/ogg" | |
codecs := make([]string, 0, len(streams)) | |
for _, stream := range streams { | |
flags |= stream.codec.flag | |
codecs = append(codecs, stream.codec.name) | |
} | |
if flags&flagCodecVideo > 0 { | |
mime = "video/ogg" | |
} else if flags&flagCodecAudio > 0 { | |
mime = "audio/ogg" | |
} | |
if len(codecs) > 0 { | |
return fmt.Sprintf(`%s codecs="%s"`, mime, strings.Join(codecs, ", ")) | |
} | |
return mime | |
} | |
func main() { | |
// curl -LO http://techslides.com/demos/sample-videos/small.ogv | |
f, err := os.Open("small.ogv") | |
if err != nil { | |
fmt.Println(err) | |
os.Exit(1) | |
} | |
// net/http.DetectContentType only read the first 512 bytes | |
// it seems to work with this video but there is no warranty the | |
// coded infos are within the first 512 bytes. | |
buf := make([]byte, 512) | |
_, err = io.ReadFull(f, buf) | |
if err != nil { | |
os.Exit(128) | |
} | |
streams, err := peek(bytes.NewReader(buf)) | |
if err != nil { | |
fmt.Println(err) | |
os.Exit(2) | |
} | |
for _, stream := range streams { | |
fmt.Printf("stream sn=%d codec=%s\n", stream.sn, stream.codec.name) | |
} | |
fmt.Println(mimeType(streams)) | |
fmt.Println(contentType(streams)) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment