mirror of
https://github.com/celogeek/go-comic-converter.git
synced 2025-05-24 07:42:37 +02:00
split image data / zip
This commit is contained in:
parent
c9b47ac030
commit
18a5611a4a
@ -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
|
||||
}
|
||||
}
|
||||
|
44
internal/epub/image_data.go
Normal file
44
internal/epub/image_data.go
Normal 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
83
internal/epub/zip.go
Normal 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
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user