/*
 * Copyright (C) 2017-2024 Adrien Gesta-Fline
 *
 * This file is part of libAAF.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

/**
 * @file LibAAF/AAFIface/AAFIParser.c
 * @brief AAF processing
 * @author Adrien Gesta-Fline
 * @version 0.1
 * @date 27 june 2018
 *
 * @ingroup AAFIface
 * @addtogroup AAFIface
 *
 *
 *
 *
 * @{
 */

#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <locale.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "aaf/AAFDefs/AAFClassDefUIDs.h"
#include "aaf/AAFDefs/AAFDataDefs.h"
#include "aaf/AAFDefs/AAFExtEnum.h"
#include "aaf/AAFDefs/AAFInterpolatorDefs.h"
#include "aaf/AAFDefs/AAFOPDefs.h"
#include "aaf/AAFDefs/AAFOperationDefs.h"
#include "aaf/AAFDefs/AAFParameterDefs.h"
#include "aaf/AAFDefs/AAFPropertyIDs.h"
#include "aaf/AAFDefs/AAFTypeDefUIDs.h"

#include "aaf/AAFDump.h"
#include "aaf/AAFIEssenceFile.h"
#include "aaf/AAFIParser.h"
#include "aaf/AAFIface.h"
#include "aaf/AAFToText.h"

#include "aaf/MediaComposer.h"
#include "aaf/ProTools.h"
#include "aaf/Resolve.h"

#include "aaf/log.h"
#include "aaf/utils.h"

#define debug(...) \
	AAF_LOG (aafi->log, aafi, LOG_SRC_ID_AAF_IFACE, VERB_DEBUG, __VA_ARGS__)

#define warning(...) \
	AAF_LOG (aafi->log, aafi, LOG_SRC_ID_AAF_IFACE, VERB_WARNING, __VA_ARGS__)

#define error(...) \
	AAF_LOG (aafi->log, aafi, LOG_SRC_ID_AAF_IFACE, VERB_ERROR, __VA_ARGS__)

static aafRational_t AAFI_DEFAULT_TC_EDIT_RATE = { 25, 1 };

#define RESET_CTX__AudioGain(ctx)                      \
	ctx.current_clip_is_muted              = 0;    \
	ctx.current_clip_gain                  = NULL; \
	ctx.current_clip_gain_is_used          = 0;    \
	ctx.current_clip_variable_gain         = NULL; \
	ctx.current_clip_variable_gain_is_used = 0;

#define RESET_CTX__AudioChannelCombiner(ctx)         \
	ctx.current_clip_is_combined            = 0; \
	ctx.current_combined_clip_total_channel = 0; \
	ctx.current_combined_clip_channel_num   = 0; \
	ctx.current_combined_clip_forced_length = 0;

#define RESET_CONTEXT(ctx)                \
	ctx.current_track         = NULL; \
	ctx.current_audio_essence = NULL; \
	ctx.current_clip          = NULL; \
	RESET_CTX__AudioGain (ctx);       \
	RESET_CTX__AudioChannelCombiner (ctx);

static int
parse_Mob (AAF_Iface* aafi, aafObject* Mob, td* __ptd);
static int
parse_CompositionMob (AAF_Iface* aafi, aafObject* CompoMob, td* __ptd);
static int
parse_SourceMob (AAF_Iface* aafi, aafObject* SourceMob, td* __ptd);

static int
parse_MobSlot (AAF_Iface* aafi, aafObject* MobSlot, td* __ptd);
static int
parse_TimelineMobSlot (AAF_Iface* aafi, aafObject* TimelineMobSlot, td* __ptd);
static int
parse_EventMobSlot (AAF_Iface* aafi, aafObject* EventMobSlot, td* __ptd);

static int
parse_Component (AAF_Iface* aafi, aafObject* Component, td* __ptd);

static int
parse_Transition (AAF_Iface* aafi, aafObject* Transition, td* __ptd);
static int
parse_Segment (AAF_Iface* aafi, aafObject* Segment, td* __ptd);

static int
parse_Filler (AAF_Iface* aafi, aafObject* Filler, td* __ptd);
static int
parse_SourceClip (AAF_Iface* aafi, aafObject* SourceClip, td* __ptd);
static int
parse_Timecode (AAF_Iface* aafi, aafObject* Timecode, td* __ptd);
static int
parse_DescriptiveMarker (AAF_Iface* aafi, aafObject* DescriptiveMarker, td* __ptd);
static int
parse_Sequence (AAF_Iface* aafi, aafObject* Sequence, td* __ptd);
static int
parse_Selector (AAF_Iface* aafi, aafObject* Selector, td* __ptd);
static int
parse_NestedScope (AAF_Iface* aafi, aafObject* NestedScope, td* __ptd);
static int
parse_OperationGroup (AAF_Iface* aafi, aafObject* OpGroup, td* __ptd);

static int
parse_Parameter (AAF_Iface* aafi, aafObject* Parameter, td* __ptd);
static int
parse_ConstantValue (AAF_Iface* aafi, aafObject* ConstantValue, td* __ptd);
static int
parse_VaryingValue (AAF_Iface* aafi, aafObject* VaryingValue, td* __ptd);

static int
parse_EssenceDescriptor (AAF_Iface* aafi, aafObject* EssenceDesc, td* __ptd);
static int
parse_PCMDescriptor (AAF_Iface* aafi, aafObject* PCMDescriptor, td* __ptd);
static int
parse_WAVEDescriptor (AAF_Iface* aafi, aafObject* WAVEDescriptor, td* __ptd);
static int
parse_AIFCDescriptor (AAF_Iface* aafi, aafObject* AIFCDescriptor, td* __ptd);

static int
parse_DigitalImageDescriptor (AAF_Iface* aafi, aafObject* DIDescriptor, td* __ptd);
static int
parse_CDCIDescriptor (AAF_Iface* aafi, aafObject* CDCIDescriptor, td* __ptd);

static int
parse_Locator (AAF_Iface* aafi, aafObject* Locator, td* __ptd);
static int
parse_NetworkLocator (AAF_Iface* aafi, aafObject* NetworkLocator, td* __ptd);

static int
parse_EssenceData (AAF_Iface* aafi, aafObject* EssenceData, td* __ptd);

static int
retrieve_UserComments (AAF_Iface* aafi, aafObject* UserComments, aafiMetaData** metadataList);
static int
retrieve_ControlPoints (AAF_Iface* aafi, aafObject* Points, aafRational_t* times[], aafRational_t* values[]);

/* ****************************************************************************
 *                                 M o b
 * ****************************************************************************

 *                            Mob (abs)
 *                             |
 *                             |--> CompositionMob
 *                             |--> MasterMob
 *                             `--> SourceMob
 */

static int
parse_Mob (AAF_Iface* aafi, aafObject* Mob, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 1);

	int rc = 0;

	aafObject* MobSlots = aaf_get_propertyValue (Mob, PID_Mob_Slots, &AAFTypeID_MobSlotStrongReferenceVector);

	if (!MobSlots) {
		TRACE_OBJ_ERROR (aafi, Mob, &__td, "Missing Mob::Slots");
		goto err;
	}

	if (aafUIDCmp (Mob->Class->ID, &AAFClassID_CompositionMob)) {
		parse_CompositionMob (aafi, Mob, &__td);
	} else if (aafUIDCmp (Mob->Class->ID, &AAFClassID_SourceMob)) {
		parse_SourceMob (aafi, Mob, &__td);
	} else {
		/* NOTE: MasterMob is accessed directly from parse_SourceClip() */
		TRACE_OBJ_NO_SUPPORT (aafi, Mob, &__td);
	}

	aafObject* MobSlot = NULL;

	uint32_t i = 0;
	AAFI_foreach_ObjectInSet (&MobSlot, MobSlots, i, __td)
	{
		parse_MobSlot (aafi, MobSlot, &__td);
	}

	goto end;

err:
	rc = -1;

end:
	return rc;
}

static int
parse_CompositionMob (AAF_Iface* aafi, aafObject* CompoMob, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 0);

	aafUID_t* UsageCode = aaf_get_propertyValue (CompoMob, PID_Mob_UsageCode, &AAFTypeID_UsageType);

	if ((aafUIDCmp (aafi->aafd->Header.OperationalPattern, &AAFOPDef_EditProtocol) && aafUIDCmp (UsageCode, &AAFUsage_TopLevel)) ||
	    (!aafUIDCmp (aafi->aafd->Header.OperationalPattern, &AAFOPDef_EditProtocol) && (aafUIDCmp (UsageCode, &AAFUsage_TopLevel) || !UsageCode))) {
		aafi->ctx.TopLevelCompositionMob = CompoMob;
		aafi->compositionName            = aaf_get_propertyValue (CompoMob, PID_Mob_Name, &AAFTypeID_String);

		aafObject* UserComments = aaf_get_propertyValue (CompoMob, PID_Mob_UserComments, &AAFTypeID_TaggedValueStrongReferenceVector);

		if (retrieve_UserComments (aafi, UserComments, &aafi->metadata) < 0) {
			TRACE_OBJ_WARNING (aafi, CompoMob, &__td, "Error parsing Mob::UserComments");
		} else {
			TRACE_OBJ (aafi, CompoMob, &__td);
		}
	} else {
		TRACE_OBJ_NO_SUPPORT (aafi, CompoMob, &__td);
	}

	return 0;
}

static int
parse_SourceMob (AAF_Iface* aafi, aafObject* SourceMob, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 0);

	aafMobID_t* MobID = aaf_get_propertyValue (SourceMob, PID_Mob_MobID, &AAFTypeID_MobIDType);

	if (!MobID) {
		TRACE_OBJ_ERROR (aafi, SourceMob, &__td, "Missing Mob::MobID");
		return -1;
	}

	aafTimeStamp_t* CreationTime = aaf_get_propertyValue (SourceMob, PID_Mob_CreationTime, &AAFTypeID_TimeStamp);

	if (!CreationTime) {
		TRACE_OBJ_ERROR (aafi, SourceMob, &__td, "Missing Mob::CreationTime");
		return -1;
	}

	aafObject* EssenceDesc = aaf_get_propertyValue (SourceMob, PID_SourceMob_EssenceDescription, &AAFTypeID_EssenceDescriptorStrongReference);

	if (!EssenceDesc) {
		TRACE_OBJ_ERROR (aafi, SourceMob, &__td, "Missing SourceMob::EssenceDescription");
		return -1;
	}

	TRACE_OBJ (aafi, SourceMob, &__td);

	/*
	 * SourceMob can be parsed for Audio and Video.
	 * aafi->ctx.current_audio_essence is set, it means we are parsing Audio, so
	 * we need to retrieve more data.
	 */

	if (aafi->ctx.current_audio_essence) {
		aafiAudioEssenceFile* audioEssenceFile = (aafiAudioEssenceFile*)aafi->ctx.current_audio_essence;

		memcpy (audioEssenceFile->umid, MobID, sizeof (aafMobID_t));

		int rc = snprintf (audioEssenceFile->originationDate, sizeof (((aafiAudioEssenceFile*)0)->originationDate), "%04u:%02u:%02u",
		                   (CreationTime->date.year <= 9999) ? CreationTime->date.year : 0,
		                   (CreationTime->date.month <= 99) ? CreationTime->date.month : 0,
		                   (CreationTime->date.day <= 99) ? CreationTime->date.day : 0);

		assert (rc > 0 && (size_t)rc < sizeof (((aafiAudioEssenceFile*)0)->originationDate));

		rc = snprintf (audioEssenceFile->originationTime, sizeof (((aafiAudioEssenceFile*)0)->originationTime), "%02u:%02u:%02u",
		               (CreationTime->time.hour <= 99) ? CreationTime->time.hour : 0,
		               (CreationTime->time.minute <= 99) ? CreationTime->time.minute : 0,
		               (CreationTime->time.second <= 99) ? CreationTime->time.second : 0);

		assert (rc > 0 && (size_t)rc < sizeof (((aafiAudioEssenceFile*)0)->originationTime));
	}

	__td.ll[__td.lv] = 2;
	parse_EssenceDescriptor (aafi, EssenceDesc, &__td);
	__td.ll[__td.lv] = 1;

	return 0;
}

/* ****************************************************************************
 *                             M o b S l o t
 * ****************************************************************************
 *
 *                          MobSlot (abs)
 *                             |
 *                             |--> TimelineMobSlot
 *                             |--> EventMobSlot
 *                             `--> StaticMobSlot
 */

static int
parse_MobSlot (AAF_Iface* aafi, aafObject* MobSlot, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 1);

	aafObject* Segment = aaf_get_propertyValue (MobSlot, PID_MobSlot_Segment, &AAFTypeID_SegmentStrongReference);

	if (!Segment) {
		TRACE_OBJ_ERROR (aafi, MobSlot, &__td, "Missing MobSlot::Segment");
		return -1;
	}

	if (aafUIDCmp (MobSlot->Class->ID, &AAFClassID_TimelineMobSlot)) {
		if (parse_TimelineMobSlot (aafi, MobSlot, &__td) < 0) {
			return -1;
		}
	} else if (aafUIDCmp (MobSlot->Class->ID, &AAFClassID_EventMobSlot)) {
		if (parse_EventMobSlot (aafi, MobSlot, &__td) < 0) {
			return -1;
		}
	} else {
		TRACE_OBJ_NO_SUPPORT (aafi, MobSlot, &__td);
		return -1;
	}

	parse_Segment (aafi, Segment, &__td);

	return 0;
}

static int
parse_TimelineMobSlot (AAF_Iface* aafi, aafObject* TimelineMobSlot, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 0);

	aafObject* ParentMob = aaf_get_ObjectAncestor (TimelineMobSlot, &AAFClassID_Mob);

	if (!ParentMob) {
		TRACE_OBJ_ERROR (aafi, TimelineMobSlot, &__td, "Could not retrieve parent Mob");
		return -1;
	}

	uint32_t* track_num = aaf_get_propertyValue (TimelineMobSlot, PID_MobSlot_PhysicalTrackNumber, &AAFTypeID_UInt32);

	if (!track_num) {
		debug ("Missing MobSlot::PhysicalTrackNumber");
	}

	aafObject* Segment = aaf_get_propertyValue (TimelineMobSlot, PID_MobSlot_Segment, &AAFTypeID_SegmentStrongReference);

	if (!Segment) {
		TRACE_OBJ_ERROR (aafi, TimelineMobSlot, &__td, "Missing MobSlot::Segment");
		return -1;
	}

	aafWeakRef_t* dataDefWeakRef = aaf_get_propertyValue (Segment, PID_Component_DataDefinition, &AAFTypeID_DataDefinitionWeakReference);

	if (!dataDefWeakRef) {
		TRACE_OBJ_ERROR (aafi, Segment, &__td, "Could not retrieve Component::DataDefinition from Segment child");
		return -1;
	}

	aafUID_t* DataDefinition = aaf_get_DataIdentificationByWeakRef (aafi->aafd, dataDefWeakRef);

	if (!DataDefinition) {
		TRACE_OBJ_ERROR (aafi, TimelineMobSlot, &__td, "Could not retrieve DataDefinition");
		return -1;
	}

	aafRational_t* edit_rate = aaf_get_propertyValue (TimelineMobSlot, PID_TimelineMobSlot_EditRate, &AAFTypeID_Rational);

	if (!edit_rate) {
		TRACE_OBJ_ERROR (aafi, TimelineMobSlot, &__td, "Missing TimelineMobSlot::EditRate");
		return -1;
	}

	if (aafUIDCmp (ParentMob->Class->ID, &AAFClassID_CompositionMob)) {
		/*
		 * Each TimelineMobSlot represents a track, either audio or video.
		 *
		 * The Timeline MobSlot::Segment should hold a Sequence of Components.
		 * This Sequence represents the timeline track. Therefore, each SourceClip
		 * contained in the Sequence::Components represents a clip on the timeline.
		 *
		 * CompositionMob can have TimelineMobSlots, StaticMobSlots, EventMobSlots
		 */

		/*
		 * TODO: implement multiple TopLevel compositions support
		 */

		if (aafUIDCmp (DataDefinition, &AAFDataDef_Sound) ||
		    aafUIDCmp (DataDefinition, &AAFDataDef_LegacySound)) {
			/*
			 * « In a CompositionMob or MasterMob, PhysicalTrackNumber is the output
			 * channel number that the MobSlot should be routed to when played. »
			 */

			if (aafi->ctx.TopLevelCompositionMob == ParentMob /*!parentMobUsageCode || aafUIDCmp( parentMobUsageCode, &AAFUsage_TopLevel )*/ /*!aafi->ctx.is_inside_derivation_chain*/) {
				TRACE_OBJ (aafi, TimelineMobSlot, &__td);

				uint32_t tracknumber = (track_num) ? *track_num : (aafi->Audio->track_count + 1);

				aafiAudioTrack* track = aafi_newAudioTrack (aafi);

				track->number    = tracknumber;
				track->name      = aaf_get_propertyValue (TimelineMobSlot, PID_MobSlot_SlotName, &AAFTypeID_String);
				track->edit_rate = edit_rate;

				aafi->Audio->track_count += 1;

				aafi->ctx.current_track = track;

				/*
				 * Avid Media Composer
				 */

				void* TimelineMobAttributeList = aaf_get_propertyValue (TimelineMobSlot, aaf_get_PropertyIDByName (aafi->aafd, "TimelineMobAttributeList"), &AAFTypeID_TaggedValueStrongReferenceVector);

				if (TimelineMobAttributeList) {
					int32_t* solo = aaf_get_TaggedValueByName (aafi->aafd, TimelineMobAttributeList, "AudioMixerCompSolo", &AAFTypeID_Int32);
					int32_t* mute = aaf_get_TaggedValueByName (aafi->aafd, TimelineMobAttributeList, "AudioMixerCompMute", &AAFTypeID_Int32);

					if (solo && *solo) {
						track->solo = 1;
					}

					if (mute && *mute) {
						track->mute = 1;
					}
				}
			} else {
				TRACE_OBJ (aafi, TimelineMobSlot, &__td);
			}
		} else if (aafUIDCmp (DataDefinition, &AAFDataDef_Picture) ||
		           aafUIDCmp (DataDefinition, &AAFDataDef_LegacyPicture)) {
			if (aafi->Video->Tracks) {
				TRACE_OBJ_ERROR (aafi, TimelineMobSlot, &__td, "Current implementation supports only one video track");
				return -1;
			}

			TRACE_OBJ (aafi, TimelineMobSlot, &__td);

			uint32_t tracknumber = (track_num) ? *track_num : 1 /* current implementation supports only one video track */;

			aafiVideoTrack* track = aafi_newVideoTrack (aafi);

			track->number    = tracknumber;
			track->name      = aaf_get_propertyValue (TimelineMobSlot, PID_MobSlot_SlotName, &AAFTypeID_String);
			track->edit_rate = edit_rate;
		} else if (aafUIDCmp (DataDefinition, &AAFDataDef_Timecode) ||
		           aafUIDCmp (DataDefinition, &AAFDataDef_LegacyTimecode)) {
			TRACE_OBJ (aafi, TimelineMobSlot, &__td);
		} else if (aafUIDCmp (DataDefinition, &AAFDataDef_DescriptiveMetadata)) {
			/*
			 * Avid Media Composer 23.12, markers with duration (no-duration markers are held by AAFClassID_EventMobSlot)
			 *
		│ 04179│     └──◻ AAFClassID_TimelineMobSlot [slot:3003 track:12] (DataDef: AAFDataDef_DescriptiveMetadata) : Segmentation
		│ 01946│          └──◻ AAFClassID_Sequence (Length: 4)
		│ 03031│               └──◻ AAFClassID_DescriptiveMarker (Length: 4)   (MetaProps: CommentMarkerAttributeList[0xffd4] CommentMarkerColor[0xffdb] CommentMarkerUSer[0xffda] CommentMarkerDate[0xffd9] CommentMarkerTime[0xffd8])
			 */

			TRACE_OBJ (aafi, TimelineMobSlot, &__td);
		} else {
			TRACE_OBJ_NO_SUPPORT (aafi, TimelineMobSlot, &__td);
			return -1;
		}
	} else if (aafUIDCmp (ParentMob->Class->ID, &AAFClassID_MasterMob)) {
		TRACE_OBJ (aafi, TimelineMobSlot, &__td);
	} else if (aafUIDCmp (ParentMob->Class->ID, &AAFClassID_SourceMob)) {
		/*
		 * SourceMob can be parsed for Audio and Video.
		 * aafi->ctx.current_audio_essence is set, it means we are parsing Audio, so
		 * we need to retrieve more data.
		 */

		if (aafi->ctx.current_audio_essence) {
			aafPosition_t* Origin = aaf_get_propertyValue (TimelineMobSlot, PID_TimelineMobSlot_Origin, &AAFTypeID_PositionType);

			if (!Origin) {
				TRACE_OBJ_ERROR (aafi, TimelineMobSlot, &__td, "Missing TimelineMobSlot::Origin");
				return -1;
			}

			TRACE_OBJ (aafi, TimelineMobSlot, &__td);

			aafiAudioEssenceFile* audioEssenceFile = (aafiAudioEssenceFile*)aafi->ctx.current_audio_essence;

			audioEssenceFile->sourceMobSlotOrigin   = *Origin;
			audioEssenceFile->sourceMobSlotEditRate = edit_rate;
		} else {
			TRACE_OBJ (aafi, TimelineMobSlot, &__td);
		}
	} else {
		/*
		 * AAFClassID_MasterMob and AAFClassID_SourceMob are accessed directly from TimelineMobSlot > SourceClip
		 */
		TRACE_OBJ_NO_SUPPORT (aafi, TimelineMobSlot, &__td);
		return -1;
	}

	return 0;
}

