Skip to content

Commit

Permalink
Add support for MGA S-ADM Virtual Tracks (SMPTE ST 2067-203) (#376)
Browse files Browse the repository at this point in the history
* Adds support for ST 2067-203 MGA S-ADM Plug-in

---------

Co-authored-by: IMFTool <[email protected]>
Co-authored-by: Florian Schleich <[email protected]>
  • Loading branch information
3 people authored Sep 10, 2024
1 parent 9c0f01e commit e3fd12f
Show file tree
Hide file tree
Showing 137 changed files with 9,417 additions and 24 deletions.
34 changes: 34 additions & 0 deletions src/main/java/com/netflix/imflibrary/IMFConstraints.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.netflix.imflibrary.st0377.PartitionPack;
import com.netflix.imflibrary.st0377.header.*;
import com.netflix.imflibrary.st2067_201.IABEssenceDescriptor;
import com.netflix.imflibrary.st2067_203.MGASoundEssenceDescriptor;
import com.netflix.imflibrary.utils.ErrorLogger;
import com.netflix.imflibrary.utils.Utilities;

Expand Down Expand Up @@ -450,6 +451,10 @@ else if(essenceType.equals(HeaderPartition.EssenceTypeEnum.IABEssence))
{
targetMXFDataDefinition = MXFDataDefinition.SOUND;
}
else if(essenceType.equals(HeaderPartition.EssenceTypeEnum.MGASADMEssence))
{
targetMXFDataDefinition = MXFDataDefinition.SOUND;
}
else{
targetMXFDataDefinition = MXFDataDefinition.DATA;
}
Expand Down Expand Up @@ -561,6 +566,35 @@ private boolean hasWaveAudioEssenceDescriptor()
return iabEssenceDescriptor;
}

/**
* Gets the first MGASoundEssenceDescriptor structural metadata set from
* an OP1A-conformant MXF Header partition. Returns null if none is found
* @return returns the first MGASoundEssenceDescriptor
*/
public @Nullable MGASoundEssenceDescriptor getMGASoundEssenceDescriptor()
{
MGASoundEssenceDescriptor mgaSoundEssenceDescriptor = null;
GenericPackage genericPackage = this.headerPartitionOP1A.getHeaderPartition().getPreface().getContentStorage().
getEssenceContainerDataList().get(0).getLinkedPackage();
SourcePackage filePackage = (SourcePackage)genericPackage;
for (TimelineTrack timelineTrack : filePackage.getTimelineTracks())
{
Sequence sequence = timelineTrack.getSequence();
MXFDataDefinition filePackageMxfDataDefinition = sequence.getMxfDataDefinition();
if (filePackageMxfDataDefinition.equals(MXFDataDefinition.SOUND))
{
GenericDescriptor genericDescriptor = filePackage.getGenericDescriptor();
if (genericDescriptor instanceof MGASoundEssenceDescriptor)
{
mgaSoundEssenceDescriptor = (MGASoundEssenceDescriptor)genericDescriptor;
break;
}
}
}

return mgaSoundEssenceDescriptor;
}

