summaryrefslogtreecommitdiff
path: root/pngmeta/altwriter.go
diff options
context:
space:
mode:
authorGrail Finder <wohilas@gmail.com>2025-03-10 20:08:07 +0300
committerGrail Finder <wohilas@gmail.com>2025-03-10 20:08:07 +0300
commit0497cdcfe6af5f50871413ed3feb0d5316a1ba00 (patch)
tree694aef95084cc30c7a9c96552ec98ad7647206c6 /pngmeta/altwriter.go
parent10f0efbb2a5b8da1371eee8dd74c07d62d92bfb9 (diff)
Refactor: pngmeta rewrite
Diffstat (limited to 'pngmeta/altwriter.go')
-rw-r--r--pngmeta/altwriter.go137
1 files changed, 137 insertions, 0 deletions
diff --git a/pngmeta/altwriter.go b/pngmeta/altwriter.go
new file mode 100644
index 0000000..ebef172
--- /dev/null
+++ b/pngmeta/altwriter.go
@@ -0,0 +1,137 @@
+package pngmeta
+
+import (
+ "bytes"
+ "elefant/models"
+ "encoding/base64"
+ "encoding/binary"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "hash/crc32"
+ "io"
+ "os"
+)
+
+const (
+ pngHeader = "\x89PNG\r\n\x1a\n"
+ textChunkType = "tEXt"
+)
+
+// // PngEmbed holds the key-value pair to embed into the PNG file.
+// type PngEmbed struct {
+// Key string
+// Value string
+// }
+
+// WriteToPng embeds the metadata into the specified PNG file and writes the result to outfile.
+func WriteToPng(metadata *models.CharCardSpec, sourcePath, outfile string) error {
+ pngData, err := os.ReadFile(sourcePath)
+ if err != nil {
+ return err
+ }
+
+ jsonData, err := json.Marshal(metadata)
+ if err != nil {
+ return err
+ }
+
+ base64Data := base64.StdEncoding.EncodeToString(jsonData)
+ embedData := PngEmbed{
+ Key: "elefant", // Replace with appropriate key constant
+ Value: base64Data,
+ }
+
+ var outputBuffer bytes.Buffer
+ if _, err := outputBuffer.Write([]byte(pngHeader)); err != nil {
+ return err
+ }
+
+ chunks, iend, err := processChunks(pngData[8:])
+ if err != nil {
+ return err
+ }
+
+ for _, chunk := range chunks {
+ outputBuffer.Write(chunk)
+ }
+
+ newChunk := createTextChunk(embedData)
+ outputBuffer.Write(newChunk)
+ outputBuffer.Write(iend)
+
+ return os.WriteFile(outfile, outputBuffer.Bytes(), 0666)
+}
+
+// processChunks extracts non-tEXt chunks and locates the IEND chunk
+func processChunks(data []byte) ([][]byte, []byte, error) {
+ var (
+ chunks [][]byte
+ iendChunk []byte
+ reader = bytes.NewReader(data)
+ )
+
+ for {
+ var chunkLength uint32
+ if err := binary.Read(reader, binary.BigEndian, &chunkLength); err != nil {
+ if errors.Is(err, io.EOF) {
+ break
+ }
+ return nil, nil, fmt.Errorf("error reading chunk length: %w", err)
+ }
+
+ chunkType := make([]byte, 4)
+ if _, err := reader.Read(chunkType); err != nil {
+ return nil, nil, fmt.Errorf("error reading chunk type: %w", err)
+ }
+
+ chunkData := make([]byte, chunkLength)
+ if _, err := reader.Read(chunkData); err != nil {
+ return nil, nil, fmt.Errorf("error reading chunk data: %w", err)
+ }
+
+ crc := make([]byte, 4)
+ if _, err := reader.Read(crc); err != nil {
+ return nil, nil, fmt.Errorf("error reading CRC: %w", err)
+ }
+
+ fullChunk := bytes.NewBuffer(nil)
+ binary.Write(fullChunk, binary.BigEndian, chunkLength)
+ fullChunk.Write(chunkType)
+ fullChunk.Write(chunkData)
+ fullChunk.Write(crc)
+
+ switch string(chunkType) {
+ case "IEND":
+ iendChunk = fullChunk.Bytes()
+ return chunks, iendChunk, nil
+ case textChunkType:
+ continue // Skip existing tEXt chunks
+ default:
+ chunks = append(chunks, fullChunk.Bytes())
+ }
+ }
+
+ return nil, nil, errors.New("IEND chunk not found")
+}
+
+// createTextChunk generates a valid tEXt chunk with proper CRC
+func createTextChunk(embed PngEmbed) []byte {
+ content := bytes.NewBuffer(nil)
+ content.WriteString(embed.Key)
+ content.WriteByte(0) // Null separator
+ content.WriteString(embed.Value)
+
+ data := content.Bytes()
+ crc := crc32.NewIEEE()
+ crc.Write([]byte(textChunkType))
+ crc.Write(data)
+
+ chunk := bytes.NewBuffer(nil)
+ binary.Write(chunk, binary.BigEndian, uint32(len(data)))
+ chunk.Write([]byte(textChunkType))
+ chunk.Write(data)
+ binary.Write(chunk, binary.BigEndian, crc.Sum32())
+
+ return chunk.Bytes()
+}