static int
parse_EventMobSlot (AAF_Iface* aafi, aafObject* EventMobSlot, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 0);

	aafRational_t* edit_rate = aaf_get_propertyValue (EventMobSlot, PID_EventMobSlot_EditRate, &AAFTypeID_Rational);

	if (!edit_rate) {
		TRACE_OBJ_ERROR (aafi, EventMobSlot, &__td, "Missing EventMobSlot::EditRate");
		return -1;
	}

	TRACE_OBJ (aafi, EventMobSlot, &__td);

	aafi->ctx.current_markers_edit_rate = edit_rate;

	return 0;
}

/* ****************************************************************************
 *                           C o m p o n e n t
 * ****************************************************************************
 *
 *                     Component (abs)
 *                          |
 *                    ,-----------.
 *                    |           |
 *               Transition    Segment (abs)
 *                                |
 *                                |--> Sequence
 *                                |--> Filler
 *                                |--> TimeCode
 *                                |--> OperationGroup
 *                                `--> SourceReference (abs)
 *                                            |
 *                                            `--> SourceClip
 */

static int
parse_Component (AAF_Iface* aafi, aafObject* Component, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 0);

	if (aafUIDCmp (Component->Class->ID, &AAFClassID_Transition)) {
		parse_Transition (aafi, Component, &__td);
	} else {
		parse_Segment (aafi, Component, &__td);
	}

	return 0;
}

static int
parse_Transition (AAF_Iface* aafi, aafObject* Transition, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 1);

	/*
	 * A Transition between a Filler and a SourceClip sets a Fade In.
	 * A Transition between a SourceClip and a Filler sets a Fade Out.
	 * A Transition between two SourceClips sets a Cross-Fade.
	 *
	 * Since the Transition applies to the elements that suround it in
	 * the Sequence, the OperationGroup::InputSegments is left unused.
	 */

	aafWeakRef_t* dataDefWeakRef = aaf_get_propertyValue (Transition, PID_Component_DataDefinition, &AAFTypeID_DataDefinitionWeakReference);

	if (!dataDefWeakRef) {
		TRACE_OBJ_ERROR (aafi, Transition, &__td, "Missing Component::DataDefinition.");
		return -1;
	}

	aafUID_t* DataDefinition = aaf_get_DataIdentificationByWeakRef (aafi->aafd, dataDefWeakRef);

	if (!DataDefinition) {
		TRACE_OBJ_ERROR (aafi, Transition, &__td, "Could not retrieve DataDefinition");
		return -1;
	}

	if (!aafUIDCmp (DataDefinition, &AAFDataDef_Sound) &&
	    !aafUIDCmp (DataDefinition, &AAFDataDef_LegacySound)) {
		TRACE_OBJ_ERROR (aafi, Transition, &__td, "Current implementation does not support video Transitions");
		return -1;
	}

	int64_t* length = aaf_get_propertyValue (Transition, PID_Component_Length, &AAFTypeID_LengthType);

	if (!length) {
		TRACE_OBJ_ERROR (aafi, Transition, &__td, "Missing Component::Length");
		return -1;
	}

	aafObject* OpGroup = aaf_get_propertyValue (Transition, PID_Transition_OperationGroup, &AAFTypeID_OperationGroupStrongReference);

	if (!OpGroup) {
		TRACE_OBJ_ERROR (aafi, Transition, &__td, "Missing Transition::OperationGroup");
		return -1;
	}

	aafPosition_t* cutPoint = aaf_get_propertyValue (Transition, PID_Transition_CutPoint, &AAFTypeID_PositionType);

	if (!cutPoint) {
		/* not encountered though */
		debug ("Missing Transition::CutPoint : setting cut point to Transition::Length/2");
	}

	uint32_t fadeType = 0;

	if (Transition->prev != NULL && aafUIDCmp (Transition->prev->Class->ID, &AAFClassID_Filler)) {
		fadeType |= AAFI_TRANS_FADE_IN;
	} else if (Transition->next != NULL && aafUIDCmp (Transition->next->Class->ID, &AAFClassID_Filler)) {
		fadeType |= AAFI_TRANS_FADE_OUT;
	} else if (Transition->next != NULL && aafUIDCmp (Transition->next->Class->ID, &AAFClassID_Filler) == 0 &&
	           Transition->prev != NULL && aafUIDCmp (Transition->prev->Class->ID, &AAFClassID_Filler) == 0) {
		fadeType |= AAFI_TRANS_XFADE;
	} else {
		TRACE_OBJ_ERROR (aafi, Transition, &__td, "Could not guess if type is FadeIn, FadeOut or xFade");
		return -1;
	}

	TRACE_OBJ (aafi, Transition, &__td);

	aafiTransition* Trans = aafi_newTransition (aafi, aafi->ctx.current_track);

	Trans->len    = *length;
	Trans->flags  = fadeType;
	Trans->cut_pt = (cutPoint) ? *cutPoint : (Trans->len / 2);

	/*
	 * OperationGroup *might* contain a Parameter (ParameterDef_Level) specifying
	 * the fade curve. However, this parameter is optional regarding AAF_EditProtocol
	 * and there is most likely no implementation that exports custom fade curves.
	 * Thus, we only retrieve ParameterDef_Level to possibly set interpolation, and we
	 * always set the fade as defined in AAF_EditProtocol, with only two points :
	 *
	 * « ParameterDef_Level (optional; default is a VaryingValue object
	 * with two control points: Value 0 at time 0, and value 1 at time 1) »
	 */

	if (fadeType & AAFI_TRANS_FADE_IN ||
	    fadeType & AAFI_TRANS_XFADE) {
		Trans->value_a[0].numerator   = 0;
		Trans->value_a[0].denominator = 0;
		Trans->value_a[1].numerator   = 1;
		Trans->value_a[1].denominator = 1;
	} else if (fadeType & AAFI_TRANS_FADE_OUT) {
		Trans->value_a[0].numerator   = 1;
		Trans->value_a[0].denominator = 1;
		Trans->value_a[1].numerator   = 0;
		Trans->value_a[1].denominator = 0;
	}

	aafi->ctx.current_transition = Trans;

	parse_OperationGroup (aafi, OpGroup, &__td);

	aafi->ctx.current_transition = NULL;
	aafi->ctx.current_track->current_pos -= *length;

	return 0;
}

int
parse_Segment (AAF_Iface* aafi, aafObject* Segment, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 0);

	if (aafUIDCmp (Segment->Class->ID, &AAFClassID_Sequence)) {
		return parse_Sequence (aafi, Segment, &__td);
	} else if (aafUIDCmp (Segment->Class->ID, &AAFClassID_SourceClip)) {
		return parse_SourceClip (aafi, Segment, &__td);
	} else if (aafUIDCmp (Segment->Class->ID, &AAFClassID_OperationGroup)) {
		return parse_OperationGroup (aafi, Segment, &__td);
	} else if (aafUIDCmp (Segment->Class->ID, &AAFClassID_Filler)) {
		return parse_Filler (aafi, Segment, &__td);
	} else if (aafUIDCmp (Segment->Class->ID, &AAFClassID_Selector)) {
		return parse_Selector (aafi, Segment, &__td);
	} else if (aafUIDCmp (Segment->Class->ID, &AAFClassID_NestedScope)) {
		return parse_NestedScope (aafi, Segment, &__td);
	} else if (aafUIDCmp (Segment->Class->ID, &AAFClassID_Timecode)) {
		return parse_Timecode (aafi, Segment, &__td);
	} else if (aafUIDCmp (Segment->Class->ID, &AAFClassID_DescriptiveMarker)) {
		return parse_DescriptiveMarker (aafi, Segment, &__td);
	} else if (aafUIDCmp (Segment->Class->ID, &AAFClassID_EssenceGroup)) {
		/*
		 * Should provide support for multiple essences representing the same
		 * source material with different resolution, compression, codec, etc.
		 *
		 * TODO To be tested with Avid and rendered effects ?
		 */

		__td.lv++;
		TRACE_OBJ_NO_SUPPORT (aafi, Segment, &__td);
		__td.lv--;

		return -1;
	} else {
		__td.lv++;
		TRACE_OBJ_NO_SUPPORT (aafi, Segment, &__td);
		__td.lv--;

		return -1;
	}

	return 0;
}

static int
parse_Filler (AAF_Iface* aafi, aafObject* Filler, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 1);
	__td.eob = 1;

	aafWeakRef_t* dataDefWeakRef = aaf_get_propertyValue (Filler, PID_Component_DataDefinition, &AAFTypeID_DataDefinitionWeakReference);

	if (!dataDefWeakRef) {
		TRACE_OBJ_ERROR (aafi, Filler, &__td, "Missing Component::DataDefinition.");
		return -1;
	}

	aafUID_t* DataDefinition = aaf_get_DataIdentificationByWeakRef (aafi->aafd, dataDefWeakRef);

	if (!DataDefinition) {
		TRACE_OBJ_ERROR (aafi, Filler, &__td, "Could not retrieve DataDefinition");
		return -1;
	}

	/*
	 * This represents an empty space on the timeline, between two clips,
	 * which is Component::Length long.
	 * TODO: is realy parent mandatory a Sequence or Selector ?
	 */

	int64_t* length = aaf_get_propertyValue (Filler, PID_Component_Length, &AAFTypeID_LengthType);

	if (!length) {
		TRACE_OBJ_ERROR (aafi, Filler, &__td, "Missing Component::Length");
		return -1;
	}

	if (aafUIDCmp (DataDefinition, &AAFDataDef_Sound) ||
	    aafUIDCmp (DataDefinition, &AAFDataDef_LegacySound)) {
		aafi->ctx.current_track->current_pos += *length;
	} else if (aafUIDCmp (DataDefinition, &AAFDataDef_Picture) ||
	           aafUIDCmp (DataDefinition, &AAFDataDef_LegacyPicture)) {
		aafi->Video->Tracks->current_pos += *length;
	}

	TRACE_OBJ (aafi, Filler, &__td);

	return 0;
}

