github.com/boki/go-xmp@v1.0.1/models/qt/model.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  // Quicktime Metadata storage options
    16  //
    17  // 1) Quicktime Metadata
    18  //   - namespace, key, value triple stored in mdta atoms
    19  //   - namespace: reverse-DNS name
    20  //   - some quicktime tags may be exported this way as well
    21  //   - camera manufacturers (e.g. Arri) use this to store clip metadata
    22  // 2) Quicktime User Data
    23  //   - Four-CC tags defined by Apple or 3GPP ISO standard
    24  //   - stored in udta atoms
    25  //   - flavours
    26  //      - 3GPP: subset for ISO/MP4 files, different tag values, no multi-lang strings
    27  //      - iTunes: specific to the iTunes store (used for music, video, apps, books)
    28  //      - Quicktime: full set with multi-language string support
    29  //
    30  // References:
    31  //
    32  //   1) https://developer.apple.com/library/content/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-25538
    33  //   2) http://search.cpan.org/dist/MP4-Info-1.04/
    34  //   3) http://xhelmboyx.tripod.com/formats/mp4-layout.txt
    35  //   4) http://wiki.multimedia.cx/index.php?title=Apple_QuickTime
    36  //   5) ISO 14496-12 (http://read.pudn.com/downloads64/ebook/226547/ISO_base_media_file_format.pdf)
    37  //   6) ISO 14496-16 (http://www.iec-normen.de/previewpdf/info_isoiec14496-16%7Bed2.0%7Den.pdf)
    38  //   7) http://atomicparsley.sourceforge.net/mpeg-4files.html
    39  //   8) http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart3.pdf (Oct 2008)
    40  //   9) QuickTime file format specification 2010-05-03
    41  //   10) http://standards.iso.org/ittf/PubliclyAvailableStandards/c051533_ISO_IEC_14496-12_2008.zip
    42  //   11) http://getid3.sourceforge.net/source/module.audio-video.quicktime.phps
    43  //   12) http://qtra.apple.com/atoms.html
    44  //   13) http://www.etsi.org/deliver/etsi_ts/126200_126299/126244/10.01.00_60/ts_126244v100100p.pdf
    45  //   14) https://github.com/appsec-labs/iNalyzer/blob/master/scinfo.m
    46  //   15) http://nah6.com/~itsme/cvs-xdadevtools/iphone/tools/decodesinf.pl
    47  //   16) https://github.com/sannies/mp4parser
    48  //   17) http://www.mp4ra.org/filetype.html
    49  //   18) https://github.com/WordPress/WordPress/blob/master/wp-includes/ID3/module.audio-video.quicktime.php
    50  
    51  // Package qt implements metadata found in Apple Quicktime (MOV) files.
    52  package qt
    53  
    54  import (
    55  	"fmt"
    56  	"strings"
    57  
    58  	"trimmer.io/go-xmp/models/ixml"
    59  	"trimmer.io/go-xmp/xmp"
    60  )
    61  
    62  var (
    63  	NsQuicktime = xmp.NewNamespace("qt", "http://ns.apple.com/quicktime/1.0/", NewModel)
    64  )
    65  
    66  func init() {
    67  	xmp.Register(NsQuicktime, xmp.MovieMetadata, xmp.CameraMetadata)
    68  }
    69  
    70  func NewModel(name string) xmp.Model {
    71  	return &QtInfo{}
    72  }
    73  
    74  type QtInfo struct {
    75  	Udta    *QtUserdata `qt:"-" xmp:"qt:udta"`
    76  	Mdta    *QtMetadata `qt:"-" xmp:"qt:mdta"`
    77  	Player  *QtPlayer   `qt:"-" xmp:"qt:player"`
    78  	ProApps *QtProApps  `qt:"-" xmp:"qt:proapps"`
    79  
    80  	// external structs
    81  	IXML *ixml.IXML    `qt:"-" xmp:"-"`
    82  	XMP  *xmp.Document `qt:"XMP_" xmp:"-"`
    83  
    84  	// unknown 3rd party tags
    85  	Extension xmp.TagList `qt:",any" xmp:"qt:extension"`
    86  }
    87  
    88  func (m *QtInfo) Namespaces() xmp.NamespaceList {
    89  	return xmp.NamespaceList{NsQuicktime}
    90  }
    91  
    92  func (m *QtInfo) Can(nsName string) bool {
    93  	return nsName == NsQuicktime.GetName()
    94  }
    95  
    96  func (x *QtInfo) SyncModel(d *xmp.Document) error {
    97  	if x.Udta != nil {
    98  		if err := x.Udta.SyncModel(d); err != nil {
    99  			return err
   100  		}
   101  	}
   102  	if x.Mdta != nil {
   103  		if err := x.Mdta.SyncModel(d); err != nil {
   104  			return err
   105  		}
   106  	}
   107  	if x.Player != nil {
   108  		if err := x.Player.SyncModel(d); err != nil {
   109  			return err
   110  		}
   111  	}
   112  	if x.ProApps != nil {
   113  		if err := x.ProApps.SyncModel(d); err != nil {
   114  			return err
   115  		}
   116  	}
   117  	return nil
   118  }
   119  
   120  func (x *QtInfo) SyncFromXMP(d *xmp.Document) error {
   121  	if x.Udta != nil {
   122  		if err := x.Udta.SyncFromXMP(d); err != nil {
   123  			return err
   124  		}
   125  	}
   126  	if x.Mdta != nil {
   127  		if err := x.Mdta.SyncFromXMP(d); err != nil {
   128  			return err
   129  		}
   130  	}
   131  	if x.Player != nil {
   132  		if err := x.Player.SyncFromXMP(d); err != nil {
   133  			return err
   134  		}
   135  	}
   136  	if x.ProApps != nil {
   137  		if err := x.ProApps.SyncFromXMP(d); err != nil {
   138  			return err
   139  		}
   140  	}
   141  	return nil
   142  }
   143  
   144  func (x QtInfo) SyncToXMP(d *xmp.Document) error {
   145  	if x.XMP != nil {
   146  		if err := d.Merge(x.XMP, xmp.MERGE); err != nil {
   147  			return err
   148  		}
   149  	}
   150  	if x.Udta != nil {
   151  		if err := x.Udta.SyncToXMP(d); err != nil {
   152  			return err
   153  		}
   154  	}
   155  	if x.Mdta != nil {
   156  		if err := x.Mdta.SyncToXMP(d); err != nil {
   157  			return err
   158  		}
   159  	}
   160  	if x.Player != nil {
   161  		if err := x.Player.SyncToXMP(d); err != nil {
   162  			return err
   163  		}
   164  	}
   165  	if x.ProApps != nil {
   166  		if err := x.ProApps.SyncToXMP(d); err != nil {
   167  			return err
   168  		}
   169  	}
   170  	if x.IXML != nil {
   171  		d.AddModel(x.IXML)
   172  	}
   173  	return nil
   174  }
   175  
   176  func (x *QtInfo) CanTag(tag string) bool {
   177  	switch {
   178  	case len(tag) == 4:
   179  		v := &QtUserdata{}
   180  		_, err := xmp.GetNativeField(v, tag)
   181  		return err == nil
   182  	case strings.HasPrefix("com.apple.quicktime.player", tag):
   183  		v := &QtPlayer{}
   184  		return v.CanTag(tag)
   185  	case strings.HasPrefix("com.apple.quicktime", tag):
   186  		v := &QtMetadata{}
   187  		return v.CanTag(tag)
   188  	case strings.HasPrefix("com.apple.proapps", tag):
   189  		v := &QtProApps{}
   190  		return v.CanTag(tag)
   191  	case tag == "XMP_" || tag == "iXML" || tag == "info.ixml.xml" || tag == "info.ixml.metadata" || tag == "info.ixml.info":
   192  		return true
   193  	}
   194  	return false
   195  }
   196  
   197  func (x *QtInfo) GetTag(tag string) (string, error) {
   198  	switch {
   199  	case len(tag) == 4:
   200  		if x.Udta == nil {
   201  			return "", nil
   202  		}
   203  		if v, err := xmp.GetNativeField(x.Udta, tag); err != nil {
   204  			return "", fmt.Errorf("%s: %v", NsQuicktime.GetName(), err)
   205  		} else {
   206  			return v, nil
   207  		}
   208  	case strings.HasPrefix(tag, "com.apple.quicktime.player"):
   209  		if x.Player == nil {
   210  			return "", nil
   211  		}
   212  		if v, err := xmp.GetNativeField(x.Player, tag); err != nil {
   213  			return "", fmt.Errorf("%s: %v", NsQuicktime.GetName(), err)
   214  		} else {
   215  			return v, nil
   216  		}
   217  	case strings.HasPrefix(tag, "com.apple.quicktime"):
   218  		if x.Mdta == nil {
   219  			return "", nil
   220  		}
   221  		if v, err := xmp.GetNativeField(x.Mdta, tag); err != nil {
   222  			return "", fmt.Errorf("%s: %v", NsQuicktime.GetName(), err)
   223  		} else {
   224  			return v, nil
   225  		}
   226  	case strings.HasPrefix(tag, "com.apple.proapps"):
   227  		if x.ProApps == nil {
   228  			return "", nil
   229  		}
   230  		if v, err := xmp.GetNativeField(x.ProApps, tag); err != nil {
   231  			return "", fmt.Errorf("%s: %v", NsQuicktime.GetName(), err)
   232  		} else {
   233  			return v, nil
   234  		}
   235  	}
   236  	return "", nil
   237  }
   238  
   239  func (x *QtInfo) SetTag(tag, value string) error {
   240  	switch {
   241  	case len(tag) == 4:
   242  		if x.Udta == nil {
   243  			x.Udta = &QtUserdata{}
   244  		}
   245  		if err := xmp.SetNativeField(x.Udta, tag, value); err != nil {
   246  			return fmt.Errorf("%s: %v", NsQuicktime.GetName(), err)
   247  		}
   248  	case strings.HasPrefix(tag, "com.apple.quicktime.player"):
   249  		if x.Player == nil {
   250  			x.Player = &QtPlayer{}
   251  		}
   252  		if err := xmp.SetNativeField(x.Player, tag, value); err != nil {
   253  			return fmt.Errorf("%s: %v", NsQuicktime.GetName(), err)
   254  		}
   255  	case strings.HasPrefix(tag, "com.apple.quicktime"):
   256  		if x.Mdta == nil {
   257  			x.Mdta = &QtMetadata{}
   258  		}
   259  		if err := xmp.SetNativeField(x.Mdta, tag, value); err != nil {
   260  			return fmt.Errorf("%s: %v", NsQuicktime.GetName(), err)
   261  		}
   262  	case strings.HasPrefix(tag, "com.apple.proapps"):
   263  		if x.ProApps == nil {
   264  			x.ProApps = &QtProApps{}
   265  		}
   266  		if err := xmp.SetNativeField(x.ProApps, tag, value); err != nil {
   267  			return fmt.Errorf("%s: %v", NsQuicktime.GetName(), err)
   268  		}
   269  	case tag == "iXML" || tag == "info.ixml.xml" || tag == "info.ixml.metadata" || tag == "info.ixml.info":
   270  		i := &ixml.IXML{}
   271  		if err := i.ParseXML([]byte(value)); err != nil {
   272  			return fmt.Errorf("%s: parsing ixml: %v", NsQuicktime.GetName(), err)
   273  		} else {
   274  			x.IXML = i
   275  		}
   276  	case tag == "XMP_":
   277  		v := xmp.NewDocument()
   278  		if err := xmp.Unmarshal([]byte(value), v); err != nil {
   279  			return fmt.Errorf("%s: parsing embedded xmp: %v", NsQuicktime.GetName(), err)
   280  		} else {
   281  			x.XMP = v
   282  		}
   283  	default:
   284  		// silently ignore all other sorts of tags
   285  	}
   286  	return nil
   287  }
   288  
   289  func (x *QtInfo) ListTags() (xmp.TagList, error) {
   290  	if l, err := xmp.ListNativeFields(x); err != nil {
   291  		return nil, fmt.Errorf("%s: %v", NsQuicktime.GetName(), err)
   292  	} else {
   293  		return l, nil
   294  	}
   295  }