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 }