static int
parse_SourceClip (AAF_Iface* aafi, aafObject* SourceClip, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 1);

	aafWeakRef_t* dataDefWeakRef = aaf_get_propertyValue (SourceClip, PID_Component_DataDefinition, &AAFTypeID_DataDefinitionWeakReference);

	if (!dataDefWeakRef) {
		TRACE_OBJ_ERROR (aafi, SourceClip, &__td, "Missing Component::DataDefinition.");
		return -1;
	}

	aafUID_t* DataDefinition = aaf_get_DataIdentificationByWeakRef (aafi->aafd, dataDefWeakRef);

	if (!DataDefinition) {
		TRACE_OBJ_ERROR (aafi, SourceClip, &__td, "Could not retrieve DataDefinition");
		return -1;
	}

	aafObject* ParentMob = aaf_get_ObjectAncestor (SourceClip, &AAFClassID_Mob);

	if (!ParentMob) {
		TRACE_OBJ_ERROR (aafi, SourceClip, &__td, "Could not retrieve parent Mob");
		return -1;
	}

	aafMobID_t* parentMobID = aaf_get_propertyValue (ParentMob, PID_Mob_MobID, &AAFTypeID_MobIDType);

	if (!parentMobID) {
		TRACE_OBJ_ERROR (aafi, SourceClip, &__td, "Missing parent Mob::MobID");
		return -1;
	}

	aafUID_t* parentMobUsageCode = aaf_get_propertyValue (ParentMob, PID_Mob_UsageCode, &AAFTypeID_UsageType);

	if (!parentMobUsageCode) {
		debug ("Missing parent Mob Mob::UsageCode");
	}

	aafMobID_t* sourceID = aaf_get_propertyValue (SourceClip, PID_SourceReference_SourceID, &AAFTypeID_MobIDType);

	uint32_t* SourceMobSlotID = aaf_get_propertyValue (SourceClip, PID_SourceReference_SourceMobSlotID, &AAFTypeID_UInt32);

	if (!SourceMobSlotID) {
		TRACE_OBJ_ERROR (aafi, SourceClip, &__td, "Missing SourceReference::SourceMobSlotID");
		return -1;
	}

	/*
	 * TODO: handle SourceReference::ChannelIDs and SourceReference::MonoSourceSlotIDs
	 * (Multi-channels)
	 */

	aafObject* targetMob     = NULL;
	aafObject* targetMobSlot = NULL;

	if (sourceID == NULL || aafMobIDCmp (sourceID, &AAFMOBID_NULL)) {
		/*
		 * p.49 : To create a SourceReference that refers to a MobSlot within
		 * the same Mob as the SourceReference, omit the SourceID property.
		 *
		 * [SourceID] Identifies the Mob being referenced. If the property has a
		 * value 0, it means that the Mob owning the SourceReference describes
		 * the original source.
		 *
		 * TODO: in that case, is MobSlots NULL ?
		 */

		// sourceID = parentMobID;
		// targetMob = ParentMob;

		debug ("SourceReference::SourceID is missing or NULL");
	} else {
		targetMob = aaf_get_MobByID (aafi->aafd->Mobs, sourceID);

		if (!targetMob) {
			TRACE_OBJ_ERROR (aafi, SourceClip, &__td, "Could not retrieve target Mob by ID : %s", aaft_MobIDToText (sourceID));
			return -1;
		}

		aafObject* targetMobSlots = aaf_get_propertyValue (targetMob, PID_Mob_Slots, &AAFTypeID_MobSlotStrongReferenceVector);

		if (!targetMobSlots) {
			TRACE_OBJ_ERROR (aafi, SourceClip, &__td, "Missing target Mob::Slots");
			return -1;
		}

		targetMobSlot = aaf_get_MobSlotBySlotID (targetMobSlots, *SourceMobSlotID);

		if (!targetMobSlot) {
			/* TODO check if there is a workaround :
			 * valgrind --track-origins=yes --leak-check=full ./bin/AAFInfo --trace --aaf-essences  --aaf-clips /home/agfline/Programming/libaaf/LibAAF/test/private/aaf/ADP/ADP_STTRACK_CLIPGAIN_TRACKGAIN_XFADE_NOOPTONEXPORT.aaf --verb 3
			 */
			TRACE_OBJ_ERROR (aafi, SourceClip, &__td, "Could not retrieve target MobSlot ID : %u", *SourceMobSlotID);
			return -1;
		}
	}

	/* *** Clip *** */

	if (aafUIDCmp (ParentMob->Class->ID, &AAFClassID_CompositionMob)) {
		int64_t* length = aaf_get_propertyValue (SourceClip, PID_Component_Length, &AAFTypeID_LengthType);

		if (!length) {
			TRACE_OBJ_ERROR (aafi, SourceClip, &__td, "Missing Component::Length");
			return -1;
		}

		int64_t* startTime = aaf_get_propertyValue (SourceClip, PID_SourceClip_StartTime, &AAFTypeID_PositionType);

		if (!startTime) {
			TRACE_OBJ_ERROR (aafi, SourceClip, &__td, "Missing SourceClip::StartTime");
			return -1;
		}

		if (aafUIDCmp (DataDefinition, &AAFDataDef_Sound) ||
		    aafUIDCmp (DataDefinition, &AAFDataDef_LegacySound)) {
			if (aafi->ctx.TopLevelCompositionMob == ParentMob /*!parentMobUsageCode || aafUIDCmp( parentMobUsageCode, &AAFUsage_TopLevel )*/) {
				if (aafi->ctx.current_clip_is_combined &&
				    aafi->ctx.current_combined_clip_channel_num > 0) {
					/*
					 * Parsing multichannel audio clip (AAFOperationDef_AudioChannelCombiner)
					 * We already parsed the first SourceClip in AAFOperationDef_AudioChannelCombiner.
					 * We just have to check everything match for all clips left (each clip represents a channel)
					 *
				│ 02277│               ├──◻ AAFClassID_OperationGroup (OpIdent: AAFOperationDef_AudioChannelCombiner; Length: 517207)
				│ 02816│               │    ├──◻ AAFClassID_SourceClip (Length: 516000)
				│ 02821│               │    │    └──◻ AAFClassID_MasterMob (UsageCode: n/a) : 7
				│ 04622│               │    │         └──◻ AAFClassID_TimelineMobSlot [slot:1 track:1] (DataDef : AAFDataDef_Sound)
				│ 03185│               │    │              └──◻ AAFClassID_SourceClip (Length: 523723)
				│ 04346│               │    │                   └──◻ AAFClassID_SourceMob (UsageCode: n/a) : 7
				│ 01393│               │    │                        └──◻ AAFClassID_WAVEDescriptor
				│ 01532│               │    │                             └──◻ AAFClassID_NetworkLocator : file://localhost/C:/Users/user/Desktop/res/7.1-SMPTE-channel-identif.wav
				│      │               │    │
				│ 02666│               │    ├──◻ AAFClassID_SourceClip (Length: 516000)
				│ 02666│               │    ├──◻ AAFClassID_SourceClip (Length: 516000)
				│ 02666│               │    ├──◻ AAFClassID_SourceClip (Length: 516000)
				│ 02666│               │    ├──◻ AAFClassID_SourceClip (Length: 516000)
				│ 02666│               │    ├──◻ AAFClassID_SourceClip (Length: 516000)
				│ 02666│               │    ├──◻ AAFClassID_SourceClip (Length: 516000)
				│ 02666│               │    └──◻ AAFClassID_SourceClip (Length: 516000)
					 */

					if (aafi->ctx.current_combined_clip_forced_length == 0 &&
					    aafi->ctx.current_clip->len != *length) {
						TRACE_OBJ_ERROR (aafi, SourceClip, &__td, "SourceClip length does not match first one in AAFOperationDef_AudioChannelCombiner");
						return -1;
					}

					if (targetMob && !aafUIDCmp (targetMob->Class->ID, &AAFClassID_MasterMob)) {
						TRACE_OBJ_ERROR (aafi, SourceClip, &__td, "Multichannel-combined SourceClip does not target a MasterMob: %s", aaft_ClassIDToText (aafi->aafd, targetMob->Class->ID));
						return -1;
					}

					if (aafMobIDCmp (aafi->ctx.current_clip->essencePointerList->essenceFile->masterMobID, sourceID) &&
					    aafi->ctx.current_clip->essencePointerList->essenceFile->masterMobSlotID == *SourceMobSlotID) {
						/*
						 * Clip channel rely on the same audio file source (single multichannel file)
						 *
						 * Assume that all clip channels will point to the same multichannel essence file, in the right order.
						 * (Davinci Resolve multichannel clips)
						 */

						aafi->ctx.current_clip->essencePointerList->essenceChannel = 0;

						TRACE_OBJ_INFO (aafi, SourceClip, &__td, "Ignore parsing of clip channel %i pointing to the same audio source file", aafi->ctx.current_combined_clip_channel_num + 1);
						return 0;
					}
				}

				if (!aafi->ctx.current_clip_is_combined ||
				    (aafi->ctx.current_clip_is_combined && aafi->ctx.current_combined_clip_channel_num == 0)) {
					/*
					 * Create new clip, only if we are parsing a single mono clip, or if
					 * we are parsing the first SourceClip describing the first channel of
					 * a multichannel clip inside an AAFOperationDef_AudioChannelCombiner
					 */

					aafiAudioClip* audioClip = aafi_newAudioClip (aafi, aafi->ctx.current_track);

					aafiTimelineItem* timelineItem = audioClip->timelineItem;

					timelineItem->pos = aafi->ctx.current_track->current_pos;
					timelineItem->len = (aafi->ctx.current_combined_clip_forced_length) ? aafi->ctx.current_combined_clip_forced_length : *length;

					audioClip->gain       = aafi->ctx.current_clip_gain;
					audioClip->automation = aafi->ctx.current_clip_variable_gain;
					audioClip->mute       = aafi->ctx.current_clip_is_muted;
					audioClip->pos        = aafi->ctx.current_track->current_pos;

					if (aafi->ctx.avid_warp_clip_edit_rate) {
						audioClip->essence_offset = aafi_convertUnit (*startTime, aafi->ctx.avid_warp_clip_edit_rate, aafi->ctx.current_track->edit_rate);
						audioClip->len            = aafi_convertUnit ((aafi->ctx.current_combined_clip_forced_length) ? aafi->ctx.current_combined_clip_forced_length : *length, aafi->ctx.avid_warp_clip_edit_rate, aafi->ctx.current_track->edit_rate);
					} else {
						audioClip->essence_offset = *startTime;
						audioClip->len            = (aafi->ctx.current_combined_clip_forced_length) ? aafi->ctx.current_combined_clip_forced_length : *length;
					}

					aafi->ctx.current_track->current_pos += audioClip->len;
					aafi->ctx.current_track->clipCount++;

					aafi->ctx.current_clip_gain_is_used++;
					aafi->ctx.current_clip_variable_gain_is_used++;

					/*
					 * ComponentAttributeList is non-standard, but used by Avid Media Composer
					 * and Davinci Resolve to attach Clip Notes.
					 */

					void* ComponentAttributeList = aaf_get_propertyValue (SourceClip, aaf_get_PropertyIDByName (aafi->aafd, "ComponentAttributeList"), &AAFTypeID_TaggedValueStrongReferenceVector);

					if (ComponentAttributeList) {
						char* comment = aaf_get_TaggedValueByName (aafi->aafd, ComponentAttributeList, "_COMMENT", &AAFTypeID_String);

						if (comment) {
							aafiMetaData* meta = aafi_newMetadata (aafi, &audioClip->metadata);

							if (!meta) {
								warning ("Could not create new Metadata.");
							} else {
								meta->text = comment;
								meta->name = laaf_util_c99strdup ("_COMMENT");

								if (!meta->name) {
									error ("Could not duplicate meta name : %s", "_COMMENT");
									aafiMetaData* tmp = meta->next;
									aafi_freeMetadata (&meta);
									audioClip->metadata = tmp;
								}
							}
						}
					}

					aafi->ctx.current_clip = audioClip;
				}

				if (aafi->ctx.current_clip_is_combined == 0) {
					if (aafi->ctx.current_track->format != AAFI_TRACK_FORMAT_NOT_SET &&
					    aafi->ctx.current_track->format != AAFI_TRACK_FORMAT_MONO) {
						TRACE_OBJ_ERROR (aafi, SourceClip, &__td, "Track format (%u) does not match current clip (%u)", aafi->ctx.current_track->format, AAFI_TRACK_FORMAT_MONO);
					} else {
						aafi->ctx.current_track->format = AAFI_TRACK_FORMAT_MONO;
					}
				}
			} else if (aafUIDCmp (parentMobUsageCode, &AAFUsage_SubClip)) {
				/* Exemple of AAFUsage_SubClip in ../test/private/aaf/AAF/E2R_52min/224E2R190008_ENQUETES_DE_REGION_N32_LA_SECHERESSE_UNE_FATALITE.aaf
				 * TODO: Looks like a sub-clip is just used to reference an existing clip, but to show it in UI with a different name.
				 * It seems that sub-clip length always matches MasterMob > SourceLip length
				 * Therefore, we should only parse its name.
				 *
			│ 02709││         ├──◻ AAFClassID_SourceClip (Length: 183)
			│ 02723││         │    └──◻ AAFClassID_CompositionMob (UsageCode: AAFUsage_SubClip) : plato 8 1ere.Copy.01.new.06  (MetaProps: SubclipBegin[0xfff6] SubclipFullLength[0xfff7] MobAttributeList[0xfff9])
			│ 04478││         │         └──◻ AAFClassID_TimelineMobSlot [slot:2 track:2] (DataDef : AAFDataDef_Sound)
			│ 02978││         │              └──◻ AAFClassID_SourceClip (Length: 681)
			│ 02983││         │                   └──◻ AAFClassID_MasterMob (UsageCode: n/a) : FTVHD-X400-0946.new.06  (MetaProps: MobAttributeList[0xfff9] ConvertFrameRate[0xfff8])
			│ 04561││         │                        └──◻ AAFClassID_TimelineMobSlot [slot:2 track:2] (DataDef : AAFDataDef_Sound) : FTVHD-X400-0946
			│ 03188││         │                             └──◻ AAFClassID_SourceClip (Length: 681)
			│ 04305││         │                                  └──◻ AAFClassID_SourceMob (UsageCode: n/a) : FTVHD-X400-0946.PHYS  (MetaProps: MobAttributeList[0xfff9])
			│ 01342││         │                                       └──◻ AAFClassID_PCMDescriptor   (MetaProps: DataOffset[0xffde])
				 */

				aafi->ctx.current_clip->subClipName = aaf_get_propertyValue (ParentMob, PID_Mob_Name, &AAFTypeID_String);

				if (!aafi->ctx.current_clip->subClipName) {
					debug ("Missing parent Mob::Name (sub-clip name)");
				}

				aafObject* UserComments = aaf_get_propertyValue (ParentMob, PID_Mob_UserComments, &AAFTypeID_TaggedValueStrongReferenceVector);

				if (retrieve_UserComments (aafi, UserComments, &aafi->ctx.current_clip->metadata) < 0) {
					warning ("Error parsing parent Mob::UserComments");
				}
			} else if (aafUIDCmp (parentMobUsageCode, &AAFUsage_AdjustedClip)) {
				// if ( aafi->ctx.current_adjusted_clip_gain ) {
				// 	aafi_applyGainOffset( aafi, &aafi->ctx.current_clip->gain, aafi->ctx.current_adjusted_clip_gain );
				// 	aafi_freeAudioGain( aafi->ctx.current_adjusted_clip_gain );
				// 	aafi->ctx.current_adjusted_clip_gain = NULL;
				// }
			} else if (!parentMobUsageCode) {
				debug ("CompositionMob UsageCode is NULL. Keep on parsing...");
			} else {
				debug ("Unsupported CompositionMob UsageCode: %s", aaft_UsageCodeToText (parentMobUsageCode));
				TRACE_OBJ_NO_SUPPORT (aafi, SourceClip, &__td);
				return -1;
			}

			if (targetMob && aafUIDCmp (targetMob->Class->ID, &AAFClassID_MasterMob)) {
				if (!targetMobSlot) {
					TRACE_OBJ_ERROR (aafi, SourceClip, &__td, "Missing target MobSlot");
					return -1;
				}

				TRACE_OBJ (aafi, SourceClip, &__td);

				__td.lv++;
				TRACE_OBJ (aafi, targetMob, &__td);

				parse_MobSlot (aafi, targetMobSlot, &__td);
			} else if (targetMob && aafUIDCmp (targetMob->Class->ID, &AAFClassID_CompositionMob)) {
				/*
				 * If SourceClip points to a CompositionMob instead of a MasterMob, we
				 * are at the begining (or inside) a derivation chain.
				 */

				TRACE_OBJ (aafi, SourceClip, &__td);

				__td.lv++;
				TRACE_OBJ (aafi, targetMob, &__td);

				parse_MobSlot (aafi, targetMobSlot, &__td);
			} else {
				TRACE_OBJ_ERROR (aafi, SourceClip, &__td, "Targeted Mob no supported: %s", aaft_ClassIDToText (aafi->aafd, targetMob->Class->ID));
				return -1;
			}

		} else if (aafUIDCmp (DataDefinition, &AAFDataDef_Picture) ||
		           aafUIDCmp (DataDefinition, &AAFDataDef_LegacyPicture)) {
			if (aafi->ctx.TopLevelCompositionMob != ParentMob) {
				TRACE_OBJ_ERROR (aafi, SourceClip, &__td, "Current implementation does not support parsing video SourceClip out of TopLevel CompositionMob");
				return -1;
			}

			if (aafi->Video->Tracks->timelineItems) {
				TRACE_OBJ_ERROR (aafi, SourceClip, &__td, "Current implementation supports only one video clip");
				return -1;
			}

			/* Add the new clip */

			aafiVideoClip* videoClip = aafi_newVideoClip (aafi, aafi->Video->Tracks);

			aafiTimelineItem* timelineItem = videoClip->timelineItem;

			timelineItem->pos = aafi->Video->Tracks->current_pos;
			timelineItem->len = *length;

			videoClip->pos            = aafi->Video->Tracks->current_pos;
			videoClip->len            = *length;
			videoClip->essence_offset = *startTime;

			aafi->Video->Tracks->current_pos += videoClip->len;

			aafi->ctx.current_video_clip = videoClip;

			if (targetMob && aafUIDCmp (targetMob->Class->ID, &AAFClassID_MasterMob)) {
				if (!targetMobSlot) {
					TRACE_OBJ_ERROR (aafi, SourceClip, &__td, "Missing target MobSlot");
					return -1;
				}

				TRACE_OBJ (aafi, SourceClip, &__td);

				__td.lv++;
				TRACE_OBJ (aafi, targetMob, &__td);

				parse_MobSlot (aafi, targetMobSlot, &__td);
			} else {
				TRACE_OBJ_ERROR (aafi, SourceClip, &__td, "Current implementation does not support video SourceClip not targetting a MasterMob: %s", (targetMob) ? aaft_ClassIDToText (aafi->aafd, targetMob->Class->ID) : "[MISSING TARGET MOB]");
				return -1;
			}
		}
	}

	/* *** Essence *** */

	else if (aafUIDCmp (ParentMob->Class->ID, &AAFClassID_MasterMob)) {
		aafMobID_t* masterMobID = aaf_get_propertyValue (ParentMob, PID_Mob_MobID, &AAFTypeID_MobIDType);

		if (!masterMobID) {
			TRACE_OBJ_ERROR (aafi, SourceClip, &__td, "Missing parent Mob::MobID");
			return -1;
		}

		aafObject* ParentMobSlot = aaf_get_ObjectAncestor (SourceClip, &AAFClassID_MobSlot);

		if (!ParentMobSlot) {
			TRACE_OBJ_ERROR (aafi, SourceClip, &__td, "Could not retrieve parent MobSlot");
			return -1;
		}

		uint32_t* masterMobSlotID = aaf_get_propertyValue (ParentMobSlot, PID_MobSlot_SlotID, &AAFTypeID_UInt32);

		uint32_t* essenceChannelNum = aaf_get_propertyValue (ParentMobSlot, PID_MobSlot_PhysicalTrackNumber, &AAFTypeID_UInt32);

		if (aafUIDCmp (DataDefinition, &AAFDataDef_Sound) ||
		    aafUIDCmp (DataDefinition, &AAFDataDef_LegacySound)) {
			if (!aafi->ctx.current_clip) {
				TRACE_OBJ_ERROR (aafi, SourceClip, &__td, "aafi->ctx.current_clip not set");
				return -1;
			}

			/*
			 * Check if this Essence has already been retrieved
			 */

			aafiAudioEssenceFile* audioEssenceFile = NULL;

			AAFI_foreachAudioEssenceFile (aafi, audioEssenceFile)
			{
				if (aafMobIDCmp (audioEssenceFile->sourceMobID, sourceID) && audioEssenceFile->sourceMobSlotID == (unsigned)*SourceMobSlotID) {
					__td.eob = 1;
					TRACE_OBJ_INFO (aafi, SourceClip, &__td, "Essence already parsed: Linking with %s", audioEssenceFile->name);
					aafi->ctx.current_clip->essencePointerList = aafi_newAudioEssencePointer (aafi, &aafi->ctx.current_clip->essencePointerList, audioEssenceFile, essenceChannelNum);
					return 0;
				}
			}

			/* new Essence, carry on. */

			audioEssenceFile = aafi_newAudioEssence (aafi);

			audioEssenceFile->masterMobSlotID = *masterMobSlotID;
			audioEssenceFile->masterMobID     = masterMobID;
			audioEssenceFile->name            = aaf_get_propertyValue (ParentMob, PID_Mob_Name, &AAFTypeID_String);

			if (audioEssenceFile->name == NULL) {
				debug ("Missing parent Mob::Name (essence file name)");
			}

			audioEssenceFile->sourceMobSlotID = *SourceMobSlotID;
			audioEssenceFile->sourceMobID     = sourceID;

			aafObject* SourceMob = aaf_get_MobByID (aafi->aafd->Mobs, audioEssenceFile->sourceMobID);

			if (!SourceMob) {
				TRACE_OBJ_ERROR (aafi, SourceClip, &__td, "Could not retrieve SourceMob by ID : %s", aaft_MobIDToText (audioEssenceFile->sourceMobID));
				return -1;
			}

			aafi->ctx.current_audio_essence = audioEssenceFile;

			void* MobUserComments = aaf_get_propertyValue (ParentMob, PID_Mob_UserComments, &AAFTypeID_TaggedValueStrongReferenceVector);

			if (retrieve_UserComments (aafi, MobUserComments, &audioEssenceFile->metadata) < 0) {
				TRACE_OBJ_WARNING (aafi, SourceClip, &__td, "Error parsing parent Mob::UserComments");
			} else {
				TRACE_OBJ (aafi, SourceClip, &__td);
			}

			audioEssenceFile->SourceMob = SourceMob;

			aafObject* EssenceData = aaf_get_EssenceDataByMobID (aafi->aafd, audioEssenceFile->sourceMobID);

			if (EssenceData)
				__td.ll[__td.lv] = 2;

			parse_Mob (aafi, SourceMob, &__td);

			if (EssenceData)
				__td.ll[__td.lv] = 1;

			if (EssenceData) {
				/* If EssenceData was found, it means essence is embedded */
				parse_EssenceData (aafi, EssenceData, &__td);
				__td.ll[__td.lv] = 0;
			}

			aafi_build_unique_audio_essence_name (aafi, audioEssenceFile);

			aafi->ctx.current_clip->essencePointerList = aafi_newAudioEssencePointer (aafi, &aafi->ctx.current_clip->essencePointerList, audioEssenceFile, essenceChannelNum);
			aafi->ctx.current_audio_essence            = NULL;
		} else if (aafUIDCmp (DataDefinition, &AAFDataDef_Picture) ||
		           aafUIDCmp (DataDefinition, &AAFDataDef_LegacyPicture)) {
			if (!aafi->ctx.current_video_clip) {
				TRACE_OBJ_ERROR (aafi, SourceClip, &__td, "aafi->ctx.current_video_clip not set");
				return -1;
			}

			/*
			 * Check if this Essence has already been retrieved
			 */

			aafiVideoEssence* videoEssenceFile = NULL;

			AAFI_foreachVideoEssence (aafi, videoEssenceFile)
			{
				if (aafMobIDCmp (videoEssenceFile->sourceMobID, sourceID) && videoEssenceFile->sourceMobSlotID == (unsigned)*SourceMobSlotID) {
					__td.eob = 1;
					TRACE_OBJ_INFO (aafi, SourceClip, &__td, "Essence already parsed: Linking with %s", videoEssenceFile->name);
					aafi->ctx.current_video_clip->Essence = videoEssenceFile;
					return 0;
				}
			}

			/* new Essence, carry on. */

			videoEssenceFile = aafi_newVideoEssence (aafi);

			aafi->ctx.current_video_clip->Essence = videoEssenceFile;

			videoEssenceFile->masterMobSlotID = *masterMobSlotID;
			videoEssenceFile->masterMobID     = masterMobID;
			videoEssenceFile->name            = aaf_get_propertyValue (ParentMob, PID_Mob_Name, &AAFTypeID_String);

			if (videoEssenceFile->name == NULL) {
				debug ("Missing parent Mob::Name (essence file name)");
			}

			videoEssenceFile->sourceMobSlotID = *SourceMobSlotID;
			videoEssenceFile->sourceMobID     = sourceID;

			TRACE_OBJ (aafi, SourceClip, &__td);

			aafObject* SourceMob = aaf_get_MobByID (aafi->aafd->Mobs, videoEssenceFile->sourceMobID);

			if (!SourceMob) {
				TRACE_OBJ_ERROR (aafi, SourceClip, &__td, "Could not retrieve SourceMob by ID : %s", aaft_MobIDToText (videoEssenceFile->sourceMobID));
				return -1;
			}

			videoEssenceFile->SourceMob = SourceMob;

			aafi->ctx.current_video_essence = videoEssenceFile;

			aafObject* EssenceData = aaf_get_EssenceDataByMobID (aafi->aafd, videoEssenceFile->sourceMobID);

			parse_Mob (aafi, SourceMob, &__td);

			if (EssenceData) {
				/*
				 * It means essence is embedded, otherwise it's not.
				 */
				parse_EssenceData (aafi, EssenceData, &__td);
			}

			/*
			 * No need to check for uniqueness, current version supports only one video clip.
			 */
			videoEssenceFile->unique_name = laaf_util_c99strdup (videoEssenceFile->name);

			if (!videoEssenceFile->unique_name) {
				TRACE_OBJ_ERROR (aafi, SourceClip, &__td, "Could not duplicate video essence unique name : %s", videoEssenceFile->name);
				return -1;
			}

			aafi->ctx.current_video_essence = NULL;
		}
	} else if (aafUIDCmp (ParentMob->Class->ID, &AAFClassID_SourceMob)) {
		/* Nothing to parse here at first glance : SourceMob > TimelineMobSlot > SourceClip */
		TRACE_OBJ (aafi, SourceClip, &__td);
	} else {
		TRACE_OBJ_NO_SUPPORT (aafi, SourceClip, &__td);
		return -1;
	}

	return 0;
}

static int
parse_Timecode (AAF_Iface* aafi, aafObject* Timecode, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 1);
	__td.eob = 1;

	aafPosition_t* tc_start = aaf_get_propertyValue (Timecode, PID_Timecode_Start, &AAFTypeID_PositionType);

	if (tc_start == NULL) {
		TRACE_OBJ_ERROR (aafi, Timecode, &__td, "Missing Timecode::Start");
		return -1;
	}

	uint16_t* tc_fps = aaf_get_propertyValue (Timecode, PID_Timecode_FPS, &AAFTypeID_UInt16);

	if (tc_fps == NULL) {
		TRACE_OBJ_ERROR (aafi, Timecode, &__td, "Missing Timecode::FPS");
		return -1;
	}

	uint8_t* tc_drop = aaf_get_propertyValue (Timecode, PID_Timecode_Drop, &AAFTypeID_UInt8);

	if (tc_drop == NULL) {
		TRACE_OBJ_ERROR (aafi, Timecode, &__td, "Missing Timecode::Drop");
		return -1;
	}

	/* TODO this should be retrieved directly from TimelineMobSlot */

	aafObject* ParentMobSlot = aaf_get_ObjectAncestor (Timecode, &AAFClassID_MobSlot);

	if (!ParentMobSlot) {
		TRACE_OBJ_ERROR (aafi, Timecode, &__td, "Could not retrieve parent MobSlot");
		return -1;
	}

	aafRational_t* tc_edit_rate = aaf_get_propertyValue (ParentMobSlot, PID_TimelineMobSlot_EditRate, &AAFTypeID_Rational);

	if (tc_edit_rate == NULL) {
		TRACE_OBJ_ERROR (aafi, Timecode, &__td, "Missing parent TimelineMobSlot::EditRate");
		return -1;
	}

	if (aafi->Timecode) {
		TRACE_OBJ_WARNING (aafi, Timecode, &__td, "Timecode was already set, ignoring (%lu, %u fps)", *tc_start, *tc_fps);
		return -1;
	}

	aafiTimecode* tc = calloc (1, sizeof (aafiTimecode));

	if (!tc) {
		TRACE_OBJ_ERROR (aafi, Timecode, &__td, "Out of memory");
		return -1;
	}

	tc->start     = *tc_start;
	tc->fps       = *tc_fps;
	tc->drop      = *tc_drop;
	tc->edit_rate = tc_edit_rate;

	aafi->Timecode = tc;

	TRACE_OBJ (aafi, Timecode, &__td);

	return 0;
}

