split image data / zip

This commit is contained in:
Celogeek 2022-12-28 19:45:06 +01:00
parent c9b47ac030
commit 18a5611a4a
Signed by: celogeek
GPG Key ID: E6B7BDCFC446233A
3 changed files with 140 additions and 103 deletions

View File

@ -1,13 +1,8 @@
package epub
import (
"archive/zip"
"bytes"
"compress/flate"
"fmt"
"hash/crc32"
"io/fs"
"os"
"path/filepath"
"runtime"
"sort"
@ -22,11 +17,6 @@ import (
imageconverter "go-comic-converter/internal/image-converter"
)
type ImageData struct {
Header *zip.FileHeader
Data []byte
}
type Image struct {
Id int
Data *ImageData
@ -119,60 +109,6 @@ func (e *EPub) SetLimitMb(l int) *EPub {
return e
}
func (e *EPub) WriteMagic(wz *zip.Writer) error {
t := time.Now()
fh := &zip.FileHeader{
Name: "mimetype",
Method: zip.Store,
Modified: t,
ModifiedTime: uint16(t.Second()/2 + t.Minute()<<5 + t.Hour()<<11),
ModifiedDate: uint16(t.Day() + int(t.Month())<<5 + (t.Year()-1980)<<9),
CompressedSize64: 20,
UncompressedSize64: 20,
CRC32: 0x2cab616f,
}
fh.SetMode(0600)
m, err := wz.CreateRaw(fh)
if err != nil {
return err
}
_, err = m.Write([]byte("application/epub+zip"))
return err
}
func (e *EPub) WriteImage(wz *zip.Writer, image *Image) error {
m, err := wz.CreateRaw(image.Data.Header)
if err != nil {
return err
}
_, err = m.Write(image.Data.Data)
return err
}
func (e *EPub) WriteFile(wz *zip.Writer, file string, data any) error {
var content []byte
switch b := data.(type) {
case string:
content = []byte(b)
case []byte:
content = b
default:
return fmt.Errorf("support string of []byte")
}
m, err := wz.CreateHeader(&zip.FileHeader{
Name: file,
Modified: time.Now(),
Method: zip.Deflate,
})
if err != nil {
return err
}
_, err = m.Write(content)
return err
}
func (e *EPub) Render(templateString string, data any) string {
tmpl, err := e.TemplateProcessor.Parse(templateString)
if err != nil {
@ -237,35 +173,13 @@ func (e *EPub) LoadDir(dirname string) *EPub {
e.ViewHeight,
e.Quality,
)
cdata := bytes.NewBuffer([]byte{})
wcdata, err := flate.NewWriter(cdata, flate.BestCompression)
if err != nil {
panic(err)
}
wcdata.Write(data)
wcdata.Close()
if err != nil {
panic(err)
}
t := time.Now()
name := fmt.Sprintf("OEBPS/Images/%d.jpg", task.Id)
if task.Id == 0 {
name = "OEBPS/Images/cover.jpg"
}
results <- &Image{
task.Id,
&ImageData{
&zip.FileHeader{
Name: name,
CompressedSize64: uint64(cdata.Len()),
UncompressedSize64: uint64(len(data)),
CRC32: crc32.Checksum(data, crc32.IEEETable),
Method: zip.Deflate,
ModifiedTime: uint16(t.Second()/2 + t.Minute()<<5 + t.Hour()<<11),
ModifiedDate: uint16(t.Day() + int(t.Month())<<5 + (t.Year()-1980)<<9),
},
cdata.Bytes(),
},
NewImageData(name, data),
w,
h,
}
@ -313,20 +227,17 @@ func (e *EPub) GetParts() []*EpubPart {
maxSize := uint64(e.LimitMb * 1024 * 1024)
sizeImage := func(image *Image) uint64 {
// image + header + len Name + xhtml size
return image.Data.Header.CompressedSize64 + 30 + uint64(len(image.Data.Header.Name))
}
xhtmlSize := uint64(1024)
// descriptor files + image
baseSize := uint64(16*1024) + sizeImage(cover)
baseSize := uint64(16*1024) + cover.Data.CompressedSize()
currentSize := baseSize
currentImages := make([]*Image, 0)
part := 1
for _, img := range images {
if len(currentImages) > 0 && currentSize+sizeImage(img)+xhtmlSize > maxSize {
imgSize := img.Data.CompressedSize() + xhtmlSize
if len(currentImages) > 0 && currentSize+imgSize > maxSize {
epubPart = append(epubPart, &EpubPart{
Cover: cover,
Images: currentImages,
@ -335,7 +246,7 @@ func (e *EPub) GetParts() []*EpubPart {
currentSize = baseSize
currentImages = make([]*Image, 0)
}
currentSize += sizeImage(img) + xhtmlSize
currentSize += imgSize
currentImages = append(currentImages, img)
}
if len(currentImages) > 0 {
@ -368,11 +279,13 @@ func (e *EPub) Write() error {
if totalParts > 1 {
suffix = fmt.Sprintf(" PART_%02d", i+1)
}
path := fmt.Sprintf("%s%s%s", e.Path[0:len(e.Path)-len(ext)], suffix, ext)
w, err := os.Create(path)
wz, err := NewEpubZip(path)
if err != nil {
return err
}
defer wz.Close()
zipContent := []ZipContent{
{"META-INF/container.xml", TEMPLATE_CONTAINER},
@ -387,25 +300,22 @@ func (e *EPub) Write() error {
})},
}
wz := zip.NewWriter(w)
defer wz.Close()
if err = e.WriteMagic(wz); err != nil {
if err = wz.WriteMagic(); err != nil {
return err
}
for _, content := range zipContent {
if err := e.WriteFile(wz, content.Name, content.Content); err != nil {
if err := wz.WriteFile(content.Name, content.Content); err != nil {
return err
}
}
e.WriteImage(wz, part.Cover)
wz.WriteImage(part.Cover.Data)
for _, img := range part.Images {
text := fmt.Sprintf("OEBPS/Text/%d.xhtml", img.Id)
if err := e.WriteFile(wz, text, e.Render(TEMPLATE_TEXT, img)); err != nil {
if err := wz.WriteFile(text, e.Render(TEMPLATE_TEXT, img)); err != nil {
return err
}
if err := e.WriteImage(wz, img); err != nil {
if err := wz.WriteImage(img.Data); err != nil {
return err
}
}

View File

@ -0,0 +1,44 @@
package epub
import (
"archive/zip"
"bytes"
"compress/flate"
"hash/crc32"
"time"
)
type ImageData struct {
Header *zip.FileHeader
Data []byte
}
func (img *ImageData) CompressedSize() uint64 {
return img.Header.CompressedSize64 + 30 + uint64(len(img.Header.Name))
}
func NewImageData(name string, data []byte) *ImageData {
cdata := bytes.NewBuffer([]byte{})
wcdata, err := flate.NewWriter(cdata, flate.BestCompression)
if err != nil {
panic(err)
}
wcdata.Write(data)
wcdata.Close()
if err != nil {
panic(err)
}
t := time.Now()
return &ImageData{
&zip.FileHeader{
Name: name,
CompressedSize64: uint64(cdata.Len()),
UncompressedSize64: uint64(len(data)),
CRC32: crc32.Checksum(data, crc32.IEEETable),
Method: zip.Deflate,
ModifiedTime: uint16(t.Second()/2 + t.Minute()<<5 + t.Hour()<<11),
ModifiedDate: uint16(t.Day() + int(t.Month())<<5 + (t.Year()-1980)<<9),
},
cdata.Bytes(),
}
}

83
internal/epub/zip.go Normal file
View File

@ -0,0 +1,83 @@
package epub
import (
"archive/zip"
"fmt"
"os"
"time"
)
type EpubZip struct {
w *os.File
wz *zip.Writer
}
func NewEpubZip(path string) (*EpubZip, error) {
w, err := os.Create(path)
if err != nil {
return nil, err
}
wz := zip.NewWriter(w)
return &EpubZip{w, wz}, nil
}
func (e *EpubZip) Close() error {
if err := e.wz.Close(); err != nil {
return err
}
return e.w.Close()
}
func (e *EpubZip) WriteMagic() error {
t := time.Now()
fh := &zip.FileHeader{
Name: "mimetype",
Method: zip.Store,
Modified: t,
ModifiedTime: uint16(t.Second()/2 + t.Minute()<<5 + t.Hour()<<11),
ModifiedDate: uint16(t.Day() + int(t.Month())<<5 + (t.Year()-1980)<<9),
CompressedSize64: 20,
UncompressedSize64: 20,
CRC32: 0x2cab616f,
}
fh.SetMode(0600)
m, err := e.wz.CreateRaw(fh)
if err != nil {
return err
}
_, err = m.Write([]byte("application/epub+zip"))
return err
}
func (e *EpubZip) WriteImage(image *ImageData) error {
m, err := e.wz.CreateRaw(image.Header)
if err != nil {
return err
}
_, err = m.Write(image.Data)
return err
}
func (e *EpubZip) WriteFile(file string, data any) error {
var content []byte
switch b := data.(type) {
case string:
content = []byte(b)
case []byte:
content = b
default:
return fmt.Errorf("support string of []byte")
}
m, err := e.wz.CreateHeader(&zip.FileHeader{
Name: file,
Modified: time.Now(),
Method: zip.Deflate,
})
if err != nil {
return err
}
_, err = m.Write(content)
return err
}