/**
* A method that returns the IMF Essence Component type.
* @return essenceTypeEnum an enumeration constant corresponding to the IMFEssenceComponent type
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.netflix.imflibrary.st2067_2.Composition.VirtualTrack;
import com.netflix.imflibrary.st2067_2.IMFEssenceComponentVirtualTrack;
import com.netflix.imflibrary.st2067_201.IABTrackFileConstraints;
import com.netflix.imflibrary.st2067_203.MGASADMTrackFileConstraints;
import com.netflix.imflibrary.utils.ByteArrayByteRangeProvider;
import com.netflix.imflibrary.utils.ByteArrayDataProvider;
import com.netflix.imflibrary.utils.ByteProvider;
Expand Down Expand Up @@ -592,12 +593,14 @@ public static List<ErrorLogger.ErrorObject> validateIMFTrackFileHeaderMetadata(L
0L,
(long)payloadRecord.getPayload().length,
imfErrorLogger);

MXFOperationalPattern1A.HeaderPartitionOP1A headerPartitionOP1A = MXFOperationalPattern1A.checkOperationalPattern1ACompliance(headerPartition, imfErrorLogger);
IMFConstraints.HeaderPartitionIMF headerPartitionIMF = IMFConstraints.checkIMFCompliance(headerPartitionOP1A, imfErrorLogger);
if (headerPartitionIMF.getEssenceType() == HeaderPartition.EssenceTypeEnum.IABEssence) {
IABTrackFileConstraints.checkCompliance(headerPartitionIMF, imfErrorLogger);
}
if (headerPartitionIMF.getEssenceType() == HeaderPartition.EssenceTypeEnum.MGASADMEssence) {
MGASADMTrackFileConstraints.checkCompliance(headerPartitionIMF, imfErrorLogger);
}
}
catch (IMFException | MXFException e){
if(headerPartition != null) {
Expand Down Expand Up @@ -710,6 +713,9 @@ private static List<ErrorLogger.ErrorObject> checkVirtualTrackAndEssencesHeaderP
if (headerPartitionIMF.hasMatchingEssence(HeaderPartition.EssenceTypeEnum.IABEssence)) {
IABTrackFileConstraints.checkCompliance(headerPartitionIMF, imfErrorLogger);
}
if (headerPartitionIMF.hasMatchingEssence(HeaderPartition.EssenceTypeEnum.MGASADMEssence)) {
MGASADMTrackFileConstraints.checkCompliance(headerPartitionIMF, imfErrorLogger);
}
}
catch (IMFException | MXFException e){
if(headerPartition != null) {
Expand Down Expand Up @@ -922,7 +928,7 @@ public static List<ErrorLogger.ErrorObject> validateOPL(PayloadRecord opl) throw
}

/**
* A stateless method, used for IMP containing IAB tracks, that will validate that the index edit rate in the index segment matches the one in the descriptor (according to Section 5.7 of SMPTE ST 2067-201:2019)
* A stateless method, used for IMP containing IAB and/or MGA S-ADM tracks, that will validate that the index edit rate in the index segment matches the one in the descriptor (according to Section 5.7 of SMPTE ST 2067-201:2019)
* @param headerPartitionPayloadRecords - a list of IMF Essence Component partition payloads for header partitions
* @param indexSegmentPayloadRecords - a list of IMF Essence Component partition payloads for index partitions
* @return list of error messages encountered while validating
Expand Down Expand Up @@ -970,6 +976,8 @@ public static List<ErrorLogger.ErrorObject> validateIndexEditRate(List<PayloadRe
IndexTableSegment indexTableSegment = new IndexTableSegment(imfEssenceComponentByteProvider, header);
if (headerPartitionIMF.hasMatchingEssence(HeaderPartition.EssenceTypeEnum.IABEssence)) {
IABTrackFileConstraints.checkIndexEditRate(headerPartitionIMF, indexTableSegment, imfErrorLogger);
} else if (headerPartitionIMF.hasMatchingEssence(HeaderPartition.EssenceTypeEnum.MGASADMEssence)) {
MGASADMTrackFileConstraints.checkIndexEditRate(headerPartitionIMF, indexTableSegment, imfErrorLogger);
}
} else {
imfEssenceComponentByteProvider.skipBytes(header.getVSize());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
import com.netflix.imflibrary.st0377.header.InterchangeObject;
import com.netflix.imflibrary.st0377.header.Preface;
import com.netflix.imflibrary.st0377.header.SourcePackage;
import com.netflix.imflibrary.st2067_201.IABTrackFileConstraints;
import com.netflix.imflibrary.st2067_203.MGASADMTrackFileConstraints;
import com.netflix.imflibrary.utils.ByteArrayDataProvider;
import com.netflix.imflibrary.utils.ByteProvider;
import com.netflix.imflibrary.utils.ErrorLogger;
Expand Down Expand Up @@ -133,6 +135,10 @@ private void setHeaderPartitionIMF(long inclusiveRangeStart, long inclusiveRange
//validate header partition
MXFOperationalPattern1A.HeaderPartitionOP1A headerPartitionOP1A = MXFOperationalPattern1A.checkOperationalPattern1ACompliance(headerPartition, imfErrorLogger);
this.headerPartition = IMFConstraints.checkIMFCompliance(headerPartitionOP1A, imfErrorLogger);
if (this.headerPartition != null) {
IABTrackFileConstraints.checkCompliance(this.headerPartition, imfErrorLogger);
MGASADMTrackFileConstraints.checkCompliance(this.headerPartition, imfErrorLogger);
}
}
catch (MXFException | IMFException e){
if(headerPartition == null){
Expand Down Expand Up @@ -500,6 +506,7 @@ HeaderPartition.EssenceTypeEnum getEssenceType(@Nonnull IMFErrorLogger imfErrorL
supportedEssenceComponentTypes.add(HeaderPartition.EssenceTypeEnum.MainAudioEssence);
supportedEssenceComponentTypes.add(HeaderPartition.EssenceTypeEnum.MarkerEssence);
supportedEssenceComponentTypes.add(HeaderPartition.EssenceTypeEnum.IABEssence);
supportedEssenceComponentTypes.add(HeaderPartition.EssenceTypeEnum.MGASADMEssence);
List<HeaderPartition.EssenceTypeEnum> supportedEssenceTypesFound = new ArrayList<>();
List<HeaderPartition.EssenceTypeEnum> essenceTypes = this.getHeaderPartitionIMF(imfErrorLogger).getHeaderPartitionOP1A().getHeaderPartition().getEssenceTypes();

Expand Down Expand Up @@ -720,6 +727,7 @@ else if(e instanceof MXFException){
supportedEssenceComponentTypes.add(HeaderPartition.EssenceTypeEnum.MainImageEssence);
supportedEssenceComponentTypes.add(HeaderPartition.EssenceTypeEnum.MainAudioEssence);
supportedEssenceComponentTypes.add(HeaderPartition.EssenceTypeEnum.MarkerEssence);
supportedEssenceComponentTypes.add(HeaderPartition.EssenceTypeEnum.MGASADMEssence);
if(imfTrackFileReader != null
&& imfTrackFileCPLBuilder != null
&& supportedEssenceComponentTypes.contains(imfTrackFileReader.getEssenceType(imfErrorLogger))) {
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/com/netflix/imflibrary/app/IMPAnalyzer.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.netflix.imflibrary.st2067_2.ApplicationCompositionFactory;
import com.netflix.imflibrary.st2067_2.Composition;
import com.netflix.imflibrary.st2067_2.IMFEssenceComponentVirtualTrack;
import com.netflix.imflibrary.st2067_203.IMFMGASADMConstraintsChecker;
import com.netflix.imflibrary.utils.ByteArrayDataProvider;
import com.netflix.imflibrary.utils.ByteProvider;
import com.netflix.imflibrary.utils.ErrorLogger;
Expand Down Expand Up @@ -456,6 +457,12 @@ public static List<ApplicationComposition> analyzeApplicationCompositions( File
Set<UUID> trackFileIDsSet = trackFileIDToHeaderPartitionPayLoadMap
.keySet();

// ST 2067-203 MGASADMVirtualTrackParameterSet checks
List<ErrorLogger.ErrorObject> errors = IMFMGASADMConstraintsChecker.checkMGASADMVirtualTrackParameterSet(applicationComposition);
// Report MGASADMVirtualTrackParameterSet as both CPL and Virtual Track errors
compositionConformanceErrorLogger.addAllErrors(errors);
compositionErrorLogger.addAllErrors(errors);

try {
if (!isCompositionComplete(applicationComposition, trackFileIDsSet, compositionConformanceErrorLogger)) {
for (IMFEssenceComponentVirtualTrack virtualTrack : applicationComposition.getEssenceVirtualTracks()) {
Expand Down
45 changes: 43 additions & 2 deletions src/main/java/com/netflix/imflibrary/st0377/HeaderPartition.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
import com.netflix.imflibrary.st2067_2.Composition;
import com.netflix.imflibrary.st2067_201.IABEssenceDescriptor;
import com.netflix.imflibrary.st2067_201.IABSoundfieldLabelSubDescriptor;
import com.netflix.imflibrary.st2067_203.MGASoundEssenceDescriptor;
import com.netflix.imflibrary.st2067_203.MGASoundfieldGroupLabelSubDescriptor;
import com.netflix.imflibrary.utils.ByteArrayDataProvider;
import com.netflix.imflibrary.utils.ByteProvider;
import com.netflix.imflibrary.utils.ErrorLogger;
Expand Down Expand Up @@ -431,6 +433,10 @@ else if (dependentInterchangeObject instanceof SoundFieldGroupLabelSubDescriptor
IABEssenceDescriptor iabEssenceDescriptor = new IABEssenceDescriptor((IABEssenceDescriptor.IABEssenceDescriptorBO) interchangeObjectBO);
this.cacheInterchangeObject(iabEssenceDescriptor);
uidToMetadataSets.put(interchangeObjectBO.getInstanceUID(), iabEssenceDescriptor);
} else if(interchangeObjectBO.getClass().getEnclosingClass().equals(MGASoundEssenceDescriptor.class)){
MGASoundEssenceDescriptor mgaSoundEssenceDescriptor = new MGASoundEssenceDescriptor((MGASoundEssenceDescriptor.MGASoundEssenceDescriptorBO) interchangeObjectBO);
this.cacheInterchangeObject(mgaSoundEssenceDescriptor);
uidToMetadataSets.put(interchangeObjectBO.getInstanceUID(), mgaSoundEssenceDescriptor);
} else if(interchangeObjectBO.getClass().getEnclosingClass().equals(TimedTextDescriptor.class)){
List<TimeTextResourceSubDescriptor> subDescriptorList = new ArrayList<>();
for(Node dependent : node.depends) {
Expand Down Expand Up @@ -476,7 +482,6 @@ else if (dependentInterchangeObject instanceof SoundFieldGroupLabelSubDescriptor
*/
private InterchangeObject.InterchangeObjectBO constructInterchangeObjectBO(Class clazz, KLVPacket.Header header, ByteProvider byteProvider, Map localTagToUIDMap, IMFErrorLogger imfErrorLogger) throws IOException{
try {

Constructor<?> constructor = clazz.getConstructor(KLVPacket.Header.class, ByteProvider.class, Map.class, IMFErrorLogger.class);
InterchangeObject.InterchangeObjectBO interchangeObjectBO = (InterchangeObject.InterchangeObjectBO)constructor.newInstance(header, byteProvider, localTagToUIDMap, imfErrorLogger);
String simpleClassName = interchangeObjectBO.getClass().getSimpleName();
Expand Down Expand Up @@ -721,6 +726,16 @@ public String getAudioEssenceSpokenLanguage() throws IOException {
this.imfErrorLogger.addError(IMFErrorLogger.IMFErrors.ErrorCodes.IMF_ESSENCE_COMPONENT_ERROR, IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, String.format("Language Codes (%s, %s) do not match across the IABSoundFieldLabelSubDescriptors", rfc5646SpokenLanguage, iabSoundfieldLabelSubDescriptor.getRFC5646SpokenLanguage()));
}
}
} else if (this.hasMGASoundEssenceDescriptor()) {
List<InterchangeObject> mgaSoundfieldGroupLabelSubDescriptors = this.getMGASoundfieldGroupLabelSubDescriptors();
for (InterchangeObject subDescriptor : mgaSoundfieldGroupLabelSubDescriptors) {
MGASoundfieldGroupLabelSubDescriptor mgaSoundfieldLabelSubDescriptor = (MGASoundfieldGroupLabelSubDescriptor) subDescriptor;
if (rfc5646SpokenLanguage == null) {
rfc5646SpokenLanguage = mgaSoundfieldLabelSubDescriptor.getRFC5646SpokenLanguage();
} else if (!rfc5646SpokenLanguage.equals(mgaSoundfieldLabelSubDescriptor.getRFC5646SpokenLanguage())) {
this.imfErrorLogger.addError(IMFErrorLogger.IMFErrors.ErrorCodes.IMF_ESSENCE_COMPONENT_ERROR, IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, String.format("Language Codes (%s, %s) do not match across the MGASoundfieldGroupLabelSubDescriptor", rfc5646SpokenLanguage, mgaSoundfieldLabelSubDescriptor.getRFC5646SpokenLanguage()));
}
}
}
return rfc5646SpokenLanguage;
}
Expand Down Expand Up @@ -789,6 +804,15 @@ public boolean hasIABEssenceDescriptor()
return this.hasInterchangeObject(IABEssenceDescriptor.class);
}