static int
parse_DescriptiveMarker (AAF_Iface* aafi, aafObject* DescriptiveMarker, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 1);

	aafPosition_t* start = aaf_get_propertyValue (DescriptiveMarker, PID_Event_Position, &AAFTypeID_PositionType);

	if (!start) {
		/*
		 * « If an Event is in a TimelineMobSlot or a StaticMobSlot, it shall not have a Position
		 * property. If an Event is in a EventMobSlot, it shall have a Position property. »
		 */
		TRACE_OBJ_ERROR (aafi, DescriptiveMarker, &__td, "Missing Event::Position");
		return -1;
	}

	TRACE_OBJ (aafi, DescriptiveMarker, &__td);

	aafPosition_t* length  = aaf_get_propertyValue (DescriptiveMarker, PID_Component_Length, &AAFTypeID_PositionType);
	char*          comment = aaf_get_propertyValue (DescriptiveMarker, PID_Event_Comment, &AAFTypeID_String);
	char*          name    = aaf_get_propertyValue (DescriptiveMarker, aaf_get_PropertyIDByName (aafi->aafd, "CommentMarkerUser"), &AAFTypeID_String);

	if (!name) {
		/* Avid Media Composer 23.12 */
		name = aaf_get_propertyValue (DescriptiveMarker, aaf_get_PropertyIDByName (aafi->aafd, "CommentMarkerUSer"), &AAFTypeID_String);
	}

	uint16_t*    RGBColor     = NULL;
	aafProperty* RGBColorProp = aaf_get_property (DescriptiveMarker, aaf_get_PropertyIDByName (aafi->aafd, "CommentMarkerColor"));

	if (RGBColorProp) {
		if (RGBColorProp->len != sizeof (uint16_t) * 3) {
			error ("CommentMarkerColor has wrong size: %u", RGBColorProp->len);
		} else {
			RGBColor = RGBColorProp->val;

			/* big endian to little endian */
			RGBColor[0] = (RGBColor[0] >> 8) | (RGBColor[0] << 8);
			RGBColor[1] = (RGBColor[1] >> 8) | (RGBColor[1] << 8);
			RGBColor[2] = (RGBColor[2] >> 8) | (RGBColor[2] << 8);
		}
	}

	aafi_newMarker (aafi, aafi->ctx.current_markers_edit_rate, *start, ((length) ? *length : 0), name, comment, &RGBColor);

	return 0;
}

static int
parse_Sequence (AAF_Iface* aafi, aafObject* Sequence, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 1);

	aafObject* Component  = NULL;
	aafObject* Components = aaf_get_propertyValue (Sequence, PID_Sequence_Components, &AAFTypeID_ComponentStrongReferenceVector);

	if (!Components) {
		TRACE_OBJ_ERROR (aafi, Sequence, &__td, "Missing Sequence::Components");
		return -1;
	}

	TRACE_OBJ (aafi, Sequence, &__td);

	/*
	 * "Audio Warp" OperationGroup appears in Avid Media Composer AAF files, when
	 * a clip with a different frame rate from the project was *linked* into Avid
	 * rather than beeing properly imported.
	 *
	 * "Audio Warp" OperationGroup is pointing to a Sequence with two ComponentAttributes:
	 *    _MIXMATCH_RATE_NUM
	 *    _MIXMATCH_RATE_DENOM
	 *
	 * Those parameters set the valid edit rate for SourceClip::StartTime (essence
	 * offset) and Component::Length, in total violation of the standard saying that
	 * TimelineMobSlot::EditRate shall always be used.
	 *
│ 02009││    │         ├──◻ AAFClassID_OperationGroup (OpIdent: Audio Warp; Length: 168)   (MetaProps: ComponentAttributeList[0xffc9])
│ 02283││    │         │    ├──◻ AAFClassID_ConstantValue (ParamDef: AvidMotionInputFormat; Type: AAFTypeID_Int32) : 2
│ 02283││    │         │    ├──◻ AAFClassID_ConstantValue (ParamDef: AAFParameterDef_SpeedRatio; Type: AAFTypeID_Rational) : 1/2
│ 02283││    │         │    ├──◻ AAFClassID_ConstantValue (ParamDef: AvidPhase; Type: AAFTypeID_Int32) : 0
│ 02283││    │         │    ├──◻ AAFClassID_ConstantValue (ParamDef: AvidMotionOutputFormat; Type: AAFTypeID_Int32) : 0
│ 02283││    │         │    └──◻ AAFClassID_ConstantValue (ParamDef: AvidMotionPulldown; Type: AAFTypeID_Int32) : 0
│ 01628││    │         │    └──◻ AAFClassID_Sequence (Length: 336)  (MetaProps: ComponentAttributeList[0xffc9])
│ 01195││    │         │         └──◻ AAFClassID_SourceClip (Length: 336)
│ 01198││    │         │              └──◻ AAFClassID_MasterMob (UsageCode: n/a) : UTS_LIMO_LIMO_3_2024_01_30_11_15_45.new.04  (MetaProps: MobAttributeList[0xfff9] ConvertFrameRate[0xfff8])
│ 00513││    │         │                   └──◻ AAFClassID_TimelineMobSlot [slot:1 track:1] (DataDef: AAFDataDef_Sound)
│ 01628││    │         │                        └──◻ AAFClassID_Sequence (Length: 576)
│ 01359││    │         │                             └──◻ AAFClassID_SourceClip (Length: 576)
│ 00272││    │         │                                  ├──◻ AAFClassID_SourceMob (UsageCode: n/a)   (MetaProps: MobAttributeList[0xfff9])
│ 02806││    │         │                                  │    ├──◻ AAFClassID_WAVEDescriptor (ContainerIdent : AAFContainerDef_AAF)
│ 03010││    │         │                                  │    │    └──◻ AAFClassID_NetworkLocator (URLString: file://10.87.230.71/mixage/AAF_Vers_Mixage/MONTAGE_06/4550985%20SUIVI%20AGRICULTEURS%20VERS%20PARIS.aaf)
│      ││    │         │                                  │    │
│ 00532││    │         │                                  │    └──◻ AAFClassID_TimelineMobSlot [slot:1 track:1] (DataDef: AAFDataDef_Sound)
│ 01470││    │         │                                  │         └──◻ AAFClassID_SourceClip (Length: 576)
│ 03071││    │         │                                  └──◻ AAFClassID_EssenceData (Data: Data-2702)

	 */

	void* ComponentAttributeList = aaf_get_propertyValue (Sequence, aaf_get_PropertyIDByName (aafi->aafd, "ComponentAttributeList"), &AAFTypeID_TaggedValueStrongReferenceVector);

	if (ComponentAttributeList) {
		int32_t* rateNum   = aaf_get_TaggedValueByName (aafi->aafd, ComponentAttributeList, "_MIXMATCH_RATE_NUM", &AAFTypeID_Int32);
		int32_t* rateDenom = aaf_get_TaggedValueByName (aafi->aafd, ComponentAttributeList, "_MIXMATCH_RATE_DENOM", &AAFTypeID_Int32);

		if (rateNum && rateDenom) {
			aafi->ctx.avid_warp_clip_edit_rate = malloc (sizeof (aafRational_t));

			if (!aafi->ctx.avid_warp_clip_edit_rate) {
				error ("Out of memory");
				return -1;
			}

			aafi->ctx.avid_warp_clip_edit_rate->numerator   = *rateNum;
			aafi->ctx.avid_warp_clip_edit_rate->denominator = *rateDenom;
			debug ("Got Avid audio warp edit rate : %i/%i",
			       aafi->ctx.avid_warp_clip_edit_rate->numerator,
			       aafi->ctx.avid_warp_clip_edit_rate->denominator);
		}
	}

	uint32_t i = 0;
	AAFI_foreach_ObjectInSet (&Component, Components, i, __td)
	{
		parse_Component (aafi, Component, &__td);
	}

	free (aafi->ctx.avid_warp_clip_edit_rate);
	aafi->ctx.avid_warp_clip_edit_rate = NULL;

	return 0;
}

static int
parse_Selector (AAF_Iface* aafi, aafObject* Selector, td* __ptd)
{
	/*
	 * The Selector class is a sub-class of the Segment class.
	 *
	 * The Selector class provides the value of a single Segment (PID_Selector_Selected)
	 * while preserving references to unused alternatives (PID_Selector_Alternates)
	 */

	struct trace_dump __td;
	__td_set (__td, __ptd, 1);

	aafObject* Selected = aaf_get_propertyValue (Selector, PID_Selector_Selected, &AAFTypeID_SegmentStrongReference);

	if (!Selected) {
		TRACE_OBJ_ERROR (aafi, Selector, &__td, "Missing Selector::Selected");
		return -1;
	}

	TRACE_OBJ (aafi, Selector, &__td);

	aafObject* Alternates = aaf_get_propertyValue (Selector, PID_Selector_Alternates, &AAFTypeID_SegmentStrongReferenceVector);

	if (Alternates) {
		__td.lv++;
		TRACE_OBJ_INFO (aafi, Alternates, &__td, "Selector Alternates (dropping)");
		__td.lv--;
	}

	/*
	 * ComponentAttributeList is non-standard, used by Avid Media Composer and
	 * Davinci Resolve to describe a disabled (muted) clip. This way, any unaware
	 * implementation will parse Selected Object containing a Filler and ignore
	 * the disabled clip inside Alternates.
	 */

	void* ComponentAttributeList = aaf_get_propertyValue (Selector, aaf_get_PropertyIDByName (aafi->aafd, "ComponentAttributeList"), &AAFTypeID_TaggedValueStrongReferenceVector);

	if (ComponentAttributeList) {
		int32_t* disabledClip = aaf_get_TaggedValueByName (aafi->aafd, ComponentAttributeList, "_DISABLE_CLIP_FLAG", &AAFTypeID_Int32);

		if (disabledClip) {
			if (*disabledClip) {
				aafi->ctx.current_clip_is_muted = 1;
			}

			/*
			 * When Selector has _DISABLE_CLIP_FLAG, Alternates should point
			 * to a single Alternates Object containing the disabled clip.
			 */
			if (Alternates) {
				return parse_Segment (aafi, Alternates, &__td);
			} else {
				return parse_Segment (aafi, Selected, &__td);
			}
		}
	} else {
		/*
		 * without specific software implementation, we stick to Selected Object
		 * and forget about any Alternates Objects.
		 */
		return parse_Segment (aafi, Selected, &__td);
	}

	return -1;
}

static int
parse_NestedScope (AAF_Iface* aafi, aafObject* NestedScope, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 1);

	/*
	 * NestedScope seems to be only used for video clips in Avid Media Composer.
	 * Not sure how to handle it...
	 */

	aafObject* Slot  = NULL;
	aafObject* Slots = aaf_get_propertyValue (NestedScope, PID_NestedScope_Slots, &AAFTypeID_SegmentStrongReferenceVector);

	if (!Slots) {
		TRACE_OBJ_ERROR (aafi, NestedScope, &__td, "Missing NestedScope::Slots");
		return -1;
	}

	TRACE_OBJ (aafi, NestedScope, &__td);

	uint32_t i = 0;
	AAFI_foreach_ObjectInSet (&Slot, Slots, i, __td)
	{
		parse_Segment (aafi, Slot, &__td);
	}

	return 0;
}

static int
parse_OperationGroup (AAF_Iface* aafi, aafObject* OpGroup, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 1);

	if (!aaf_get_property (OpGroup, PID_OperationGroup_InputSegments) &&
	    !aaf_get_property (OpGroup, PID_OperationGroup_Parameters)) {
		__td.eob = 1;
	}

	aafObject* ParentMob = aaf_get_ObjectAncestor (OpGroup, &AAFClassID_Mob);

	if (!ParentMob) {
		TRACE_OBJ_ERROR (aafi, OpGroup, &__td, "Could not retrieve parent Mob");
		return -1;
	}

	if (!aafUIDCmp (ParentMob->Class->ID, &AAFClassID_CompositionMob)) {
		TRACE_OBJ_ERROR (aafi, OpGroup, &__td, "OperationGroup is currently supported only in CompositionMob, not in %s", aaft_ClassIDToText (aafi->aafd, ParentMob->Class->ID));
		return -1;
	}

	int rc = 0;

	aafWeakRef_t* OperationDefWeakRef = aaf_get_propertyValue (OpGroup, PID_OperationGroup_Operation, &AAFTypeID_OperationDefinitionWeakReference);

	if (!OperationDefWeakRef) {
		TRACE_OBJ_ERROR (aafi, OpGroup, &__td, "Missing OperationGroup::Operation");
		return -1;
	}

	aafUID_t* OperationIdentification = aaf_get_OperationIdentificationByWeakRef (aafi->aafd, OperationDefWeakRef);

	if (!OperationIdentification) {
		TRACE_OBJ_ERROR (aafi, OpGroup, &__td, "Could not retrieve OperationIdentification");
		return -1;
	}

	/*
	 * We need to check if OperationGroup is a direct child of TopLevelCompositionMob > TimelineMobSlot.
	 *  - If it is, it means that the OperationGroup affect the current Track.
	 *  - If it's not (eg. it's a child of a Sequence), then OperationGroup applies
	 *    to all descendent clips.
	 *
	 * NOTE: OperationGroup can be a child of another OperationGroup. So we can't
	 * just check direct Parent, we need to loop.
	 */

	aafObject* OperationGroupParent = OpGroup->Parent;
	while (OperationGroupParent && aafUIDCmp (OperationGroupParent->Class->ID, &AAFClassID_OperationGroup)) {
		OperationGroupParent = OperationGroupParent->Parent;
	}

	if (!OperationGroupParent) {
		error ("OperationGroup has no parent !");
		return -1;
	}

	if (aafUIDCmp (OperationGroupParent->Class->ID, &AAFClassID_TimelineMobSlot) &&
	    ParentMob == aafi->ctx.TopLevelCompositionMob) {
		aafi->ctx.current_opgroup_affect_track = 1;
	} else {
		aafi->ctx.current_opgroup_affect_track = 0;
	}

	if (aafUIDCmp (OperationIdentification, &AAFOperationDef_MonoAudioDissolve)) {
		if (!aafUIDCmp (OpGroup->Parent->Class->ID, &AAFClassID_Transition)) {
			TRACE_OBJ_ERROR (aafi, OpGroup, &__td, "Parent should be AAFClassID_Transition");
			return -1;
		}

		TRACE_OBJ (aafi, OpGroup, &__td);

		aafiTransition* Trans = aafi->ctx.current_transition;

		/*
		 * Mono Audio Dissolve (Fade, Cross Fade)
		 *
		 * The same parameter (curve/level) is applied to the outgoing fade on first
		 * clip (if any) and to the incoming fade on second clip (if any).
		 */

		Trans->flags |= AAFI_TRANS_SINGLE_CURVE;

		aafObject* Param      = NULL;
		aafObject* Parameters = aaf_get_propertyValue (OpGroup, PID_OperationGroup_Parameters, &AAFTypeID_ParameterStrongReferenceVector);

		uint32_t i = 0;
		AAFI_foreach_ObjectInSet (&Param, Parameters, i, __td)
		{
			parse_Parameter (aafi, Param, &__td);
		}

		/*
		 * Avid Media Composer doesn't use the standard method to set interpolation. Instead,
		 * it always sets InterpolationIdentification to Linear, and it sets the actual
		 * interpolation in both :
		 * 	- OperationGroup > ComponentAttributeList > _ATN_AUDIO_DISSOLVE_CURVETYPE
		 * 	- OperationGroup > Parameters > AAFClassID_ConstantValue (ParamDef: Curve Type; Type: AAFTypeID_Int32) : 1
		 *
		 * Note: _ATN_AUDIO_DISSOLVE_CURVETYPE was observed since v8.4 (2015), however "Curve Type" was observed since v18.12.7.
		 * Using _ATN_AUDIO_DISSOLVE_CURVETYPE provides a better support for older Avid MC versions.
		 */

		void* ComponentAttributeList = aaf_get_propertyValue (OpGroup, aaf_get_PropertyIDByName (aafi->aafd, "ComponentAttributeList"), &AAFTypeID_TaggedValueStrongReferenceVector);

		if (ComponentAttributeList) {
			int32_t* curveType = aaf_get_TaggedValueByName (aafi->aafd, ComponentAttributeList, "_ATN_AUDIO_DISSOLVE_CURVETYPE", &AAFTypeID_Int32);

			if (curveType) {
				switch (*curveType) {
					case AVID_MEDIA_COMPOSER_CURVE_TYPE_LINEAR:
						Trans->flags &= ~(AAFI_INTERPOL_MASK);
						Trans->flags |= AAFI_INTERPOL_LINEAR;
						break;
					case AVID_MEDIA_COMPOSER_CURVE_TYPE_EQUAL_POWER:
						Trans->flags &= ~(AAFI_INTERPOL_MASK);
						Trans->flags |= AAFI_INTERPOL_POWER;
						break;
					default:
						debug ("Unknown Avid Media Composer fade curve: %i", *curveType);
						break;
				}
			}
		}

		if (!(Trans->flags & AAFI_INTERPOL_MASK)) {
			debug ("Setting fade interpolation to default Linear");
			Trans->flags |= AAFI_INTERPOL_LINEAR;
		}

		aafi_dump_obj (aafi, NULL, &__td, 0, NULL, -1, "");
	} else if (aafUIDCmp (OperationIdentification, &AAFOperationDef_AudioChannelCombiner)) {
		TRACE_OBJ (aafi, OpGroup, &__td);

		aafObject* InputSegment  = NULL;
		aafObject* InputSegments = aaf_get_propertyValue (OpGroup, PID_OperationGroup_InputSegments, &AAFTypeID_SegmentStrongReferenceVector);

		aafi->ctx.current_clip_is_combined            = 1;
		aafi->ctx.current_combined_clip_total_channel = InputSegments->Header->_entryCount;
		aafi->ctx.current_combined_clip_channel_num   = 0;
		aafi->ctx.current_combined_clip_forced_length = 0;

		if (resolve_AAF (aafi)) {
			/*
			 * This is clearly a violation of the standard (p 57). When Davinci Resolve
			 * exports multichannel clips, it does not set SourceClip::Length correctly.
			 * Instead, it's more like some sort of frame-rounded value which doesn't match
			 * the timeline. However, the correct value is set to OperationGroup::length...
			 */
			int64_t* length                               = aaf_get_propertyValue (OpGroup, PID_Component_Length, &AAFTypeID_LengthType);
			aafi->ctx.current_combined_clip_forced_length = (length) ? *length : 0;
		}

		uint32_t i = 0;
		AAFI_foreach_ObjectInSet (&InputSegment, InputSegments, i, __td)
		{
			parse_Segment (aafi, InputSegment, &__td);
			aafi->ctx.current_combined_clip_channel_num++;
		}

		aafi_dump_obj (aafi, NULL, &__td, 0, NULL, -1, "");

		aafiAudioTrack* current_track = aafi->ctx.current_track;

		aafiTrackFormat_e track_format = AAFI_TRACK_FORMAT_UNKNOWN;

		if (aafi->ctx.current_combined_clip_total_channel == 2) {
			track_format = AAFI_TRACK_FORMAT_STEREO;
		} else if (aafi->ctx.current_combined_clip_total_channel == 6) {
			track_format = AAFI_TRACK_FORMAT_5_1;
		} else if (aafi->ctx.current_combined_clip_total_channel == 8) {
			track_format = AAFI_TRACK_FORMAT_7_1;
		} else {
			TRACE_OBJ_ERROR (aafi, OpGroup, &__td, "Unknown track format (%u)", aafi->ctx.current_combined_clip_total_channel);
			RESET_CTX__AudioChannelCombiner (aafi->ctx);
			return -1;
		}

		if (current_track->format != AAFI_TRACK_FORMAT_NOT_SET &&
		    current_track->format != track_format) {
			TRACE_OBJ_ERROR (aafi, OpGroup, &__td, "Track format (%u) does not match current clip (%u)", current_track->format, track_format);
			RESET_CTX__AudioChannelCombiner (aafi->ctx);
			return -1;
		}

		current_track->format = track_format;
		RESET_CTX__AudioChannelCombiner (aafi->ctx);
	} else if (aafUIDCmp (OperationIdentification, &AAFOperationDef_MonoAudioGain)) {
		aafObject* Param      = NULL;
		aafObject* Parameters = aaf_get_propertyValue (OpGroup, PID_OperationGroup_Parameters, &AAFTypeID_ParameterStrongReferenceVector);

		if (!Parameters) {
			TRACE_OBJ_ERROR (aafi, OpGroup, &__td, "Missing OperationGroup::Parameters");
			rc = -1;
			goto end; /* we still have to parse InputSegments, so we're not losing any clip */
		}

		TRACE_OBJ (aafi, OpGroup, &__td);

		uint32_t i = 0;
		AAFI_foreach_ObjectInSet (&Param, Parameters, i, __td)
		{
			parse_Parameter (aafi, Param, &__td);
		}
	} else if (aafUIDCmp (OperationIdentification, &AAFOperationDef_MonoAudioPan)) {
		/* TODO Should Only be Track-based (first Segment of TimelineMobSlot.) */

		aafObject* Param      = NULL;
		aafObject* Parameters = aaf_get_propertyValue (OpGroup, PID_OperationGroup_Parameters, &AAFTypeID_ParameterStrongReferenceVector);

		if (!Parameters) {
			TRACE_OBJ_ERROR (aafi, OpGroup, &__td, "Missing OperationGroup::Parameters");
			rc = -1;
			goto end; /* we still have to parse InputSegments, so we're not losing any clip */
		}

		TRACE_OBJ (aafi, OpGroup, &__td);

		uint32_t i = 0;
		AAFI_foreach_ObjectInSet (&Param, Parameters, i, __td)
		{
			parse_Parameter (aafi, Param, &__td);
		}
	} else if (aafUIDCmp (OperationIdentification, aaf_get_OperationDefIDByName (aafi->aafd, "Audio Warp"))) {
		aafObject* Param      = NULL;
		aafObject* Parameters = aaf_get_propertyValue (OpGroup, PID_OperationGroup_Parameters, &AAFTypeID_ParameterStrongReferenceVector);

		if (!Parameters) {
			TRACE_OBJ_ERROR (aafi, OpGroup, &__td, "Missing OperationGroup::Parameters");
			rc = -1;
			goto end; /* we still have to parse InputSegments, so we're not losing any clip */
		}

		TRACE_OBJ (aafi, OpGroup, &__td);

		uint32_t i = 0;
		AAFI_foreach_ObjectInSet (&Param, Parameters, i, __td)
		{
			parse_Parameter (aafi, Param, &__td);
		}
	} else {
		/*
		 * Unknown usage and implementation, not encountered yet:
		 *
		 * 	AAFOperationDef_MonoAudioMixdown
		 * 	AAFOperationDef_StereoAudioGain
		 * 	AAFOperationDef_TwoParameterMonoAudioDissolve
		 * 	AAFOperationDef_StereoAudioDissolve
		 */
		debug ("Unsupported OperationIdentification: %s", aaft_OperationDefToText (aafi->aafd, OperationIdentification));
		TRACE_OBJ_NO_SUPPORT (aafi, OpGroup, &__td);

		aafObject* Param      = NULL;
		aafObject* Parameters = aaf_get_propertyValue (OpGroup, PID_OperationGroup_Parameters, &AAFTypeID_ParameterStrongReferenceVector);

		if (!Parameters) {
			// TRACE_OBJ_ERROR( aafi, OpGroup, &__td, "Missing OperationGroup::Parameters" );
			rc = -1;
			goto end; /* we still have to parse InputSegments, so we're not losing any clip */
		}

		uint32_t i = 0;
		AAFI_foreach_ObjectInSet (&Param, Parameters, i, __td)
		{
			parse_Parameter (aafi, Param, &__td);
		}
	}

