github.com/boki/go-xmp@v1.0.1/models/ixml/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  // TODO:
    16  // - io.Writer interface with maxLen and padding to combine with file writers
    17  // - UID generator
    18  //   <Manufacturer><Device><DeviceSN><Date><Time><Random><FileSetIndex>
    19  //   - 3 chars for the Manufacturer ID: AAT, AVI, DIG, FOS, GAL, HHB, MTI, NAG, ZAX, etc...
    20  //   - 3 chars for the Device ID: CAN, PMX, POR, etc...
    21  //   - 5 chars for the Device Serial Number: 12345
    22  //   - 8 chars for the date: YYYYMMDD
    23  //   - 6 chars for the time: HHMMSS
    24  //   - 3 chars for pure random digits that can very well be the millisecond in the given time second, to ensure that two generations occurring in the same second have a different UID.
    25  //   - 4 chars for the file set index. These 4 digits are set to zero in the <FAMILY_UID> and to the <FILE_SET_INDEX> value in both the <FILE_UID> and BEXT Originator Reference of every files in the set.
    26  
    27  // Package ixml implements the iXML audio chunk metadata standard for broadcast wave audio.
    28  package ixml
    29  
    30  import (
    31  	"encoding/xml"
    32  	"fmt"
    33  	"strings"
    34  
    35  	"trimmer.io/go-xmp/models/xmp_dm"
    36  	"trimmer.io/go-xmp/xmp"
    37  )
    38  
    39  const ixmlVersion = "2.0"
    40  
    41  var (
    42  	NsIXML = xmp.NewNamespace("iXML", "http://ns.adobe.com/ixml/1.0/", NewModel)
    43  )
    44  
    45  func init() {
    46  	xmp.Register(NsIXML, xmp.SoundMetadata)
    47  }
    48  
    49  func NewModel(name string) xmp.Model {
    50  	return &IXML{
    51  		Version: ixmlVersion,
    52  	}
    53  }
    54  
    55  func MakeModel(d *xmp.Document) (*IXML, error) {
    56  	m, err := d.MakeModel(NsIXML)
    57  	if err != nil {
    58  		return nil, err
    59  	}
    60  	x, _ := m.(*IXML)
    61  	return x, nil
    62  }
    63  
    64  func FindModel(d *xmp.Document) *IXML {
    65  	if m := d.FindModel(NsIXML); m != nil {
    66  		return m.(*IXML)
    67  	}
    68  	return nil
    69  }
    70  
    71  type IXML struct {
    72  	XMLName              xml.Name                `xml:"BWFXML"                            xmp:"-"`
    73  	Version              string                  `xml:"IXML_VERSION"                      xmp:"iXML:version"`
    74  	Project              string                  `xml:"PROJECT,omitempty"                 xmp:"iXML:project"`
    75  	SceneName            string                  `xml:"SCENE,omitempty"                   xmp:"iXML:sceneName"`
    76  	SoundRoll            string                  `xml:"TAPE,omitempty"                    xmp:"iXML:soundRoll"`
    77  	Take                 int                     `xml:"TAKE,omitempty"                    xmp:"iXML:take"`
    78  	TakeType             TakeTypeList            `xml:"TAKE_TYPE,omitempty"               xmp:"iXML:takeType"` // v2.0+
    79  	IsNotGood            Bool                    `xml:"NO_GOOD,omitempty"                 xmp:"-"`             // v1.9-
    80  	IsFalseStart         Bool                    `xml:"FALSE_START,omitempty"             xmp:"-"`             // v1.9-
    81  	IsWildTrack          Bool                    `xml:"WILD_TRACK,omitempty"              xmp:"-"`             // v1.9-
    82  	PreRecordSamplecount int                     `xml:"PRE_RECORD_SAMPLECOUNT,omitempty"  xmp:"-"`             // v1.3-
    83  	IsCircled            Bool                    `xml:"CIRCLED,omitempty"                 xmp:"iXML:circled"`
    84  	FileUID              string                  `xml:"FILE_UID,omitempty"                xmp:"iXML:fileUid"`
    85  	UserBits             string                  `xml:"UBITS,omitempty"                   xmp:"iXML:userBits"`
    86  	Note                 string                  `xml:"NOTE,omitempty"                    xmp:"iXML:note"`
    87  	SyncPoints           SyncPointList           `xml:"SYNC_POINT_LIST,omitempty"         xmp:"iXML:syncPoints"`
    88  	Speed                *Speed                  `xml:"SPEED,omitempty"                   xmp:"iXML:speed"`
    89  	History              *History                `xml:"HISTORY,omitempty"                 xmp:"iXML:history"`
    90  	FileSet              *FileSet                `xml:"FILE_SET,omitempty"                xmp:"iXML:fileSet"`
    91  	TrackList            TrackList               `xml:"TRACK_LIST,omitempty"              xmp:"iXML:trackList"`
    92  	UserData             *UserData               `xml:"USER,omitempty"                    xmp:"iXML:userData"`
    93  	Location             *Location               `xml:"LOCATION,omitempty"                xmp:"iXML:location"`
    94  	Extension            xmp.NamedExtensionArray `xml:",any"                              xmp:"iXML:extension,any"`
    95  }
    96  
    97  func (m *IXML) Namespaces() xmp.NamespaceList {
    98  	return xmp.NamespaceList{NsIXML}
    99  }
   100  
   101  func (m *IXML) Can(nsName string) bool {
   102  	return nsName == NsIXML.GetName()
   103  }
   104  
   105  func (x *IXML) SyncModel(d *xmp.Document) error {
   106  	return nil
   107  }
   108  
   109  func (x *IXML) SyncFromXMP(d *xmp.Document) error {
   110  	return nil
   111  }
   112  
   113  func (x *IXML) SyncToXMP(d *xmp.Document) error {
   114  	dm, err := xmpdm.MakeModel(d)
   115  	if err != nil {
   116  		return err
   117  	}
   118  	dm.TapeName = x.SoundRoll
   119  	dm.Scene = x.SceneName
   120  	dm.TrackNumber = x.Take
   121  	dm.LogComment = x.Note
   122  	dm.ProjectName = x.Project
   123  	dm.Good = xmp.Bool(x.IsCircled.Value())
   124  	if x.Speed != nil {
   125  		dm.StartTimecode = x.Speed.XmpTimecode()
   126  		dm.AudioSampleRate = x.Speed.FileSampleRate.Value()
   127  	}
   128  	return nil
   129  }
   130  
   131  func (x *IXML) CanTag(tag string) bool {
   132  	_, err := xmp.GetNativeField(x, tag)
   133  	return err == nil
   134  }
   135  
   136  func (x *IXML) GetTag(tag string) (string, error) {
   137  	if v, err := xmp.GetNativeField(x, tag); err != nil {
   138  		return "", fmt.Errorf("%s: %v", NsIXML.GetName(), err)
   139  	} else {
   140  		return v, nil
   141  	}
   142  }
   143  
   144  func (x *IXML) SetTag(tag, value string) error {
   145  	if err := xmp.SetNativeField(x, tag, value); err != nil {
   146  		return fmt.Errorf("%s: %v", NsIXML.GetName(), err)
   147  	}
   148  	return nil
   149  }
   150  
   151  func (x *IXML) ParseXML(data []byte) error {
   152  	// must strip type funcs to avoid recursion
   153  	type _t IXML
   154  	m := &IXML{}
   155  	if err := xml.Unmarshal(data, (*_t)(m)); err != nil {
   156  		return fmt.Errorf("ixml: parse error: %v", err)
   157  	}
   158  	if err := m.upgrade(); err != nil {
   159  		return err
   160  	}
   161  	*x = *m
   162  	return nil
   163  }
   164  
   165  func (x *IXML) UnmarshalText(data []byte) error {
   166  	return x.ParseXML(data)
   167  }
   168  
   169  func (x *IXML) upgrade() error {
   170  	if err := x.v13_to_v14(); err != nil {
   171  		return err
   172  	}
   173  	if err := x.v1x_to_v20(); err != nil {
   174  		return err
   175  	}
   176  	return nil
   177  }
   178  
   179  func (x *IXML) v1x_to_v20() error {
   180  	if x.IsNotGood {
   181  		x.TakeType.Add(TakeTypeNoGood)
   182  		x.IsNotGood = false
   183  	}
   184  	if x.IsFalseStart {
   185  		x.TakeType.Add(TakeTypeFalseStart)
   186  		x.IsFalseStart = false
   187  	}
   188  	if x.IsWildTrack {
   189  		x.TakeType.Add(TakeTypeWildTrack)
   190  		x.IsWildTrack = false
   191  	}
   192  	if x.UserData != nil {
   193  		x.UserData.Comment = strings.TrimSpace(x.UserData.Comment)
   194  	}
   195  	return nil
   196  }
   197  
   198  func (x *IXML) v13_to_v14() error {
   199  	if x.PreRecordSamplecount > 0 {
   200  		if i, ok := x.SyncPoints.ContainsFunc(SyncPointPreRecordSamplecount); ok {
   201  			x.SyncPoints[i].Low = x.PreRecordSamplecount
   202  		} else {
   203  			x.SyncPoints = append(x.SyncPoints, SyncPoint{
   204  				Type:     SyncPointRelative,
   205  				Function: SyncPointPreRecordSamplecount,
   206  				Comment:  "upgrade from deprecated property root.PRE_RECORD_SAMPLECOUNT",
   207  				Low:      x.PreRecordSamplecount,
   208  			})
   209  		}
   210  		x.PreRecordSamplecount = 0
   211  	}
   212  	return nil
   213  }