From 34716414eba02e0f27345877de1588d9456b062e Mon Sep 17 00:00:00 2001 From: Hans-Nikolas Locher Date: Mon, 30 Mar 2020 11:16:41 +0200 Subject: [PATCH 1/5] Change folder name (audioRoutingMixing) according to package name - lower case initial letter --- .../AudioRoutingMixingMacro.java | 0 .../{AudioRoutingMixing => audioRoutingMixing}/InputEntity.java | 0 .../OutputAudioChannel.java | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename src/main/java/com/netflix/imflibrary/st2067_100/macro/{AudioRoutingMixing => audioRoutingMixing}/AudioRoutingMixingMacro.java (100%) rename src/main/java/com/netflix/imflibrary/st2067_100/macro/{AudioRoutingMixing => audioRoutingMixing}/InputEntity.java (100%) rename src/main/java/com/netflix/imflibrary/st2067_100/macro/{AudioRoutingMixing => audioRoutingMixing}/OutputAudioChannel.java (100%) diff --git a/src/main/java/com/netflix/imflibrary/st2067_100/macro/AudioRoutingMixing/AudioRoutingMixingMacro.java b/src/main/java/com/netflix/imflibrary/st2067_100/macro/audioRoutingMixing/AudioRoutingMixingMacro.java similarity index 100% rename from src/main/java/com/netflix/imflibrary/st2067_100/macro/AudioRoutingMixing/AudioRoutingMixingMacro.java rename to src/main/java/com/netflix/imflibrary/st2067_100/macro/audioRoutingMixing/AudioRoutingMixingMacro.java diff --git a/src/main/java/com/netflix/imflibrary/st2067_100/macro/AudioRoutingMixing/InputEntity.java b/src/main/java/com/netflix/imflibrary/st2067_100/macro/audioRoutingMixing/InputEntity.java similarity index 100% rename from src/main/java/com/netflix/imflibrary/st2067_100/macro/AudioRoutingMixing/InputEntity.java rename to src/main/java/com/netflix/imflibrary/st2067_100/macro/audioRoutingMixing/InputEntity.java diff --git a/src/main/java/com/netflix/imflibrary/st2067_100/macro/AudioRoutingMixing/OutputAudioChannel.java b/src/main/java/com/netflix/imflibrary/st2067_100/macro/audioRoutingMixing/OutputAudioChannel.java similarity index 100% rename from src/main/java/com/netflix/imflibrary/st2067_100/macro/AudioRoutingMixing/OutputAudioChannel.java rename to src/main/java/com/netflix/imflibrary/st2067_100/macro/audioRoutingMixing/OutputAudioChannel.java From 0ba5257047739fa0cc0d57ec3d1e91675bfb8f83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Achard?= Date: Fri, 5 Jul 2019 09:25:20 +0200 Subject: [PATCH 2/5] Application 4 basic support --- .../com/netflix/imflibrary/Colorimetry.java | 2 + .../st2067_2/Application4Composition.java | 269 ++++++++++++++++++ .../ApplicationCompositionFactory.java | 5 + 3 files changed, 276 insertions(+) create mode 100644 src/main/java/com/netflix/imflibrary/st2067_2/Application4Composition.java diff --git a/src/main/java/com/netflix/imflibrary/Colorimetry.java b/src/main/java/com/netflix/imflibrary/Colorimetry.java index 1be3992b..27bf8b75 100644 --- a/src/main/java/com/netflix/imflibrary/Colorimetry.java +++ b/src/main/java/com/netflix/imflibrary/Colorimetry.java @@ -21,6 +21,7 @@ public enum Colorimetry { Color5(ColorPrimaries.ITU2020, TransferCharacteristic.ITU2020, CodingEquation.ITU2020NCL), Color6(ColorPrimaries.P3D65, TransferCharacteristic.SMPTEST2084, CodingEquation.None), Color7(ColorPrimaries.ITU2020, TransferCharacteristic.SMPTEST2084, CodingEquation.ITU2020NCL), + Color_App4_Lin(ColorPrimaries.XYZ, TransferCharacteristic.Linear, CodingEquation.None), Color_App5_AP0(ColorPrimaries.ACES, TransferCharacteristic.Linear, CodingEquation.None), Unknown(ColorPrimaries.Unknown, TransferCharacteristic.Unknown, CodingEquation.Unknown); @@ -115,6 +116,7 @@ public static enum ColorPrimaries { ITU2020(UL.fromULAsURNStringToUL("urn:smpte:ul:06.0E.2B.34.04.01.01.0D.04.01.01.01.03.04.00.00")), P3D65(UL.fromULAsURNStringToUL("urn:smpte:ul:06.0E.2B.34.04.01.01.0D.04.01.01.01.03.06.00.00")), ACES(UL.fromULAsURNStringToUL("urn:smpte:ul:06.0e.2b.34.04.01.01.0d.04.01.01.01.03.07.00.00")), + XYZ(UL.fromULAsURNStringToUL("urn:smpte:ul:06.0e.2b.34.04.01.01.0d.04.01.01.01.03.08.00.00")), Unknown(null); private final UL colorPrimariesUL; diff --git a/src/main/java/com/netflix/imflibrary/st2067_2/Application4Composition.java b/src/main/java/com/netflix/imflibrary/st2067_2/Application4Composition.java new file mode 100644 index 00000000..e1a1574c --- /dev/null +++ b/src/main/java/com/netflix/imflibrary/st2067_2/Application4Composition.java @@ -0,0 +1,269 @@ +/* + * + * Copyright 2019 Eclair Media SAS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.netflix.imflibrary.st2067_2; + +import com.netflix.imflibrary.Colorimetry; +import com.netflix.imflibrary.Colorimetry.ColorModel; +import com.netflix.imflibrary.Colorimetry.Quantization; +import com.netflix.imflibrary.Colorimetry.Sampling; +import com.netflix.imflibrary.IMFErrorLogger; +import com.netflix.imflibrary.IMFErrorLoggerImpl; +import com.netflix.imflibrary.st0377.header.UL; +import com.netflix.imflibrary.st0422.JP2KContentKind; +import com.netflix.imflibrary.st2067_2.ApplicationCompositionFactory.ApplicationCompositionType; +import com.netflix.imflibrary.utils.Fraction; + +import javax.annotation.Nonnull; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import static com.netflix.imflibrary.Colorimetry.CodingEquation; +import static com.netflix.imflibrary.Colorimetry.ColorModel; +import static com.netflix.imflibrary.Colorimetry.ColorPrimaries; +import static com.netflix.imflibrary.Colorimetry.Quantization; +import static com.netflix.imflibrary.Colorimetry.Sampling; +import static com.netflix.imflibrary.Colorimetry.TransferCharacteristic; +import static com.netflix.imflibrary.st0377.header.GenericPictureEssenceDescriptor.FrameLayoutType; + + +/** + * A class that models Composition with Application 4 constraints from 2067-40 specification + */ +public class Application4Composition extends AbstractApplicationComposition { + public static final Integer MAX_XYZ_IMAGE_FRAME_WIDTH = 8192; + public static final Integer MAX_XYZ_IMAGE_FRAME_HEIGHT = 6224; + public static final SetsampleRateSupported = Collections.unmodifiableSet(new HashSet() {{ + add(new Fraction(16)); add(new Fraction(200, 11)); add(new Fraction(20)); add(new Fraction(240, 11)); add(new Fraction(24)); add(new Fraction(25)); + add(new Fraction(30)); add(new Fraction(48)); add(new Fraction(50)); add(new Fraction(60)); add(new Fraction(100)); add(new Fraction(120));}}); + public static final Map>colorToBitDepthMap = Collections.unmodifiableMap(new HashMap>() {{ + put(Colorimetry.Unknown, new HashSet(){{ }}); + put(Colorimetry.Color_App4_Lin, new HashSet(){{ add(16); }}); + }}); + public static final SetbitDepthsSupported = Collections.unmodifiableSet(new HashSet() {{ + add(16); }}); + + private static final Set ignoreSet = Collections.unmodifiableSet(new HashSet() {{ + add("SignalStandard"); + add("ActiveFormatDescriptor"); + add("VideoLineMap"); + add("AlphaTransparency"); + add("StoredF2Offset"); + add("SampledYOffset"); + add("Image Alignment Offset"); + add("Image Start Offset"); + add("Image End Offset"); + add("FieldDominance"); + add("Coding Equations"); + add("Alternative Center Cuts"); + }}); + + public Application4Composition(@Nonnull IMFCompositionPlaylistType imfCompositionPlaylistType) { + this(imfCompositionPlaylistType, new HashSet<>()); + } + + public Application4Composition(@Nonnull IMFCompositionPlaylistType imfCompositionPlaylistType, Set homogeneitySelectionSet) { + + super(imfCompositionPlaylistType, ignoreSet, homogeneitySelectionSet); + + try + { + CompositionImageEssenceDescriptorModel imageEssenceDescriptorModel = getCompositionImageEssenceDescriptorModel(); + + if (imageEssenceDescriptorModel != null) + { + Application4Composition.validateGenericPictureEssenceDescriptor(imageEssenceDescriptorModel, ApplicationCompositionType.APPLICATION_4_COMPOSITION_TYPE, imfErrorLogger); + Application4Composition.validateImageCharacteristics(imageEssenceDescriptorModel, ApplicationCompositionType.APPLICATION_4_COMPOSITION_TYPE, imfErrorLogger); + } + } + catch (Exception e) { + imfErrorLogger.addError(IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("Exception in validating EssenceDescriptors in APPLICATION_4_COMPOSITION_TYPE: %s ", e.getMessage())); + } + } + + + public static void validateGenericPictureEssenceDescriptor(CompositionImageEssenceDescriptorModel imageEssenceDescriptorModel, + ApplicationCompositionFactory.ApplicationCompositionType applicationCompositionType, + IMFErrorLogger imfErrorLogger) + { + UUID imageEssenceDescriptorID = imageEssenceDescriptorModel.getImageEssencedescriptorID(); + + ColorModel colorModel = imageEssenceDescriptorModel.getColorModel(); + if( colorModel.equals(ColorModel.Unknown)) { + imfErrorLogger.addError(IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("EssenceDescriptor with ID %s has Invalid color components as per %s", + imageEssenceDescriptorID.toString(), applicationCompositionType.toString())); + return; + } + + Integer storedWidth = imageEssenceDescriptorModel.getStoredWidth(); + Integer storedHeight = imageEssenceDescriptorModel.getStoredHeight(); + if ((storedWidth <= 0) || (storedHeight <= 0)) { + imfErrorLogger.addError(IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("EssenceDescriptor with ID %s has invalid storedWidth(%d) or storedHeight(%d) as per %s", + imageEssenceDescriptorID.toString(), storedWidth, storedHeight, applicationCompositionType.toString())); + } + + Integer sampleWidth = imageEssenceDescriptorModel.getSampleWidth(); + Integer sampleHeight = imageEssenceDescriptorModel.getSampleHeight(); + if ((sampleWidth != null && !sampleWidth.equals(storedWidth)) || + (sampleHeight != null && !sampleHeight.equals(storedHeight))) { + imfErrorLogger.addError(IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("EssenceDescriptor with ID %s has invalid sampleWidth(%d) or sampleHeight(%d) as per %s", + imageEssenceDescriptorID.toString(), sampleWidth != null ? sampleWidth : 0, sampleHeight != null ? sampleHeight : 0, + applicationCompositionType.toString())); + } + + if( imageEssenceDescriptorModel.getStoredOffset() != null) { + imfErrorLogger.addError(IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("EssenceDescriptor with ID %s invalid StoredOffset as per %s", + imageEssenceDescriptorID.toString(), applicationCompositionType.toString())); + } + + ColorPrimaries colorPrimaries = imageEssenceDescriptorModel.getColorPrimaries(); + if(colorPrimaries.equals(ColorPrimaries.Unknown)) { + imfErrorLogger.addError(IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("EssenceDescriptor with ID %s has invalid ColorPrimaries as per %s", + imageEssenceDescriptorID.toString(), applicationCompositionType.toString())); + } + + TransferCharacteristic transferCharacteristic = imageEssenceDescriptorModel.getTransferCharacteristic(); + if(transferCharacteristic.equals(TransferCharacteristic.Unknown)) { + imfErrorLogger.addError(IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("EssenceDescriptor with ID %s has invalid TransferCharacteristic as per %s", + imageEssenceDescriptorID.toString(), applicationCompositionType.toString())); + } + + Colorimetry color = imageEssenceDescriptorModel.getColor(); + if(color.equals(Colorimetry.Unknown)) { + imfErrorLogger.addError(IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("EssenceDescriptor with ID %s has invalid ColorPrimaries(%s)-TransferCharacteristic(%s) combination as per %s", + imageEssenceDescriptorID.toString(), colorPrimaries.name(), transferCharacteristic.name(), applicationCompositionType.toString())); + } + + FrameLayoutType frameLayoutType = imageEssenceDescriptorModel.getFrameLayoutType(); + UL essenceContainerFormatUL = imageEssenceDescriptorModel.getEssenceContainerFormatUL(); + if(essenceContainerFormatUL != null) { + JP2KContentKind contentKind = JP2KContentKind.valueOf(essenceContainerFormatUL.getULAsBytes()[14]); + if ((frameLayoutType.equals(FrameLayoutType.FullFrame) && !contentKind.equals(JP2KContentKind.P1))) { + imfErrorLogger.addError(IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("EssenceDescriptor with ID %s has invalid JPEG-2000 ContentKind (%s) indicated by the ContainerFormat as per %s", + imageEssenceDescriptorID.toString(), contentKind, applicationCompositionType.toString())); + } + } + } + + public static void validateImageCharacteristics(CompositionImageEssenceDescriptorModel imageEssenceDescriptorModel, + ApplicationCompositionType applicationCompositionType, + IMFErrorLogger imfErrorLogger) + { + UUID imageEssenceDescriptorID = imageEssenceDescriptorModel.getImageEssencedescriptorID(); + + ColorModel colorModel = imageEssenceDescriptorModel.getColorModel(); + if( !colorModel.equals(ColorModel.RGB) ) { + imfErrorLogger.addError(IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("XYZ EssenceDescriptor with ID %s has Invalid color components as per %s", + imageEssenceDescriptorID.toString(), applicationCompositionType.toString())); + return; + } + + //storedWidth + Integer storedWidth = imageEssenceDescriptorModel.getStoredWidth(); + if ((storedWidth > MAX_XYZ_IMAGE_FRAME_WIDTH)) { + imfErrorLogger.addError(IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("EssenceDescriptor with ID %s has invalid StoredWidth(%d) for ColorModel(%s) as per %s", + imageEssenceDescriptorID.toString(), storedWidth, colorModel.name(), applicationCompositionType.toString())); + } + + //storedHeight + Integer storedHeight = imageEssenceDescriptorModel.getStoredHeight(); + if ((storedHeight > MAX_XYZ_IMAGE_FRAME_HEIGHT)) { + imfErrorLogger.addError(IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("EssenceDescriptor with ID %s has invalid storedHeight(%d) for ColorModel(%s) as per %s", + imageEssenceDescriptorID.toString(), storedHeight, colorModel.name(), applicationCompositionType.toString())); + } + + //PixelBitDepth + Integer pixelBitDepth = imageEssenceDescriptorModel.getPixelBitDepth(); + Colorimetry color = imageEssenceDescriptorModel.getColor(); + if( !bitDepthsSupported.contains(pixelBitDepth) || !colorToBitDepthMap.get(color).contains(pixelBitDepth)) { + imfErrorLogger.addError(IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("EssenceDescriptor with ID %s invalid PixelBitDepth(%d) for Color(%s) as per %s", + imageEssenceDescriptorID.toString(), pixelBitDepth, color.name(), applicationCompositionType.toString())); + } + + //FrameLayout + FrameLayoutType frameLayoutType = imageEssenceDescriptorModel.getFrameLayoutType(); + if (!frameLayoutType.equals(FrameLayoutType.FullFrame)) { + imfErrorLogger.addError(IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("EssenceDescriptor with ID %s has invalid FrameLayout(%s) as per %s", + imageEssenceDescriptorID.toString(), frameLayoutType.name(), applicationCompositionType.toString())); + } + + //SampleRate + Fraction sampleRate = imageEssenceDescriptorModel.getSampleRate(); + Set frameRateSupported = sampleRateSupported; + if (!frameRateSupported.contains(sampleRate)) { + imfErrorLogger.addError(IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("EssenceDescriptor with ID %s has Invalid SampleRate(%s) for ColorModel(%s) as per %s", + imageEssenceDescriptorID.toString(), sampleRate.toString(), colorModel.name(), applicationCompositionType.toString())); + } + + //Sampling + Sampling sampling = imageEssenceDescriptorModel.getSampling(); + if((!sampling.equals(Sampling.Sampling444))) { + imfErrorLogger.addError(IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("EssenceDescriptor with ID %s has Invalid Sampling(%s) for ColorModel(%s) as per %s", + imageEssenceDescriptorID.toString(), sampling.name(), colorModel.name(), applicationCompositionType.toString())); + } + + //Quantization + Quantization quantization = imageEssenceDescriptorModel.getQuantization(); + if(!(quantization.equals(Quantization.QE2))) { + imfErrorLogger.addError(IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("EssenceDescriptor with ID %s has Invalid Quantization(%s) for ColorModel(%s) as per %s", + imageEssenceDescriptorID.toString(), quantization.name(), colorModel.name(), applicationCompositionType.toString())); + } + } + + public ApplicationCompositionType getApplicationCompositionType() { + return ApplicationCompositionType.APPLICATION_4_COMPOSITION_TYPE; + } + +} diff --git a/src/main/java/com/netflix/imflibrary/st2067_2/ApplicationCompositionFactory.java b/src/main/java/com/netflix/imflibrary/st2067_2/ApplicationCompositionFactory.java index c4e28e16..640ad6dc 100755 --- a/src/main/java/com/netflix/imflibrary/st2067_2/ApplicationCompositionFactory.java +++ b/src/main/java/com/netflix/imflibrary/st2067_2/ApplicationCompositionFactory.java @@ -46,6 +46,10 @@ public class ApplicationCompositionFactory { add("http://www.smpte-ra.org/schemas/2067-21/2016"); }}); + private static final Set namespacesApplication4Composition = Collections.unmodifiableSet(new HashSet() {{ + add("http://www.smpte-ra.org/schemas/2067-40/2016"); + }}); + private static final Set namespacesApplication5Composition = Collections.unmodifiableSet(new HashSet() {{ add("http://www.smpte-ra.org/ns/2067-50/2017"); }}); @@ -53,6 +57,7 @@ public class ApplicationCompositionFactory { public enum ApplicationCompositionType { APPLICATION_2_COMPOSITION_TYPE(Application2Composition.class, namespacesApplication2Composition), APPLICATION_2E_COMPOSITION_TYPE(Application2ExtendedComposition.class, namespacesApplication2EComposition), + APPLICATION_4_COMPOSITION_TYPE(Application4Composition.class, namespacesApplication4Composition), APPLICATION_5_COMPOSITION_TYPE(Application5Composition.class, namespacesApplication5Composition), APPLICATION_UNSUPPORTED_COMPOSITION_TYPE(ApplicationUnsupportedComposition.class, Collections.unmodifiableSet(new HashSet<>())); private Set nameSpaceSet; From 4152e69e274ac1cf601367dfaa6a86ae93c3eeb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Achard?= Date: Wed, 10 Jul 2019 11:28:56 +0200 Subject: [PATCH 3/5] Implement App#4 CompositionPlaylist constraints --- .../st2067_2/Application4Composition.java | 40 +++++ .../imflibrary/st2067_2/Composition.java | 153 ++++++++++++++++++ .../CompositionModel_st2067_2_2013.java | 10 ++ .../CompositionModel_st2067_2_2016.java | 9 ++ .../st2067_2/IMFCompositionPlaylistType.java | 28 +++- 5 files changed, 239 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/netflix/imflibrary/st2067_2/Application4Composition.java b/src/main/java/com/netflix/imflibrary/st2067_2/Application4Composition.java index e1a1574c..c8219425 100644 --- a/src/main/java/com/netflix/imflibrary/st2067_2/Application4Composition.java +++ b/src/main/java/com/netflix/imflibrary/st2067_2/Application4Composition.java @@ -86,6 +86,9 @@ public Application4Composition(@Nonnull IMFCompositionPlaylistType imfCompositio try { + // Validate CPL Constraints + validateCompositionPlaylist(imfCompositionPlaylistType, ApplicationCompositionType.APPLICATION_4_COMPOSITION_TYPE, imfErrorLogger); + // Validate Image Essence Constraints CompositionImageEssenceDescriptorModel imageEssenceDescriptorModel = getCompositionImageEssenceDescriptorModel(); if (imageEssenceDescriptorModel != null) @@ -101,6 +104,43 @@ public Application4Composition(@Nonnull IMFCompositionPlaylistType imfCompositio } } + public static void validateCompositionPlaylist(IMFCompositionPlaylistType imfCompositionPlaylistType, + ApplicationCompositionFactory.ApplicationCompositionType applicationCompositionType, + IMFErrorLogger imfErrorLogger) + { + //ContentKind + String contentKind = imfCompositionPlaylistType.getContentKind(); + if(contentKind == null) { + imfErrorLogger.addError(IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("ContentKind shall be present as per %s", applicationCompositionType.toString())); + } + + //Creator + String creator = imfCompositionPlaylistType.getCreator(); + if(creator == null) { + imfErrorLogger.addError(IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("Creator shall be present as per %s", applicationCompositionType.toString())); + } + + //Issuer + String issuer = imfCompositionPlaylistType.getIssuer(); + if(issuer == null) { + imfErrorLogger.addError(IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("Issuer shall be present as per %s", applicationCompositionType.toString())); + } + + //ContentVersionList + if( imfCompositionPlaylistType.getContentVersionList().getContentVersions().size() == 0) { + imfErrorLogger.addError(IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("At least one ContentVersion shall be present as per %s", applicationCompositionType.toString())); + } + + } + public static void validateGenericPictureEssenceDescriptor(CompositionImageEssenceDescriptorModel imageEssenceDescriptorModel, ApplicationCompositionFactory.ApplicationCompositionType applicationCompositionType, diff --git a/src/main/java/com/netflix/imflibrary/st2067_2/Composition.java b/src/main/java/com/netflix/imflibrary/st2067_2/Composition.java index 53672697..80398ef3 100644 --- a/src/main/java/com/netflix/imflibrary/st2067_2/Composition.java +++ b/src/main/java/com/netflix/imflibrary/st2067_2/Composition.java @@ -32,12 +32,14 @@ import org.xml.sax.SAXException; import javax.annotation.concurrent.Immutable; +import javax.swing.text.AbstractDocument.Content; import javax.xml.bind.JAXBException; import java.io.File; import java.io.IOException; import java.math.BigInteger; import java.util.*; import java.util.List; +import java.util.Map; /** * This class represents a canonical model of the XML type 'CompositionPlaylistType' defined by SMPTE st2067-3, @@ -520,4 +522,155 @@ public HeaderPartition getHeaderPartition(){ } } + /** + * This class is an immutable implementation of a content version, described as an ID and a label + */ + @Immutable + public static final class ContentVersion { + private final String id; + private final String labelText; + + /** + * Constructor for the content version. + * + * @param id the Id of the content represented by the CompositionPlaylist + * @param labelText the human readable description of the content + */ + public ContentVersion(String id, String labelText) { + this.id = id; + this.labelText = labelText; + } + + /** + * Getter for the content version Id + * + * @return a string value corresponding to the frame rate numerator + */ + public String getId() { + return this.id; + } + + /** + * Getter for the content version description + * + * @return a string value corresponding to the frame rate denominator + */ + public String getLabelText() { + return this.labelText; + } + + /** + * A method that returns a string representation of a Composition object + * + * @return string representing the object + */ + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("=================== ContentVersion =====================\n"); + sb.append(String.format("id = %s, labelText = %s%n", this.id, this.labelText)); + return sb.toString(); + } + + /** + * Overridden equals method. + * + * @param object the ContentVersion to be compared with. + * @return boolean false if the object is null or is not an instance of the ContentVersion class. + */ + @Override + public boolean equals(Object object) { + if (object == null + || !(object instanceof ContentVersion)) { + return false; + } + ContentVersion other = (ContentVersion) object; + return ((this.getId().equals(other.getId())) && (this.getLabelText().equals(other.getLabelText()))); + } + + /** + * A Java compliant implementation of the hashCode() method + * + * @return integer containing the hash code corresponding to this object + */ + @Override + public int hashCode() { + int hash = 1; + hash = hash * 31 + this.id.hashCode(); + hash = hash * 31 + this.labelText.hashCode(); + return hash; + } + + } + + /** + * This class is an immutable implementation of a content version list + */ + @Immutable + public static final class ContentVersionList { + private final List contentVersions; + private final IMFErrorLogger imfErrorLogger; + + /** + * Constructor for the content version list. + * + * @param contentVersions the list of content versions + */ + public ContentVersionList(List contentVersions) { + this.contentVersions = contentVersions; + + this.imfErrorLogger = new IMFErrorLoggerImpl(); + validateContentVersions(); + } + + /** + * Constructor for the content version list. + * + * @param contentVersions the list of content versions + */ + public ContentVersionList(Map contentVersions) { + this.contentVersions = new ArrayList(); + for (Map.Entry entry : contentVersions.entrySet()) { + this.contentVersions.add(new ContentVersion(entry.getKey(), entry.getValue())); + } + + this.imfErrorLogger = new IMFErrorLoggerImpl(); + validateContentVersions(); + } + + /** + * Validate content version list. + * + * @throws Validation failed + */ + private void validateContentVersions() { + + Set contentIds = new HashSet<>(); + for (ContentVersion contentVersion : contentVersions) { + contentIds.add(contentVersion.getId()); + } + + if (contentIds.size() < contentVersions.size()) { + imfErrorLogger.addError(IMFErrorLogger.IMFErrors.ErrorCodes.IMF_CPL_ERROR, IMFErrorLogger.IMFErrors + .ErrorLevels.NON_FATAL, String.format( + "No two ContentVersion elements shall have identical Id elements")); + + } + + if(imfErrorLogger.hasFatalErrors()) + { + throw new IMFException("Failed to create IMFBaseResourceType", imfErrorLogger); + } + } + + /** + * Getter for the content version list + * + * @return a list value containing all content versions + */ + public List getContentVersions() { + return this.contentVersions; + } + + } + } \ No newline at end of file diff --git a/src/main/java/com/netflix/imflibrary/st2067_2/CompositionModel_st2067_2_2013.java b/src/main/java/com/netflix/imflibrary/st2067_2/CompositionModel_st2067_2_2013.java index a0737b5e..c9e00352 100755 --- a/src/main/java/com/netflix/imflibrary/st2067_2/CompositionModel_st2067_2_2013.java +++ b/src/main/java/com/netflix/imflibrary/st2067_2/CompositionModel_st2067_2_2013.java @@ -4,6 +4,7 @@ import com.netflix.imflibrary.exceptions.IMFException; import com.netflix.imflibrary.utils.UUIDHelper; import com.netflix.imflibrary.writerTools.CompositionPlaylistBuilder_2013; + import org.w3c.dom.Element; @@ -13,8 +14,10 @@ import java.util.Collections; import java.util.LinkedHashSet; import java.util.LinkedList; +import java.util.HashMap; import java.util.List; import java.util.Set; +import java.util.Map; import java.util.UUID; /** @@ -189,6 +192,11 @@ public static IMFCompositionPlaylistType getCompositionPlaylist (@Nonnull org.sm } } + Map contentVersionList = new HashMap(); + for (org.smpte_ra.schemas.st2067_2_2013.ContentVersionType entry : compositionPlaylistType.getContentVersionList().getContentVersion()) { + contentVersionList.put(entry.getId(), entry.getLabelText().getValue()); + } + return new IMFCompositionPlaylistType(compositionPlaylistType.getId(), compositionPlaylistType.getEditRate(), (compositionPlaylistType.getAnnotation() == null ? null : compositionPlaylistType.getAnnotation().getValue()), @@ -196,6 +204,8 @@ public static IMFCompositionPlaylistType getCompositionPlaylist (@Nonnull org.sm (compositionPlaylistType.getCreator() == null ? null : compositionPlaylistType.getCreator().getValue()), (compositionPlaylistType.getContentOriginator() == null ? null : compositionPlaylistType.getContentOriginator().getValue()), (compositionPlaylistType.getContentTitle() == null ? null : compositionPlaylistType.getContentTitle().getValue()), + (compositionPlaylistType.getContentKind() == null ? null : compositionPlaylistType.getContentKind().getValue()), + contentVersionList, Collections.synchronizedList(segmentList), Collections.synchronizedList(essenceDescriptorList), "org.smpte_ra.schemas.st2067_2_2013", applicationIDs); diff --git a/src/main/java/com/netflix/imflibrary/st2067_2/CompositionModel_st2067_2_2016.java b/src/main/java/com/netflix/imflibrary/st2067_2/CompositionModel_st2067_2_2016.java index 4faa3b28..60156f8f 100755 --- a/src/main/java/com/netflix/imflibrary/st2067_2/CompositionModel_st2067_2_2016.java +++ b/src/main/java/com/netflix/imflibrary/st2067_2/CompositionModel_st2067_2_2016.java @@ -13,8 +13,10 @@ import java.util.Collections; import java.util.LinkedHashSet; import java.util.LinkedList; +import java.util.HashMap; import java.util.List; import java.util.Set; +import java.util.Map; import java.util.UUID; /** @@ -205,6 +207,11 @@ public static IMFCompositionPlaylistType getCompositionPlaylist (@Nonnull org.sm } } + Map contentVersionList = new HashMap(); + for (org.smpte_ra.schemas.st2067_2_2016.ContentVersionType entry : compositionPlaylistType.getContentVersionList().getContentVersion()) { + contentVersionList.put(entry.getId(), entry.getLabelText().getValue()); + } + return new IMFCompositionPlaylistType( compositionPlaylistType.getId(), compositionPlaylistType.getEditRate(), (compositionPlaylistType.getAnnotation() == null ? null : compositionPlaylistType.getAnnotation().getValue()), @@ -212,6 +219,8 @@ public static IMFCompositionPlaylistType getCompositionPlaylist (@Nonnull org.sm (compositionPlaylistType.getCreator() == null ? null : compositionPlaylistType.getCreator().getValue()), (compositionPlaylistType.getContentOriginator() == null ? null : compositionPlaylistType.getContentOriginator().getValue()), (compositionPlaylistType.getContentTitle() == null ? null : compositionPlaylistType.getContentTitle().getValue()), + (compositionPlaylistType.getContentKind() == null ? null : compositionPlaylistType.getContentKind().getValue()), + contentVersionList, Collections.synchronizedList(segmentList), Collections.synchronizedList(essenceDescriptorList), "org.smpte_ra.schemas.st2067_2_2016", applicationIDs diff --git a/src/main/java/com/netflix/imflibrary/st2067_2/IMFCompositionPlaylistType.java b/src/main/java/com/netflix/imflibrary/st2067_2/IMFCompositionPlaylistType.java index b477f493..42793f2f 100644 --- a/src/main/java/com/netflix/imflibrary/st2067_2/IMFCompositionPlaylistType.java +++ b/src/main/java/com/netflix/imflibrary/st2067_2/IMFCompositionPlaylistType.java @@ -62,6 +62,8 @@ final class IMFCompositionPlaylistType { private final String creator; private final String contentOriginator; private final String contentTitle; + private final String contentKind; + private final Composition.ContentVersionList contentVersionList; private final List segmentList; private final List essenceDescriptorList; private final IMFErrorLogger imfErrorLogger; @@ -82,12 +84,14 @@ public IMFCompositionPlaylistType(String id, String creator, String contentOriginator, String contentTitle, + String contentKind, + Map contentVersionList, List segmentList, List essenceDescriptorList, String coreConstraintsVersion, String applicationId) { - this(id, editRate, annotation, issuer, creator, contentOriginator, contentTitle, segmentList, essenceDescriptorList, coreConstraintsVersion, (applicationId == null ? new HashSet<>() : new HashSet(Arrays.asList(applicationId)))); + this(id, editRate, annotation, issuer, creator, contentOriginator, contentTitle, contentKind, contentVersionList, segmentList, essenceDescriptorList, coreConstraintsVersion, (applicationId == null ? new HashSet<>() : new HashSet(Arrays.asList(applicationId)))); } public IMFCompositionPlaylistType(String id, @@ -97,6 +101,8 @@ public IMFCompositionPlaylistType(String id, String creator, String contentOriginator, String contentTitle, + String contentKind, + Map contentVersionList, List segmentList, List essenceDescriptorList, String coreConstraintsVersion, @@ -104,10 +110,12 @@ public IMFCompositionPlaylistType(String id, { this.id = UUIDHelper.fromUUIDAsURNStringToUUID(id); Composition.EditRate rate = null; + Composition.ContentVersionList versions = null; imfErrorLogger = new IMFErrorLoggerImpl(); try { rate = new Composition.EditRate(editRate); + versions = new Composition.ContentVersionList(contentVersionList); } catch(IMFException e) { @@ -120,6 +128,8 @@ public IMFCompositionPlaylistType(String id, this.creator = creator; this.contentOriginator = contentOriginator; this.contentTitle = contentTitle; + this.contentKind = contentKind; + this.contentVersionList= versions; this.segmentList = segmentList; this.essenceDescriptorList = essenceDescriptorList; this.coreConstraintsVersion = coreConstraintsVersion; @@ -432,6 +442,22 @@ public String getContentTitle(){ return this.contentTitle; } + /** + * Getter for the Composition Playlist contentKind + * @return a string representing contentKind of the Composition Playlist + */ + public String getContentKind(){ + return this.contentKind; + } + + /** + * Getter for the Composition Playlist contentVersionList + * @return an object representing contentVersionList of the Composition Playlist + */ + public Composition.ContentVersionList getContentVersionList(){ + return this.contentVersionList; + } + /** * Getter for the SegmentList of the Composition Playlist * @return a string representing the SegmentList of the Composition Playlist From 4ec9bc1c163227b2b256c6d25012a49a7378fd7a Mon Sep 17 00:00:00 2001 From: Hans-Nikolas Locher Date: Mon, 27 Apr 2020 18:35:31 +0200 Subject: [PATCH 4/5] Avoid loop on contentVersion if contentVersionList is empty (and null) --- .../st2067_2/CompositionModel_st2067_2_2016.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/netflix/imflibrary/st2067_2/CompositionModel_st2067_2_2016.java b/src/main/java/com/netflix/imflibrary/st2067_2/CompositionModel_st2067_2_2016.java index 60156f8f..181b3452 100755 --- a/src/main/java/com/netflix/imflibrary/st2067_2/CompositionModel_st2067_2_2016.java +++ b/src/main/java/com/netflix/imflibrary/st2067_2/CompositionModel_st2067_2_2016.java @@ -208,10 +208,13 @@ public static IMFCompositionPlaylistType getCompositionPlaylist (@Nonnull org.sm } Map contentVersionList = new HashMap(); - for (org.smpte_ra.schemas.st2067_2_2016.ContentVersionType entry : compositionPlaylistType.getContentVersionList().getContentVersion()) { - contentVersionList.put(entry.getId(), entry.getLabelText().getValue()); + if (compositionPlaylistType.getContentVersionList() != null) { + for (org.smpte_ra.schemas.st2067_2_2016.ContentVersionType entry : compositionPlaylistType.getContentVersionList().getContentVersion()) { + contentVersionList.put(entry.getId(), entry.getLabelText().getValue()); + } } + return new IMFCompositionPlaylistType( compositionPlaylistType.getId(), compositionPlaylistType.getEditRate(), (compositionPlaylistType.getAnnotation() == null ? null : compositionPlaylistType.getAnnotation().getValue()), From 21b75eb8182c944a6a4d3432558f5f25c8fdd255 Mon Sep 17 00:00:00 2001 From: Hans-Nikolas Locher Date: Mon, 27 Apr 2020 18:55:32 +0200 Subject: [PATCH 5/5] Replace jdk.nashorn.internal.ir.annotations.Immutable imports - With regular javax.annotation.concurrent.Immutable --- .../OutputProfileListModel_st2067_100_2014.java | 11 ++++++----- .../st2067_201/IABSoundfieldLabelSubDescriptor.java | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/netflix/imflibrary/st2067_100/OutputProfileListModel_st2067_100_2014.java b/src/main/java/com/netflix/imflibrary/st2067_100/OutputProfileListModel_st2067_100_2014.java index 9aa0f990..4d5db1fb 100755 --- a/src/main/java/com/netflix/imflibrary/st2067_100/OutputProfileListModel_st2067_100_2014.java +++ b/src/main/java/com/netflix/imflibrary/st2067_100/OutputProfileListModel_st2067_100_2014.java @@ -23,7 +23,7 @@ import com.netflix.imflibrary.st2067_100.macro.scale.ScaleInputImageSequence; import com.netflix.imflibrary.st2067_100.macro.scale.ScaleMacro; import com.netflix.imflibrary.st2067_100.macro.scale.ScaleOutputImageSequence; -import jdk.nashorn.internal.ir.annotations.Immutable; + import org.smpte_ra.schemas._2067_100._2014.OutputProfileListType.AliasList.Alias; import org.smpte_ra.schemas._2067_100._2014.PresetMacroType; import org.smpte_ra.schemas._2067_101._2014.crop_macro.ImageCropMacroType; @@ -38,6 +38,7 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import javax.annotation.concurrent.Immutable; /** * A class that models OutputProfileList as per specification st2067-100:2014. @@ -126,7 +127,7 @@ private CropMacro createCropMacro(ImageCropMacroType imageCropMacroType) { inputImageSequence.getInset().getTop().intValue(), inputImageSequence.getInset().getBottom().intValue()); - CropInputImageSequence input = new CropInputImageSequence(inputImageSequence.getAnnotation() != null ? inputImageSequence.getAnnotation().getValue() : null, + CropInputImageSequence input = new CropInputImageSequence(inputImageSequence.getAnnotation() != null ? inputImageSequence.getAnnotation().getValue() : null, inputImageSequence.getHandle(), mxfRectangleEnum, inset); if(imageCropMacroType.getOutputImageSequence() == null) { @@ -156,7 +157,7 @@ private ScaleMacro createScaleMacro(ImageScaleMacroType imageScaleMacroType) { String.format("Missing InputImageSequence in %s macro", imageScaleMacroType.getName())); } ImageScaleMacroType.InputImageSequence inputImageSequence = imageScaleMacroType.getInputImageSequence(); - ScaleInputImageSequence input = new ScaleInputImageSequence(inputImageSequence.getAnnotation() != null ? inputImageSequence.getAnnotation().getValue() : null, + ScaleInputImageSequence input = new ScaleInputImageSequence(inputImageSequence.getAnnotation() != null ? inputImageSequence.getAnnotation().getValue() : null, inputImageSequence.getHandle()); if(imageScaleMacroType.getOutputImageSequence() == null) { @@ -188,7 +189,7 @@ private PixelDecoderMacro createPixelDecoderMacro(PixelDecoderType pixelDecoderT String.format("Missing InputImageSequence in %s macro", pixelDecoderType.getName())); } PixelDecoderType.InputImageSequence inputImageSequence = pixelDecoderType.getInputImageSequence(); - PixelDecoderInputImageSequence input = new PixelDecoderInputImageSequence(inputImageSequence.getAnnotation() != null ? inputImageSequence.getAnnotation().getValue() : null, + PixelDecoderInputImageSequence input = new PixelDecoderInputImageSequence(inputImageSequence.getAnnotation() != null ? inputImageSequence.getAnnotation().getValue() : null, inputImageSequence.getHandle()); if(pixelDecoderType.getOutputReferenceImageSequence() == null) { @@ -210,7 +211,7 @@ private PixelEncoderMacro createPixelEncoderMacro(PixelEncoderType pixelEncoderT String.format("Missing InputImageSequence in %s macro", pixelEncoderType.getName())); } PixelEncoderType.InputReferenceImageSequence inputImageSequence = pixelEncoderType.getInputReferenceImageSequence(); - PixelEncoderInputImageSequence input = new PixelEncoderInputImageSequence(inputImageSequence.getAnnotation() != null ? inputImageSequence.getAnnotation().getValue() : null, + PixelEncoderInputImageSequence input = new PixelEncoderInputImageSequence(inputImageSequence.getAnnotation() != null ? inputImageSequence.getAnnotation().getValue() : null, inputImageSequence.getHandle()); if(pixelEncoderType.getOutputImageSequence() == null) { diff --git a/src/main/java/com/netflix/imflibrary/st2067_201/IABSoundfieldLabelSubDescriptor.java b/src/main/java/com/netflix/imflibrary/st2067_201/IABSoundfieldLabelSubDescriptor.java index 38cc81c9..01440863 100644 --- a/src/main/java/com/netflix/imflibrary/st2067_201/IABSoundfieldLabelSubDescriptor.java +++ b/src/main/java/com/netflix/imflibrary/st2067_201/IABSoundfieldLabelSubDescriptor.java @@ -9,10 +9,10 @@ import com.netflix.imflibrary.st0377.header.StructuralMetadata; import com.netflix.imflibrary.st0377.header.UL; import com.netflix.imflibrary.utils.ByteProvider; -import jdk.nashorn.internal.ir.annotations.Immutable; import java.io.IOException; import java.util.Map; +import javax.annotation.concurrent.Immutable; /** * Object model corresponding to MultiChannelAudioLabelSubDescriptor structural metadata set defined in st20167-201:201x