end:

	/*
	 * Parses Segments in the OperationGroup::InputSegments, only if
	 * OperationGroup is not a Transition as a Transition has no InputSegments,
	 * and not an AudioChannelCombiner as they were already parsed.
	 */

	if (!aafUIDCmp (OpGroup->Parent->Class->ID, &AAFClassID_Transition) &&
	    !aafUIDCmp (OperationIdentification, &AAFOperationDef_AudioChannelCombiner)) {
		aafObject* InputSegment  = NULL;
		aafObject* InputSegments = aaf_get_propertyValue (OpGroup, PID_OperationGroup_InputSegments, &AAFTypeID_SegmentStrongReferenceVector);

		uint32_t i = 0;
		AAFI_foreach_ObjectInSet (&InputSegment, InputSegments, i, __td)
		{
			parse_Segment (aafi, InputSegment, &__td);
		}
	}

	/* End of current OperationGroup context. */

	aafObject* Obj = OpGroup;
	for (; Obj != NULL && aafUIDCmp (Obj->Class->ID, &AAFClassID_ContentStorage) == 0; Obj = Obj->Parent)
		if (!aafUIDCmp (Obj->Class->ID, &AAFClassID_OperationGroup))
			break;

	if (aafUIDCmp (OperationIdentification, &AAFOperationDef_MonoAudioGain)) {
		if (!aafUIDCmp (Obj->Class->ID, &AAFClassID_TimelineMobSlot)) {
			if (aafi->ctx.current_clip_gain_is_used == 0) {
				aafi_freeAudioGain (aafi->ctx.current_clip_gain);
			}

			if (aafi->ctx.current_clip_variable_gain_is_used == 0) {
				aafi_freeAudioGain (aafi->ctx.current_clip_variable_gain);
			}

			RESET_CTX__AudioGain (aafi->ctx);
		}
	}

	return rc;
}

/*
 *           Parameter (abs)
 *               |
 *       ,--------------.
 *       |              |
 * ConstantValue   VaryingValue
 *
 *
 * A Parameter object shall be owned by an OperationGroup object.
 */

static int
parse_Parameter (AAF_Iface* aafi, aafObject* Parameter, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 0);

	if (aafUIDCmp (Parameter->Class->ID, &AAFClassID_ConstantValue)) {
		return parse_ConstantValue (aafi, Parameter, &__td);
	} else if (aafUIDCmp (Parameter->Class->ID, &AAFClassID_VaryingValue)) {
		return parse_VaryingValue (aafi, Parameter, &__td);
	} else {
		__td_set (__td, __ptd, 1);
		TRACE_OBJ_ERROR (aafi, Parameter, &__td, "Parameter is neither of class Constant nor Varying : %s", aaft_ClassIDToText (aafi->aafd, Parameter->Class->ID));
	}

	return -1;
}

static int
parse_ConstantValue (AAF_Iface* aafi, aafObject* ConstantValue, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 1);

	if (!aaf_get_propertyValue (ConstantValue->Parent, PID_OperationGroup_InputSegments, &AAFTypeID_SegmentStrongReferenceVector)) {
		__td.eob = 1;
	}

	aafUID_t* ParamDef = aaf_get_propertyValue (ConstantValue, PID_Parameter_Definition, &AAFTypeID_AUID);

	if (!ParamDef) {
		TRACE_OBJ_ERROR (aafi, ConstantValue, &__td, "Missing Parameter::Definition");
		return -1;
	}

	aafWeakRef_t* OperationDefWeakRef = aaf_get_propertyValue (ConstantValue->Parent, PID_OperationGroup_Operation, &AAFTypeID_OperationDefinitionWeakReference);

	if (!OperationDefWeakRef) {
		TRACE_OBJ_ERROR (aafi, ConstantValue, &__td, "Missing OperationGroup::Operation");
		return -1;
	}

	aafUID_t* OperationIdentification = aaf_get_OperationIdentificationByWeakRef (aafi->aafd, OperationDefWeakRef);

	if (!OperationIdentification) {
		TRACE_OBJ_ERROR (aafi, ConstantValue, &__td, "Could not retrieve OperationIdentification from parent");
		return -1;
	}

	aafIndirect_t* Indirect = aaf_get_propertyValue (ConstantValue, PID_ConstantValue_Value, &AAFTypeID_Indirect);

	if (!Indirect) {
		TRACE_OBJ_ERROR (aafi, ConstantValue, &__td, "Missing ConstantValue::Value");
		return -1;
	}

	aafObject* ParentMob = aaf_get_ObjectAncestor (ConstantValue, &AAFClassID_Mob);

	aafUID_t* mobUsageCode = aaf_get_propertyValue (ParentMob, PID_Mob_UsageCode, &AAFTypeID_UsageType);

	if (aafUIDCmp (OperationIdentification, &AAFOperationDef_MonoAudioGain) &&
	    aafUIDCmp (ParamDef, &AAFParameterDef_Amplitude)) {
		aafRational_t* value = aaf_get_indirectValue (aafi->aafd, Indirect, &AAFTypeID_Rational);

		if (!value) {
			TRACE_OBJ_ERROR (aafi, ConstantValue, &__td, "Could not retrieve Indirect value for ConstantValue::Value");
			return -1;
		}

		aafiAudioGain* Gain = aafi_newAudioGain (aafi, AAFI_AUDIO_GAIN_CONSTANT, 0, value);

		if (!Gain) {
			TRACE_OBJ_ERROR (aafi, ConstantValue, &__td, "Could not create new gain");
			return -1;
		}

		if (aafUIDCmp (mobUsageCode, &AAFUsage_AdjustedClip)) {
			/*
			 * « Some applications support the notion of an adjusted-clip in which an
			 * effect is applied directly to a clip and applies to all uses of that
			 * clip, e.g. an audio gain effect. »
			 *
			 * Only Avid Media Composer seems to make use of AdjustedClip, in a way that
			 * it doesn't affect the timeline composition. It looks like any gain applied
			 * to a source clip (inside a bin), is just audible when playing that clip
			 * in preview.
			 *
			 * Thus, we just ignore it.
			 */
			debug ("Ignoring AdjustedClip audio level: %i/%i (%+05.1lf dB) ", Gain->value[0].numerator, Gain->value[0].denominator, 20 * log10 (aafRationalToDouble (Gain->value[0])));
			TRACE_OBJ_WARNING (aafi, ConstantValue, &__td, "Ignoring AdjustedClip audio level");
			aafi_freeAudioGain (Gain);
		} else if (aafi->ctx.current_opgroup_affect_track) {
			/*
			 * Track-based Volume
			 */
			if (!aafi->ctx.current_track) {
				TRACE_OBJ_ERROR (aafi, ConstantValue, &__td, "Current track not set, dropping this volume: %i/%i (%+05.1lf dB)", Gain->value[0].numerator, Gain->value[0].denominator, 20 * log10 (aafRationalToDouble (Gain->value[0])));
				aafi_freeAudioGain (Gain);
				return -1;
			} else if (aafi->ctx.current_track->gain) {
				TRACE_OBJ_ERROR (aafi, ConstantValue, &__td, "Track volume was already set, dropping this one: %i/%i (%+05.1lf dB)", Gain->value[0].numerator, Gain->value[0].denominator, 20 * log10 (aafRationalToDouble (Gain->value[0])));
				aafi_freeAudioGain (Gain);
				return -1;
			} else {
				aafi->ctx.current_track->gain = Gain;
				TRACE_OBJ (aafi, ConstantValue, &__td);
			}
		} else {
			/*
			 * Clip-based Gain
			 * Gain is saved in context and it will be set to all OperationGroup descendent clips.
			 */
			if (aafi->ctx.current_clip_gain) {
				TRACE_OBJ_ERROR (aafi, ConstantValue, &__td, "Clip gain was already set, dropping this one: %i/%i (%+05.1lf dB)", Gain->value[0].numerator, Gain->value[0].denominator, 20 * log10 (aafRationalToDouble (Gain->value[0])));
				aafi_freeAudioGain (Gain);
				return -1;
			} else {
				aafi->ctx.current_clip_gain = Gain;
				TRACE_OBJ (aafi, ConstantValue, &__td);
			}
		}
	} else if (aafUIDCmp (OperationIdentification, &AAFOperationDef_MonoAudioPan) &&
	           aafUIDCmp (ParamDef, &AAFParameterDef_Pan)) {
		aafRational_t* value = aaf_get_indirectValue (aafi->aafd, Indirect, &AAFTypeID_Rational);

		if (!value) {
			TRACE_OBJ_ERROR (aafi, ConstantValue, &__td, "Could not retrieve Indirect value for ConstantValue::Value");
			return -1;
		}

		if (!aafi->ctx.current_opgroup_affect_track) {
			/*
			 * « Pan automation shall be track-based. If an application has a different
			 * native representation (e.g., clip-based pan), it shall convert to and
			 * from its native representation when exporting and importing the composition. »
			 *
			 * NOTE: Never encountered clip-based pan AAF.
			 */
			TRACE_OBJ_ERROR (aafi, ConstantValue, &__td, "Pan shall be track based");
			return -1;
		}

		if (!aafi->ctx.current_track) {
			TRACE_OBJ_ERROR (aafi, ConstantValue, &__td, "Current track not set");
			return -1;
		}

		aafiAudioPan* Pan = aafi_newAudioPan (aafi, AAFI_AUDIO_GAIN_CONSTANT, 0, value);

		if (!Pan) {
			TRACE_OBJ_ERROR (aafi, ConstantValue, &__td, "Could not create new pan");
			return -1;
		}

		aafi->ctx.current_track->pan = Pan;
		TRACE_OBJ (aafi, ConstantValue, &__td);
	} else {
		TRACE_OBJ_NO_SUPPORT (aafi, ConstantValue, &__td);
	}

	return 0;
}

