github.com/boki/go-xmp@v1.0.1/models/xmp_dm/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  // Package xmpdm implements the XMP Dynamic Media namespace as defined by XMP Specification Part 2.
    16  package xmpdm
    17  
    18  import (
    19  	"bytes"
    20  	"encoding/json"
    21  	"fmt"
    22  	"math"
    23  	"sort"
    24  	"strconv"
    25  	"strings"
    26  	"time"
    27  	"trimmer.io/go-xmp/xmp"
    28  )
    29  
    30  // 1.2.3.1 Part
    31  // [/]time:##     - duration from node=0
    32  // [/]time:##d##  - duration from given node
    33  // [/]time:##r##  - range from given node to end
    34  //
    35  // ## is FrameCount
    36  type Part struct {
    37  	Path     string
    38  	Start    FrameCount
    39  	Duration FrameCount
    40  }
    41  
    42  func prefixIfNot(s, p string) string {
    43  	if strings.HasPrefix(s, p) {
    44  		return s
    45  	}
    46  	return p + s
    47  }
    48  
    49  func NewPart(v string) Part {
    50  	return Part{
    51  		Path: prefixIfNot(v, "/"),
    52  	}
    53  }
    54  
    55  func NewPartList(s ...string) PartList {
    56  	l := make(PartList, len(s))
    57  	for i, v := range s {
    58  		l[i].Path = prefixIfNot(v, "/")
    59  	}
    60  	return l
    61  }
    62  
    63  func (x Part) IsZero() bool {
    64  	return x.Path == "" && x.Start.IsZero() && x.Duration.IsZero()
    65  }
    66  
    67  func (x Part) MarshalXMP(e *xmp.Encoder, node *xmp.Node, m xmp.Model) error {
    68  	if x.IsZero() {
    69  		return nil
    70  	}
    71  	type _t Part
    72  	return e.EncodeElement(_t(x), node)
    73  }
    74  
    75  func (x *Part) UnmarshalText(data []byte) error {
    76  	p := Part{}
    77  	v := string(data)
    78  	tokens := strings.Split(v, "time:")
    79  	p.Path = tokens[0]
    80  	if len(tokens) > 1 {
    81  		t := tokens[1]
    82  		if i := strings.Index(t, "d"); i > -1 {
    83  			// node + duration
    84  			if err := p.Start.UnmarshalText([]byte(t[:i])); err != nil {
    85  				return fmt.Errorf("xmp: invalid part node: %v", err)
    86  			}
    87  			if err := p.Duration.UnmarshalText([]byte(t[i+1:])); err != nil {
    88  				return fmt.Errorf("xmp: invalid part duration: %v", err)
    89  			}
    90  		} else if i = strings.Index(t, "r"); i > -1 {
    91  			// node + end range
    92  			if err := p.Start.UnmarshalText([]byte(t[:i])); err != nil {
    93  				return fmt.Errorf("xmp: invalid part range node: %v", err)
    94  			}
    95  			var end FrameCount
    96  			if err := end.UnmarshalText([]byte(t[i+1:])); err != nil {
    97  				return fmt.Errorf("xmp: invalid part range end: %v", err)
    98  			}
    99  			p.Duration = end.Sub(p.Start)
   100  		} else {
   101  			// duration with zero node
   102  			if err := p.Duration.UnmarshalText([]byte(t)); err != nil {
   103  				return fmt.Errorf("xmp: invalid part duration: %v", err)
   104  			}
   105  		}
   106  	}
   107  	*x = p
   108  	return nil
   109  }
   110  
   111  func (x Part) MarshalText() ([]byte, error) {
   112  	buf := bytes.Buffer{}
   113  	buf.WriteString(x.Path)
   114  	if len(x.Path) > 0 && !strings.HasSuffix(x.Path, "/") {
   115  		buf.WriteByte('/')
   116  	}
   117  	if x.Duration.IsZero() {
   118  		return buf.Bytes(), nil
   119  	}
   120  	buf.WriteString("time:")
   121  	if !x.Start.IsZero() {
   122  		buf.WriteString(x.Start.String())
   123  		buf.WriteByte('d')
   124  	}
   125  	buf.WriteString(x.Duration.String())
   126  	return buf.Bytes(), nil
   127  }
   128  
   129  type PartList []Part
   130  
   131  func (x PartList) String() string {
   132  	if b, err := x.MarshalText(); err == nil {
   133  		return string(b)
   134  	}
   135  	return ""
   136  }
   137  
   138  func (x *PartList) Add(v string) {
   139  	*x = append(*x, Part{Path: v})
   140  }
   141  
   142  func (x *PartList) AddPart(p Part) {
   143  	*x = append(*x, p)
   144  }
   145  
   146  func (x *PartList) UnmarshalText(data []byte) error {
   147  	l := make(PartList, 0)
   148  	for _, v := range bytes.Split(data, []byte(";")) {
   149  		p := Part{}
   150  		if err := p.UnmarshalText(v); err != nil {
   151  			return err
   152  		}
   153  		l = append(l, p)
   154  	}
   155  	*x = l
   156  	return nil
   157  }
   158  
   159  func (x PartList) MarshalText() ([]byte, error) {
   160  	s := make([][]byte, len(x))
   161  	for i, v := range x {
   162  		b, _ := v.MarshalText()
   163  		if !bytes.HasPrefix(b, []byte("/")) {
   164  			b = append([]byte("/"), b...)
   165  		}
   166  		s[i] = b
   167  	}
   168  	return bytes.Join(s, []byte(";")), nil
   169  }
   170  
   171  // 1.2.4.1 ResourceRef - stRef:maskMarkers closed choice
   172  type MaskType string
   173  
   174  const (
   175  	MaskAll  MaskType = "All"
   176  	MaskNone MaskType = "None"
   177  )
   178  
   179  // 1.2.6.3 FrameCount
   180  // ##<FrameRate>
   181  type FrameCount struct {
   182  	Count int64
   183  	Rate  FrameRate
   184  }
   185  
   186  func (x FrameCount) IsZero() bool {
   187  	return x.Count == 0 && x.Rate.IsZero()
   188  }
   189  
   190  func (x FrameCount) Sub(y FrameCount) FrameCount {
   191  	return FrameCount{
   192  		Count: x.Count - y.Count,
   193  		Rate:  x.Rate,
   194  	}
   195  }
   196  
   197  func (x FrameCount) IsSmaller(y FrameCount) bool {
   198  	return float64(x.Count)*x.Rate.Value() < float64(y.Count)*y.Rate.Value()
   199  }
   200  
   201  func (x *FrameCount) UnmarshalText(data []byte) error {
   202  	var err error
   203  	c := FrameCount{
   204  		Count: 0,
   205  		Rate:  FrameRate{1, 1},
   206  	}
   207  	v := string(data)
   208  	if fpos := strings.Index(v, "f"); fpos > -1 {
   209  		// ##f##s## format
   210  		if err := c.Rate.UnmarshalText([]byte(v[fpos:])); err != nil {
   211  			return fmt.Errorf("xmp: invalid frame count '%s': %v", v, err)
   212  		}
   213  		v = v[:fpos]
   214  	}
   215  	// special "maximum"
   216  	if v == "maximum" {
   217  		c.Count = -1
   218  	} else {
   219  		// ## format
   220  		if c.Count, err = strconv.ParseInt(v, 10, 64); err != nil {
   221  			return fmt.Errorf("xmp: invalid frame counter value '%s': %v", v, err)
   222  		}
   223  	}
   224  	*x = c
   225  	return nil
   226  }
   227  
   228  func (x FrameCount) String() string {
   229  	switch x.Count {
   230  	case 0:
   231  		return "0"
   232  	case -1:
   233  		return "maximum"
   234  	default:
   235  		buf := bytes.Buffer{}
   236  		buf.WriteString(strconv.FormatInt(x.Count, 10))
   237  		buf.WriteString(x.Rate.String())
   238  		return buf.String()
   239  	}
   240  }
   241  
   242  func (x FrameCount) MarshalText() ([]byte, error) {
   243  	return []byte(x.String()), nil
   244  }
   245  
   246  // 1.2.6.1 beatSpliceStretch
   247  type BeatSpliceStretch struct {
   248  	RiseInDecibel      float64   `xmp:"xmpDM:riseInDecibel,attr"`
   249  	RiseInTimeDuration MediaTime `xmp:"xmpDM:riseInTimeDuration"`
   250  	UseFileBeatsMarker xmp.Bool  `xmp:"xmpDM:useFileBeatsMarker,attr"`
   251  }
   252  
   253  func (x BeatSpliceStretch) IsZero() bool {
   254  	return x.RiseInDecibel == 0 && x.RiseInTimeDuration.IsZero() && !x.UseFileBeatsMarker.Value()
   255  }
   256  
   257  // 1.2.6.2 CuePointParam
   258  type CuePointParam struct {
   259  	Key   string `xmp:"xmpDM:key,attr"`
   260  	Value string `xmp:"xmpDM:value,attr"`
   261  }
   262  
   263  func (x CuePointParam) IsZero() bool {
   264  	return x.Value == "" && x.Key == ""
   265  }
   266  
   267  type CuePointParamArray []CuePointParam
   268  
   269  func (x CuePointParamArray) Typ() xmp.ArrayType {
   270  	return xmp.ArrayTypeOrdered
   271  }
   272  
   273  func (x CuePointParamArray) MarshalXMP(e *xmp.Encoder, node *xmp.Node, m xmp.Model) error {
   274  	return xmp.MarshalArray(e, node, x.Typ(), x)
   275  }
   276  
   277  func (x *CuePointParamArray) UnmarshalXMP(d *xmp.Decoder, node *xmp.Node, m xmp.Model) error {
   278  	return xmp.UnmarshalArray(d, node, x.Typ(), x)
   279  }
   280  
   281  type VideoFrameRate float64
   282  
   283  func (x *VideoFrameRate) UnmarshalText(data []byte) error {
   284  	// try detecting supported strings
   285  	switch strings.ToUpper(string(data)) {
   286  	case "NTSC":
   287  		*x = VideoFrameRate(29.97)
   288  		return nil
   289  	case "PAL":
   290  		*x = VideoFrameRate(25)
   291  		return nil
   292  	}
   293  
   294  	// try parsing as rational
   295  	rat := &xmp.Rational{}
   296  	if err := rat.UnmarshalText(data); err == nil {
   297  		*x = VideoFrameRate(rat.Value())
   298  		return nil
   299  	}
   300  
   301  	// parse as float value, strip trailing "fps"
   302  	v := strings.TrimSpace(strings.TrimSuffix(string(data), "fps"))
   303  	v = strings.TrimSuffix(v, "i")
   304  	v = strings.TrimSuffix(v, "p")
   305  	if f, err := strconv.ParseFloat(v, 64); err != nil {
   306  		return fmt.Errorf("xmp: invalid video frame rate value '%s': %v", v, err)
   307  	} else {
   308  		*x = VideoFrameRate(f)
   309  	}
   310  	return nil
   311  }
   312  
   313  // 1.2.6.4 FrameRate
   314  // "f###"
   315  // "f###s###"
   316  type FrameRate struct {
   317  	Rate int64
   318  	Base int64
   319  }
   320  
   321  func (x FrameRate) Value() float64 {
   322  	if x.Base == 0 {
   323  		return 0
   324  	}
   325  	return float64(x.Rate) / float64(x.Base)
   326  }
   327  
   328  func (x FrameRate) String() string {
   329  	buf := bytes.Buffer{}
   330  	buf.WriteByte('f')
   331  	buf.WriteString(strconv.FormatInt(x.Rate, 10))
   332  	if x.Base > 1 {
   333  		buf.WriteByte('s')
   334  		buf.WriteString(strconv.FormatInt(x.Base, 10))
   335  	}
   336  	return buf.String()
   337  }
   338  
   339  func (x FrameRate) IsZero() bool {
   340  	return x.Base == 0 || x.Rate == 0
   341  }
   342  
   343  func (x *FrameRate) UnmarshalText(data []byte) error {
   344  	if !bytes.HasPrefix(data, []byte("f")) {
   345  		return fmt.Errorf("xmp: invalid frame rate value '%s'", string(data))
   346  	}
   347  	v := data[1:]
   348  	r := FrameRate{}
   349  	tokens := bytes.Split(v, []byte("s"))
   350  	var err error
   351  	r.Rate, err = strconv.ParseInt(string(tokens[0]), 10, 64)
   352  	if err != nil {
   353  		return fmt.Errorf("xmp: invalid frame rate value '%s': %v", string(data), err)
   354  	}
   355  	if len(tokens) > 1 {
   356  		r.Base, err = strconv.ParseInt(string(tokens[1]), 10, 64)
   357  		if err != nil {
   358  			return fmt.Errorf("xmp: invalid frame rate value '%s': %v", string(data), err)
   359  		}
   360  	}
   361  	if r.Base == 0 {
   362  		r.Base = 1
   363  	}
   364  	*x = r
   365  	return nil
   366  }
   367  
   368  func (x FrameRate) MarshalText() ([]byte, error) {
   369  	return []byte(x.String()), nil
   370  }
   371  
   372  // 1.2.6.5 Marker
   373  type Marker struct {
   374  	Comment        string             `xmp:"xmpDM:comment,attr"`
   375  	CuePointParams CuePointParamArray `xmp:"xmpDM:cuePointParams"`
   376  	CuePointType   string             `xmp:"xmpDM:cuePointType,attr"`
   377  	Duration       FrameCount         `xmp:"xmpDM:duration,attr"`
   378  	Location       xmp.Uri            `xmp:"xmpDM:location"`
   379  	Name           string             `xmp:"xmpDM:name,attr"`
   380  	Probability    float64            `xmp:"xmpDM:probability,attr"`
   381  	Speaker        string             `xmp:"xmpDM:speaker,attr"`
   382  	StartTime      FrameCount         `xmp:"xmpDM:startTime,attr"`
   383  	Target         string             `xmp:"xmpDM:target,attr"`
   384  	Types          MarkerTypeList     `xmp:"xmpDM:type"`
   385  }
   386  
   387  func (x Marker) IsZero() bool {
   388  	return x.Comment == "" &&
   389  		len(x.CuePointParams) == 0 &&
   390  		x.CuePointType == "" &&
   391  		x.Duration.IsZero() &&
   392  		x.Location.IsZero() &&
   393  		x.Name == "" &&
   394  		x.Probability == 0 &&
   395  		x.Speaker == "" &&
   396  		x.StartTime.IsZero() &&
   397  		x.Target == "" &&
   398  		len(x.Types) == 0
   399  }
   400  
   401  type MarkerList []Marker
   402  
   403  func (x MarkerList) Typ() xmp.ArrayType {
   404  	return xmp.ArrayTypeOrdered
   405  }
   406  
   407  func (x MarkerList) MarshalXMP(e *xmp.Encoder, node *xmp.Node, m xmp.Model) error {
   408  	return xmp.MarshalArray(e, node, x.Typ(), x)
   409  }
   410  
   411  func (x *MarkerList) UnmarshalXMP(d *xmp.Decoder, node *xmp.Node, m xmp.Model) error {
   412  	return xmp.UnmarshalArray(d, node, x.Typ(), x)
   413  }
   414  
   415  func (x MarkerList) Len() int           { return len(x) }
   416  func (x MarkerList) Swap(i, j int)      { x[i], x[j] = x[j], x[i] }
   417  func (x MarkerList) Less(i, j int) bool { return x[i].StartTime.IsSmaller(x[j].StartTime) }
   418  
   419  func (x *MarkerList) Sort() {
   420  	sort.Sort(x)
   421  }
   422  
   423  func (x MarkerList) Filter(types MarkerTypeList) MarkerList {
   424  	l := make(MarkerList, 0)
   425  	for _, v := range x {
   426  		if len(types.Intersect(v.Types)) > 0 {
   427  			l = append(l, v)
   428  		}
   429  	}
   430  	return l
   431  }
   432  
   433  type MarkerTypeList []MarkerType
   434  
   435  func (x MarkerTypeList) Intersect(y MarkerTypeList) MarkerTypeList {
   436  	m := make(map[MarkerType]bool)
   437  	for _, v := range x {
   438  		m[v] = true
   439  	}
   440  	r := make(MarkerTypeList, 0)
   441  	for _, v := range y {
   442  		if _, ok := m[v]; ok {
   443  			r = append(r, v)
   444  		}
   445  	}
   446  	return r
   447  }
   448  
   449  func (x MarkerTypeList) Contains(t MarkerType) bool {
   450  	for _, v := range x {
   451  		if v == t {
   452  			return true
   453  		}
   454  	}
   455  	return false
   456  }
   457  
   458  // 1.2.6.6 Media
   459  type Media struct {
   460  	Duration     MediaTime `xmp:"xmpDM:duration"`
   461  	Managed      xmp.Bool  `xmp:"xmpDM:managed,attr"`
   462  	Path         xmp.Uri   `xmp:"xmpDM:path,attr"`
   463  	StartTime    MediaTime `xmp:"xmpDM:startTime"`
   464  	Track        string    `xmp:"xmpDM:track,attr"`
   465  	WebStatement xmp.Uri   `xmp:"xmpDM:webStatement"`
   466  }
   467  
   468  type MediaArray []Media
   469  
   470  func (x MediaArray) Typ() xmp.ArrayType {
   471  	return xmp.ArrayTypeUnordered
   472  }
   473  
   474  func (x MediaArray) MarshalXMP(e *xmp.Encoder, node *xmp.Node, m xmp.Model) error {
   475  	return xmp.MarshalArray(e, node, x.Typ(), x)
   476  }
   477  
   478  func (x *MediaArray) UnmarshalXMP(d *xmp.Decoder, node *xmp.Node, m xmp.Model) error {
   479  	return xmp.UnmarshalArray(d, node, x.Typ(), x)
   480  }
   481  
   482  // 1.2.6.7 ProjectLink
   483  type ProjectLink struct {
   484  	Path xmp.Uri  `xmp:"xmpDM:path"`
   485  	Type FileType `xmp:"xmpDM:type,attr"`
   486  }
   487  
   488  func (x ProjectLink) IsZero() bool {
   489  	return x.Path == "" && x.Type == ""
   490  }
   491  
   492  // 1.2.6.8 resampleStretch
   493  type ResampleStretch struct {
   494  	Quality Quality `xmp:"xmpDM:quality,attr"`
   495  }
   496  
   497  func (x ResampleStretch) IsZero() bool {
   498  	return x.Quality == ""
   499  }
   500  
   501  // 1.2.6.9 Time
   502  type MediaTime struct {
   503  	Scale xmp.Rational `xmp:"xmpDM:scale,attr"`
   504  	Value int64        `xmp:"xmpDM:value,attr"`
   505  }
   506  
   507  func (x MediaTime) IsZero() bool {
   508  	return x.Value == 0 && x.Scale.Den == 0
   509  }
   510  
   511  // Parse when used as attribute (not standard-conform)
   512  // xmpDM:duration="0:07.680"
   513  func (x *MediaTime) UnmarshalXMPAttr(d *xmp.Decoder, a xmp.Attr) error {
   514  	v := string(a.Value)
   515  	var dur time.Duration
   516  	fields := strings.Split(v, ".")
   517  	if len(fields) > 1 {
   518  		if i, err := strconv.ParseInt(fields[1], 10, 64); err == nil {
   519  			dur = time.Duration(i * int64(math.Pow10(9-len(fields[1]))))
   520  		}
   521  	}
   522  	fields = strings.Split(fields[0], ":")
   523  	var h, m, s int
   524  	switch len(fields) {
   525  	case 3:
   526  		h, _ = strconv.Atoi(fields[0])
   527  		m, _ = strconv.Atoi(fields[1])
   528  		s, _ = strconv.Atoi(fields[2])
   529  	case 2:
   530  		m, _ = strconv.Atoi(fields[0])
   531  		s, _ = strconv.Atoi(fields[1])
   532  	case 1:
   533  		s, _ = strconv.Atoi(fields[0])
   534  	}
   535  	dur += time.Hour*time.Duration(h) + time.Minute*time.Duration(m) + time.Second*time.Duration(s)
   536  	*x = MediaTime{
   537  		Value: int64(dur / time.Millisecond),
   538  		Scale: xmp.Rational{
   539  			Num: 1,
   540  			Den: 1000,
   541  		},
   542  	}
   543  	return nil
   544  }
   545  
   546  // Part 2: 1.2.6.10 Timecode
   547  
   548  func ParseTimecodeFormat(f float64, isdrop bool) TimecodeFormat {
   549  	switch {
   550  	case 23.975 <= f && f < 23.997:
   551  		return TimecodeFormat23976
   552  	case f == 24:
   553  		return TimecodeFormat24
   554  	case f == 25:
   555  		return TimecodeFormat25
   556  	case 29.96 < f && f < 29.98:
   557  		if isdrop {
   558  			return TimecodeFormat2997
   559  		} else {
   560  			return TimecodeFormat2997ND
   561  		}
   562  	case f == 30:
   563  		return TimecodeFormat30
   564  	case f == 50:
   565  		return TimecodeFormat50
   566  	case 59.93 < f && f < 59.95:
   567  		if isdrop {
   568  			return TimecodeFormat5994
   569  		} else {
   570  			return TimecodeFormat5994
   571  		}
   572  	case f == 60:
   573  		return TimecodeFormat60
   574  	default:
   575  		return TimecodeFormatInvalid
   576  	}
   577  }
   578  
   579  type Timecode struct {
   580  	Format      TimecodeFormat `xmp:"xmpDM:timeFormat"`
   581  	Value       string         `xmp:"xmpDM:timeValue"` // hh:mm:ss:ff or hh;mm;ss;ff
   582  	H           int            `xmp:"-"`
   583  	M           int            `xmp:"-"`
   584  	S           int            `xmp:"-"`
   585  	F           int            `xmp:"-"`
   586  	IsDropFrame bool           `xmp:"-"`
   587  }
   588  
   589  func (x Timecode) String() string {
   590  	sep := ':'
   591  	if x.IsDropFrame {
   592  		sep = ';'
   593  	}
   594  	buf := bytes.Buffer{}
   595  	if x.H < 10 {
   596  		buf.WriteByte('0')
   597  	}
   598  	buf.WriteString(strconv.FormatInt(int64(x.H), 10))
   599  	buf.WriteRune(sep)
   600  	if x.M < 10 {
   601  		buf.WriteByte('0')
   602  	}
   603  	buf.WriteString(strconv.FormatInt(int64(x.M), 10))
   604  	buf.WriteRune(sep)
   605  	if x.S < 10 {
   606  		buf.WriteByte('0')
   607  	}
   608  	buf.WriteString(strconv.FormatInt(int64(x.S), 10))
   609  	buf.WriteRune(sep)
   610  	if x.F < 10 {
   611  		buf.WriteByte('0')
   612  	}
   613  	buf.WriteString(strconv.FormatInt(int64(x.F), 10))
   614  	return buf.String()
   615  }
   616  
   617  func (x *Timecode) Unpack() error {
   618  	sep := ":"
   619  	if x.IsDropFrame {
   620  		sep = ";"
   621  	}
   622  	fields := strings.Split(x.Value, sep)
   623  	if len(fields) < 4 {
   624  		return fmt.Errorf("found only %d of 4 fields", len(fields))
   625  	}
   626  	var err error
   627  	if x.H, err = strconv.Atoi(fields[0]); err != nil {
   628  		return err
   629  	}
   630  	if x.M, err = strconv.Atoi(fields[1]); err != nil {
   631  		return err
   632  	}
   633  	if x.S, err = strconv.Atoi(fields[2]); err != nil {
   634  		return err
   635  	}
   636  	if x.F, err = strconv.Atoi(fields[3]); err != nil {
   637  		return err
   638  	}
   639  	return nil
   640  }
   641  
   642  func (x Timecode) IsZero() bool {
   643  	return x.Value == "" && x.Format == ""
   644  }
   645  
   646  func (x Timecode) MarshalJSON() ([]byte, error) {
   647  	x.Value = x.String()
   648  	type _t Timecode
   649  	return json.Marshal(_t(x))
   650  }
   651  
   652  func (x *Timecode) UnmarshalJSON(data []byte) error {
   653  	xt := struct {
   654  		Format TimecodeFormat
   655  		Value  string
   656  	}{}
   657  	if err := json.Unmarshal(data, &xt); err != nil {
   658  		return fmt.Errorf("xmp: invalid timecode: %v", err)
   659  	}
   660  	if len(xt.Value) == 0 {
   661  		xt.Value = "00:00:00:00"
   662  	}
   663  	tc := Timecode{
   664  		Value:       xt.Value,
   665  		Format:      xt.Format,
   666  		IsDropFrame: strings.Contains(xt.Value, ";"),
   667  	}
   668  	if err := tc.Unpack(); err != nil {
   669  		return fmt.Errorf("xmp: invalid timecode '%s': %v", tc.Value, err)
   670  	}
   671  	*x = tc
   672  	return nil
   673  }
   674  
   675  func (x Timecode) MarshalXMP(e *xmp.Encoder, node *xmp.Node, m xmp.Model) error {
   676  	x.Value = x.String()
   677  	type _t Timecode
   678  	return e.EncodeElement(_t(x), node)
   679  }
   680  
   681  func (x *Timecode) UnmarshalXMP(d *xmp.Decoder, node *xmp.Node, m xmp.Model) error {
   682  	// Note: to avoid recursion we unmarshal into a temporary struct type
   683  	//       and copy over the result
   684  
   685  	// support both the attribute form and the element form
   686  	var tc Timecode
   687  	xt := struct {
   688  		Format TimecodeFormat `xmp:"xmpDM:timeFormat"`
   689  		Value  string         `xmp:"xmpDM:timeValue"`
   690  	}{}
   691  	if err := d.DecodeElement(&xt, node); err != nil {
   692  		return fmt.Errorf("xmp: invalid timecode '%s': %v", node.Value, err)
   693  	}
   694  	if len(xt.Value) == 0 {
   695  		xt.Value = "00:00:00:00"
   696  	}
   697  	tc = Timecode{
   698  		Value:       xt.Value,
   699  		Format:      xt.Format,
   700  		IsDropFrame: strings.Contains(xt.Value, ";"),
   701  	}
   702  	if err := tc.Unpack(); err != nil {
   703  		return fmt.Errorf("xmp: invalid timecode '%s': %v", tc.Value, err)
   704  	}
   705  	*x = tc
   706  	return nil
   707  }
   708  
   709  // 1.2.6.11 timeScaleStretch
   710  type TimeScaleStretch struct {
   711  	Overlap   float64 `xmp:"xmpDM:frameOverlappingPercentage,attr"`
   712  	FrameSize float64 `xmp:"xmpDM:frameSize,attr"`
   713  	Quality   Quality `xmp:"xmpDM:quality,attr"`
   714  }
   715  
   716  func (x TimeScaleStretch) IsZero() bool {
   717  	return x.Overlap == 0 && x.FrameSize == 0 && x.Quality == ""
   718  }
   719  
   720  // 1.2.6.12 Track
   721  type Track struct {
   722  	FrameRate FrameRate      `xmp:"xmpDM:frameRate,attr"`
   723  	Markers   MarkerList     `xmp:"xmpDM:markers"`
   724  	Name      string         `xmp:"xmpDM:trackName,attr"`
   725  	Type      MarkerTypeList `xmp:"xmpDM:trackType,attr"`
   726  }
   727  
   728  type TrackArray []Track
   729  
   730  func (x TrackArray) Typ() xmp.ArrayType {
   731  	return xmp.ArrayTypeUnordered
   732  }
   733  
   734  func (x TrackArray) MarshalXMP(e *xmp.Encoder, node *xmp.Node, m xmp.Model) error {
   735  	return xmp.MarshalArray(e, node, x.Typ(), x)
   736  }
   737  
   738  func (x *TrackArray) UnmarshalXMP(d *xmp.Decoder, node *xmp.Node, m xmp.Model) error {
   739  	return xmp.UnmarshalArray(d, node, x.Typ(), x)
   740  }