github.com/boki/go-xmp@v1.0.1/models/pm/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  // http://forums.camerabits.com/index.php?topic=903.0
    16  
    17  // Package pm implements metadata written by Photomechanic software found in many professional images.
    18  package pm
    19  
    20  import (
    21  	"bytes"
    22  	"fmt"
    23  	"strconv"
    24  	"strings"
    25  	"time"
    26  
    27  	"trimmer.io/go-xmp/xmp"
    28  )
    29  
    30  var (
    31  	NsPm = xmp.NewNamespace("photomechanic", "http://ns.camerabits.com/photomechanic/1.0/", NewModel)
    32  )
    33  
    34  func init() {
    35  	xmp.Register(NsPm, xmp.ImageMetadata)
    36  }
    37  
    38  func NewModel(name string) xmp.Model {
    39  	return &Photomechanic{}
    40  }
    41  
    42  func MakeModel(d *xmp.Document) (*Photomechanic, error) {
    43  	m, err := d.MakeModel(NsPm)
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  	x, _ := m.(*Photomechanic)
    48  	return x, nil
    49  }
    50  
    51  func FindModel(d *xmp.Document) *Photomechanic {
    52  	if m := d.FindModel(NsPm); m != nil {
    53  		return m.(*Photomechanic)
    54  	}
    55  	return nil
    56  }
    57  
    58  func (x Photomechanic) Can(nsName string) bool {
    59  	return nsName == "photomechanic"
    60  }
    61  
    62  func (x *Photomechanic) Namespaces() xmp.NamespaceList {
    63  	return xmp.NamespaceList{NsPm}
    64  }
    65  
    66  func (x *Photomechanic) SyncModel(d *xmp.Document) error {
    67  	return nil
    68  }
    69  
    70  func (x *Photomechanic) SyncFromXMP(d *xmp.Document) error {
    71  	return nil
    72  }
    73  
    74  func (x Photomechanic) SyncToXMP(d *xmp.Document) error {
    75  	return nil
    76  }
    77  
    78  func (x *Photomechanic) CanTag(tag string) bool {
    79  	_, err := xmp.GetNativeField(x, tag)
    80  	return err == nil
    81  }
    82  
    83  func (x *Photomechanic) GetTag(tag string) (string, error) {
    84  	if v, err := xmp.GetNativeField(x, tag); err != nil {
    85  		return "", fmt.Errorf("%s: %v", NsPm.GetName(), err)
    86  	} else {
    87  		return v, nil
    88  	}
    89  }
    90  
    91  func (x *Photomechanic) SetTag(tag, value string) error {
    92  	if err := xmp.SetNativeField(x, tag, value); err != nil {
    93  		return fmt.Errorf("%s: %v", NsPm.GetName(), err)
    94  	}
    95  	return nil
    96  }
    97  
    98  type Photomechanic struct {
    99  	EditStatus        string          `xmp:"photomechanic:EditStatus"`        // edit status field from IPTC record
   100  	ColorClass        int             `xmp:"photomechanic:ColorClass"`        // USA
   101  	CountryCode       string          `xmp:"photomechanic:CountryCode"`       // USA
   102  	TimeCreated       Time            `xmp:"photomechanic:TimeCreated"`       // HHMMSS+dHdS
   103  	Prefs             Preferences     `xmp:"photomechanic:Prefs"`             // T:C:R:F
   104  	Tagged            xmp.Bool        `xmp:"photomechanic:Tagged"`            // "True"
   105  	Version           string          `xmp:"photomechanic:PMVersion"`         // PM5
   106  	ColorClassEval    int             `xmp:"photomechanic:ColorClassEval"`    // ="2"
   107  	ColorClassApply   xmp.Bool        `xmp:"photomechanic:ColorClassApply"`   // ="True"
   108  	RatingEval        int             `xmp:"photomechanic:RatingEval"`        // ="4"
   109  	RatingApply       xmp.Bool        `xmp:"photomechanic:RatingApply"`       // ="True"
   110  	TagEval           int             `xmp:"photomechanic:TagEval"`           // ="0"
   111  	TagApply          xmp.Bool        `xmp:"photomechanic:TagApply"`          // ="False"
   112  	CaptionMergeStyle int             `xmp:"photomechanic:CaptionMergeStyle"` // ="1"
   113  	ApplyAPCustom     int             `xmp:"photomechanic:ApplyAPCustom"`     // ="0"
   114  	MergeAPCustom     int             `xmp:"photomechanic:MergeAPCustom"`     // ="0"
   115  	ApplyDateType     int             `xmp:"photomechanic:ApplyDateType"`     // ="2"
   116  	FieldsToApply     xmp.StringArray `xmp:"photomechanic:FieldsToApply"`
   117  }
   118  
   119  type Time time.Time
   120  
   121  const pmTimeFormat = "150405-0700"
   122  
   123  // HH = 24hr hour value (0-23)
   124  // MM = minute value (0-59)
   125  // SS = second value (0-59)
   126  // dH = GMT hour delta (+/- 12)
   127  // dS = GMT minute delta (0-59)
   128  
   129  func (x Time) IsZero() bool {
   130  	return time.Time(x).IsZero()
   131  }
   132  
   133  func (x Time) MarshalText() ([]byte, error) {
   134  	if x.IsZero() {
   135  		return nil, nil
   136  	}
   137  	return []byte(time.Time(x).Format(pmTimeFormat)), nil
   138  }
   139  
   140  func (x *Time) UnmarshalText(data []byte) error {
   141  	if len(data) == 0 {
   142  		return nil
   143  	}
   144  	if t, err := time.Parse(pmTimeFormat, string(data)); err != nil {
   145  		return fmt.Errorf("photomechanic: invalid time value '%s'", string(data))
   146  	} else {
   147  		*x = Time(t)
   148  	}
   149  	return nil
   150  }
   151  
   152  // T:C:R:F
   153  type Preferences struct {
   154  	TagStatus  int // T = Tag status (0 or 1)
   155  	ColorClass int // C = Color Class value (0-8)
   156  	Rating     int // R = Rating (0-5)
   157  	Frame      int // F = Frame number of image or -1 if undetermined
   158  }
   159  
   160  func (x Preferences) IsZero() bool {
   161  	return x.TagStatus == 0 && x.ColorClass == 0 && x.Rating == 0 && x.Frame == 0
   162  }
   163  
   164  func (x Preferences) MarshalText() ([]byte, error) {
   165  	buf := bytes.Buffer{}
   166  	buf.WriteString(strconv.FormatInt(int64(x.TagStatus), 10))
   167  	buf.WriteByte(':')
   168  	buf.WriteString(strconv.FormatInt(int64(x.ColorClass), 10))
   169  	buf.WriteByte(':')
   170  	buf.WriteString(strconv.FormatInt(int64(x.Rating), 10))
   171  	buf.WriteByte(':')
   172  	buf.WriteString(strconv.FormatInt(int64(x.Frame), 10))
   173  	return buf.Bytes(), nil
   174  }
   175  
   176  func (x *Preferences) UnmarshalText(data []byte) error {
   177  	p := Preferences{}
   178  	var err error
   179  	fields := strings.Split(string(data), ":")
   180  	if len(fields) != 4 {
   181  		return fmt.Errorf("photomechanic: invalid prefs value '%s'", string(data))
   182  	}
   183  	if p.TagStatus, err = strconv.Atoi(fields[0]); err != nil {
   184  		return fmt.Errorf("photomechanic: invalid tag status value '%s': %v", string(data), err)
   185  	}
   186  	if p.ColorClass, err = strconv.Atoi(fields[1]); err != nil {
   187  		return fmt.Errorf("photomechanic: invalid color class value '%s': %v", string(data), err)
   188  	}
   189  	if p.Rating, err = strconv.Atoi(fields[2]); err != nil {
   190  		return fmt.Errorf("photomechanic: invalid rating value '%s': %v", string(data), err)
   191  	}
   192  	if p.Frame, err = strconv.Atoi(fields[3]); err != nil {
   193  		return fmt.Errorf("photomechanic: invalid frame number value '%s': %v", string(data), err)
   194  	}
   195  	*x = p
   196  	return nil
   197  }