static int
parse_VaryingValue (AAF_Iface* aafi, aafObject* VaryingValue, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 1);

	if (!aaf_get_propertyValue (VaryingValue->Parent, PID_OperationGroup_InputSegments, &AAFTypeID_SegmentStrongReferenceVector)) {
		__td.eob = 1;
	}

	aafUID_t* ParamDef = aaf_get_propertyValue (VaryingValue, PID_Parameter_Definition, &AAFTypeID_AUID);

	if (!ParamDef) {
		TRACE_OBJ_ERROR (aafi, VaryingValue, &__td, "Missing Parameter::Definition");
		return -1;
	}

	aafWeakRef_t* OperationDefWeakRef = aaf_get_propertyValue (VaryingValue->Parent, PID_OperationGroup_Operation, &AAFTypeID_OperationDefinitionWeakReference);

	if (!OperationDefWeakRef) {
		TRACE_OBJ_ERROR (aafi, VaryingValue, &__td, "Missing OperationGroup::Operation");
		return -1;
	}

	aafUID_t* OperationIdentification = aaf_get_OperationIdentificationByWeakRef (aafi->aafd, OperationDefWeakRef);

	if (!OperationIdentification) {
		TRACE_OBJ_ERROR (aafi, VaryingValue, &__td, "Could not retrieve OperationIdentification from parent");
		return -1;
	}

	aafWeakRef_t* InterpolationDefWeakRef = aaf_get_propertyValue (VaryingValue, PID_VaryingValue_Interpolation, &AAFTypeID_InterpolationDefinitionWeakReference);

	if (!InterpolationDefWeakRef) {
		TRACE_OBJ_ERROR (aafi, VaryingValue, &__td, "Missing VaryingValue::Interpolation.");
		return -1;
	}

	aafiInterpolation_e interpolation               = 0;
	aafUID_t*           InterpolationIdentification = aaf_get_InterpolationIdentificationByWeakRef (aafi->aafd, InterpolationDefWeakRef);

	if (!InterpolationIdentification) {
		TRACE_OBJ_WARNING (aafi, VaryingValue, &__td, "Could not retrieve InterpolationIdentification: Falling back to Linear");
		interpolation = AAFI_INTERPOL_LINEAR;
	} else if (aafUIDCmp (InterpolationIdentification, &AAFInterpolationDef_None)) {
		interpolation = AAFI_INTERPOL_NONE;
	} else if (aafUIDCmp (InterpolationIdentification, &AAFInterpolationDef_Linear)) {
		interpolation = AAFI_INTERPOL_LINEAR;
	} else if (aafUIDCmp (InterpolationIdentification, &AAFInterpolationDef_Power)) {
		interpolation = AAFI_INTERPOL_POWER;
	} else if (aafUIDCmp (InterpolationIdentification, &AAFInterpolationDef_Constant)) {
		interpolation = AAFI_INTERPOL_CONSTANT;
	} else if (aafUIDCmp (InterpolationIdentification, &AAFInterpolationDef_BSpline)) {
		interpolation = AAFI_INTERPOL_BSPLINE;
	} else if (aafUIDCmp (InterpolationIdentification, &AAFInterpolationDef_Log)) {
		interpolation = AAFI_INTERPOL_LOG;
	} else {
		TRACE_OBJ_WARNING (aafi, VaryingValue, &__td, "Unknown InterpolationIdentification value: Falling back to Linear");
		interpolation = AAFI_INTERPOL_LINEAR;
	}

	aafObject* Points = aaf_get_propertyValue (VaryingValue, PID_VaryingValue_PointList, &AAFTypeID_ControlPointStrongReferenceVector);

	if (!Points) {
		/*
		 * Some AAF files from ProTools and LogicPro break standard by having no
		 * PointList entry for AAFOperationDef_MonoAudioGain.
		 */
		TRACE_OBJ_WARNING (aafi, VaryingValue, &__td, "Missing VaryingValue::PointList");
		return -1;
	}

	if (aafUIDCmp (OperationIdentification, &AAFOperationDef_MonoAudioDissolve) &&
	    aafUIDCmp (ParamDef, &AAFParameterDef_Level)) {
		aafiTransition* Trans = aafi->ctx.current_transition;

		if (!Trans) {
			TRACE_OBJ_ERROR (aafi, VaryingValue, &__td, "Context current_transition not set");
			return -1;
		}

		Trans->flags |= interpolation;

		/*
		 * OperationGroup *might* contain a Parameter (ParameterDef_Level) specifying
		 * the fade curve. However, this parameter is optional regarding AAF_EditProtocol
		 * and there is most likely no implementation that exports custom fade curves.
		 * Thus, we only retrieve ParameterDef_Level to set interpolation, and we
		 * always set the fade as defined in AAF_EditProtocol, with only two points :
		 *
		 * « ParameterDef_Level (optional; default is a VaryingValue object
		 * with two control points: Value 0 at time 0, and value 1 at time 1) »
		 */

		TRACE_OBJ (aafi, VaryingValue, &__td);
	} else if (aafUIDCmp (OperationIdentification, &AAFOperationDef_MonoAudioGain) &&
	           (aafUIDCmp (ParamDef, &AAFParameterDef_Amplitude) || aafUIDCmp (ParamDef, aaf_get_ParamDefIDByName (aafi->aafd, "AvidControlClipRatio")))) {
		aafiAudioGain* Gain = aafi_newAudioGain (aafi, 0, interpolation, NULL);

		if (!Gain) {
			TRACE_OBJ_ERROR (aafi, VaryingValue, &__td, "Could not create new gain");
			return -1;
		}

		int pts_cnt = retrieve_ControlPoints (aafi, Points, &Gain->time, &Gain->value);

		if (pts_cnt < 0) {
			TRACE_OBJ_ERROR (aafi, VaryingValue, &__td, "Could not retrieve ControlPoints");
			free (Gain);
			return -1;
		}

		Gain->pts_cnt = (unsigned int)pts_cnt;

		// for ( int i = 0; i < Gain->pts_cnt; i++ ) {
		// 	debug( "time_%i : %i/%i   value_%i : %i/%i", i, Gain->time[i].numerator, Gain->time[i].denominator, i, Gain->value[i].numerator, Gain->value[i].denominator );
		// }

		/*
		 * If gain has 2 ControlPoints with both the same value, it means
		 * we have a flat gain curve. So we can assume constant gain here.
		 */

		if (Gain->pts_cnt == 2 &&
		    (Gain->value[0].numerator == Gain->value[1].numerator) &&
		    (Gain->value[0].denominator == Gain->value[1].denominator)) {
			if (aafRationalToDouble (Gain->value[0]) == 1.0f) {
				/*
				 * Skipping any 1:1 gain allows not to miss any other actual gain (eg. DR_Audio_Levels.aaf, Resolve 18.5.AAF)
				 *
				     │ 02412││         ├──◻ AAFClassID_OperationGroup (OpIdent: AAFOperationDef_MonoAudioGain; Length: 284630)
				 err │ 03839││         │    ├──◻ AAFClassID_VaryingValue : : Value is continuous 1:1 (0db), skipping it.
				     │      ││         │    │
				     │ 02412││         │    └──◻ AAFClassID_OperationGroup (OpIdent: AAFOperationDef_MonoAudioGain; Length: 284630)
				     │ 03660││         │         ├──◻ AAFClassID_ConstantValue (Value: 6023/536870912  -99.0 dB)
				     │ 02983││         │         └──◻ AAFClassID_SourceClip (Length: 284630)
				     │ 02988││         │              └──◻ AAFClassID_MasterMob (UsageCode: n/a) : speech-sample.mp3 - -100db
				     │ 04553││         │                   └──◻ AAFClassID_TimelineMobSlot [slot:1 track:1] (DataDef : AAFDataDef_Sound)
				     │ 03193││         │                        └──◻ AAFClassID_SourceClip (Length: 284630)
				     │ 04297││         │                             └──◻ AAFClassID_SourceMob (UsageCode: n/a) : speech-sample.mp3 - -100db
				     │ 01342││         │                                  └──◻ AAFClassID_PCMDescriptor
				     │ 01529││         │                                       └──◻ AAFClassID_NetworkLocator : file:///C:/Users/user/Desktop/libAAF/test/res/speech-sample.mp3
				 */
				TRACE_OBJ_INFO (aafi, VaryingValue, &__td, "Value is continuous 1:1 (0db), skipping it.");
				aafi_freeAudioGain (Gain);
				return -1;
			}
			Gain->flags |= AAFI_AUDIO_GAIN_CONSTANT;
		} else {
			Gain->flags |= AAFI_AUDIO_GAIN_VARIABLE;
		}

		if (aafi->ctx.current_opgroup_affect_track) {
			/*
			 * Track-based Volume
			 */
			if (!aafi->ctx.current_track) {
				TRACE_OBJ_ERROR (aafi, VaryingValue, &__td, "Current track not set, dropping this volume");
				aafi_freeAudioGain (Gain);
				return -1;
			}
			if (aafi->ctx.current_track->gain) {
				TRACE_OBJ_ERROR (aafi, VaryingValue, &__td, "Track volume was already set");
				aafi_freeAudioGain (Gain);
				return -1;
			} else {
				aafi->ctx.current_track->gain = Gain;
				TRACE_OBJ (aafi, VaryingValue, &__td);
			}
		} else {
			/*
			 * Clip-based Gain
			 * Gain is saved in context and it will be set to all OperationGroup descendent clips.
			 */
			if (Gain->flags & AAFI_AUDIO_GAIN_CONSTANT) {
				if (aafi->ctx.current_clip_gain) {
					TRACE_OBJ_ERROR (aafi, VaryingValue, &__td, "Clip gain was already set");
					aafi_freeAudioGain (Gain);
					return -1;
				} else {
					aafi->ctx.current_clip_gain = Gain;
					TRACE_OBJ (aafi, VaryingValue, &__td);
				}
			} else {
				if (aafi->ctx.current_clip_variable_gain) {
					TRACE_OBJ_ERROR (aafi, VaryingValue, &__td, "Clip automation was already set");
					aafi_freeAudioGain (Gain);
					return -1;
				} else {
					aafi->ctx.current_clip_variable_gain = Gain;
					TRACE_OBJ (aafi, VaryingValue, &__td);
				}
			}
		}
	} else if (aafUIDCmp (OperationIdentification, &AAFOperationDef_MonoAudioPan) &&
	           aafUIDCmp (ParamDef, &AAFParameterDef_Pan)) {
		if (!aafi->ctx.current_opgroup_affect_track) {
			/*
			 * « Pan automation shall be track-based. If an application has a different
			 * native representation (e.g., clip-based pan), it shall convert to and
			 * from its native representation when exporting and importing the composition. »
			 *
			 * NOTE: Never encountered clip-based pan AAF.
			 */
			TRACE_OBJ_ERROR (aafi, VaryingValue, &__td, "Pan shall be track based");
			return -1;
		}

		if (!aafi->ctx.current_track) {
			TRACE_OBJ_ERROR (aafi, VaryingValue, &__td, "Current track not set");
			return -1;
		}

		if (aafi->ctx.current_track->pan) {
			TRACE_OBJ_ERROR (aafi, VaryingValue, &__td, "Track Pan was already set");
			return -1;
		}

		aafiAudioPan* Pan = aafi_newAudioPan (aafi, 0, interpolation, NULL);

		if (!Pan) {
			TRACE_OBJ_ERROR (aafi, VaryingValue, &__td, "Could not create new pan");
			return -1;
		}

		int pts_cnt = retrieve_ControlPoints (aafi, Points, &Pan->time, &Pan->value);

		if (pts_cnt < 0) {
			TRACE_OBJ_ERROR (aafi, VaryingValue, &__td, "Could not retrieve ControlPoints");
			free (Pan);
			return -1;
		}

		Pan->pts_cnt = (unsigned int)pts_cnt;

		// for ( int i = 0; i < Gain->pts_cnt; i++ ) {
		// 	debug( "time_%i : %i/%i   value_%i : %i/%i", i, Gain->time[i].numerator, Gain->time[i].denominator, i, Gain->value[i].numerator, Gain->value[i].denominator  );
		// }

		/*
		 * If Pan has 2 ControlPoints with both the same value, it means
		 * we have a constant Pan curve. So we can assume constant Pan here.
		 */

		if (Pan->pts_cnt == 2 &&
		    (Pan->value[0].numerator == Pan->value[1].numerator) &&
		    (Pan->value[0].denominator == Pan->value[1].denominator)) {
			Pan->flags |= AAFI_AUDIO_GAIN_CONSTANT;
		} else {
			Pan->flags |= AAFI_AUDIO_GAIN_VARIABLE;
		}

		aafi->ctx.current_track->pan = Pan;
		TRACE_OBJ (aafi, VaryingValue, &__td);
	} else {
		TRACE_OBJ_NO_SUPPORT (aafi, VaryingValue, &__td);
	}

	return 0;
}

/* ****************************************************************************
 *                    E s s e n c e D e s c r i p t o r
 * ****************************************************************************
 *
 *  EssenceDescriptor (abs)
 *          |
 *          |--> FileDescriptor (abs)
 *          |          |
 *          |          |--> WAVEDescriptor
 *          |          |--> AIFCDescriptor
 *          |          |--> SoundDescriptor
 *          |          |          |
 *          |          |          `--> PCMDescriptor
 *          |          |
 *          |          `--> DigitalImageDescriptor (abs)
 *          |                     |
 *          |                     `--> CDCIDescriptor
 *          |
 *          |
 *          |--> PhysicalDescriptor
 *          `--> TapeDescriptor
 */

static int
parse_EssenceDescriptor (AAF_Iface* aafi, aafObject* EssenceDesc, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 1);

	if (!aaf_get_property (EssenceDesc, PID_EssenceDescriptor_Locator))
		__td.eob = 1;

	if (aafUIDCmp (EssenceDesc->Class->ID, &AAFClassID_PCMDescriptor)) {
		parse_PCMDescriptor (aafi, EssenceDesc, &__td);
	} else if (aafUIDCmp (EssenceDesc->Class->ID, &AAFClassID_WAVEDescriptor)) {
		parse_WAVEDescriptor (aafi, EssenceDesc, &__td);
	} else if (aafUIDCmp (EssenceDesc->Class->ID, &AAFClassID_AIFCDescriptor)) {
		parse_AIFCDescriptor (aafi, EssenceDesc, &__td);
	} else if (aafUIDCmp (EssenceDesc->Class->ID, &AAFClassID_SoundDescriptor)) {
		/* Compressed Audio (MP3, AAC ?). Not encountered yet (Davinci Resolve describes MP3 using PCMDescriptor...) */
		TRACE_OBJ_NO_SUPPORT (aafi, EssenceDesc, &__td);
	} else if (aafUIDCmp (EssenceDesc->Class->ID, &AAFClassID_AES3PCMDescriptor)) {
		/* Not described in specs, not encountered yet. */
		TRACE_OBJ_NO_SUPPORT (aafi, EssenceDesc, &__td);
	} else if (aafUIDCmp (EssenceDesc->Class->ID, &AAFClassID_MultipleDescriptor)) {
		/*
		 * A MultipleDescriptor contains a vector of FileDescriptor objects and is
		 * used when the file source consists of multiple tracks of essence (e.g MXF).
		 * Each essence track is described by a MobSlots object in the SourceMob and a
		 * FileDescriptor object. The FileDescriptor is linked to the MobSlot by
		 * setting the FileDescriptor::LinkedSlotID property equal to the
		 * MobSlot::SlotID property.
		 *
		 * -> test.aaf
		 * -> /test/private/thirdparty/2997fps-DFTC.aaf
		 */

		TRACE_OBJ_NO_SUPPORT (aafi, EssenceDesc, &__td);
	} else if (aafUIDCmp (EssenceDesc->Class->ID, &AAFClassID_CDCIDescriptor)) {
		parse_CDCIDescriptor (aafi, EssenceDesc, &__td);
	} else {
		TRACE_OBJ_NO_SUPPORT (aafi, EssenceDesc, &__td);
	}

	/*
	 * Locators are a property of EssenceDescriptor. The property holds a vector of
	 * Locators object, that should provide information to help find a file that
	 * contains the essence (WAV, MXF, etc.) or to help find the physical media.
	 *
	 * A Locator can either be a NetworkLocator or a TextLocator.
	 *
	 * A NetworkLocator holds a URLString property :
	 *
	 * p.41 : Absolute Uniform Resource Locator (URL) complying with RFC 1738 or relative
	 * Uniform Resource Identifier (URI) complying with RFC 2396 for file containing
	 * the essence. If it is a relative URI, the base URI is determined from the URI
	 * of the AAF file itself.
	 * Informative note: A valid URL or URI uses a constrained character set and uses
	 * the / character as the path separator.
	 */

	aafObject* Locator  = NULL;
	aafObject* Locators = aaf_get_propertyValue (EssenceDesc, PID_EssenceDescriptor_Locator, &AAFTypeID_LocatorStrongReferenceVector);

	uint32_t i = 0;
	AAFI_foreach_ObjectInSet (&Locator, Locators, i, __td)
	{
		/* TODO retrieve all locators, then when searching file, try all parsed locators. */
		parse_Locator (aafi, Locator, &__td);
	}

	return 0;
}

static int
parse_PCMDescriptor (AAF_Iface* aafi, aafObject* PCMDescriptor, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 0);

	aafiAudioEssenceFile* audioEssenceFile = (aafiAudioEssenceFile*)aafi->ctx.current_audio_essence;

	if (!audioEssenceFile) {
		TRACE_OBJ_ERROR (aafi, PCMDescriptor, &__td, "aafi->ctx.current_audio_essence not set");
		return -1;
	}

	audioEssenceFile->type = AAFI_ESSENCE_TYPE_PCM;

	/* Duration of the essence in sample units (not edit units !) */
	aafPosition_t* length = aaf_get_propertyValue (PCMDescriptor, PID_FileDescriptor_Length, &AAFTypeID_PositionType);

	if (!length) {
		TRACE_OBJ_ERROR (aafi, PCMDescriptor, &__td, "Missing FileDescriptor::Length");
		return -1;
	}

	audioEssenceFile->length = *length;

	uint32_t* channels = aaf_get_propertyValue (PCMDescriptor, PID_SoundDescriptor_Channels, &AAFTypeID_UInt32);

	if (!channels) {
		TRACE_OBJ_ERROR (aafi, PCMDescriptor, &__td, "Missing SoundDescriptor::Channels");
		return -1;
	}

	if (*channels >= USHRT_MAX) {
		TRACE_OBJ_ERROR (aafi, PCMDescriptor, &__td, "SoundDescriptor::Channels bigger than USHRT_MAX");
		return -1;
	}

	audioEssenceFile->channels = *(uint16_t*)channels;

	aafRational_t* samplerate = aaf_get_propertyValue (PCMDescriptor, PID_FileDescriptor_SampleRate, &AAFTypeID_Rational);

	if (!samplerate) {
		TRACE_OBJ_ERROR (aafi, PCMDescriptor, &__td, "Missing FileDescriptor::SampleRate");
		return -1;
	}

	if (samplerate->denominator != 1) {
		TRACE_OBJ_ERROR (aafi, PCMDescriptor, &__td, "FileDescriptor::SampleRate should be integer but is %i/%i", samplerate->numerator, samplerate->denominator);
		return -1;
	}

	if (samplerate->numerator < 0) {
		TRACE_OBJ_ERROR (aafi, PCMDescriptor, &__td, "FileDescriptor::SampleRate value is invalid : %i", samplerate->numerator);
		return -1;
	}

	audioEssenceFile->samplerate = (uint32_t)samplerate->numerator;

	audioEssenceFile->samplerateRational->numerator   = samplerate->numerator;
	audioEssenceFile->samplerateRational->denominator = samplerate->denominator;

	uint32_t* samplesize = aaf_get_propertyValue (PCMDescriptor, PID_SoundDescriptor_QuantizationBits, &AAFTypeID_UInt32);

	if (!samplesize) {
		TRACE_OBJ_ERROR (aafi, PCMDescriptor, &__td, "Missing SoundDescriptor::QuantizationBits");
		return -1;
	}

	if (*samplesize >= USHRT_MAX) {
		TRACE_OBJ_ERROR (aafi, PCMDescriptor, &__td, "SoundDescriptor::QuantizationBits bigger than USHRT_MAX : %u", samplesize);
		return -1;
	}

	TRACE_OBJ (aafi, PCMDescriptor, &__td);

	audioEssenceFile->samplesize = *(uint16_t*)samplesize;

	return 0;
}

static int
parse_WAVEDescriptor (AAF_Iface* aafi, aafObject* WAVEDescriptor, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 0);

	aafiAudioEssenceFile* audioEssenceFile = (aafiAudioEssenceFile*)aafi->ctx.current_audio_essence;

	if (!audioEssenceFile) {
		TRACE_OBJ_ERROR (aafi, WAVEDescriptor, &__td, "aafi->ctx.current_audio_essence not set");
		return -1;
	}

	audioEssenceFile->type = AAFI_ESSENCE_TYPE_WAVE;

	aafProperty* summary = aaf_get_property (WAVEDescriptor, PID_WAVEDescriptor_Summary);

	if (!summary) {
		TRACE_OBJ_ERROR (aafi, WAVEDescriptor, &__td, "Missing WAVEDescriptor::Summary");
		return -1;
	}

	audioEssenceFile->summary = summary;

	/*
	 * NOTE : Summary is parsed later in "post-processing" aafi_retrieveData(),
	 * to be sure clips and essences are linked, so we are able to fallback on
	 * essence stream in case summary does not contain the full header part.
	 *
	 * TODO parse it here
	 */

	TRACE_OBJ (aafi, WAVEDescriptor, &__td);

	return 0;
}

static int
parse_AIFCDescriptor (AAF_Iface* aafi, aafObject* AIFCDescriptor, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 0);

	aafiAudioEssenceFile* audioEssenceFile = (aafiAudioEssenceFile*)aafi->ctx.current_audio_essence;

	if (!audioEssenceFile) {
		TRACE_OBJ_ERROR (aafi, AIFCDescriptor, &__td, "aafi->ctx.current_audio_essence not set");
		return -1;
	}

	audioEssenceFile->type = AAFI_ESSENCE_TYPE_AIFC;

	aafProperty* summary = aaf_get_property (AIFCDescriptor, PID_AIFCDescriptor_Summary);

	if (!summary) {
		TRACE_OBJ_ERROR (aafi, AIFCDescriptor, &__td, "Missing AIFCDescriptor::Summary");
		return -1;
	}

	audioEssenceFile->summary = summary;

	/*
	 * NOTE : Summary is parsed later in "post-processing" aafi_retrieveData(),
	 * to be sure clips and essences are linked, so we are able to fallback on
	 * essence stream in case summary does not contain the full header part.
	 */

	TRACE_OBJ (aafi, AIFCDescriptor, &__td);

	return 0;
}

static int
parse_DigitalImageDescriptor (AAF_Iface* aafi, aafObject* DIDescriptor, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 0);

	/* TODO parse and save content to videoEssenceFile */

	aafiVideoEssence* videoEssenceFile = aafi->ctx.current_video_essence;

	if (!videoEssenceFile) {
		TRACE_OBJ_ERROR (aafi, DIDescriptor, &__td, "aafi->ctx.current_video_essence not set");
		return -1;
	}

	/*
	 * « Informative note: In the case of picture essence, the Sample Rate is usually the frame rate. The value should be
	 * numerically exact, for example {25,1} or {30000, 1001}. »
	 *
	 * « Informative note: Care should be taken if a sample rate of {2997,100} is encountered, since this may have been intended
	 * as a (mistaken) approximation to the exact value. »
	 */

	aafRational_t* framerate = aaf_get_propertyValue (DIDescriptor, PID_FileDescriptor_SampleRate, &AAFTypeID_Rational);

	if (!framerate) {
		TRACE_OBJ_ERROR (aafi, DIDescriptor, &__td, "Missing FileDescriptor::SampleRate (framerate)");
		return -1;
	}

	videoEssenceFile->framerate = framerate;

	debug ("Video framerate : %i/%i", framerate->numerator, framerate->denominator);

	/*
	 * All mandatory properties below are treated as optional, because we assume that
	 * video will be an external file so we are not using those, and because some AAF
	 * implementations does not even set those mandatory properties (eg. Davinci Resolve).
	 *
	 * TODO: parse PID_FileDescriptor_Length ?
	 */

	return 0;
}

static int
parse_CDCIDescriptor (AAF_Iface* aafi, aafObject* CDCIDescriptor, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 0);

	/* TODO parse CDCI class */

	int rc = parse_DigitalImageDescriptor (aafi, CDCIDescriptor, __ptd);

	if (!rc)
		TRACE_OBJ (aafi, CDCIDescriptor, &__td);

	return rc;
}

/*
 *            Locator (abs)
 *               |
 *       ,---------------.
 *       |               |
 * NetworkLocator   TextLocator
 */

static int
parse_Locator (AAF_Iface* aafi, aafObject* Locator, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 1);
	__td.eob = 1;

	if (aafUIDCmp (Locator->Class->ID, &AAFClassID_NetworkLocator)) {
		parse_NetworkLocator (aafi, Locator, &__td);
	} else if (aafUIDCmp (Locator->Class->ID, &AAFClassID_TextLocator)) {
		/*
		 * A TextLocator object provides information to the user to help locate the file
		 * containing the essence or to locate the physical media. The TextLocator is not
		 * intended for applications to use without user intervention.
		 *
		 * Not encountered yet.
		 */

		TRACE_OBJ_NO_SUPPORT (aafi, Locator, &__td);

		char* name = aaf_get_propertyValue (Locator, PID_TextLocator_Name, &AAFTypeID_String);
		debug ("Got an AAFClassID_TextLocator : \"%s\"", name);
		free (name);
	} else {
		TRACE_OBJ_NO_SUPPORT (aafi, Locator, &__td);
	}

	return 0;
}

