diff --git a/metadata.go b/metadata.go index e6456d981871d8d79d561bea1f1ed4797658a608..4746bd3d0d370dc42585711ce59b9d9a471e0e0d 100644 --- a/metadata.go +++ b/metadata.go @@ -7,7 +7,7 @@ import ( "regexp" "strings" - "github.com/clee/gobipocket" + "git.autistici.org/ale/liber/third_party/gobipocket" "github.com/meskio/epubgo" ) diff --git a/third_party/gobipocket/README b/third_party/gobipocket/README new file mode 100644 index 0000000000000000000000000000000000000000..9afb2a4fe723fae9cdeffe1da29fe34a38b9d3b9 --- /dev/null +++ b/third_party/gobipocket/README @@ -0,0 +1 @@ +From https://github.com/clee/gobipocket, last commit pre-decompression. diff --git a/third_party/gobipocket/mobipocket.go b/third_party/gobipocket/mobipocket.go new file mode 100644 index 0000000000000000000000000000000000000000..d5d587b65714d17ea1f68d75ea3fff4fbd67d555 --- /dev/null +++ b/third_party/gobipocket/mobipocket.go @@ -0,0 +1,161 @@ +package mobipocket + +import ( + "encoding/binary" + "fmt" + "io" + "os" +) + +type Mobipocket struct { + Metadata map[string][]string + RawTextRecords [][]byte +} + +// Open reads the file into memory and parses the headers to +// populate the Metadata +func Open(path string) (m *Mobipocket, e error) { + f, e := os.Open(path) + if e != nil { + return nil, e + } + + m = new(Mobipocket) + m.parse(f) + return m, nil +} + +func (mobi *Mobipocket) parse(r io.ReaderAt) { + exth := map[int]string{ + 100: "author", + 101: "publisher", + 103: "description", + 104: "isbn", + 105: "subject", + 106: "pubdate", + 113: "asin", + 503: "title", + 504: "asin", + } + + compressionTypes := map[int]string{ + 1: "none", + 2: "palmdoc", + 17480: "huffcdic", + } + + drmTypes := map[int]string{ + 0: "unencrypted", + 1: "deprecated", + 2: "encrypted", + } + + m := make(map[string][]string) + + s := makeShortReader(r) + l := makeLongReader(r) + str := makeStringReader(r) + + // uint16 at 0x4C holds the number of records + // first record's offset is a uint32 at 0x4E + //recordCount := int(s(0x4C)) + firstRecordOffset := int64(l(0x4E)) + headerLength := int64(l(firstRecordOffset + 0x14)) + + mobiversion := int(l(firstRecordOffset + 0x24)) + fmt.Printf("MobiPocket file version: %d\n", mobiversion) + + m["compression"] = []string{compressionTypes[int(s(firstRecordOffset))]} + m["textLength"] = []string{fmt.Sprintf("%d", l(firstRecordOffset+0x04))} + firstTextRecord := 1 // int(s(firstRecordOffset + 0xC0)) + numberTextRecords := int(s(firstRecordOffset + 0x08)) + + //fmt.Printf("PalmDB record count: %d (first text: %d, numTR: %d)\n", recordCount, firstTextRecord, numberTextRecords) + /* + for i := int(0); i < recordCount; i++ { + fmt.Printf("\trecord %d @ 0x%04x\n", i, l(0x4E + int64(i * 8))) + } + */ + mobi.RawTextRecords = make([][]byte, 0) + for i := firstTextRecord; i < firstTextRecord+numberTextRecords; i++ { + recordStart := l(0x4E + int64(i*8)) + nextRecordStart := l(0x4E + int64((i+1)*8)) + record := make([]byte, nextRecordStart-recordStart) + _, err := r.ReadAt(record, int64(recordStart)) + if err != nil { + panic(err) + } + //fmt.Printf("read record %d @ 0x%04x, length 0x%04x bytes\n", i, recordStart, len(record)) + mobi.RawTextRecords = append(mobi.RawTextRecords, record) + } + m["drm"] = []string{drmTypes[int(s(firstRecordOffset+0x0C))]} + + fullTitlePos := int64(l(firstRecordOffset + 0x54)) + fullTitleLength := int64(l(firstRecordOffset + 0x58)) + m["title"] = []string{str(firstRecordOffset+fullTitlePos, fullTitleLength)} + + extendedFlags := int64(l(firstRecordOffset + 0x80)) + if extendedFlags&0x40 != 0x40 { + mobi.Metadata = m + return + } + + // extended header block should start with string EXTH + if str(firstRecordOffset+headerLength+16, 4) != "EXTH" { + fmt.Printf("extended header is all wrong, man!\n") + mobi.Metadata = m + return + } + + extBaseOffset := int64(firstRecordOffset + headerLength + 16) + extCount := int(l(extBaseOffset + 8)) + + pos := int64(12) + for i := 0; i < extCount; i++ { + recordType := int(l(extBaseOffset + pos)) + recordLength := int64(l(extBaseOffset + pos + 4)) + + key, valid := exth[recordType] + if valid { + m[key] = append(m[key], str(extBaseOffset+pos+8, recordLength-8)) + } + + pos += recordLength + } + + mobi.Metadata = m + return +} + +func makeShortReader(r io.ReaderAt) func(int64) uint16 { + return func(o int64) uint16 { + s := make([]byte, 2) + _, err := r.ReadAt(s, o) + if err != nil { + panic(err) + } + return binary.BigEndian.Uint16(s) + } +} + +func makeLongReader(r io.ReaderAt) func(int64) uint32 { + return func(o int64) uint32 { + l := make([]byte, 4) + _, err := r.ReadAt(l, o) + if err != nil { + panic(err) + } + return binary.BigEndian.Uint32(l) + } +} + +func makeStringReader(r io.ReaderAt) func(int64, int64) string { + return func(o, l int64) string { + s := make([]byte, l) + _, err := r.ReadAt(s, o) + if err != nil { + panic(err) + } + return string(s) + } +}