gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

ifd.go (16652B)


      1 package exifcommon
      2 
      3 import (
      4 	"errors"
      5 	"fmt"
      6 	"strings"
      7 
      8 	"github.com/dsoprea/go-logging"
      9 )
     10 
     11 var (
     12 	ifdLogger = log.NewLogger("exifcommon.ifd")
     13 )
     14 
     15 var (
     16 	ErrChildIfdNotMapped = errors.New("no child-IFD for that tag-ID under parent")
     17 )
     18 
     19 // MappedIfd is one node in the IFD-mapping.
     20 type MappedIfd struct {
     21 	ParentTagId uint16
     22 	Placement   []uint16
     23 	Path        []string
     24 
     25 	Name     string
     26 	TagId    uint16
     27 	Children map[uint16]*MappedIfd
     28 }
     29 
     30 // String returns a descriptive string.
     31 func (mi *MappedIfd) String() string {
     32 	pathPhrase := mi.PathPhrase()
     33 	return fmt.Sprintf("MappedIfd<(0x%04X) [%s] PATH=[%s]>", mi.TagId, mi.Name, pathPhrase)
     34 }
     35 
     36 // PathPhrase returns a non-fully-qualified IFD path.
     37 func (mi *MappedIfd) PathPhrase() string {
     38 	return strings.Join(mi.Path, "/")
     39 }
     40 
     41 // TODO(dustin): Refactor this to use IfdIdentity structs.
     42 
     43 // IfdMapping describes all of the IFDs that we currently recognize.
     44 type IfdMapping struct {
     45 	rootNode *MappedIfd
     46 }
     47 
     48 // NewIfdMapping returns a new IfdMapping struct.
     49 func NewIfdMapping() (ifdMapping *IfdMapping) {
     50 	rootNode := &MappedIfd{
     51 		Path:     make([]string, 0),
     52 		Children: make(map[uint16]*MappedIfd),
     53 	}
     54 
     55 	return &IfdMapping{
     56 		rootNode: rootNode,
     57 	}
     58 }
     59 
     60 // NewIfdMappingWithStandard retruns a new IfdMapping struct preloaded with the
     61 // standard IFDs.
     62 func NewIfdMappingWithStandard() (ifdMapping *IfdMapping, err error) {
     63 	defer func() {
     64 		if state := recover(); state != nil {
     65 			err = log.Wrap(state.(error))
     66 		}
     67 	}()
     68 
     69 	im := NewIfdMapping()
     70 
     71 	err = LoadStandardIfds(im)
     72 	log.PanicIf(err)
     73 
     74 	return im, nil
     75 }
     76 
     77 // Get returns the node given the path slice.
     78 func (im *IfdMapping) Get(parentPlacement []uint16) (childIfd *MappedIfd, err error) {
     79 	defer func() {
     80 		if state := recover(); state != nil {
     81 			err = log.Wrap(state.(error))
     82 		}
     83 	}()
     84 
     85 	ptr := im.rootNode
     86 	for _, tagId := range parentPlacement {
     87 		if descendantPtr, found := ptr.Children[tagId]; found == false {
     88 			log.Panicf("ifd child with tag-ID (%04x) not registered: [%s]", tagId, ptr.PathPhrase())
     89 		} else {
     90 			ptr = descendantPtr
     91 		}
     92 	}
     93 
     94 	return ptr, nil
     95 }
     96 
     97 // GetWithPath returns the node given the path string.
     98 func (im *IfdMapping) GetWithPath(pathPhrase string) (mi *MappedIfd, err error) {
     99 	defer func() {
    100 		if state := recover(); state != nil {
    101 			err = log.Wrap(state.(error))
    102 		}
    103 	}()
    104 
    105 	if pathPhrase == "" {
    106 		log.Panicf("path-phrase is empty")
    107 	}
    108 
    109 	path := strings.Split(pathPhrase, "/")
    110 	ptr := im.rootNode
    111 
    112 	for _, name := range path {
    113 		var hit *MappedIfd
    114 		for _, mi := range ptr.Children {
    115 			if mi.Name == name {
    116 				hit = mi
    117 				break
    118 			}
    119 		}
    120 
    121 		if hit == nil {
    122 			log.Panicf("ifd child with name [%s] not registered: [%s]", name, ptr.PathPhrase())
    123 		}
    124 
    125 		ptr = hit
    126 	}
    127 
    128 	return ptr, nil
    129 }
    130 
    131 // GetChild is a convenience function to get the child path for a given parent
    132 // placement and child tag-ID.
    133 func (im *IfdMapping) GetChild(parentPathPhrase string, tagId uint16) (mi *MappedIfd, err error) {
    134 	defer func() {
    135 		if state := recover(); state != nil {
    136 			err = log.Wrap(state.(error))
    137 		}
    138 	}()
    139 
    140 	mi, err = im.GetWithPath(parentPathPhrase)
    141 	log.PanicIf(err)
    142 
    143 	for _, childMi := range mi.Children {
    144 		if childMi.TagId == tagId {
    145 			return childMi, nil
    146 		}
    147 	}
    148 
    149 	// Whether or not an IFD is defined in data, such an IFD is not registered
    150 	// and would be unknown.
    151 	log.Panic(ErrChildIfdNotMapped)
    152 	return nil, nil
    153 }
    154 
    155 // IfdTagIdAndIndex represents a specific part of the IFD path.
    156 //
    157 // This is a legacy type.
    158 type IfdTagIdAndIndex struct {
    159 	Name  string
    160 	TagId uint16
    161 	Index int
    162 }
    163 
    164 // String returns a descriptive string.
    165 func (itii IfdTagIdAndIndex) String() string {
    166 	return fmt.Sprintf("IfdTagIdAndIndex<NAME=[%s] ID=(%04x) INDEX=(%d)>", itii.Name, itii.TagId, itii.Index)
    167 }
    168 
    169 // ResolvePath takes a list of names, which can also be suffixed with indices
    170 // (to identify the second, third, etc.. sibling IFD) and returns a list of
    171 // tag-IDs and those indices.
    172 //
    173 // Example:
    174 //
    175 // - IFD/Exif/Iop
    176 // - IFD0/Exif/Iop
    177 //
    178 // This is the only call that supports adding the numeric indices.
    179 func (im *IfdMapping) ResolvePath(pathPhrase string) (lineage []IfdTagIdAndIndex, err error) {
    180 	defer func() {
    181 		if state := recover(); state != nil {
    182 			err = log.Wrap(state.(error))
    183 		}
    184 	}()
    185 
    186 	pathPhrase = strings.TrimSpace(pathPhrase)
    187 
    188 	if pathPhrase == "" {
    189 		log.Panicf("can not resolve empty path-phrase")
    190 	}
    191 
    192 	path := strings.Split(pathPhrase, "/")
    193 	lineage = make([]IfdTagIdAndIndex, len(path))
    194 
    195 	ptr := im.rootNode
    196 	empty := IfdTagIdAndIndex{}
    197 	for i, name := range path {
    198 		indexByte := name[len(name)-1]
    199 		index := 0
    200 		if indexByte >= '0' && indexByte <= '9' {
    201 			index = int(indexByte - '0')
    202 			name = name[:len(name)-1]
    203 		}
    204 
    205 		itii := IfdTagIdAndIndex{}
    206 		for _, mi := range ptr.Children {
    207 			if mi.Name != name {
    208 				continue
    209 			}
    210 
    211 			itii.Name = name
    212 			itii.TagId = mi.TagId
    213 			itii.Index = index
    214 
    215 			ptr = mi
    216 
    217 			break
    218 		}
    219 
    220 		if itii == empty {
    221 			log.Panicf("ifd child with name [%s] not registered: [%s]", name, pathPhrase)
    222 		}
    223 
    224 		lineage[i] = itii
    225 	}
    226 
    227 	return lineage, nil
    228 }
    229 
    230 // FqPathPhraseFromLineage returns the fully-qualified IFD path from the slice.
    231 func (im *IfdMapping) FqPathPhraseFromLineage(lineage []IfdTagIdAndIndex) (fqPathPhrase string) {
    232 	fqPathParts := make([]string, len(lineage))
    233 	for i, itii := range lineage {
    234 		if itii.Index > 0 {
    235 			fqPathParts[i] = fmt.Sprintf("%s%d", itii.Name, itii.Index)
    236 		} else {
    237 			fqPathParts[i] = itii.Name
    238 		}
    239 	}
    240 
    241 	return strings.Join(fqPathParts, "/")
    242 }
    243 
    244 // PathPhraseFromLineage returns the non-fully-qualified IFD path from the
    245 // slice.
    246 func (im *IfdMapping) PathPhraseFromLineage(lineage []IfdTagIdAndIndex) (pathPhrase string) {
    247 	pathParts := make([]string, len(lineage))
    248 	for i, itii := range lineage {
    249 		pathParts[i] = itii.Name
    250 	}
    251 
    252 	return strings.Join(pathParts, "/")
    253 }
    254 
    255 // StripPathPhraseIndices returns a non-fully-qualified path-phrase (no
    256 // indices).
    257 func (im *IfdMapping) StripPathPhraseIndices(pathPhrase string) (strippedPathPhrase string, err error) {
    258 	defer func() {
    259 		if state := recover(); state != nil {
    260 			err = log.Wrap(state.(error))
    261 		}
    262 	}()
    263 
    264 	lineage, err := im.ResolvePath(pathPhrase)
    265 	log.PanicIf(err)
    266 
    267 	strippedPathPhrase = im.PathPhraseFromLineage(lineage)
    268 	return strippedPathPhrase, nil
    269 }
    270 
    271 // Add puts the given IFD at the given position of the tree. The position of the
    272 // tree is referred to as the placement and is represented by a set of tag-IDs,
    273 // where the leftmost is the root tag and the tags going to the right are
    274 // progressive descendants.
    275 func (im *IfdMapping) Add(parentPlacement []uint16, tagId uint16, name string) (err error) {
    276 	defer func() {
    277 		if state := recover(); state != nil {
    278 			err = log.Wrap(state.(error))
    279 		}
    280 	}()
    281 
    282 	// TODO(dustin): !! It would be nicer to provide a list of names in the placement rather than tag-IDs.
    283 
    284 	ptr, err := im.Get(parentPlacement)
    285 	log.PanicIf(err)
    286 
    287 	path := make([]string, len(parentPlacement)+1)
    288 	if len(parentPlacement) > 0 {
    289 		copy(path, ptr.Path)
    290 	}
    291 
    292 	path[len(path)-1] = name
    293 
    294 	placement := make([]uint16, len(parentPlacement)+1)
    295 	if len(placement) > 0 {
    296 		copy(placement, ptr.Placement)
    297 	}
    298 
    299 	placement[len(placement)-1] = tagId
    300 
    301 	childIfd := &MappedIfd{
    302 		ParentTagId: ptr.TagId,
    303 		Path:        path,
    304 		Placement:   placement,
    305 		Name:        name,
    306 		TagId:       tagId,
    307 		Children:    make(map[uint16]*MappedIfd),
    308 	}
    309 
    310 	if _, found := ptr.Children[tagId]; found == true {
    311 		log.Panicf("child IFD with tag-ID (%04x) already registered under IFD [%s] with tag-ID (%04x)", tagId, ptr.Name, ptr.TagId)
    312 	}
    313 
    314 	ptr.Children[tagId] = childIfd
    315 
    316 	return nil
    317 }
    318 
    319 func (im *IfdMapping) dumpLineages(stack []*MappedIfd, input []string) (output []string, err error) {
    320 	defer func() {
    321 		if state := recover(); state != nil {
    322 			err = log.Wrap(state.(error))
    323 		}
    324 	}()
    325 
    326 	currentIfd := stack[len(stack)-1]
    327 
    328 	output = input
    329 	for _, childIfd := range currentIfd.Children {
    330 		stackCopy := make([]*MappedIfd, len(stack)+1)
    331 
    332 		copy(stackCopy, stack)
    333 		stackCopy[len(stack)] = childIfd
    334 
    335 		// Add to output, but don't include the obligatory root node.
    336 		parts := make([]string, len(stackCopy)-1)
    337 		for i, mi := range stackCopy[1:] {
    338 			parts[i] = mi.Name
    339 		}
    340 
    341 		output = append(output, strings.Join(parts, "/"))
    342 
    343 		output, err = im.dumpLineages(stackCopy, output)
    344 		log.PanicIf(err)
    345 	}
    346 
    347 	return output, nil
    348 }
    349 
    350 // DumpLineages returns a slice of strings representing all mappings.
    351 func (im *IfdMapping) DumpLineages() (output []string, err error) {
    352 	defer func() {
    353 		if state := recover(); state != nil {
    354 			err = log.Wrap(state.(error))
    355 		}
    356 	}()
    357 
    358 	stack := []*MappedIfd{im.rootNode}
    359 	output = make([]string, 0)
    360 
    361 	output, err = im.dumpLineages(stack, output)
    362 	log.PanicIf(err)
    363 
    364 	return output, nil
    365 }
    366 
    367 // LoadStandardIfds loads the standard IFDs into the mapping.
    368 func LoadStandardIfds(im *IfdMapping) (err error) {
    369 	defer func() {
    370 		if state := recover(); state != nil {
    371 			err = log.Wrap(state.(error))
    372 		}
    373 	}()
    374 
    375 	err = im.Add(
    376 		[]uint16{},
    377 		IfdStandardIfdIdentity.TagId(), IfdStandardIfdIdentity.Name())
    378 
    379 	log.PanicIf(err)
    380 
    381 	err = im.Add(
    382 		[]uint16{IfdStandardIfdIdentity.TagId()},
    383 		IfdExifStandardIfdIdentity.TagId(), IfdExifStandardIfdIdentity.Name())
    384 
    385 	log.PanicIf(err)
    386 
    387 	err = im.Add(
    388 		[]uint16{IfdStandardIfdIdentity.TagId(), IfdExifStandardIfdIdentity.TagId()},
    389 		IfdExifIopStandardIfdIdentity.TagId(), IfdExifIopStandardIfdIdentity.Name())
    390 
    391 	log.PanicIf(err)
    392 
    393 	err = im.Add(
    394 		[]uint16{IfdStandardIfdIdentity.TagId()},
    395 		IfdGpsInfoStandardIfdIdentity.TagId(), IfdGpsInfoStandardIfdIdentity.Name())
    396 
    397 	log.PanicIf(err)
    398 
    399 	return nil
    400 }
    401 
    402 // IfdTag describes a single IFD tag and its parent (if any).
    403 type IfdTag struct {
    404 	parentIfdTag *IfdTag
    405 	tagId        uint16
    406 	name         string
    407 }
    408 
    409 func NewIfdTag(parentIfdTag *IfdTag, tagId uint16, name string) IfdTag {
    410 	return IfdTag{
    411 		parentIfdTag: parentIfdTag,
    412 		tagId:        tagId,
    413 		name:         name,
    414 	}
    415 }
    416 
    417 // ParentIfd returns the IfdTag of this IFD's parent.
    418 func (it IfdTag) ParentIfd() *IfdTag {
    419 	return it.parentIfdTag
    420 }
    421 
    422 // TagId returns the tag-ID of this IFD.
    423 func (it IfdTag) TagId() uint16 {
    424 	return it.tagId
    425 }
    426 
    427 // Name returns the simple name of this IFD.
    428 func (it IfdTag) Name() string {
    429 	return it.name
    430 }
    431 
    432 // String returns a descriptive string.
    433 func (it IfdTag) String() string {
    434 	parentIfdPhrase := ""
    435 	if it.parentIfdTag != nil {
    436 		parentIfdPhrase = fmt.Sprintf(" PARENT=(0x%04x)[%s]", it.parentIfdTag.tagId, it.parentIfdTag.name)
    437 	}
    438 
    439 	return fmt.Sprintf("IfdTag<TAG-ID=(0x%04x) NAME=[%s]%s>", it.tagId, it.name, parentIfdPhrase)
    440 }
    441 
    442 var (
    443 	// rootStandardIfd is the standard root IFD.
    444 	rootStandardIfd = NewIfdTag(nil, 0x0000, "IFD") // IFD
    445 
    446 	// exifStandardIfd is the standard "Exif" IFD.
    447 	exifStandardIfd = NewIfdTag(&rootStandardIfd, 0x8769, "Exif") // IFD/Exif
    448 
    449 	// iopStandardIfd is the standard "Iop" IFD.
    450 	iopStandardIfd = NewIfdTag(&exifStandardIfd, 0xA005, "Iop") // IFD/Exif/Iop
    451 
    452 	// gpsInfoStandardIfd is the standard "GPS" IFD.
    453 	gpsInfoStandardIfd = NewIfdTag(&rootStandardIfd, 0x8825, "GPSInfo") // IFD/GPSInfo
    454 )
    455 
    456 // IfdIdentityPart represents one component in an IFD path.
    457 type IfdIdentityPart struct {
    458 	Name  string
    459 	Index int
    460 }
    461 
    462 // String returns a fully-qualified IFD path.
    463 func (iip IfdIdentityPart) String() string {
    464 	if iip.Index > 0 {
    465 		return fmt.Sprintf("%s%d", iip.Name, iip.Index)
    466 	} else {
    467 		return iip.Name
    468 	}
    469 }
    470 
    471 // UnindexedString returned a non-fully-qualified IFD path.
    472 func (iip IfdIdentityPart) UnindexedString() string {
    473 	return iip.Name
    474 }
    475 
    476 // IfdIdentity represents a single IFD path and provides access to various
    477 // information and representations.
    478 //
    479 // Only global instances can be used for equality checks.
    480 type IfdIdentity struct {
    481 	ifdTag    IfdTag
    482 	parts     []IfdIdentityPart
    483 	ifdPath   string
    484 	fqIfdPath string
    485 }
    486 
    487 // NewIfdIdentity returns a new IfdIdentity struct.
    488 func NewIfdIdentity(ifdTag IfdTag, parts ...IfdIdentityPart) (ii *IfdIdentity) {
    489 	ii = &IfdIdentity{
    490 		ifdTag: ifdTag,
    491 		parts:  parts,
    492 	}
    493 
    494 	ii.ifdPath = ii.getIfdPath()
    495 	ii.fqIfdPath = ii.getFqIfdPath()
    496 
    497 	return ii
    498 }
    499 
    500 // NewIfdIdentityFromString parses a string like "IFD/Exif" or "IFD1" or
    501 // something more exotic with custom IFDs ("SomeIFD4/SomeChildIFD6"). Note that
    502 // this will valid the unindexed IFD structure (because the standard tags from
    503 // the specification are unindexed), but not, obviously, any indices (e.g.
    504 // the numbers in "IFD0", "IFD1", "SomeIFD4/SomeChildIFD6"). It is
    505 // required for the caller to check whether these specific instances
    506 // were actually parsed out of the stream.
    507 func NewIfdIdentityFromString(im *IfdMapping, fqIfdPath string) (ii *IfdIdentity, err error) {
    508 	defer func() {
    509 		if state := recover(); state != nil {
    510 			err = log.Wrap(state.(error))
    511 		}
    512 	}()
    513 
    514 	lineage, err := im.ResolvePath(fqIfdPath)
    515 	log.PanicIf(err)
    516 
    517 	var lastIt *IfdTag
    518 	identityParts := make([]IfdIdentityPart, len(lineage))
    519 	for i, itii := range lineage {
    520 		// Build out the tag that will eventually point to the IFD represented
    521 		// by the right-most part in the IFD path.
    522 
    523 		it := &IfdTag{
    524 			parentIfdTag: lastIt,
    525 			tagId:        itii.TagId,
    526 			name:         itii.Name,
    527 		}
    528 
    529 		lastIt = it
    530 
    531 		// Create the next IfdIdentity part.
    532 
    533 		iip := IfdIdentityPart{
    534 			Name:  itii.Name,
    535 			Index: itii.Index,
    536 		}
    537 
    538 		identityParts[i] = iip
    539 	}
    540 
    541 	ii = NewIfdIdentity(*lastIt, identityParts...)
    542 	return ii, nil
    543 }
    544 
    545 func (ii *IfdIdentity) getFqIfdPath() string {
    546 	partPhrases := make([]string, len(ii.parts))
    547 	for i, iip := range ii.parts {
    548 		partPhrases[i] = iip.String()
    549 	}
    550 
    551 	return strings.Join(partPhrases, "/")
    552 }
    553 
    554 func (ii *IfdIdentity) getIfdPath() string {
    555 	partPhrases := make([]string, len(ii.parts))
    556 	for i, iip := range ii.parts {
    557 		partPhrases[i] = iip.UnindexedString()
    558 	}
    559 
    560 	return strings.Join(partPhrases, "/")
    561 }
    562 
    563 // String returns a fully-qualified IFD path.
    564 func (ii *IfdIdentity) String() string {
    565 	return ii.fqIfdPath
    566 }
    567 
    568 // UnindexedString returns a non-fully-qualified IFD path.
    569 func (ii *IfdIdentity) UnindexedString() string {
    570 	return ii.ifdPath
    571 }
    572 
    573 // IfdTag returns the tag struct behind this IFD.
    574 func (ii *IfdIdentity) IfdTag() IfdTag {
    575 	return ii.ifdTag
    576 }
    577 
    578 // TagId returns the tag-ID of the IFD.
    579 func (ii *IfdIdentity) TagId() uint16 {
    580 	return ii.ifdTag.TagId()
    581 }
    582 
    583 // LeafPathPart returns the last right-most path-part, which represents the
    584 // current IFD.
    585 func (ii *IfdIdentity) LeafPathPart() IfdIdentityPart {
    586 	return ii.parts[len(ii.parts)-1]
    587 }
    588 
    589 // Name returns the simple name of this IFD.
    590 func (ii *IfdIdentity) Name() string {
    591 	return ii.LeafPathPart().Name
    592 }
    593 
    594 // Index returns the index of this IFD (more then one IFD under a parent IFD
    595 // will be numbered [0..n]).
    596 func (ii *IfdIdentity) Index() int {
    597 	return ii.LeafPathPart().Index
    598 }
    599 
    600 // Equals returns true if the two IfdIdentity instances are effectively
    601 // identical.
    602 //
    603 // Since there's no way to get a specific fully-qualified IFD path without a
    604 // certain slice of parts and all other fields are also derived from this,
    605 // checking that the fully-qualified IFD path is equals is sufficient.
    606 func (ii *IfdIdentity) Equals(ii2 *IfdIdentity) bool {
    607 	return ii.String() == ii2.String()
    608 }
    609 
    610 // NewChild creates an IfdIdentity for an IFD that is a child of the current
    611 // IFD.
    612 func (ii *IfdIdentity) NewChild(childIfdTag IfdTag, index int) (iiChild *IfdIdentity) {
    613 	if *childIfdTag.parentIfdTag != ii.ifdTag {
    614 		log.Panicf("can not add child; we are not the parent:\nUS=%v\nCHILD=%v", ii.ifdTag, childIfdTag)
    615 	}
    616 
    617 	childPart := IfdIdentityPart{childIfdTag.name, index}
    618 	childParts := append(ii.parts, childPart)
    619 
    620 	iiChild = NewIfdIdentity(childIfdTag, childParts...)
    621 	return iiChild
    622 }
    623 
    624 // NewSibling creates an IfdIdentity for an IFD that is a sibling to the current
    625 // one.
    626 func (ii *IfdIdentity) NewSibling(index int) (iiSibling *IfdIdentity) {
    627 	parts := make([]IfdIdentityPart, len(ii.parts))
    628 
    629 	copy(parts, ii.parts)
    630 	parts[len(parts)-1].Index = index
    631 
    632 	iiSibling = NewIfdIdentity(ii.ifdTag, parts...)
    633 	return iiSibling
    634 }
    635 
    636 var (
    637 	// IfdStandardIfdIdentity represents the IFD path for IFD0.
    638 	IfdStandardIfdIdentity = NewIfdIdentity(rootStandardIfd, IfdIdentityPart{"IFD", 0})
    639 
    640 	// IfdExifStandardIfdIdentity represents the IFD path for IFD0/Exif0.
    641 	IfdExifStandardIfdIdentity = IfdStandardIfdIdentity.NewChild(exifStandardIfd, 0)
    642 
    643 	// IfdExifIopStandardIfdIdentity represents the IFD path for IFD0/Exif0/Iop0.
    644 	IfdExifIopStandardIfdIdentity = IfdExifStandardIfdIdentity.NewChild(iopStandardIfd, 0)
    645 
    646 	// IfdGPSInfoStandardIfdIdentity represents the IFD path for IFD0/GPSInfo0.
    647 	IfdGpsInfoStandardIfdIdentity = IfdStandardIfdIdentity.NewChild(gpsInfoStandardIfd, 0)
    648 
    649 	// Ifd1StandardIfdIdentity represents the IFD path for IFD1.
    650 	Ifd1StandardIfdIdentity = NewIfdIdentity(rootStandardIfd, IfdIdentityPart{"IFD", 1})
    651 )