static int
parse_NetworkLocator (AAF_Iface* aafi, aafObject* NetworkLocator, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 0);

	/*
	 * This holds an URI pointing to the essence file, when it is not embedded.
	 * However, sometimes it holds an URI to the AAF file itself when essence is
	 * embedded so it's not a reliable way to guess if essence is embedded or not.
	 */

	char* original_file_path = aaf_get_propertyValue (NetworkLocator, PID_NetworkLocator_URLString, &AAFTypeID_String);

	if (original_file_path == NULL) {
		TRACE_OBJ_ERROR (aafi, NetworkLocator, &__td, "Missing NetworkLocator::URLString");
		goto err;
	}

	if (aafi->ctx.current_audio_essence) {
		if (aafi->ctx.current_audio_essence->original_file_path) {
			TRACE_OBJ_ERROR (aafi, NetworkLocator, &__td, "File path was already set");
			goto err;
		} else {
			aafi->ctx.current_audio_essence->original_file_path = original_file_path;
		}
	} else if (aafi->ctx.current_video_essence) {
		if (aafi->ctx.current_video_essence->original_file_path) {
			TRACE_OBJ_ERROR (aafi, NetworkLocator, &__td, "File path was already set");
			goto err;
		} else {
			aafi->ctx.current_video_essence->original_file_path = original_file_path;
		}
	} else {
		TRACE_OBJ_ERROR (aafi, NetworkLocator, &__td, "No current essence set");
		goto err;
	}

	TRACE_OBJ (aafi, NetworkLocator, &__td);

	return 0;

err:
	free (original_file_path);

	return -1;
}

static int
parse_EssenceData (AAF_Iface* aafi, aafObject* EssenceData, td* __ptd)
{
	struct trace_dump __td;
	__td_set (__td, __ptd, 1);
	__td.eob = 1;

	int rc = 0;

	char* streamName = NULL;
	char* dataPath   = NULL;

	aafiAudioEssenceFile* audioEssenceFile = (aafiAudioEssenceFile*)aafi->ctx.current_audio_essence;

	if (!audioEssenceFile) {
		TRACE_OBJ_ERROR (aafi, EssenceData, &__td, "aafi->ctx.current_audio_essence not set");
		return -1;
	}

	streamName = aaf_get_propertyValue (EssenceData, PID_EssenceData_Data, &AAFTypeID_String);

	if (!streamName) {
		TRACE_OBJ_ERROR (aafi, EssenceData, &__td, "Missing EssenceData::Data");
		goto err;
	}

	char* path = aaf_get_ObjectPath (EssenceData);

	if (!path) {
		TRACE_OBJ_ERROR (aafi, EssenceData, &__td, "Could not retrieve EssenceData node path");
		goto err;
	}

	dataPath = laaf_util_build_path (AAF_DIR_SEP_STR, path, streamName, NULL);

	if (!dataPath) {
		TRACE_OBJ_ERROR (aafi, EssenceData, &__td, "Could not build Data stream path");
		goto err;
	}

	cfbNode* DataNode = cfb_getNodeByPath (aafi->aafd->cfbd, dataPath, 0);

	if (!DataNode) {
		TRACE_OBJ_ERROR (aafi, EssenceData, &__td, "Could not retrieve Data stream node: %s", dataPath);
		goto err;
	}

	TRACE_OBJ (aafi, EssenceData, &__td);

	debug ("Embedded data stream : %s", dataPath);

	audioEssenceFile->node        = DataNode;
	audioEssenceFile->is_embedded = 1;

	rc = 0;

	goto end;

err:
	rc = -1;

end:

	free (streamName);
	free (dataPath);

	return rc;
}

static int
retrieve_UserComments (AAF_Iface* aafi, aafObject* UserComments, aafiMetaData** metadataList)
{
	aafObject* UserComment = NULL;

	int error = 0;

	AAF_foreach_ObjectInSet (&UserComment, UserComments, NULL)
	{
		char* text = NULL;
		char* name = NULL;

		if (!aafUIDCmp (UserComment->Class->ID, &AAFClassID_TaggedValue)) {
			warning ("Parsing UserComments: Expected TaggedValue but got %s", aaft_ClassIDToText (aafi->aafd, UserComment->Class->ID));
			goto UserCommentError;
		}

		name = aaf_get_propertyValue (UserComment, PID_TaggedValue_Name, &AAFTypeID_String);

		if (!name) {
			warning ("Parsing UserComments: Missing TaggedValue::Name");
			goto UserCommentError;
		}

		aafIndirect_t* Indirect = aaf_get_propertyValue (UserComment, PID_TaggedValue_Value, &AAFTypeID_Indirect);

		if (!Indirect) {
			warning ("Parsing UserComments: Missing TaggedValue::Value");
			goto UserCommentError;
		}

		text = aaf_get_indirectValue (aafi->aafd, Indirect, &AAFTypeID_String);

		if (!text) {
			warning ("Parsing UserComments: Could not retrieve Indirect value for TaggedValue::Value");
			goto UserCommentError;
		}

		aafiMetaData* Comment = aafi_newMetadata (aafi, metadataList);

		if (!Comment) {
			warning ("Parsing UserComments: Could not create new UserComment");
			goto UserCommentError;
		}

		Comment->name = name;
		Comment->text = text;

		continue;

	UserCommentError:

		error++;

		free (name);
		free (text);
	}

	if (error)
		return -1;

	return 0;
}

static int
retrieve_ControlPoints (AAF_Iface* aafi, aafObject* Points, aafRational_t* times[], aafRational_t* values[])
{
	/*
	 * We do not handle trace here, because there could possibly be hundreds of
	 * ControlPoints to print, plus retrieve_ControlPoints() is being called before
	 * VaryingValue Object is logged.
	 */

	*times  = calloc (Points->Header->_entryCount, sizeof (aafRational_t));
	*values = calloc (Points->Header->_entryCount, sizeof (aafRational_t));

	if (!*times || !*values) {
		error ("Out of memory");
		return -1;
	}

	aafObject* Point = NULL;

	unsigned int i = 0;

	AAF_foreach_ObjectInSet (&Point, Points, NULL)
	{
		if (!aafUIDCmp (Point->Class->ID, &AAFClassID_ControlPoint)) {
			error ("Object is not AAFClassID_ControlPoint : %s", aaft_ClassIDToText (aafi->aafd, Point->Class->ID));
			continue;
		}

		aafRational_t* time = aaf_get_propertyValue (Point, PID_ControlPoint_Time, &AAFTypeID_Rational);

		if (!time) {
			error ("Missing ControlPoint::Time");

			free (*times);
			*times = NULL;
			free (*values);
			*values = NULL;

			return -1;
		}

		aafIndirect_t* Indirect = aaf_get_propertyValue (Point, PID_ControlPoint_Value, &AAFTypeID_Indirect);

		if (!Indirect) {
			error ("Missing Indirect ControlPoint::Value");

			free (*times);
			*times = NULL;
			free (*values);
			*values = NULL;

			return -1;
		}

		aafRational_t* value = aaf_get_indirectValue (aafi->aafd, Indirect, &AAFTypeID_Rational);

		if (!value) {
			error ("Could not retrieve Indirect value for ControlPoint::Value");

			free (*times);
			*times = NULL;
			free (*values);
			*values = NULL;

			return -1;
		}

		memcpy ((*times + i), time, sizeof (aafRational_t));
		memcpy ((*values + i), value, sizeof (aafRational_t));

		i++;
	}

	if (Points->Header->_entryCount != i) {
		warning ("ControlPoints _entryCount (%i) does not match iteration (%i).", Points->Header->_entryCount, i);
		return (int)i;
	}

	return (int)Points->Header->_entryCount;
}

int
aafi_retrieveData (AAF_Iface* aafi)
{
	/* this __td is only here for debug/error, normal trace is printed from parse_Mob() */
	static td __td;
	__td.fn  = __LINE__;
	__td.pfn = 0;
	__td.lv  = 0;
	__td.ll  = calloc (1024, sizeof (int));
	if (!__td.ll) {
		error ("Out of memory");
		return -1;
	}
	__td.ll[0] = 0;

	int        compositionMobParsed = 0;
	aafObject* Mob                  = NULL;

	uint32_t i = 0;
	AAFI_foreach_ObjectInSet (&Mob, aafi->aafd->Mobs, i, __td)
	{
		if (aafUIDCmp (Mob->Class->ID, &AAFClassID_MasterMob) ||
		    aafUIDCmp (Mob->Class->ID, &AAFClassID_SourceMob)) {
			// TRACE_OBJ_WARNING( aafi, Mob, &__td, " PRINTS FOR DEBUG ONLY: Will be parsed later" );
			continue;
		}

		if (!aafUIDCmp (Mob->Class->ID, &AAFClassID_CompositionMob)) {
			/* there should not be anything other than MasterMob, SourceMob or CompositionMob */
			TRACE_OBJ_NO_SUPPORT (aafi, Mob, &__td);
			continue;
		}

		aafUID_t* UsageCode = aaf_get_propertyValue (Mob, PID_Mob_UsageCode, &AAFTypeID_UsageType);

		if (!aafUIDCmp (UsageCode, &AAFUsage_TopLevel) && (aafUIDCmp (aafi->aafd->Header.OperationalPattern, &AAFOPDef_EditProtocol) || UsageCode)) {
			/*
			 * If we run against AAFOPDef_EditProtocol, we process only TopLevels CompositionMobs.
			 * If there is more than one, we have multiple Compositions in a single AAF.
			 */
			// TRACE_OBJ_WARNING( aafi, Mob, &__td, " PRINTS FOR DEBUG ONLY: Will be parsed later" );
			continue;
		}

		if (compositionMobParsed) {
			TRACE_OBJ_ERROR (aafi, Mob, &__td, "Multiple top level CompositionMob not supported yet");
			continue;
		}

		RESET_CONTEXT (aafi->ctx);

		__td.fn  = __LINE__;
		__td.pfn = 0;
		__td.lv  = 0;

		parse_Mob (aafi, Mob, &__td);

		if (aafUIDCmp (UsageCode, &AAFUsage_TopLevel)) {
			compositionMobParsed = 1;
		}
	}

	free (__td.ll);

	if (aafi->Timecode == NULL) {
		/* TODO, shouldn't we leave aafi->Timecode as NULL ? */
		warning ("No timecode found in file. Setting to 00:00:00:00 @ 25fps");

		aafiTimecode* tc = calloc (1, sizeof (aafiTimecode));

		if (!tc) {
			error ("Out of memory");
			return -1;
		}

		tc->start     = 0;
		tc->fps       = 25;
		tc->drop      = 0;
		tc->edit_rate = &AAFI_DEFAULT_TC_EDIT_RATE;

		aafi->Timecode = tc;
	}

	/* Post processing */

	char* commonPathPart = NULL;

	int                   externalAudioEssenceFileCount = 0;
	aafiAudioEssenceFile* audioEssenceFile              = NULL;

	AAFI_foreachAudioEssenceFile (aafi, audioEssenceFile)
	{
		if (!audioEssenceFile->is_embedded) {
			if (!audioEssenceFile->original_file_path) {
				continue;
			}

			externalAudioEssenceFileCount++;

			if (!commonPathPart) {
				int rc = laaf_util_snprintf_realloc (&commonPathPart, 0, 0, "%s", audioEssenceFile->original_file_path);

				if (rc < 0) {
					error ("Failed to set commonPathPart");
					free (commonPathPart);
					commonPathPart = NULL;
				}

				// printf("Firstinit: %s\n", commonPathPart);

				continue;
			}

			char* a = commonPathPart;
			char* b = audioEssenceFile->original_file_path;

			char* aEnd = a + strlen (a);
			char* bEnd = b + strlen (b);

			while (a < aEnd && b < bEnd) {
				if (*a != *b) {
					// printf("%c != %c\n", *a, *b );
					break;
				}
				a++;
				b++;
			}

			// printf("Then: %s                         %s\n", commonPathPart, audioEssenceFile->original_file_path);

			if (a < aEnd) {
				*a = 0x00;
			}

			// audioEssenceFile->usable_file_path = aafi_locate_external_essence_file( aafi, audioEssenceFile->original_file_path, aafi->ctx.options.media_location );
			//
			// if ( audioEssenceFile->usable_file_path == NULL ) {
			// 	warning( "Could not locate external audio essence file '%s'", audioEssenceFile->original_file_path );
			// }
		}
	}

	if (externalAudioEssenceFileCount <= 1) {
		// printf("commonPathPart: %s\n", commonPathPart );
		free (commonPathPart);
		commonPathPart = NULL;
	} else if (commonPathPart && *commonPathPart != 0x00) {
		/*
		 * Sometimes, all essence file names share a common prefix (Logic Pro).
		 * That's why we need to trim from the end, up to the last dir sep.
		 */

		char* p = commonPathPart + strlen (commonPathPart);

		while (p > commonPathPart) {
			if (*p == '/') { /* parsing URI, so will always be '/' as separator character */
				break;
			} else {
				*p = 0x00;
			}
			p--;
		}
	}

	audioEssenceFile = NULL;

	AAFI_foreachAudioEssenceFile (aafi, audioEssenceFile)
	{
		if (!audioEssenceFile->is_embedded) {
			audioEssenceFile->usable_file_path = aafi_locate_external_essence_file (aafi, audioEssenceFile->original_file_path, commonPathPart, aafi->ctx.options.media_location);

			if (audioEssenceFile->usable_file_path == NULL) {
				warning ("Could not locate external audio essence file '%s'", audioEssenceFile->original_file_path);
			}
		}

		if (audioEssenceFile->summary || audioEssenceFile->usable_file_path) {
			aafi_parse_audio_essence (aafi, audioEssenceFile);
		}
	}

	/*
	 * Define AAF samplerate and samplesize with the most used values accross all audio essences.
	 */

	uint32_t maxOccurence = 0;

	AAFI_foreachAudioEssenceFile (aafi, audioEssenceFile)
	{
		uint32_t              count = 1;
		aafiAudioEssenceFile* ae    = NULL;

		if (audioEssenceFile->samplerate == aafi->Audio->samplerate &&
		    audioEssenceFile->samplesize == aafi->Audio->samplesize) {
			continue;
		}

		AAFI_foreachEssence (audioEssenceFile->next, ae)
		{
			if (audioEssenceFile->samplerate == ae->samplerate && audioEssenceFile->samplesize == ae->samplesize) {
				count++;
			}
		}

		debug ("Essence count @ %u Hz / %u bits : %i", audioEssenceFile->samplerate, audioEssenceFile->samplesize, count);

		if (count > maxOccurence) {
			maxOccurence                    = count;
			aafi->Audio->samplesize         = audioEssenceFile->samplesize;
			aafi->Audio->samplerate         = audioEssenceFile->samplerate;
			aafi->Audio->samplerateRational = audioEssenceFile->samplerateRational;
		}
	}

	aafiVideoEssence* videoEssenceFile = NULL;

	AAFI_foreachVideoEssence (aafi, videoEssenceFile)
	{
		if (videoEssenceFile->original_file_path == NULL) {
			continue;
		}

		videoEssenceFile->usable_file_path = aafi_locate_external_essence_file (aafi, videoEssenceFile->original_file_path, commonPathPart, aafi->ctx.options.media_location);

		if (videoEssenceFile->usable_file_path == NULL) {
			error ("Could not locate external video essence file '%s'", videoEssenceFile->original_file_path);
			continue;
		}
	}

	free (commonPathPart);

	aafPosition_t   trackEnd   = 0;
	aafiAudioTrack* audioTrack = NULL;

	AAFI_foreachAudioTrack (aafi, audioTrack)
	{
		if (aafi->compositionLength_editRate) {
			trackEnd = aafi_convertUnit (audioTrack->current_pos, audioTrack->edit_rate, aafi->compositionLength_editRate);
		} else {
			trackEnd = audioTrack->current_pos;
		}

		if (trackEnd > aafi->compositionLength) {
			debug ("Setting compositionLength with audio track \"%s\" (%u) : %" PRIi64, audioTrack->name, audioTrack->number, audioTrack->current_pos);
			aafi->compositionLength          = audioTrack->current_pos;
			aafi->compositionLength_editRate = audioTrack->edit_rate;
		}

		aafiTimelineItem* audioItem = NULL;
		aafiAudioClip*    audioClip = NULL;

		AAFI_foreachTrackItem (audioTrack, audioItem)
		{
			if (audioItem->type != AAFI_AUDIO_CLIP) {
				continue;
			}

			audioClip           = (aafiAudioClip*)audioItem->data;
			audioClip->channels = aafi_getAudioEssencePointerChannelCount (audioClip->essencePointerList);

			/*
			 * we check if any previous clip is using the exact same essence pointer,
			 * to avoid duplication and allow to detect when multiple clips are using
			 * the same essence.
			 */
			aafiAudioEssencePointer* prev = aafi_audioEssencePointer_exists_before (aafi, audioClip->essencePointerList);

			if (prev) {
				audioClip->essencePointerList = prev;
			}
		}
	}

	aafiVideoTrack* videoTrack = NULL;

	AAFI_foreachVideoTrack (aafi, videoTrack)
	{
		if (aafi->compositionLength_editRate) {
			trackEnd = aafi_convertUnit (videoTrack->current_pos, videoTrack->edit_rate, aafi->compositionLength_editRate);
		} else {
			trackEnd = videoTrack->current_pos;
		}

		if (trackEnd > aafi->compositionLength) {
			debug ("Setting compositionLength with video track \"%s\" (%u) : %" PRIi64, videoTrack->name, videoTrack->number, videoTrack->current_pos);
			aafi->compositionLength          = videoTrack->current_pos;
			aafi->compositionLength_editRate = videoTrack->edit_rate;
		}
	}

	aafi->compositionStart          = aafi->Timecode->start;
	aafi->compositionStart_editRate = aafi->Timecode->edit_rate;

	if (protools_AAF (aafi)) {
		protools_post_processing (aafi);
	}

	return 0;
}

