github.com/boki/go-xmp@v1.0.1/xmp/xmp_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 // Types as defined in ISO 16684-1:2011(E) 8.2.1 (Core value types) 16 // - Bool 17 // - Date 18 // - AgentName 19 // - GUID 20 // - Uri 21 // - Url 22 // - Rational 23 // - Rating (enum) 24 // Derived Types as defined in ISO 16684-1:2011(E) 8.2.2 (Derived value types) 25 // - Locale 26 // - GPSCoord 27 // Array Types 28 // - DateList (ordered) 29 // - GUIDList (ordered) 30 // - UriList (ordered) 31 // - UriArray (unordered) 32 // - UrlList (ordered) 33 // - UrlArray (unordered) 34 // - RationalArray (unordered) 35 // - LocaleArray 36 37 package xmp 38 39 import ( 40 "bytes" 41 "encoding/json" 42 "fmt" 43 "strconv" 44 "strings" 45 "time" 46 ) 47 48 type Zero interface { 49 IsZero() bool 50 } 51 52 // 8.2.1.1 Boolean 53 type Bool bool 54 55 const ( 56 True Bool = true 57 False Bool = false 58 ) 59 60 func (x Bool) Value() bool { 61 return bool(x) 62 } 63 64 func (x Bool) MarshalText() ([]byte, error) { 65 if x { 66 return []byte("True"), nil 67 } 68 return []byte("False"), nil 69 } 70 71 func (x *Bool) UnmarshalText(data []byte) error { 72 s := string(data) 73 switch s { 74 case "True", "true", "TRUE": 75 *x = true 76 case "False", "false", "FALSE": 77 *x = false 78 default: 79 return fmt.Errorf("xmp: invalid bool value '%s'", s) 80 } 81 return nil 82 } 83 84 func (x Bool) MarshalJSON() ([]byte, error) { 85 return json.Marshal(x.Value()) 86 } 87 88 // 8.2.1.2 Date 89 type Date time.Time 90 91 func NewDate(t time.Time) Date { 92 return Date(t) 93 } 94 95 func Now() Date { 96 return Date(time.Now()) 97 } 98 99 func (x Date) Time() Time { 100 return Time(x.Value()) 101 } 102 103 func (x Date) Value() time.Time { 104 return time.Time(x) 105 } 106 107 func (x Date) String() string { 108 return time.Time(x).Format(time.RFC3339) 109 } 110 111 func (x Date) IsZero() bool { 112 return time.Time(x).IsZero() 113 } 114 115 func (x Date) MarshalText() ([]byte, error) { 116 if x.IsZero() { 117 return nil, nil 118 } 119 return []byte(x.String()), nil 120 } 121 122 var dateFormats []string = []string{ 123 "2006-01-02T15:04:05.999999999", // XMP 124 time.RFC3339, // XMP 125 "2006-01-02T15:04-07:00", // EXIF 126 "2006-01-02", // EXIF 127 "2006-01-02 15:04:05", // EXR 128 "2006-01-02T15:04:05.999999999Z07:00", // XMP 129 "2006-01-02T15:04:05.999999999Z", 130 "2006-01-02T15:04:05Z", 131 "2006-01-02T15:04:05-0700", 132 "2006:01:02 15:04:05.999", // MXF 133 "2006/01/02T15:04:05-07:00", // Arri CSV 134 "06/01/02T15:04:05-07:00", // Arri CSV 135 "20060102T15h04m05-07:00", // Arri QT 136 "20060102T15h04m05s-07:00", // Arri XML in MXF 137 "2006-01-02T15:04:05", 138 "2006-01-02T15:04Z", 139 "2006-01-02T15:04", 140 "2006-01-02 15:04", 141 "2006:01:02", // ID3 date 142 "2006-01", 143 "2006", 144 "15:04:05-07:00", // time with timezone (IPTC) 145 "15:04:05", // time without timezone (IPTC) 146 "150405-0700", // time with timezone (Getty) 147 "2006-01-02T00:00:00.000000000", // zero filler to catch potential bad date strings 148 "2006-01-00T00:00:00.000000000", // zero filler to catch potential bad date strings 149 "2006-00-00T00:00:00.000000000", // zero filler to catch potential bad date strings 150 "2006-01-02T00:00:00Z", // zero filler to catch potential bad date strings 151 "2006-01-00T00:00:00Z", // zero filler to catch potential bad date strings 152 "2006-00-00T00:00:00Z", // zero filler to catch potential bad date strings 153 } 154 155 var illegalZero StringList = StringList{ 156 "--", // ARRI undefined 157 "00/00/00T00:00:00+00:00", // ARRI zero time 158 } 159 160 // repair single-digit hours in timezones 161 // "00/00/00T00:00:00+00:00", // ARRI zero time 162 // "2011-02-15T10:15:14+1:00" 163 // "2011-02-15T10:15:14+1" 164 // "2017-09-15T20:17:41+00200", // seen from iPhone5s iOS10 video, ffprobe 165 func repairTZ(value string) string { 166 if illegalZero.Contains(value) { 167 return "0001-01-01T00:00:00Z" 168 } 169 l := len(value) 170 if l < 20 { 171 return value 172 } 173 a, b, c := value[l-5], value[l-2], value[l-6] 174 switch a { 175 case '+', '-', 'Z': 176 return value[:l-4] + "0" + value[l-4:] 177 } 178 switch b { 179 case '+', '-', 'Z': 180 return value[:l-1] + "0" + value[l-1:] + ":00" 181 } 182 switch c { 183 case '+', '-', 'Z': 184 if !strings.Contains(value[l-6:], ":") { 185 return value[:l-5] + value[l-4:] 186 } 187 } 188 return value 189 } 190 191 func ParseDate(value string) (Date, error) { 192 if value != "" { 193 value = repairTZ(value) 194 for _, f := range dateFormats { 195 if t, err := time.Parse(f, value); err == nil { 196 return Date(t), nil 197 } 198 } 199 } 200 return Date{}, fmt.Errorf("xmp: invalid datetime value '%s'", value) 201 } 202 203 func (x *Date) UnmarshalText(data []byte) error { 204 if len(data) == 0 { 205 *x = Date{} 206 return nil 207 } 208 if d, err := ParseDate(string(data)); err != nil { 209 return err 210 } else { 211 *x = d 212 } 213 return nil 214 } 215 216 type DateList []Date 217 218 func (x DateList) Typ() ArrayType { 219 return ArrayTypeOrdered 220 } 221 222 func (x DateList) MarshalXMP(e *Encoder, node *Node, m Model) error { 223 return MarshalArray(e, node, x.Typ(), x) 224 } 225 226 func (x *DateList) UnmarshalXMP(d *Decoder, node *Node, m Model) error { 227 // be resilient against broken writers (PDF) 228 if len(node.Nodes) == 0 && len(node.Value) > 0 { 229 if d, err := ParseDate(node.Value); err != nil { 230 return err 231 } else { 232 *x = append(*x, d) 233 } 234 return nil 235 } 236 return UnmarshalArray(d, node, x.Typ(), x) 237 } 238 239 // Non-Standard Time 240 type Time time.Time 241 242 func NewTime(t time.Time) Time { 243 return Time(t) 244 } 245 246 func (x Time) Value() time.Time { 247 return time.Time(x) 248 } 249 250 func (x Time) String() string { 251 return time.Time(x).Format("15:04:05-07:00") 252 } 253 254 func (x Time) IsZero() bool { 255 return time.Time(x).IsZero() 256 } 257 258 func (x Time) MarshalText() ([]byte, error) { 259 if x.IsZero() { 260 return nil, nil 261 } 262 return []byte(x.String()), nil 263 } 264 265 var timeFormats []string = []string{ 266 "15:04:05-07:00", // time with timezone (IPTC) 267 "15:04:05", // time without timezone (IPTC) 268 "150405-0700", // time with timezone (Getty) 269 } 270 271 func ParseTime(value string) (Time, error) { 272 if value != "" { 273 for _, f := range timeFormats { 274 if t, err := time.Parse(f, value); err == nil { 275 return Time(t), nil 276 } 277 } 278 } 279 return Time{}, fmt.Errorf("xmp: invalid time value '%s'", value) 280 } 281 282 func (x *Time) UnmarshalText(data []byte) error { 283 if len(data) == 0 { 284 return nil 285 } 286 if d, err := ParseTime(string(data)); err != nil { 287 return err 288 } else { 289 *x = d 290 } 291 return nil 292 } 293 294 // 8.2.2.1 AgentName 295 // 296 type AgentName string 297 298 func (x AgentName) IsZero() bool { 299 return len(x) == 0 300 } 301 302 func (x AgentName) String() string { 303 return string(x) 304 } 305 306 // 8.2.2.3 GUID (a simple non-Uri identifier) 307 // 308 type GUID string 309 310 // func newGUID(issuer string, u uuid.UUID) GUID { 311 // return GUID(strings.Join([]string{issuer, u.String()}, ":")) 312 // } 313 314 // func NewGUID() GUID { 315 // return newGUID("uuid", uuid.NewV4()) 316 // } 317 318 // func NewGUIDFrom(issuer string, u uuid.UUID) GUID { 319 // if issuer == "" { 320 // issuer = "uuid" 321 // } 322 // return newGUID(issuer, u) 323 // } 324 325 func (x GUID) IsZero() bool { 326 return x == "" 327 } 328 329 // func (x GUID) UUID() uuid.UUID { 330 // if idx := strings.LastIndex(string(x), ":"); idx > -1 { 331 // return uuid.FromStringOrNil(string(x[idx+1:])) 332 // } 333 // return uuid.FromStringOrNil(string(x)) 334 // } 335 336 func (x GUID) Issuer() string { 337 if idx := strings.LastIndex(string(x), ":"); idx > -1 { 338 return string(x[:idx]) 339 } 340 return "" 341 } 342 343 func (x GUID) Value() string { 344 return string(x) 345 } 346 347 func (x GUID) String() string { 348 return string(x) 349 } 350 351 func (x GUID) MarshalText() ([]byte, error) { 352 return []byte(x), nil 353 } 354 355 func (x *GUID) UnmarshalText(data []byte) error { 356 *x = GUID(data) 357 return nil 358 } 359 360 func (x GUID) MarshalXMP(e *Encoder, node *Node, m Model) error { 361 if x.IsZero() { 362 return nil 363 } 364 b, _ := x.MarshalText() 365 return e.EncodeElement(b, node) 366 } 367 368 type GUIDList []GUID 369 370 func (x GUIDList) Typ() ArrayType { 371 return ArrayTypeOrdered 372 } 373 374 func (x GUIDList) MarshalXMP(e *Encoder, node *Node, m Model) error { 375 return MarshalArray(e, node, x.Typ(), x) 376 } 377 378 func (x *GUIDList) UnmarshalXMP(d *Decoder, node *Node, m Model) error { 379 return UnmarshalArray(d, node, x.Typ(), x) 380 } 381 382 type Url string 383 384 func (x Url) Value() string { 385 return string(x) 386 } 387 388 func (x Url) IsZero() bool { 389 return x == "" 390 } 391 392 type UrlArray []Url 393 394 func (x UrlArray) Typ() ArrayType { 395 return ArrayTypeUnordered 396 } 397 398 func (x UrlArray) MarshalXMP(e *Encoder, node *Node, m Model) error { 399 return MarshalArray(e, node, x.Typ(), x) 400 } 401 402 func (x *UrlArray) UnmarshalXMP(d *Decoder, node *Node, m Model) error { 403 return UnmarshalArray(d, node, x.Typ(), x) 404 } 405 406 type UrlList []Url 407 408 func (x UrlList) Typ() ArrayType { 409 return ArrayTypeOrdered 410 } 411 412 func (x UrlList) MarshalXMP(e *Encoder, node *Node, m Model) error { 413 return MarshalArray(e, node, x.Typ(), x) 414 } 415 416 func (x *UrlList) UnmarshalXMP(d *Decoder, node *Node, m Model) error { 417 return UnmarshalArray(d, node, x.Typ(), x) 418 } 419 420 // 8.2.2.10 Uri (Note: special handling as rdf:resource attribute without node content) 421 // 422 type Uri string 423 424 func (x Uri) Value() string { 425 return string(x) 426 } 427 428 func (x Uri) IsZero() bool { 429 return x == "" 430 } 431 432 func NewUri(u string) Uri { 433 return Uri(u) 434 } 435 436 // - supported 437 // <xmp:BaseUrl rdf:resource="http://www.adobe.com/"/> 438 // 439 // - supported 440 // <xmp:BaseUrl rdf:parseType="Resource"> 441 // <rdf:value rdf:resource="http://www.adobe.com/"/> 442 // <xe:qualifier>artificial example</xe:qualifier> 443 // </xmp:BaseUrl> 444 // 445 // func (x Uri) MarshalXMP(e *Encoder, node *Node, m Model) error { 446 // node.Attr = append(node.Attr, xml.Attr{ 447 // Name: xml.Name{Local: "rdf:resource"}, 448 // Value: string(x), 449 // }) 450 // return nil 451 // } 452 453 func (x *Uri) UnmarshalXMP(d *Decoder, node *Node, m Model) error { 454 if attr := node.GetAttr("rdf", "resource"); len(attr) > 0 { 455 *x = Uri(attr[0].Value) 456 return nil 457 } 458 var u string 459 if err := d.DecodeElement(&u, node); err != nil { 460 return err 461 } 462 *x = Uri(u) 463 return nil 464 } 465 466 type UriArray []Uri 467 468 func (x UriArray) IsZero() bool { 469 return len(x) == 0 470 } 471 472 func (x UriArray) Typ() ArrayType { 473 return ArrayTypeUnordered 474 } 475 476 func (x UriArray) MarshalXMP(e *Encoder, node *Node, m Model) error { 477 return MarshalArray(e, node, x.Typ(), x) 478 } 479 480 func (x *UriArray) UnmarshalXMP(d *Decoder, node *Node, m Model) error { 481 return UnmarshalArray(d, node, x.Typ(), x) 482 } 483 484 type UriList []Uri 485 486 func (x UriList) IsZero() bool { 487 return len(x) == 0 488 } 489 490 func (x UriList) Typ() ArrayType { 491 return ArrayTypeOrdered 492 } 493 494 func (x UriList) MarshalXMP(e *Encoder, node *Node, m Model) error { 495 return MarshalArray(e, node, x.Typ(), x) 496 } 497 498 func (x *UriList) UnmarshalXMP(d *Decoder, node *Node, m Model) error { 499 return UnmarshalArray(d, node, x.Typ(), x) 500 } 501 502 // Rational "n/m" 503 // 504 type Rational struct { 505 Num int64 506 Den int64 507 } 508 509 func (x *Rational) Addr() *Rational { 510 if x.IsZero() { 511 return nil 512 } 513 return x 514 } 515 516 func (x Rational) IsZero() bool { 517 return x.Den == 0 && x.Num == 0 518 } 519 520 func (x Rational) Value() float64 { 521 if x.Den == 0 { 522 return 1 523 } 524 return float64(x.Num) / float64(x.Den) 525 } 526 527 func (x Rational) String() string { 528 buf := bytes.Buffer{} 529 buf.WriteString(strconv.FormatInt(x.Num, 10)) 530 buf.WriteByte('/') 531 buf.WriteString(strconv.FormatInt(x.Den, 10)) 532 return buf.String() 533 } 534 535 func (x Rational) MarshalText() ([]byte, error) { 536 return []byte(x.String()), nil 537 } 538 539 func gcd(x, y int64) int64 { 540 for y != 0 { 541 x, y = y, x%y 542 } 543 return x 544 } 545 546 // Beware: primitive conversion algorithm 547 func FloatToRational(f float32) Rational { 548 var ( 549 den int64 = 1000000 550 rnd float32 = 0.5 551 ) 552 switch { 553 case f > 2147: 554 den = 10000 555 case f > 214748: 556 den = 100 557 case f > 21474836: 558 den = 1 559 } 560 if f < 0 { 561 rnd = -0.5 562 } 563 nom := int64(f*float32(den) + rnd) 564 g := gcd(nom, den) 565 return Rational{nom / g, den / g} 566 } 567 568 func (x *Rational) UnmarshalText(data []byte) error { 569 if len(data) == 0 { 570 return nil 571 } 572 var err error 573 v := string(data) 574 r := Rational{} 575 switch { 576 case strings.Contains(v, "."): 577 var f float64 578 f, err = strconv.ParseFloat(v, 32) 579 if err == nil { 580 if f < 1 { 581 r = Rational{1, int64(1000/f+50) / 1000} 582 } else { 583 r = FloatToRational(float32(f)) 584 } 585 // fmt.Printf("Float %f in Rational %d/%d\n", f, r.Num, r.Den) 586 } 587 case strings.Contains(v, "/"): 588 _, err = fmt.Sscanf(v, "%d/%d", &r.Num, &r.Den) 589 case strings.Contains(v, " "): 590 _, err = fmt.Sscanf(v, "%d %d", &r.Num, &r.Den) 591 default: 592 r.Num, err = strconv.ParseInt(v, 10, 64) 593 r.Den = 1 594 } 595 if err != nil { 596 return fmt.Errorf("xmp: invalid rational '%s': %v", v, err) 597 } 598 *x = r 599 return nil 600 } 601 602 type RationalArray []Rational 603 604 func (a RationalArray) Typ() ArrayType { 605 return ArrayTypeOrdered 606 } 607 608 func NewRationalArray(items ...Rational) RationalArray { 609 a := make(RationalArray, 0, len(items)) 610 return append(a, items...) 611 } 612 613 func (a RationalArray) MarshalXMP(e *Encoder, node *Node, m Model) error { 614 if len(a) == 0 { 615 return nil 616 } 617 return MarshalArray(e, node, a.Typ(), a) 618 } 619 620 func (a *RationalArray) UnmarshalXMP(d *Decoder, node *Node, m Model) error { 621 return UnmarshalArray(d, node, a.Typ(), a) 622 } 623 624 // GPS Coordinates 625 // 626 // “DDD,MM,SSk” or “DDD,MM.mmk” 627 // DDD is a number of degrees 628 // MM is a number of minutes 629 // SS is a number of seconds 630 // mm is a fraction of minutes 631 // k is a single character N, S, E, or W indicating a direction (north, south, east, west) 632 type GPSCoord string 633 634 func (x GPSCoord) Value() string { 635 return string(x) 636 } 637 638 func (x GPSCoord) IsZero() bool { 639 return x == "" || x == "0,0.0000000" 640 }