/**
* Checks if this HeaderPartition object has a MGA Sound Essence Descriptor
* @return true/false depending on whether this HeaderPartition contains a MGASoundEssenceDescriptor or not
*/
public boolean hasMGASoundEssenceDescriptor()
{
return this.hasInterchangeObject(MGASoundEssenceDescriptor.class);
}

/**
* Checks if this HeaderPartition object has a CDCI Picture Essence Descriptor
* @return true/false depending on whether this HeaderPartition contains a CDCIPictureEssenceDescriptor or not
Expand Down Expand Up @@ -875,6 +899,15 @@ public List<InterchangeObject> getIABSoundFieldLabelSubDescriptors()
return this.getInterchangeObjects(IABSoundfieldLabelSubDescriptor.class);
}

/**
* Gets all the MGA Soundfield Group Label SubDescriptors associated with this HeaderPartition object
* @return list MGA Soundfield Group Label SubDescriptors contained in this header partition
*/
public List<InterchangeObject> getMGASoundfieldGroupLabelSubDescriptors()
{
return this.getInterchangeObjects(MGASoundfieldGroupLabelSubDescriptor.class);
}

/**
* Gets the timeline track associated with this HeaderPartition object corresponding to the specified UID. Returns
* null if none is found
Expand Down Expand Up @@ -1276,9 +1309,12 @@ public List<EssenceTypeEnum> getEssenceTypes() {
if(interchangeObjectBO.getClass().getEnclosingClass().equals(WaveAudioEssenceDescriptor.class)){
essenceTypes.add(EssenceTypeEnum.MainAudioEssence);
}
if(interchangeObjectBO.getClass().getEnclosingClass().equals(IABEssenceDescriptor.class)){
else if(interchangeObjectBO.getClass().getEnclosingClass().equals(IABEssenceDescriptor.class)){
essenceTypes.add(EssenceTypeEnum.IABEssence);
}
else if(interchangeObjectBO.getClass().getEnclosingClass().equals(MGASoundEssenceDescriptor.class)){
essenceTypes.add(EssenceTypeEnum.MGASADMEssence);
}
else if(interchangeObjectBO.getClass().getEnclosingClass().equals(CDCIPictureEssenceDescriptor.class)){
essenceTypes.add(EssenceTypeEnum.MainImageEssence);
}
Expand Down Expand Up @@ -1315,6 +1351,7 @@ public enum EssenceTypeEnum {
ForcedNarrativeEssence(Composition.SequenceTypeEnum.ForcedNarrativeSequence),
AncillaryDataEssence(Composition.SequenceTypeEnum.AncillaryDataSequence),
IABEssence(Composition.SequenceTypeEnum.IABSequence),
MGASADMEssence(Composition.SequenceTypeEnum.MGASADMSignalSequence),
UnsupportedEssence(Composition.SequenceTypeEnum.UnsupportedSequence);

private final Composition.SequenceTypeEnum sequenceType;
Expand Down Expand Up @@ -1352,6 +1389,8 @@ private static EssenceTypeEnum getEssenceTypeEnum(String name)
return AncillaryDataEssence;
case "IABEssence":
return IABEssence;
case "MGASADMEssence":
return MGASADMEssence;
case "UnsupportedEssence":
default:
return UnsupportedEssence;
Expand Down Expand Up @@ -1384,6 +1423,8 @@ private static String getEssenceTypeString(Composition.SequenceTypeEnum sequence
return "AncillaryDataEssence";
case IABSequence:
return "IABEssence";
case MGASADMSignalSequence:
return "MGASADMEssence";
case UnsupportedSequence:
default:
return "UnsupportedEssence";
Expand Down
Loading

0 comments on commit e3fd12f

Please sign in to comment.