void
aafi_dump_obj (AAF_Iface* aafi, aafObject* Obj, struct trace_dump* __td, int state, const char* func, int line, const char* fmt, ...)
{
	va_list        args;
	struct aafLog* log = aafi->log;

	if (aafi->ctx.options.trace == 0) {
		enum verbosityLevel_e verbtype = VERB_QUIET;

		switch (state) {
			case TD_ERROR:
				verbtype = VERB_ERROR;
				break;
			case TD_WARNING:
			case TD_NOT_SUPPORTED:
				verbtype = VERB_WARNING;
				break;
			// case TD_INFO:
			// case TD_OK:            verbtype = VERB_DEBUG;   break;
			default:
				return;
		}

		if (aafi->log->verb < verbtype) {
			return;
		}

		char* buf = NULL;

		va_start (args, fmt);

		int rc = laaf_util_vsnprintf_realloc (&buf, NULL, 0, fmt, args);

		va_end (args);

		if (rc < 0) {
			LOG_BUFFER_WRITE (log, "laaf_util_vsnprintf_realloc() error");
			return;
		}

		laaf_write_log (aafi->log, aafi, LOG_SRC_ID_AAF_IFACE, VERB_ERROR, __FILENAME__, func, line, buf);

		free (buf);

		return;
	}

	if (Obj) {
		switch (state) {
			case TD_ERROR:
				LOG_BUFFER_WRITE (log, "%serr %s%s %s", ANSI_COLOR_RED (log), ANSI_COLOR_DARKGREY (log), TREE_LINE, ANSI_COLOR_RED (log));
				break;
			case TD_WARNING:
				LOG_BUFFER_WRITE (log, "%swrn %s%s %s", ANSI_COLOR_YELLOW (log), ANSI_COLOR_DARKGREY (log), TREE_LINE, ANSI_COLOR_YELLOW (log));
				break;
			case TD_NOT_SUPPORTED:
				LOG_BUFFER_WRITE (log, "%suns %s%s %s", ANSI_COLOR_ORANGE (log), ANSI_COLOR_DARKGREY (log), TREE_LINE, ANSI_COLOR_ORANGE (log));
				break;
			default:
				LOG_BUFFER_WRITE (log, "    %s%s ", ANSI_COLOR_DARKGREY (log), TREE_LINE);
				break;
		}
		LOG_BUFFER_WRITE (log, "%05i", line);
	} else {
		LOG_BUFFER_WRITE (log, "    %s%s%s      ", ANSI_COLOR_DARKGREY (log), TREE_LINE, ANSI_COLOR_RESET (log));
	}

	LOG_BUFFER_WRITE (log, "%s%s%s", ANSI_COLOR_DARKGREY (log), TREE_LINE, ANSI_COLOR_RESET (log)); /* │ */

	/* Print left padding and vertical lines */

	if (__td->lv > 0) {
		for (int i = 0; i < __td->lv; i++) {
			/* current level iteration has more than one entry remaining in loop */

			if (__td->ll[i] > 1) {
				/* next level iteration is current trace */

				if (i + 1 == __td->lv) {
					if (Obj) {
						LOG_BUFFER_WRITE (log, "%s ", TREE_ENTRY); /* ├── */
					} else {
						LOG_BUFFER_WRITE (log, "%s ", TREE_PADDED_LINE); /* │ */
					}
				} else {
					LOG_BUFFER_WRITE (log, "%s ", TREE_PADDED_LINE); /* │ */
				}
			} else if (i + 1 == __td->lv && Obj) {
				LOG_BUFFER_WRITE (log, "%s ", TREE_LAST_ENTRY); /* └── */
			} else {
				LOG_BUFFER_WRITE (log, "    ");
			}
		}
	}

	if (Obj) {
		switch (state) {
			case TD_ERROR:
				LOG_BUFFER_WRITE (log, "%s", ANSI_COLOR_RED (log));
				break;
			case TD_WARNING:
				LOG_BUFFER_WRITE (log, "%s", ANSI_COLOR_YELLOW (log));
				break;
			case TD_NOT_SUPPORTED:
				LOG_BUFFER_WRITE (log, "%s", ANSI_COLOR_ORANGE (log));
				break;
			case TD_INFO:
			case TD_OK:
				LOG_BUFFER_WRITE (log, "%s", ANSI_COLOR_CYAN (log));
				break;
		}

		LOG_BUFFER_WRITE (log, "%s ", aaft_ClassIDToText (aafi->aafd, Obj->Class->ID));

		LOG_BUFFER_WRITE (log, "%s", ANSI_COLOR_RESET (log));

		if (aaf_ObjectInheritsClass (Obj, &AAFClassID_Mob)) {
			aafMobID_t* mobID     = aaf_get_propertyValue (Obj, PID_Mob_MobID, &AAFTypeID_MobIDType);
			char*       name      = aaf_get_propertyValue (Obj, PID_Mob_Name, &AAFTypeID_String);
			aafUID_t*   usageCode = aaf_get_propertyValue (Obj, PID_Mob_UsageCode, &AAFTypeID_UsageType);

			LOG_BUFFER_WRITE (log, "(UsageCode: %s%s%s) %s%s",
			                  ANSI_COLOR_DARKGREY (log),
			                  aaft_UsageCodeToText (usageCode),
			                  ANSI_COLOR_RESET (log),
			                  (name && name[0] != 0x00) ? ": " : "",
			                  (name) ? name : "");

			free (name);

			LOG_BUFFER_WRITE (log, " MobID: %s%s%s",
			                  ANSI_COLOR_DARKGREY (log),
			                  (mobID) ? aaft_MobIDToText (mobID) : "none",
			                  ANSI_COLOR_RESET (log));
		} else if (aafUIDCmp (Obj->Class->ID, &AAFClassID_TimelineMobSlot)) {
			aafObject* Segment        = aaf_get_propertyValue (Obj, PID_MobSlot_Segment, &AAFTypeID_SegmentStrongReference);
			char*      name           = aaf_get_propertyValue (Obj, PID_MobSlot_SlotName, &AAFTypeID_String);
			uint32_t*  slotID         = aaf_get_propertyValue (Obj, PID_MobSlot_SlotID, &AAFTypeID_UInt32);
			uint32_t*  trackNo        = aaf_get_propertyValue (Obj, PID_MobSlot_PhysicalTrackNumber, &AAFTypeID_UInt32);
			aafUID_t*  DataDefinition = NULL;

			aafWeakRef_t* dataDefWeakRef = aaf_get_propertyValue (Segment, PID_Component_DataDefinition, &AAFTypeID_DataDefinitionWeakReference);

			if (dataDefWeakRef) {
				DataDefinition = aaf_get_DataIdentificationByWeakRef (aafi->aafd, dataDefWeakRef);
			}

			LOG_BUFFER_WRITE (log, "[slot:%s%i%s track:%s%i%s] (DataDef: %s%s%s) %s%s ",
			                  ANSI_COLOR_BOLD (log),
			                  (slotID) ? (int)(*slotID) : -1,
			                  ANSI_COLOR_RESET (log),
			                  ANSI_COLOR_BOLD (log),
			                  (trackNo) ? (int)(*trackNo) : -1,
			                  ANSI_COLOR_RESET (log),
			                  (state == TD_NOT_SUPPORTED) ? ANSI_COLOR_ORANGE (log) : ANSI_COLOR_DARKGREY (log),
			                  aaft_DataDefToText (aafi->aafd, DataDefinition),
			                  ANSI_COLOR_RESET (log),
			                  (name && name[0] != 0x00) ? ": " : "",
			                  (name) ? name : "");

			free (name);
		} else if (aafUIDCmp (Obj->Class->ID, &AAFClassID_OperationGroup)) {
			aafUID_t* OperationIdentification = NULL;

			aafWeakRef_t* OperationDefWeakRef = aaf_get_propertyValue (Obj, PID_OperationGroup_Operation, &AAFTypeID_OperationDefinitionWeakReference);

			if (OperationDefWeakRef) {
				OperationIdentification = aaf_get_OperationIdentificationByWeakRef (aafi->aafd, OperationDefWeakRef);
			}

			int64_t* length = aaf_get_propertyValue (Obj, PID_Component_Length, &AAFTypeID_LengthType);

			LOG_BUFFER_WRITE (log, "(OpIdent: %s%s%s; Length: %s%li%s) ",
			                  (state == TD_NOT_SUPPORTED) ? ANSI_COLOR_ORANGE (log) : ANSI_COLOR_DARKGREY (log),
			                  aaft_OperationDefToText (aafi->aafd, OperationIdentification),
			                  ANSI_COLOR_RESET (log),
			                  ANSI_COLOR_DARKGREY (log),
			                  (length) ? *length : -1,
			                  ANSI_COLOR_RESET (log));
		} else if (aaf_ObjectInheritsClass (Obj, &AAFClassID_Component)) {
			int64_t* length = aaf_get_propertyValue (Obj, PID_Component_Length, &AAFTypeID_LengthType);

			LOG_BUFFER_WRITE (log, "(Length: %s%li%s",
			                  ANSI_COLOR_DARKGREY (log),
			                  (length) ? *length : -1,
			                  ANSI_COLOR_RESET (log));

			if (aafUIDCmp (Obj->Class->ID, &AAFClassID_Transition)) {
				aafPosition_t* cutPoint = aaf_get_propertyValue (Obj, PID_Transition_CutPoint, &AAFTypeID_PositionType);

				if (cutPoint) {
					LOG_BUFFER_WRITE (log, "; CutPoint: %s%li%s",
					                  ANSI_COLOR_DARKGREY (log),
					                  *cutPoint,
					                  ANSI_COLOR_RESET (log));
				}
			}

			LOG_BUFFER_WRITE (log, ")");
		} else if (aafUIDCmp (Obj->Class->ID, &AAFClassID_ConstantValue)) {
			aafIndirect_t* Indirect = aaf_get_propertyValue (Obj, PID_ConstantValue_Value, &AAFTypeID_Indirect);

			if (Indirect) {
				aafUID_t* ParamDef = aaf_get_propertyValue (Obj, PID_Parameter_Definition, &AAFTypeID_AUID);

				LOG_BUFFER_WRITE (log, "(ParamDef: %s%s%s; Type: %s%s%s) ",
				                  (state == TD_NOT_SUPPORTED) ? ANSI_COLOR_ORANGE (log) : ANSI_COLOR_DARKGREY (log),
				                  aaft_ParameterToText (aafi->aafd, ParamDef),
				                  ANSI_COLOR_RESET (log),
				                  ANSI_COLOR_DARKGREY (log),
				                  aaft_TypeIDToText (&Indirect->TypeDef),
				                  ANSI_COLOR_RESET (log));

				LOG_BUFFER_WRITE (log, ": %s%s%s",
				                  ANSI_COLOR_DARKGREY (log),
				                  aaft_IndirectValueToText (aafi->aafd, Indirect),
				                  ANSI_COLOR_RESET (log));

				if (aafUIDCmp (ParamDef, &AAFParameterDef_Amplitude) &&
				    aafUIDCmp (&Indirect->TypeDef, &AAFTypeID_Rational)) {
					aafRational_t* value = aaf_get_indirectValue (aafi->aafd, Indirect, NULL);

					LOG_BUFFER_WRITE (log, " %s(%+05.1lf dB)%s",
					                  ANSI_COLOR_DARKGREY (log),
					                  20 * log10 (aafRationalToDouble (*value)),
					                  ANSI_COLOR_RESET (log));
				}

				__td->eob = 0;
			}
		} else if (aafUIDCmp (Obj->Class->ID, &AAFClassID_VaryingValue)) {
			aafUID_t* ParamDef                    = aaf_get_propertyValue (Obj, PID_Parameter_Definition, &AAFTypeID_AUID);
			aafUID_t* InterpolationIdentification = NULL;

			aafWeakRef_t* InterpolationDefWeakRef = aaf_get_propertyValue (Obj, PID_VaryingValue_Interpolation, &AAFTypeID_InterpolationDefinitionWeakReference);

			if (InterpolationDefWeakRef) {
				InterpolationIdentification = aaf_get_InterpolationIdentificationByWeakRef (aafi->aafd, InterpolationDefWeakRef);
			}

			LOG_BUFFER_WRITE (log, " (ParamDef: %s%s%s; Interpol: %s%s%s) ",
			                  (state == TD_NOT_SUPPORTED) ? ANSI_COLOR_ORANGE (log) : ANSI_COLOR_DARKGREY (log),
			                  aaft_ParameterToText (aafi->aafd, ParamDef),
			                  ANSI_COLOR_RESET (log),
			                  ANSI_COLOR_DARKGREY (log),
			                  aaft_InterpolationToText (InterpolationIdentification),
			                  ANSI_COLOR_RESET (log));

			__td->eob = 0;
		} else if (aafUIDCmp (Obj->Class->ID, &AAFClassID_NetworkLocator)) {
			char* url = aaf_get_propertyValue (Obj, PID_NetworkLocator_URLString, &AAFTypeID_String);

			if (url) {
				LOG_BUFFER_WRITE (log, "(URLString: %s%s%s)",
				                  ANSI_COLOR_DARKGREY (log),
				                  url,
				                  ANSI_COLOR_RESET (log));

				free (url);
			}
		} else if (aafUIDCmp (Obj->Class->ID, &AAFClassID_EssenceData)) {
			char* streamName = aaf_get_propertyValue (Obj, PID_EssenceData_Data, &AAFTypeID_String);

			if (streamName) {
				LOG_BUFFER_WRITE (log, "(Data: %s%s%s)",
				                  ANSI_COLOR_DARKGREY (log),
				                  streamName,
				                  ANSI_COLOR_RESET (log));

				free (streamName);
			}
		} else if (aaf_ObjectInheritsClass (Obj, &AAFClassID_FileDescriptor)) {
			aafUID_t* ContainerFormat = NULL;

			aafWeakRef_t* ContainerDefWeakRef = aaf_get_propertyValue (Obj, PID_FileDescriptor_ContainerFormat, &AAFTypeID_ClassDefinitionWeakReference);

			if (ContainerDefWeakRef) {
				ContainerFormat = aaf_get_ContainerIdentificationByWeakRef (aafi->aafd, ContainerDefWeakRef);
			}

			LOG_BUFFER_WRITE (log, "(ContainerIdent : %s%s%s)",
			                  ANSI_COLOR_DARKGREY (log),
			                  aaft_ContainerToText (ContainerFormat),
			                  ANSI_COLOR_RESET (log));
		}

		if (state == TD_INFO) {
			LOG_BUFFER_WRITE (log, ": %s", ANSI_COLOR_CYAN (log));
		} else if (state == TD_WARNING) {
			LOG_BUFFER_WRITE (log, ": %s", ANSI_COLOR_YELLOW (log));
		} else if (state == TD_ERROR) {
			LOG_BUFFER_WRITE (log, ": %s", ANSI_COLOR_RED (log));
		}

		va_start (args, fmt);

		int rc = laaf_util_vsnprintf_realloc (&log->_msg, &log->_msg_size, log->_msg_pos, fmt, args);

		if (rc < 0) {
			LOG_BUFFER_WRITE (log, "laaf_util_vsnprintf_realloc() error");
		} else {
			log->_msg_pos += (size_t)rc;
		}

		va_end (args);

		if (state == TD_ERROR || state == TD_INFO) {
			LOG_BUFFER_WRITE (log, ".");
		}

		int hasUnknownProps = 0;

		if (!aafi->ctx.options.dump_class_aaf_properties) {
			aafProperty* Prop = NULL;

			for (Prop = Obj->Properties; Prop != NULL; Prop = Prop->next) {
				if (Prop->def->meta) {
					LOG_BUFFER_WRITE (log, "%s%s %s[0x%04x]", ANSI_COLOR_RESET (log), (!hasUnknownProps) ? "  (MetaProps:" : "", aaft_PIDToText (aafi->aafd, Prop->pid), Prop->pid);
					hasUnknownProps++;
				}
			}

			if (hasUnknownProps) {
				LOG_BUFFER_WRITE (log, ")");
			}
		}

		if (aafi->ctx.options.dump_tagged_value) {
			if (aaf_ObjectInheritsClass (Obj, &AAFClassID_Mob)) {
				aafObject* UserComments = aaf_get_propertyValue (Obj, PID_Mob_UserComments, &AAFTypeID_TaggedValueStrongReferenceVector);
				aafObject* Attributes   = aaf_get_propertyValue (Obj, PID_Mob_Attributes, &AAFTypeID_TaggedValueStrongReferenceVector);

				if (UserComments) {
					LOG_BUFFER_WRITE (log, "\n    Mob::UserComments:\n");
					aaf_dump_TaggedValueSet (aafi->aafd, UserComments, "     ");
				}
				if (Attributes) {
					LOG_BUFFER_WRITE (log, "\n    Mob::Attributes:\n");
					aaf_dump_TaggedValueSet (aafi->aafd, Attributes, "     ");
				}
			} else if (aaf_ObjectInheritsClass (Obj, &AAFClassID_Component)) {
				aafObject* UserComments = aaf_get_propertyValue (Obj, PID_Component_UserComments, &AAFTypeID_TaggedValueStrongReferenceVector);
				aafObject* Attributes   = aaf_get_propertyValue (Obj, PID_Component_Attributes, &AAFTypeID_TaggedValueStrongReferenceVector);

				if (UserComments) {
					LOG_BUFFER_WRITE (log, "\n    Component::UserComments:\n");
					aaf_dump_TaggedValueSet (aafi->aafd, UserComments, "     ");
				}
				if (Attributes) {
					LOG_BUFFER_WRITE (log, "\n    Component::Attributes:\n");
					aaf_dump_TaggedValueSet (aafi->aafd, Attributes, "     ");
				}
			}
		}

		if (aafi->ctx.options.dump_meta && hasUnknownProps) {
			LOG_BUFFER_WRITE (log, "\n\n%s", ANSI_COLOR_MAGENTA (log));
			LOG_BUFFER_WRITE (log, "    ======================================================================\n");
			LOG_BUFFER_WRITE (log, "                           AAF Meta Properties Dump\n");
			LOG_BUFFER_WRITE (log, "    ======================================================================\n");
			LOG_BUFFER_WRITE (log, "%s", ANSI_COLOR_RESET (log));

			aafProperty* Prop = NULL;

			for (Prop = Obj->Properties; Prop != NULL; Prop = Prop->next) {
				if (Prop->def->meta) {
					if (aafi->ctx.options.dump_meta) {
						if (Prop->sf == SF_STRONG_OBJECT_REFERENCE_VECTOR) {
							LOG_BUFFER_WRITE (log, "\n");
							LOG_BUFFER_WRITE (log, "    [%s0x%04x%s] %s (%s)\n",
							                  ANSI_COLOR_MAGENTA (log),
							                  Prop->pid,
							                  ANSI_COLOR_RESET (log),
							                  aaft_PIDToText (aafi->aafd, Prop->pid),
							                  aaft_StoredFormToText (Prop->sf));

							void* propValue = aaf_get_propertyValue (Obj, Prop->pid, &AAFTypeID_TaggedValueStrongReferenceVector);

							log->color_reset = ANSI_COLOR_MAGENTA (log);
							aaf_dump_TaggedValueSet (aafi->aafd, propValue, "     ");
							log->color_reset = NULL;
						} else {
							LOG_BUFFER_WRITE (log, "\n");
							aaf_dump_ObjectProperty (aafi->aafd, Prop, "    ");
						}
					}
				}
			}
		}

		if (aafi->ctx.options.dump_class_raw_properties && strcmp (aaft_ClassIDToText (aafi->aafd, Obj->Class->ID), aafi->ctx.options.dump_class_raw_properties) == 0) {
			LOG_BUFFER_WRITE (log, "\n\n");
			LOG_BUFFER_WRITE (log, "    ======================================================================\n");
			LOG_BUFFER_WRITE (log, "                        CFB Object Properties Dump\n");
			LOG_BUFFER_WRITE (log, "    ======================================================================\n");
			LOG_BUFFER_WRITE (log, "%s", ANSI_COLOR_DARKGREY (log));
			LOG_BUFFER_WRITE (log, "    %s\n", aaft_ClassIDToText (aafi->aafd, Obj->Class->ID));
			LOG_BUFFER_WRITE (log, "    %s/properties\n", aaf_get_ObjectPath (Obj));
			LOG_BUFFER_WRITE (log, "%s\n\n", ANSI_COLOR_RESET (log));

			aaf_dump_nodeStreamProperties (aafi->aafd, cfb_getChildNode (aafi->aafd->cfbd, "properties", Obj->Node), "    ");

			LOG_BUFFER_WRITE (log, "\n");
		}

		if (aafi->ctx.options.dump_class_aaf_properties && strcmp (aaft_ClassIDToText (aafi->aafd, Obj->Class->ID), aafi->ctx.options.dump_class_aaf_properties) == 0) {
			LOG_BUFFER_WRITE (log, "\n\n");
			LOG_BUFFER_WRITE (log, "    ======================================================================\n");
			LOG_BUFFER_WRITE (log, "                             AAF Properties Dump\n");
			LOG_BUFFER_WRITE (log, "    ======================================================================\n");
			LOG_BUFFER_WRITE (log, "%s", ANSI_COLOR_DARKGREY (log));
			LOG_BUFFER_WRITE (log, "    %s\n", aaft_ClassIDToText (aafi->aafd, Obj->Class->ID));
			LOG_BUFFER_WRITE (log, "    %s/properties\n", aaf_get_ObjectPath (Obj));
			LOG_BUFFER_WRITE (log, "%s\n\n", ANSI_COLOR_RESET (log));

			aaf_dump_ObjectProperties (aafi->aafd, Obj, "    ");

			LOG_BUFFER_WRITE (log, "\n");
		}

		LOG_BUFFER_WRITE (log, "%s", ANSI_COLOR_RESET (log));
	}

	log->log_callback (log, (void*)aafi, LOG_SRC_ID_TRACE, 0, "", "", 0, log->_msg, log->user);

	/* if end of branch, print one line padding */
	if (Obj && (__td->eob || state == TD_ERROR))
		aafi_dump_obj (aafi, NULL, __td, 0, NULL, -1, "");
}

/**
 * @}
 */
