A tool for archiving & converting scans of postcards, and information about them.

fix: Add CardColor into XMP data

I forgot to add CardColor to the metadata stored in XMP. The recent version number fix means XMP tests are now fragile, they will need fixing.

+44 -4
+4
formats/xmp/decode.go
··· 54 54 TranscriptionFront string `json:"Postcard:TranscriptionFront"` 55 55 TranscriptionBack string `json:"Postcard:TranscriptionBack"` 56 56 PhysicalThicknessMM string `json:"Postcard:PhysicalThicknessMM"` 57 + CardColor string `json:"Postcard:CardColor"` 57 58 } `json:"Postcard"` 58 59 EXIF struct { 59 60 Date string `json:"exif:DateTimeOriginal"` ··· 107 108 meta.Back.Description = js.Models.Postcard.DescriptionBack 108 109 if thick, err := strconv.ParseFloat(js.Models.Postcard.PhysicalThicknessMM, 64); err == nil { 109 110 meta.Physical.ThicknessMM = thick 111 + } 112 + if c, err := types.ColorFromString(js.Models.Postcard.CardColor); err != nil { 113 + meta.Physical.CardColor = c 110 114 } 111 115 112 116 json.Unmarshal([]byte(js.Models.Postcard.TranscriptionFront), &meta.Front.Transcription)
+2
formats/xmp/postcard.go
··· 17 17 TranscriptionFront types.AnnotatedText `xml:"Postcard:TranscriptionFront,omitempty"` 18 18 TranscriptionBack types.AnnotatedText `xml:"Postcard:TranscriptionBack,omitempty"` 19 19 PhysicalThicknessMM float64 `xml:"Postcard:PhysicalThicknessMM,omitempty"` 20 + CardColor string `xml:"Postcard:CardColor,omitempty"` 20 21 } 21 22 22 23 func addPostcardSection(sections []interface{}, meta types.Metadata) []interface{} { ··· 33 34 TranscriptionBack: meta.Back.Transcription, 34 35 35 36 PhysicalThicknessMM: meta.Physical.ThicknessMM, 37 + CardColor: meta.Physical.CardColor.String(), 36 38 } 37 39 38 40 if meta.Context.Description != "" || meta.Locale != "" {
+13
internal/testhelpers/fixtures.go
··· 1 1 package testhelpers 2 2 3 3 import ( 4 + "bytes" 4 5 "embed" 5 6 "fmt" 6 7 "image" ··· 8 9 "math/big" 9 10 "time" 10 11 12 + "github.com/jphastings/dotpostcard/internal/version" 11 13 "github.com/jphastings/dotpostcard/types" 12 14 ) 13 15 ··· 111 113 CmHeight: big.NewRat(105, 10), 112 114 }, 113 115 ThicknessMM: 0.4, 116 + CardColor: &types.Color{R: 230, G: 230, B: 217, A: 255}, 114 117 }, 115 118 }, 116 119 Front: TestImages["sample-front.png"], ··· 122 125 123 126 //go:embed sample-meta.yaml 124 127 var SampleYAML []byte 128 + 129 + func init() { 130 + // Ensure the sample XMP has the correct version number in it 131 + SampleXMP = bytes.Replace( 132 + SampleXMP, 133 + []byte("postcards/v0.0.0"), 134 + []byte(fmt.Sprintf("postcards/v%s", version.Version)), 135 + -1, 136 + ) 137 + }
+1 -1
internal/testhelpers/sample-meta.xmp
··· 1 - <?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?><x:xmpmeta xmlns:x="adobe:ns:meta/" xmlns:xmptk="postcards/v0.1.0"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description xmlns:Iptc4xmpCore="http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/"><Iptc4xmpCore:AltTextAccessibility><rdf:Alt><rdf:li xml:lang="en">Both sides of a postcard. On the front: The word &#39;Front&#39; in large blue letters On the back: Back</rdf:li></rdf:Alt></Iptc4xmpCore:AltTextAccessibility></rdf:Description><rdf:Description xmlns:Iptc4xmpExt="http://iptc.org/std/Iptc4xmpExt/2008-02-29/"><Iptc4xmpExt:ImageRegion><rdf:Bag><rdf:li rdf:parseType="Resource"><Iptc4xmpExt:Name><rdf:Alt><rdf:li xml:lang="en">Private information</rdf:li></rdf:Alt></Iptc4xmpExt:Name><Iptc4xmpExt:RegionBoundary rdf:parseType="Resource"><Iptc4xmpExt:rbUnit>relative</Iptc4xmpExt:rbUnit><Iptc4xmpExt:rbShape>polygon</Iptc4xmpExt:rbShape><Iptc4xmpExt:rbVertices><rdf:Seq><rdf:li rdf:parseType="Resource"><Iptc4xmpExt:rbX>0.3</Iptc4xmpExt:rbX><Iptc4xmpExt:rbY>0.3</Iptc4xmpExt:rbY></rdf:li><rdf:li rdf:parseType="Resource"><Iptc4xmpExt:rbX>0.4</Iptc4xmpExt:rbX><Iptc4xmpExt:rbY>0.3</Iptc4xmpExt:rbY></rdf:li><rdf:li rdf:parseType="Resource"><Iptc4xmpExt:rbX>0.4</Iptc4xmpExt:rbX><Iptc4xmpExt:rbY>0.4</Iptc4xmpExt:rbY></rdf:li><rdf:li rdf:parseType="Resource"><Iptc4xmpExt:rbX>0.3</Iptc4xmpExt:rbX><Iptc4xmpExt:rbY>0.4</Iptc4xmpExt:rbY></rdf:li></rdf:Seq></Iptc4xmpExt:rbVertices></Iptc4xmpExt:RegionBoundary></rdf:li><rdf:li rdf:parseType="Resource"><Iptc4xmpExt:Name><rdf:Alt><rdf:li xml:lang="en">Private information</rdf:li></rdf:Alt></Iptc4xmpExt:Name><Iptc4xmpExt:RegionBoundary rdf:parseType="Resource"><Iptc4xmpExt:rbUnit>relative</Iptc4xmpExt:rbUnit><Iptc4xmpExt:rbShape>polygon</Iptc4xmpExt:rbShape><Iptc4xmpExt:rbVertices><rdf:Seq><rdf:li rdf:parseType="Resource"><Iptc4xmpExt:rbX>0</Iptc4xmpExt:rbX><Iptc4xmpExt:rbY>0.5</Iptc4xmpExt:rbY></rdf:li><rdf:li rdf:parseType="Resource"><Iptc4xmpExt:rbX>0.1</Iptc4xmpExt:rbX><Iptc4xmpExt:rbY>0.5</Iptc4xmpExt:rbY></rdf:li><rdf:li rdf:parseType="Resource"><Iptc4xmpExt:rbX>0.1</Iptc4xmpExt:rbX><Iptc4xmpExt:rbY>0.7</Iptc4xmpExt:rbY></rdf:li><rdf:li rdf:parseType="Resource"><Iptc4xmpExt:rbX>0</Iptc4xmpExt:rbX><Iptc4xmpExt:rbY>0.7</Iptc4xmpExt:rbY></rdf:li></rdf:Seq></Iptc4xmpExt:rbVertices></Iptc4xmpExt:RegionBoundary></rdf:li></rdf:Bag></Iptc4xmpExt:ImageRegion></rdf:Description><rdf:Description xmlns:exif="http://ns.adobe.com/exif/1.0/"><exif:DateTimeOriginal>2006-01-02</exif:DateTimeOriginal><exif:GPSAreaInformation>Front, Italy</exif:GPSAreaInformation><exif:GPSLatitude>45,16.80000000N</exif:GPSLatitude><exif:GPSLongitude>7,39.60000000E</exif:GPSLongitude></rdf:Description><rdf:Description xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:description><rdf:Alt><rdf:li xml:lang="en">A postcard stored in the dotpostcard format (https://dotpostcard.org)</rdf:li></rdf:Alt></dc:description></rdf:Description><rdf:Description xmlns:Postcard="https://dotpostcard.org/xmp/1.0/"><Postcard:Flip>book</Postcard:Flip><Postcard:CountryCode>ITA</Postcard:CountryCode><Postcard:Sender>Alice (https://alice.example.com)</Postcard:Sender><Postcard:Recipient>Bob (https://bob.example.org)</Postcard:Recipient><Postcard:Context><rdf:Alt><rdf:li xml:lang="en-GB">This is a sample postcard, with all fields expressed.</rdf:li></rdf:Alt></Postcard:Context><Postcard:ContextAuthor>Carol (https://carol.example.net)</Postcard:ContextAuthor><Postcard:DescriptionFront>The word &#39;Front&#39; in large blue letters</Postcard:DescriptionFront><Postcard:DescriptionBack>The word &#39;Back&#39; in large red letters</Postcard:DescriptionBack><Postcard:TranscriptionFront>{&#34;text&#34;:&#34;Front&#34;}</Postcard:TranscriptionFront><Postcard:TranscriptionBack>{&#34;text&#34;:&#34;Back&#34;,&#34;annotations&#34;:[{&#34;type&#34;:&#34;locale&#34;,&#34;value&#34;:&#34;en-GB&#34;,&#34;start&#34;:0,&#34;end&#34;:4}]}</Postcard:TranscriptionBack><Postcard:PhysicalThicknessMM>0.4</Postcard:PhysicalThicknessMM></rdf:Description></rdf:RDF></x:xmpmeta><?xpacket end='w'?> 1 + <?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?><x:xmpmeta xmlns:x="adobe:ns:meta/" xmlns:xmptk="postcards/v0.0.0"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description xmlns:Iptc4xmpCore="http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/"><Iptc4xmpCore:AltTextAccessibility><rdf:Alt><rdf:li xml:lang="en">Both sides of a postcard. On the front: The word &#39;Front&#39; in large blue letters On the back: Back</rdf:li></rdf:Alt></Iptc4xmpCore:AltTextAccessibility></rdf:Description><rdf:Description xmlns:Iptc4xmpExt="http://iptc.org/std/Iptc4xmpExt/2008-02-29/"><Iptc4xmpExt:ImageRegion><rdf:Bag><rdf:li rdf:parseType="Resource"><Iptc4xmpExt:Name><rdf:Alt><rdf:li xml:lang="en">Private information</rdf:li></rdf:Alt></Iptc4xmpExt:Name><Iptc4xmpExt:RegionBoundary rdf:parseType="Resource"><Iptc4xmpExt:rbUnit>relative</Iptc4xmpExt:rbUnit><Iptc4xmpExt:rbShape>polygon</Iptc4xmpExt:rbShape><Iptc4xmpExt:rbVertices><rdf:Seq><rdf:li rdf:parseType="Resource"><Iptc4xmpExt:rbX>0.3</Iptc4xmpExt:rbX><Iptc4xmpExt:rbY>0.3</Iptc4xmpExt:rbY></rdf:li><rdf:li rdf:parseType="Resource"><Iptc4xmpExt:rbX>0.4</Iptc4xmpExt:rbX><Iptc4xmpExt:rbY>0.3</Iptc4xmpExt:rbY></rdf:li><rdf:li rdf:parseType="Resource"><Iptc4xmpExt:rbX>0.4</Iptc4xmpExt:rbX><Iptc4xmpExt:rbY>0.4</Iptc4xmpExt:rbY></rdf:li><rdf:li rdf:parseType="Resource"><Iptc4xmpExt:rbX>0.3</Iptc4xmpExt:rbX><Iptc4xmpExt:rbY>0.4</Iptc4xmpExt:rbY></rdf:li></rdf:Seq></Iptc4xmpExt:rbVertices></Iptc4xmpExt:RegionBoundary></rdf:li><rdf:li rdf:parseType="Resource"><Iptc4xmpExt:Name><rdf:Alt><rdf:li xml:lang="en">Private information</rdf:li></rdf:Alt></Iptc4xmpExt:Name><Iptc4xmpExt:RegionBoundary rdf:parseType="Resource"><Iptc4xmpExt:rbUnit>relative</Iptc4xmpExt:rbUnit><Iptc4xmpExt:rbShape>polygon</Iptc4xmpExt:rbShape><Iptc4xmpExt:rbVertices><rdf:Seq><rdf:li rdf:parseType="Resource"><Iptc4xmpExt:rbX>0</Iptc4xmpExt:rbX><Iptc4xmpExt:rbY>0.5</Iptc4xmpExt:rbY></rdf:li><rdf:li rdf:parseType="Resource"><Iptc4xmpExt:rbX>0.1</Iptc4xmpExt:rbX><Iptc4xmpExt:rbY>0.5</Iptc4xmpExt:rbY></rdf:li><rdf:li rdf:parseType="Resource"><Iptc4xmpExt:rbX>0.1</Iptc4xmpExt:rbX><Iptc4xmpExt:rbY>0.7</Iptc4xmpExt:rbY></rdf:li><rdf:li rdf:parseType="Resource"><Iptc4xmpExt:rbX>0</Iptc4xmpExt:rbX><Iptc4xmpExt:rbY>0.7</Iptc4xmpExt:rbY></rdf:li></rdf:Seq></Iptc4xmpExt:rbVertices></Iptc4xmpExt:RegionBoundary></rdf:li></rdf:Bag></Iptc4xmpExt:ImageRegion></rdf:Description><rdf:Description xmlns:exif="http://ns.adobe.com/exif/1.0/"><exif:DateTimeOriginal>2006-01-02</exif:DateTimeOriginal><exif:GPSAreaInformation>Front, Italy</exif:GPSAreaInformation><exif:GPSLatitude>45,16.80000000N</exif:GPSLatitude><exif:GPSLongitude>7,39.60000000E</exif:GPSLongitude></rdf:Description><rdf:Description xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:description><rdf:Alt><rdf:li xml:lang="en">A postcard stored in the dotpostcard format (https://dotpostcard.org)</rdf:li></rdf:Alt></dc:description></rdf:Description><rdf:Description xmlns:Postcard="https://dotpostcard.org/xmp/1.0/"><Postcard:Flip>book</Postcard:Flip><Postcard:CountryCode>ITA</Postcard:CountryCode><Postcard:Sender>Alice (https://alice.example.com)</Postcard:Sender><Postcard:Recipient>Bob (https://bob.example.org)</Postcard:Recipient><Postcard:Context><rdf:Alt><rdf:li xml:lang="en-GB">This is a sample postcard, with all fields expressed.</rdf:li></rdf:Alt></Postcard:Context><Postcard:ContextAuthor>Carol (https://carol.example.net)</Postcard:ContextAuthor><Postcard:DescriptionFront>The word &#39;Front&#39; in large blue letters</Postcard:DescriptionFront><Postcard:DescriptionBack>The word &#39;Back&#39; in large red letters</Postcard:DescriptionBack><Postcard:TranscriptionFront>{&#34;text&#34;:&#34;Front&#34;}</Postcard:TranscriptionFront><Postcard:TranscriptionBack>{&#34;text&#34;:&#34;Back&#34;,&#34;annotations&#34;:[{&#34;type&#34;:&#34;locale&#34;,&#34;value&#34;:&#34;en-GB&#34;,&#34;start&#34;:0,&#34;end&#34;:4}]}</Postcard:TranscriptionBack><Postcard:PhysicalThicknessMM>0.4</Postcard:PhysicalThicknessMM><Postcard:CardColor>#E6E6D9</Postcard:CardColor></rdf:Description></rdf:RDF></x:xmpmeta><?xpacket end='w'?>
+22 -1
types/color.go
··· 8 8 9 9 type Color color.RGBA 10 10 11 - func colorFromString(str string) (r, g, b uint8, err error) { 11 + func ColorFromString(str string) (*Color, error) { 12 + r, g, b, err := rgbFromString(str) 13 + if err != nil { 14 + return nil, err 15 + } 16 + 17 + return &Color{ 18 + R: r, 19 + G: g, 20 + B: b, 21 + }, nil 22 + } 23 + 24 + func rgbFromString(str string) (r, g, b uint8, err error) { 12 25 str = strings.Trim(str, `"`) 13 26 str = strings.TrimPrefix(str, `#`) 14 27 15 28 _, err = fmt.Sscanf(str, "%02X%02X%02X", &r, &g, &b) 16 29 return 17 30 } 31 + 32 + func (c *Color) String() string { 33 + if c == nil { 34 + return "" 35 + } 36 + 37 + return fmt.Sprintf("#%02X%02X%02X", c.R, c.G, c.B) 38 + }
+1 -1
types/json.go
··· 35 35 } 36 36 37 37 func (c *Color) UnmarshalJSON(bb []byte) error { 38 - r, g, b, err := colorFromString(string(bb)) 38 + r, g, b, err := rgbFromString(string(bb)) 39 39 if err != nil { 40 40 return err 41 41 }
+1 -1
types/yaml.go
··· 165 165 return fmt.Errorf("invalid color format, expected a string") 166 166 } 167 167 168 - r, g, b, err := colorFromString(y.Value) 168 + r, g, b, err := rgbFromString(y.Value) 169 169 if err != nil { 170 170 return err 171 171 }