github.com/boki/go-xmp@v1.0.1/models/exif/types.go (about) 1 // Copyright (c) 2017-2018 Alexander Eichhorn 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 // not use this file except in compliance with the License. You may obtain 5 // a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 // License for the specific language governing permissions and limitations 13 // under the License. 14 15 // http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf 16 17 package exif 18 19 import ( 20 "bytes" 21 "fmt" 22 "math" 23 "strconv" 24 "strings" 25 "time" 26 27 "trimmer.io/go-xmp/xmp" 28 ) 29 30 const EXIF_DATE_FORMAT = "2006:01:02 15:04:05" 31 32 type ByteArray []byte 33 34 func (x ByteArray) String() string { 35 s := make([]string, len(x)) 36 for i, v := range x { 37 s[i] = strconv.Itoa(int(v)) 38 } 39 return strings.Join(s, " ") 40 } 41 42 func (x ByteArray) MarshalText() ([]byte, error) { 43 return []byte(x.String()), nil 44 } 45 46 func (x *ByteArray) UnmarshalText(data []byte) error { 47 fields := strings.Fields(string(data)) 48 if len(fields) == 0 { 49 return nil 50 } 51 a := make(ByteArray, len(fields)) 52 for i, v := range fields { 53 if j, err := strconv.ParseUint(v, 10, 8); err != nil { 54 return fmt.Errorf("exif: invalid byte '%s': %v", v, err) 55 } else { 56 a[i] = byte(j) 57 } 58 } 59 *x = a 60 return nil 61 } 62 63 type Date time.Time 64 65 func Now() Date { 66 return Date(time.Now()) 67 } 68 69 func (x Date) IsZero() bool { 70 return time.Time(x).IsZero() 71 } 72 73 func (x Date) Value() time.Time { 74 return time.Time(x) 75 } 76 77 func (x Date) MarshalText() ([]byte, error) { 78 if x.IsZero() { 79 return nil, nil 80 } 81 return []byte(time.Time(x).Format(EXIF_DATE_FORMAT)), nil 82 } 83 84 func (x Date) MarshalXMP(e *xmp.Encoder, node *xmp.Node, m xmp.Model) error { 85 return e.EncodeElement(time.Time(x).Format(EXIF_DATE_FORMAT), node) 86 } 87 88 var timeFormats []string = []string{ 89 "2006-01-02T15:04:05.999999999", // XMP 90 "2006-01-02T15:04:05.999999999Z07:00", // XMP 91 "2006-01-02T15:04:05.999999999Z", // XMP 92 "2006-01-02T15:04:05Z", // XMP 93 "2006-01-02T15:04:05", // XMP 94 "2006-01-02T15:04", // XMP 95 "2006:01:02 15:04:05.999999999-07:00", // Exif 96 "2006:01:02 15:04:05-07:00", // Exif 97 "2006:01:02 15:04:05", // Exif 98 "2006:01:02 15:04", // Exif 99 "2006:01:02", // Exif GPS datestamp 100 "2006:01", // Exif 101 "2006", // Exif 102 } 103 104 func (x *Date) UnmarshalText(data []byte) error { 105 if len(data) == 0 { 106 return nil 107 } 108 value := string(data) 109 for _, f := range timeFormats { 110 if t, err := time.Parse(f, value); err == nil { 111 *x = Date(t) 112 return nil 113 } 114 } 115 return fmt.Errorf("exif: invalid datetime value '%s'", value) 116 } 117 118 func convertDateToXMP(d Date, s string) (xmp.Date, error) { 119 if d.IsZero() { 120 return xmp.Date{}, nil 121 } 122 xd := xmp.Date(d) 123 s = strings.TrimSpace(s) 124 if s != "" { 125 return xd, nil 126 } 127 if i, err := strconv.ParseInt(s, 10, 64); err != nil { 128 return xd, fmt.Errorf("exif: invalid subsecond format '%s': %v", s, err) 129 } else { 130 sec := d.Value().Unix() 131 nsec := i * int64(math.Pow10(9-len(s))) 132 xd = xmp.Date(time.Unix(sec, nsec)) 133 } 134 return xd, nil 135 } 136 137 type ComponentArray []Component 138 139 func (x ComponentArray) String() string { 140 s := make([]string, len(x)) 141 for i, v := range x { 142 s[i] = strconv.Itoa(int(v)) 143 } 144 return strings.Join(s, " ") 145 } 146 147 func (x ComponentArray) Typ() xmp.ArrayType { 148 return xmp.ArrayTypeOrdered 149 } 150 151 func (x ComponentArray) MarshalXMP(e *xmp.Encoder, node *xmp.Node, m xmp.Model) error { 152 return xmp.MarshalArray(e, node, x.Typ(), x) 153 } 154 155 func (x *ComponentArray) UnmarshalXMP(d *xmp.Decoder, node *xmp.Node, m xmp.Model) error { 156 return xmp.UnmarshalArray(d, node, x.Typ(), x) 157 } 158 159 type OECF struct { 160 Columns int `xmp:"exif:Columns"` 161 Rows int `xmp:"exif:Rows"` 162 Names xmp.StringArray `xmp:"exif:Names"` 163 Values xmp.RationalArray `xmp:"exif:Values"` 164 } 165 166 func (x OECF) IsZero() bool { 167 return x.Columns == 0 || x.Rows == 0 168 } 169 170 func (x *OECF) Addr() *OECF { 171 if x.IsZero() { 172 return nil 173 } 174 return x 175 } 176 177 func (x *OECF) UnmarshalText(data []byte) error { 178 var err error 179 o := OECF{} 180 for i, v := range strings.Fields(string(data)) { 181 switch { 182 case i == 0: 183 if o.Columns, err = strconv.Atoi(v); err != nil { 184 return fmt.Errorf("exif: invalid OECF columns value '%s': %v", v, err) 185 } 186 o.Names = make(xmp.StringArray, 0, o.Columns) 187 case i == 1: 188 if o.Rows, err = strconv.Atoi(v); err != nil { 189 return fmt.Errorf("exif: invalid OECF rows value '%s': %v", v, err) 190 } 191 o.Values = make(xmp.RationalArray, 0, o.Columns*o.Rows) 192 case i > 1 && i < o.Columns+1: 193 o.Names = append(o.Names, v) 194 default: 195 r := xmp.Rational{} 196 if err := r.UnmarshalText([]byte(v)); err != nil { 197 return fmt.Errorf("exif: invalid OECF value '%s': %v", v, err) 198 } 199 o.Values = append(o.Values, r) 200 } 201 } 202 *x = o 203 return nil 204 } 205 206 func (x OECF) MarshalXMP(e *xmp.Encoder, node *xmp.Node, m xmp.Model) error { 207 type _t OECF 208 return e.EncodeElement(_t(x), node) 209 } 210 211 type CFAPattern struct { 212 Columns int `xmp:"exif:Columns"` 213 Rows int `xmp:"exif:Rows"` 214 Values ByteArray `xmp:"exif:Values"` 215 } 216 217 func (x CFAPattern) IsZero() bool { 218 return x.Columns == 0 || x.Rows == 0 219 } 220 221 func (x *CFAPattern) Addr() *CFAPattern { 222 if x.IsZero() { 223 return nil 224 } 225 return x 226 } 227 228 func (x *CFAPattern) UnmarshalText(data []byte) error { 229 var err error 230 c := CFAPattern{} 231 for i, v := range strings.Fields(string(data)) { 232 switch { 233 case i == 0: 234 if c.Columns, err = strconv.Atoi(v); err != nil { 235 return fmt.Errorf("exif: invalid CFA columns value '%s': %v", v, err) 236 } 237 case i == 1: 238 if c.Rows, err = strconv.Atoi(v); err != nil { 239 return fmt.Errorf("exif: invalid CFA rows value '%s': %v", v, err) 240 } 241 c.Values = make(ByteArray, 0, c.Columns*c.Rows) 242 default: 243 if j, err := strconv.ParseUint(v, 10, 8); err != nil { 244 return fmt.Errorf("exif: invalid CFA value '%s': %v", v, err) 245 } else { 246 c.Values = append(c.Values, byte(j)) 247 } 248 } 249 } 250 *x = c 251 return nil 252 } 253 254 func (x CFAPattern) MarshalXMP(e *xmp.Encoder, node *xmp.Node, m xmp.Model) error { 255 if x.IsZero() { 256 return nil 257 } 258 type _t CFAPattern 259 return e.EncodeElement(_t(x), node) 260 } 261 262 type DeviceSettings struct { 263 Columns int `xmp:"exif:Columns"` 264 Rows int `xmp:"exif:Rows"` 265 Values xmp.StringArray `xmp:"exif:Values"` 266 } 267 268 func (x DeviceSettings) IsZero() bool { 269 return x.Columns == 0 || x.Rows == 0 270 } 271 272 func (x *DeviceSettings) Addr() *DeviceSettings { 273 if x.IsZero() { 274 return nil 275 } 276 return x 277 } 278 279 func (x *DeviceSettings) UnmarshalText(data []byte) error { 280 var err error 281 s := DeviceSettings{} 282 for i, v := range strings.Fields(string(data)) { 283 switch { 284 case i == 0: 285 if s.Columns, err = strconv.Atoi(v); err != nil { 286 return fmt.Errorf("exif: invalid device settings columns value '%s': %v", v, err) 287 } 288 case i == 1: 289 if s.Rows, err = strconv.Atoi(v); err != nil { 290 return fmt.Errorf("exif: invalid device settings rows value '%s': %v", v, err) 291 } 292 s.Values = make(xmp.StringArray, 0, s.Columns*s.Rows) 293 default: 294 s.Values = append(s.Values, v) 295 } 296 } 297 *x = s 298 return nil 299 } 300 301 func (x DeviceSettings) MarshalXMP(e *xmp.Encoder, node *xmp.Node, m xmp.Model) error { 302 if x.IsZero() { 303 return nil 304 } 305 type _t DeviceSettings 306 return e.EncodeElement(_t(x), node) 307 } 308 309 type Flash struct { 310 Fired xmp.Bool `xmp:"exif:Fired,attr,empty"` 311 Function xmp.Bool `xmp:"exif:Function,attr,empty"` 312 Mode FlashMode `xmp:"exif:Mode,attr,empty"` 313 RedEyeMode xmp.Bool `xmp:"exif:RedEyeMode,attr,empty"` 314 Return FlashReturnMode `xmp:"exif:Return,attr,empty"` 315 } 316 317 func (x Flash) IsZero() bool { 318 return x.Mode == 0 && x.Return == 0 && !x.Fired.Value() && !x.Function.Value() && !x.RedEyeMode.Value() 319 } 320 321 func (x *Flash) UnmarshalText(data []byte) error { 322 if len(data) == 0 { 323 return nil 324 } 325 v, err := strconv.ParseInt(string(data), 10, 32) 326 if err != nil { 327 return fmt.Errorf("exif: invalid flash value '%d': %v", v, err) 328 } 329 x.Fired = v&0x01 > 0 330 x.Return = FlashReturnMode(v >> 1 & 0x3) 331 x.Mode = FlashMode(v >> 3 & 0x03) 332 x.Function = v&0x20 > 0 333 x.RedEyeMode = v&0x40 > 0 334 return nil 335 } 336 337 func (x Flash) MarshalXMP(e *xmp.Encoder, node *xmp.Node, m xmp.Model) error { 338 if x.IsZero() { 339 return nil 340 } 341 type _t Flash 342 return e.EncodeElement(_t(x), node) 343 } 344 345 func (x *Flash) UnmarshalXMP(d *xmp.Decoder, node *xmp.Node, m xmp.Model) error { 346 // strip TextUnmarhal method 347 type _t Flash 348 f := _t{} 349 if err := d.DecodeElement(&f, node); err != nil { 350 return err 351 } 352 *x = Flash(f) 353 return nil 354 } 355 356 type GPSCoord [3]xmp.Rational 357 358 // unmarshal from decimal 359 func (x *GPSCoord) UnmarshalText(data []byte) error { 360 s := string(data) 361 f, err := strconv.ParseFloat(s, 64) 362 if err != nil || s[0] != '-' && len(strings.Split(s, ".")[0]) > 3 { 363 return fmt.Errorf("exif: invalid decimal GPS coordinate '%s'", s) 364 } 365 val := math.Abs(f) 366 degrees := int(math.Floor(val)) 367 minutes := int(math.Floor(60 * (val - float64(degrees)))) 368 seconds := 3600 * (val - float64(degrees) - (float64(minutes) / 60)) 369 (*x)[0] = xmp.FloatToRational(float32(degrees)) 370 (*x)[1] = xmp.FloatToRational(float32(minutes)) 371 (*x)[2] = xmp.FloatToRational(float32(seconds)) 372 return nil 373 } 374 375 func convertGPStoXMP(r GPSCoord, ref string) (xmp.GPSCoord, error) { 376 if ref == "" { 377 return "", nil 378 } 379 var deg [3]float64 380 for i, v := range r { 381 if v.Den == 0 { 382 return "", fmt.Errorf("exif: invalid GPS coordinate '%s'", v.String()) 383 } 384 deg[i] = float64(v.Num) / float64(v.Den) 385 } 386 min := deg[0]*60 + deg[1] + deg[2]/60 387 ideg := int(min / 60) 388 min -= float64(ideg) * 60 389 390 buf := bytes.Buffer{} 391 buf.Write(strconv.AppendInt(make([]byte, 0, 3), int64(ideg), 10)) 392 buf.WriteByte(',') 393 buf.Write(strconv.AppendFloat(make([]byte, 0, 24), min, 'f', 7, 64)) 394 buf.WriteString(ref) 395 return xmp.GPSCoord(buf.String()), nil 396 } 397 398 func convertGPSTimestamp(date Date, x xmp.RationalArray) xmp.Date { 399 if date.IsZero() { 400 return xmp.Date{} 401 } 402 var d time.Duration 403 for i, v := range x { 404 switch i { 405 case 0: 406 d += time.Duration(v.Value() * float64(time.Hour)) 407 case 1: 408 d += time.Duration(v.Value() * float64(time.Minute)) 409 case 2: 410 d += time.Duration(v.Value() * float64(time.Second)) 411 } 412 } 413 return xmp.Date(date.Value().Add(d)) 414 }