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  }