diff --git a/src/main/java/com/netflix/imflibrary/JPEG2000.java b/src/main/java/com/netflix/imflibrary/JPEG2000.java index 0c862143..b1d50397 100644 --- a/src/main/java/com/netflix/imflibrary/JPEG2000.java +++ b/src/main/java/com/netflix/imflibrary/JPEG2000.java @@ -115,6 +115,58 @@ public static boolean isIMF4KProfile(UL pictureEssenceCoding) { return false; } + public static boolean isIMF8KProfile(UL pictureEssenceCoding) { + if (!pictureEssenceCoding.equalsWithMask(J2K_NODE_UL, 0b1111111011111100)) + return false; + + + if (pictureEssenceCoding.getByte(14) == 0x04) { + /* lossy profile */ + + if (pictureEssenceCoding.getByte(15) < 0x02 || pictureEssenceCoding.getByte(15) > 0x31) { + /* Only Mainlevel 1-10 are allowed */ + return false; + } + + /* Sublevel 0 is not allowed */ + switch (pictureEssenceCoding.getByte(15)) { + case 0x02: /* J2K_8KIMF_SingleTileLossyProfile_M1S0 */ + case 0x04: /* J2K_8KIMF_SingleTileLossyProfile_M2S0 */ + case 0x06: /* J2K_8KIMF_SingleTileLossyProfile_M3S0 */ + case 0x08: /* J2K_8KIMF_SingleTileLossyProfile_M4S0 */ + case 0x00: /* J2K_8KIMF_SingleTileLossyProfile_M5S0 */ + case 0x0f: /* J2K_8KIMF_SingleTileLossyProfile_M6S0 */ + case 0x14: /* J2K_8KIMF_SingleTileLossyProfile_M7S0 */ + case 0x1a: /* J2K_8KIMF_SingleTileLossyProfile_M8S0 */ + case 0x21: /* J2K_8KIMF_SingleTileLossyProfile_M9S0 */ + case 0x29: /* J2K_8KIMF_SingleTileLossyProfile_M10S0 */ + return false; + } + + return true; + + } else if (pictureEssenceCoding.getByte(14) == 0x07) { + /* lossless profile */ + + switch (pictureEssenceCoding.getByte(15)) { + case 0x02: /* J2K_8KIMF_SingleMultiTileReversibleProfile_M1S0 */ + case 0x04: /* J2K_8KIMF_SingleMultiTileReversibleProfile_M2S0 */ + case 0x06: /* J2K_8KIMF_SingleMultiTileReversibleProfile_M3S0 */ + case 0x08: /* J2K_8KIMF_SingleMultiTileReversibleProfile_M4S0 */ + case 0x0b: /* J2K_8KIMF_SingleMultiTileReversibleProfile_M5S0 */ + case 0x0f: /* J2K_8KIMF_SingleMultiTileReversibleProfile_M6S0 */ + case 0x14: /* J2K_8KIMF_SingleMultiTileReversibleProfile_M7S0 */ + case 0x1a: /* J2K_8KIMF_SingleMultiTileReversibleProfile_M8S0 */ + case 0x21: /* J2K_8KIMF_SingleMultiTileReversibleProfile_M8S0 */ + case 0x29: /* J2K_8KIMF_SingleMultiTileReversibleProfile_M8S0 */ + return true; + } + } + + return false; + } + + public static boolean isAPP2HT(UL pictureEssenceCoding) { return pictureEssenceCoding.equalsIgnoreVersion(HTJ2K_UL); } diff --git a/src/main/java/com/netflix/imflibrary/st2067_2/CoreConstraints.java b/src/main/java/com/netflix/imflibrary/st2067_2/CoreConstraints.java index e9bb602c..fb001d31 100644 --- a/src/main/java/com/netflix/imflibrary/st2067_2/CoreConstraints.java +++ b/src/main/java/com/netflix/imflibrary/st2067_2/CoreConstraints.java @@ -7,6 +7,8 @@ import java.util.Collections; import java.util.List; +import com.netflix.imflibrary.validation.IMFApp2E5EDConstraintsValidator; + public final class CoreConstraints { private CoreConstraints() {} // Prevent instantiation. This class is constants and utilities only @@ -27,7 +29,9 @@ private CoreConstraints() {} // Prevent instantiation. This class is constants a { // NOTE- When adding new namespaces or core constraint versions, be sure that the most recent core constraints // are checked first. That way if there are multiple ApplicationIdentifications, the newest version is returned. - if (applicationIds.contains("http://www.smpte-ra.org/ns/2067-21/2020") || (applicationIds.contains("http://www.smpte-ra.org/ns/2067-21/2021"))) + if (applicationIds.contains("http://www.smpte-ra.org/ns/2067-21/2020") || + applicationIds.contains("http://www.smpte-ra.org/ns/2067-21/2021") || + applicationIds.contains(IMFApp2E5EDConstraintsValidator.applicationIdentification)) { return CoreConstraints.NAMESPACE_IMF_2020; } diff --git a/src/main/java/com/netflix/imflibrary/validation/ConstraintsValidatorFactory.java b/src/main/java/com/netflix/imflibrary/validation/ConstraintsValidatorFactory.java index 9d9985ca..d0fea2e8 100644 --- a/src/main/java/com/netflix/imflibrary/validation/ConstraintsValidatorFactory.java +++ b/src/main/java/com/netflix/imflibrary/validation/ConstraintsValidatorFactory.java @@ -29,6 +29,7 @@ public class ConstraintsValidatorFactory { registerValidator("http://www.smpte-ra.org/schemas/2067-3/2016", IMFCPL2016Validator::new); // IMF App #2E + registerValidator(IMFApp2E5EDConstraintsValidator.applicationIdentification, IMFApp2E5EDConstraintsValidator::new); registerValidator("http://www.smpte-ra.org/ns/2067-21/2021", IMFApp2E2021ConstraintsValidator::new); registerValidator("http://www.smpte-ra.org/ns/2067-21/2020", IMFApp2E2020ConstraintsValidator::new); registerValidator("http://www.smpte-ra.org/schemas/2067-21/2016", IMFApp2E2016ConstraintsValidator::new); diff --git a/src/main/java/com/netflix/imflibrary/validation/IMFApp2E5EDConstraintsValidator.java b/src/main/java/com/netflix/imflibrary/validation/IMFApp2E5EDConstraintsValidator.java new file mode 100644 index 00000000..4a096172 --- /dev/null +++ b/src/main/java/com/netflix/imflibrary/validation/IMFApp2E5EDConstraintsValidator.java @@ -0,0 +1,471 @@ +package com.netflix.imflibrary.validation; + +import com.netflix.imflibrary.*; +import com.netflix.imflibrary.st0377.header.GenericPictureEssenceDescriptor; +import com.netflix.imflibrary.st0377.header.UL; +import com.netflix.imflibrary.st2067_2.CompositionImageEssenceDescriptorModel; +import com.netflix.imflibrary.st2067_2.CompositionImageEssenceDescriptorModel.ProgressionOrder; +import com.netflix.imflibrary.utils.ErrorLogger; +import com.netflix.imflibrary.utils.Fraction; + +import java.util.*; + +/** + * Collection of properties and validations specific to ST 2067-21 5th edition. + */ +public class IMFApp2E5EDConstraintsValidator extends IMFApp2EConstraintsValidator { + + private static final String applicationCompositionType = "SMPTE ST 2067-21 IMF Application #2E 5th Edition"; + + public static final String applicationIdentification = "http://www.smpte-ra.org/ns/2067-21/5ED"; + + @Override + public String getConstraintsSpecification() { + return applicationCompositionType; + } + + static final Fraction[] FPS_HD = { + new Fraction(25), + new Fraction(30), + new Fraction(30000, 1001) + }; + + static final Fraction[] FPS_UHD = { + new Fraction(24), + new Fraction(24000, 1001), + new Fraction(25), + new Fraction(30), + new Fraction(30000, 1001), + new Fraction(50), + new Fraction(60), + new Fraction(60000, 1001) + }; + + static final Fraction[] FPS_4K = { + new Fraction(24), + new Fraction(24000, 1001), + new Fraction(25), + new Fraction(30), + new Fraction(30000, 1001), + new Fraction(50), + new Fraction(60), + new Fraction(60000, 1001), + new Fraction(120) + }; + + + /* Table 3 at SMPTE ST 2067-21:2023 */ + static final CharacteristicsSet[] IMAGE_CHARACTERISTICS = { + new CharacteristicsSet( + 1920, + 1080, + Arrays.asList(Colorimetry.Color1, Colorimetry.Color2, Colorimetry.Color3), + Arrays.asList(8, 10), + Arrays.asList(GenericPictureEssenceDescriptor.FrameLayoutType.SeparateFields), + Arrays.asList(FPS_HD), + Arrays.asList(Colorimetry.Sampling.Sampling422), + Arrays.asList(Colorimetry.Quantization.QE1), + Arrays.asList(Colorimetry.ColorModel.YUV) + ), + new CharacteristicsSet( + 1920, + 1080, + Arrays.asList(Colorimetry.Color1, Colorimetry.Color2, Colorimetry.Color3), + Arrays.asList(8, 10), + Arrays.asList(GenericPictureEssenceDescriptor.FrameLayoutType.FullFrame), + Arrays.asList(FPS_HD), + Arrays.asList(Colorimetry.Sampling.Sampling422), + Arrays.asList(Colorimetry.Quantization.QE1), + Arrays.asList(Colorimetry.ColorModel.YUV) + ), + new CharacteristicsSet( + 1920, + 1080, + Arrays.asList(Colorimetry.Color1, Colorimetry.Color2, Colorimetry.Color3), + Arrays.asList(8, 10), + Arrays.asList(GenericPictureEssenceDescriptor.FrameLayoutType.FullFrame), + Arrays.asList(FPS_HD), + Arrays.asList(Colorimetry.Sampling.Sampling444), + Arrays.asList(Colorimetry.Quantization.QE1), + Arrays.asList(Colorimetry.ColorModel.YUV, Colorimetry.ColorModel.RGB) + ), + new CharacteristicsSet( + 3840, + 2160, + Arrays.asList(Colorimetry.Color4), + Arrays.asList(8, 10), + Arrays.asList(GenericPictureEssenceDescriptor.FrameLayoutType.FullFrame), + Arrays.asList(FPS_UHD), + Arrays.asList(Colorimetry.Sampling.Sampling422), + Arrays.asList(Colorimetry.Quantization.QE1), + Arrays.asList(Colorimetry.ColorModel.YUV) + ), + /* QHD */ + new CharacteristicsSet( + 7680, + 4320, + Arrays.asList(Colorimetry.Color3), + Arrays.asList(8, 10, 12, 16), + Arrays.asList(GenericPictureEssenceDescriptor.FrameLayoutType.FullFrame), + Arrays.asList(FPS_UHD), + Arrays.asList(Colorimetry.Sampling.Sampling422), + Arrays.asList(Colorimetry.Quantization.QE1), + Arrays.asList(Colorimetry.ColorModel.YUV) + ), + new CharacteristicsSet( + 7680, + 4320, + Arrays.asList(Colorimetry.Color5, Colorimetry.Color8), + Arrays.asList(10, 12), + Arrays.asList(GenericPictureEssenceDescriptor.FrameLayoutType.FullFrame), + Arrays.asList(FPS_UHD), + Arrays.asList(Colorimetry.Sampling.Sampling422), + Arrays.asList(Colorimetry.Quantization.QE1), + Arrays.asList(Colorimetry.ColorModel.YUV) + ), + new CharacteristicsSet( + 7680, + 4320, + Arrays.asList(Colorimetry.Color7), + Arrays.asList(10, 12, 16), + Arrays.asList(GenericPictureEssenceDescriptor.FrameLayoutType.FullFrame), + Arrays.asList(FPS_UHD), + Arrays.asList(Colorimetry.Sampling.Sampling422), + Arrays.asList(Colorimetry.Quantization.QE1), + Arrays.asList(Colorimetry.ColorModel.YUV) + ), + /* 8K */ + new CharacteristicsSet( + 8192, + 6224, + Arrays.asList(Colorimetry.Color3), + Arrays.asList(8, 10, 12, 16), + Arrays.asList(GenericPictureEssenceDescriptor.FrameLayoutType.FullFrame), + Arrays.asList(FPS_4K), + Arrays.asList(Colorimetry.Sampling.Sampling444), + Arrays.asList(Colorimetry.Quantization.QE1, Colorimetry.Quantization.QE2), + Arrays.asList(Colorimetry.ColorModel.RGB) + ), + new CharacteristicsSet( + 8192, + 6224, + Arrays.asList(Colorimetry.Color5, Colorimetry.Color8), + Arrays.asList(10, 12), + Arrays.asList(GenericPictureEssenceDescriptor.FrameLayoutType.FullFrame), + Arrays.asList(FPS_4K), + Arrays.asList(Colorimetry.Sampling.Sampling444), + Arrays.asList(Colorimetry.Quantization.QE1, Colorimetry.Quantization.QE2), + Arrays.asList(Colorimetry.ColorModel.RGB) + ), + new CharacteristicsSet( + 8192, + 6224, + Arrays.asList(Colorimetry.Color6, Colorimetry.Color7), + Arrays.asList(10, 12, 16), + Arrays.asList(GenericPictureEssenceDescriptor.FrameLayoutType.FullFrame), + Arrays.asList(FPS_4K), + Arrays.asList(Colorimetry.Sampling.Sampling444), + Arrays.asList(Colorimetry.Quantization.QE1, Colorimetry.Quantization.QE2), + Arrays.asList(Colorimetry.ColorModel.RGB) + ) + }; + + + @Override + protected CharacteristicsSet[] getValidImageCharacteristicsSets() { + return IMAGE_CHARACTERISTICS; + } + + @Override + protected List validateJ2KProfile(CompositionImageEssenceDescriptorModel imageDescriptor) { + + IMFErrorLogger imfErrorLogger = new IMFErrorLoggerImpl(); + + UL essenceCoding = imageDescriptor.getPictureEssenceCodingUL(); + if (!essenceCoding.equalsWithMask(JPEG2000PICTURECODINGSCHEME, 0b1111111011111100)) { + imfErrorLogger.addError(IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.FATAL, + String.format("Image codec must be JPEG 2000. Found %s instead.", essenceCoding.toString() + )); + return imfErrorLogger.getErrors(); + } + + Integer width = imageDescriptor.getStoredWidth(); + Integer height = imageDescriptor.getStoredHeight(); + + if (JPEG2000.isAPP2HT(essenceCoding)) + return validateHTConstraints(imageDescriptor.getJ2KHeaderParameters()); + + if (JPEG2000.isIMF8KProfile(essenceCoding)) { + if (!(width > 4096 && width <= 8192 && height > 0 && height <= 6224)) { + imfErrorLogger.addError(IMFErrorLogger.IMFErrors.ErrorCodes.IMF_ESSENCE_COMPONENT_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("JPEG 2000 IMF 8K Profile does not support image resolution (%d/%d)", width, height)); + } + } else if (JPEG2000.isIMF4KProfile(essenceCoding)) { + if (!(width > 2048 && width <= 4096 && height > 0 && height <= 3112)) { + imfErrorLogger.addError(IMFErrorLogger.IMFErrors.ErrorCodes.IMF_ESSENCE_COMPONENT_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("JPEG 2000 IMF 4K Profile does not support image resolution (%d/%d)", width, height)); + } + } else if (JPEG2000.isIMF2KProfile(essenceCoding)) { + if (!(width > 0 && width <= 2048 && height > 0 && height <= 1556)) { + imfErrorLogger.addError(IMFErrorLogger.IMFErrors.ErrorCodes.IMF_ESSENCE_COMPONENT_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("JPEG 2000 IMF 2K Profile does not support image resolution (%d/%d)", width, height)); + } + } else if (JPEG2000.isBroadcastProfile(essenceCoding)) { + if (!(width > 0 && width <= 3840 && height > 0 && height <= 2160)) { + imfErrorLogger.addError(IMFErrorLogger.IMFErrors.ErrorCodes.IMF_ESSENCE_COMPONENT_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("JPEG 2000 Broadcast Profile does not support image resolution (%d/%d)", width, height)); + } + } else { + imfErrorLogger.addError(IMFErrorLogger.IMFErrors.ErrorCodes.IMF_ESSENCE_COMPONENT_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("Invalid JPEG 2000 Profile: %s", essenceCoding)); + } + + return imfErrorLogger.getErrors(); + } + + + /* Validate codestream parameters against constraints listed in SMPTE ST 2067-21:2023 Annex I */ + + public static List validateHTConstraints(J2KHeaderParameters p) { + + IMFErrorLogger logger = new IMFErrorLoggerImpl(); + + if (p == null) { + logger.addError( + IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.FATAL, + "APP2.HT: Missing or incomplete JPEG 2000 Sub-descriptor"); + return logger.getErrors(); + } + + if (p.xosiz != 0 || p.yosiz != 0 || p.xtosiz != 0 || p.ytosiz != 0) { + logger.addError( + IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + "APP2.HT: Invalid XOsiz, YOsiz, XTOsiz or YTOsiz"); + } + + if (p.xtsiz < p.xsiz || p.ytsiz < p.ysiz) { + logger.addError( + IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + "APP2.HT: Invalid XTsiz or XYsiz"); + } + + /* components constraints */ + if (p.csiz.length <= 0 || p.csiz.length > 4) { + logger.addError( + IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("APP2.HT: Invalid number (%d) of components", p.csiz.length)); + } + + /* x sub-sampling */ + if (p.csiz[0].xrsiz != 1) { + logger.addError( + IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + "APP2.HT: invalid horizontal sub-sampling for component 1"); + } + if (p.csiz.length > 1 && p.csiz[1].xrsiz != 1 && + (p.csiz.length <= 2 || p.csiz[1].xrsiz != 2 || p.csiz[2].xrsiz != 2)) { + logger.addError( + IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + "APP2.HT: invalid horizontal sub-sampling for component 2"); + } + if (p.csiz.length > 2 && p.csiz[2].xrsiz != 1 && (p.csiz[1].xrsiz != 2 || p.csiz[2].xrsiz != 2)) { + logger.addError( + IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + "APP2.HT: invalid horizontal sub-sampling for component 3"); + } + if (p.csiz.length > 3 && p.csiz[3].xrsiz != 1) { + logger.addError( + IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + "APP2.HT: invalid horizontal sub-sampling for component 4"); + } + + /* y sub-sampling and sample width */ + if (p.csiz[0].ssiz > 15 || p.csiz[0].ssiz < 7) { + logger.addError( + IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("APP2.HT: Invalid bit depth (%d)", p.csiz[0].ssiz + 1)); + } + for (int i = 0; i < p.csiz.length; i++) { + if (p.csiz[i].yrsiz != 1) { + logger.addError( + IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("APP2.HT: invalid vertical sub-sampling for component %d", i)); + } + if (p.csiz[i].ssiz != p.csiz[0].ssiz) { + logger.addError( + IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + "APP2.HT: all components must have the same bit depth"); + } + } + /* CAP constraints */ + + /* Pcapi is 1 for i = 15, and 0 otherwise, per ST 2067-21 Annex I; therefore, pcap = 2^(32-15) = 131072 */ + if (p.cap == null || p.cap.pcap != 131072 || p.cap.ccap == null || p.cap.ccap.length != 1) { + /* codestream shall require only Part 15 capabilities */ + logger.addError( + IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + "APP2.HT: missing or invalid CAP marker"); + return logger.getErrors(); + } + + if ((p.cap.ccap[0] & 0b1111000000000000) != 0) { + /* Bits 12-15 of Ccap15 shall be 0 */ + logger.addError( + IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + "APP2.HT: Bits 12-15 of Ccap15 shall be 0"); + } + + boolean isHTREV = (p.cap.ccap[0] & 0b100000) == 0; + + /* COD */ + + if (p.cod == null) { + logger.addError( + IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.FATAL, + "APP2.HT: Missing COD marker"); + return logger.getErrors(); + } + + /* no scod constraints */ + + /* code-block style */ + if (p.cod.cbStyle != 0b01000000) { + /* bad code-block style */ + logger.addError( + IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + "APP2.HT: Invalid default code-block style"); + } + + /* progression order - RPCL is not required, but ST 2067-21:2023 Annex I Note 3 implies a preference */ + if (p.cod.progressionOrder != ProgressionOrder.RPCL.value()) + logger.addError( + IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.FATAL, + "APP2.HT: JPEG 2000 progression order is not RPCL"); + + /* resolution layers */ + if (p.cod.numDecompLevels == 0) { + logger.addError( + IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + "APP2.HT: Number of decomposition levels must be greater than 0"); + } + + + long maxSz = Math.max(p.xsiz, p.ysiz); + if ((maxSz <= 2048 && p.cod.numDecompLevels > 5) || + (maxSz <= 4096 && p.cod.numDecompLevels > 6) || + (maxSz <= 8192 && p.cod.numDecompLevels > 7)) { + logger.addError( + IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + "APP2.HT: Invalid number of decomposition levels"); + } + + /* number of layers */ + + if (p.cod.numLayers != 1) { + logger.addError( + IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("APP2.HT: Number of layers (%d) is not 1", p.cod.numLayers)); + } + + /* code-block sizes */ + + if (p.cod.ycb < 5 || p.cod.ycb > 6) { + logger.addError( + IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("APP2.HT: Invalid vertical code-block size (ycb = %d)", p.cod.ycb)); + } + + if (p.cod.xcb < 5 || p.cod.xcb > 7) { + logger.addError( + IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + String.format("APP2.HT: Invalid horizontal code-block size (xcb = %d)", p.cod.xcb)); + } + + + /* transformation */ + + boolean isReversibleFilter = (p.cod.transformation == 1); + + if (isHTREV && !isReversibleFilter) { + logger.addError( + IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + "APP2.HT: 9-7 irreversible filter is used but HTREV is signaled in CAP"); + } + + /* precinct size */ + + if (p.cod.precinctSizes.length == 0 || p.cod.precinctSizes[0] != 0x77) { + logger.addError( + IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + "APP2.HT: Invalid N_L LL band precinct sizes"); + } + + for (int i = 1; i < p.cod.precinctSizes.length; i++) + if (p.cod.precinctSizes[i] != 0x88) { + logger.addError( + IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, + "APP2.HT: Invalid non-N_L LL band precinct sizes"); + break; + } + + /* magbp */ + + /* expected values per Annex of SMPTE ST 2067-21 */ + int maxB = p.csiz[0].ssiz + 2; + if (isReversibleFilter) { + maxB += 2 + p.cod.multiComponentTransform; + if (p.cod.numDecompLevels > 5) + maxB += 1; + } else if (p.cod.multiComponentTransform == 1 && p.csiz[0].ssiz > 9) { + maxB += 1; + } + + /* codestream value */ + int codestreamB = (p.cap.ccap[0] & 0b11111) + 8; + + if (codestreamB > 31) { + logger.addError( + IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.FATAL, + "APP2.HT: Parameter B has exceeded its limit to an extent that decoder issues are to be expected"); + } else if (codestreamB > maxB) { + logger.addError( + IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, + IMFErrorLogger.IMFErrors.ErrorLevels.WARNING, + "APP2.HT: Parameter B has exceeded expected limits"); + } + + return logger.getErrors(); + } + +} diff --git a/src/test/java/com/netflix/imflibrary/validation/IMFApp2E5EDonstraintsValidatorTest.java b/src/test/java/com/netflix/imflibrary/validation/IMFApp2E5EDonstraintsValidatorTest.java new file mode 100644 index 00000000..e52b1b61 --- /dev/null +++ b/src/test/java/com/netflix/imflibrary/validation/IMFApp2E5EDonstraintsValidatorTest.java @@ -0,0 +1,50 @@ +package com.netflix.imflibrary.validation; + +import com.netflix.imflibrary.IMFErrorLogger; +import com.netflix.imflibrary.IMFErrorLoggerImpl; +import com.netflix.imflibrary.RESTfulInterfaces.IMPValidator; +import com.netflix.imflibrary.st2067_2.IMFCompositionPlaylist; +import com.netflix.imflibrary.st2067_2.CoreConstraints; +import org.testng.Assert; +import org.testng.annotations.Test; +import testUtils.TestHelper; + +import java.io.IOException; +import java.nio.file.Path; + +@Test(groups = "unit") +public class IMFApp2E5EDonstraintsValidatorTest +{ + + @Test + public void InvalidCPL_BadPrecinctSize() throws IOException + { + Path inputFile = TestHelper.findResourceByPath("TestIMP/Application2E5ED/CPL_ASC-STEM2_HTJ2K_12BIT_LOSSLESS.xml"); + IMFErrorLogger logger = new IMFErrorLoggerImpl(); + + IMFCompositionPlaylist imfCompositionPlaylist = new IMFCompositionPlaylist(inputFile); + logger.addAllErrors(imfCompositionPlaylist.getErrors()); + logger.addAllErrors(IMPValidator.validateComposition(imfCompositionPlaylist, null)); + + Assert.assertTrue(imfCompositionPlaylist.getApplicationIdSet().contains(IMFApp2E5EDConstraintsValidator.applicationIdentification)); + + Assert.assertNotEquals(logger.getErrors().size(), 0); + } + + @Test + public void ValidCPL_BadPrecinctSize() throws IOException + { + Path inputFile = TestHelper.findResourceByPath("TestIMP/Application2E5ED/CPL_ASC-STEM2_J2K_12BIT_LOSSLESS.xml"); + IMFErrorLogger logger = new IMFErrorLoggerImpl(); + + IMFCompositionPlaylist imfCompositionPlaylist = new IMFCompositionPlaylist(inputFile); + logger.addAllErrors(imfCompositionPlaylist.getErrors()); + logger.addAllErrors(IMPValidator.validateComposition(imfCompositionPlaylist, null)); + + Assert.assertTrue(imfCompositionPlaylist.getApplicationIdSet().contains(IMFApp2E5EDConstraintsValidator.applicationIdentification)); + + Assert.assertEquals(logger.getErrors().size(), 0); + } + + +} diff --git a/src/test/resources/TestIMP/Application2E5ED/CPL_ASC-STEM2_HTJ2K_12BIT_LOSSLESS.xml b/src/test/resources/TestIMP/Application2E5ED/CPL_ASC-STEM2_HTJ2K_12BIT_LOSSLESS.xml new file mode 100644 index 00000000..abd067db --- /dev/null +++ b/src/test/resources/TestIMP/Application2E5ED/CPL_ASC-STEM2_HTJ2K_12BIT_LOSSLESS.xml @@ -0,0 +1,346 @@ + + + urn:uuid:0075deb4-e3ea-4d26-9b3e-cc5eedcd34d9 + ASC-STEM2_HTJ2K_12BIT_LOSSLESS + 2025-08-25T12:26:53-00:00 + Colorfront + Colorfront Transkoder Dev build66261 + Colorfront + ASC-STEM2_HTJ2K_12BIT_LOSSLESS + feature + + + urn:uuid:ca4906c7-89a3-449f-813b-c079dbb289a2 + ASC-STEM2_HTJ2K_12BIT_LOSSLESS + + + + + urn:uuid:3bd1e2d9-76d6-417a-881c-38c23b537319 + + urn:uuid:15baf3b6-66ea-439e-a2a5-82e82aa154af + + + urn:uuid:e0d85b5a-28d8-4114-9ffe-7bd017be2510 + 16384 + 7680 + 4320 + 0 + 0 + 7680 + 4320 + 0 + 0 + 3 + + + 11 + 1 + 1 + + + 11 + 1 + 1 + + + 11 + 1 + 1 + + + 0102000100060503400188888888888888 + 4068707078707078707078707078707070686870 + + + CompRed + 12 + + + CompGreen + 12 + + + CompBlue + 12 + + + CompNull + 0 + + + CompNull + 0 + + + CompNull + 0 + + + CompNull + 0 + + + CompNull + 0 + + + + 131072 + + 8 + + + + + 2 + 24/1 + 2399 + urn:smpte:ul:060e2b34.0401010d.0d010301.020c0600 + FullFrame + 7680 + 4320 + 7680 + 4320 + 0 + 0 + 4320 + 7680 + 0 + 0 + 0 + 7680/4320 + urn:smpte:ul:060e2b34.0401010d.04010101.010a0000 + urn:smpte:ul:060e2b34.0401010d.04010202.03010801 + urn:smpte:ul:060e2b34.0401010d.04010101.02060000 + urn:smpte:ul:060e2b34.0401010d.04010101.03040000 + 7680 + 4320 + 0 + 0 + + 42 + 0 + + + + 35400 + 14600 + + + 8500 + 39850 + + + 6550 + 2300 + + + + 15635 + 16450 + + 10000000 + 50 + 4095 + 0 + ScanningDirection_LeftToRightTopToBottom + + + CompRed + 12 + + + CompGreen + 12 + + + CompBlue + 12 + + + CompNull + 0 + + + CompNull + 0 + + + CompNull + 0 + + + CompNull + 0 + + + CompNull + 0 + + + + + + urn:uuid:c8268a8a-c3bf-456a-bdca-d8f267c87210 + + urn:uuid:f87803aa-202e-4d15-a75c-c3b3e0dd40ed + + + urn:uuid:9fc365cf-72f7-4473-ae11-09ba203c5633 + urn:smpte:ul:060e2b34.0401010d.03020201.00000000 + urn:uuid:0a9385b8-bb6f-41a4-b79e-7bb6c6e6a197 + sg51 + 5.1 + en + ASC-STEM2 + ASC-STEM2 + PRM + Sound of the program + + + urn:uuid:479a88f4-1358-417e-b305-34e65606a67f + urn:smpte:ul:060e2b34.0401010d.03020101.00000000 + urn:uuid:45775044-798b-4ae5-9308-fccf96d33242 + chL + Left + 1 + en + urn:uuid:0a9385b8-bb6f-41a4-b79e-7bb6c6e6a197 + + + urn:uuid:7ee6f0ff-aa02-4cae-8430-10efb9b84e43 + urn:smpte:ul:060e2b34.0401010d.03020102.00000000 + urn:uuid:fa26a291-7c9a-4325-9521-303910cdb0ee + chR + Right + 2 + en + urn:uuid:0a9385b8-bb6f-41a4-b79e-7bb6c6e6a197 + + + urn:uuid:9d13a53d-a4ad-419b-bbb2-dd94e482d157 + urn:smpte:ul:060e2b34.0401010d.03020103.00000000 + urn:uuid:6fc68e8c-8c38-48ed-8b58-1ad72248f349 + chC + Center + 3 + en + urn:uuid:0a9385b8-bb6f-41a4-b79e-7bb6c6e6a197 + + + urn:uuid:e947dcee-a356-4acd-a707-5135d4f543ec + urn:smpte:ul:060e2b34.0401010d.03020104.00000000 + urn:uuid:82f3fb76-1a29-4565-bfd3-7e61564b3791 + chLFE + LFE + 4 + en + urn:uuid:0a9385b8-bb6f-41a4-b79e-7bb6c6e6a197 + + + urn:uuid:12443c02-6979-4333-9c1b-ac4dd4d8c73b + urn:smpte:ul:060e2b34.0401010d.03020105.00000000 + urn:uuid:e9b03e39-19a9-4c78-95fe-717f2760d272 + chLs + Left Surround + 5 + en + urn:uuid:0a9385b8-bb6f-41a4-b79e-7bb6c6e6a197 + + + urn:uuid:b55421bf-98ba-41ff-9e25-ae9b0ebfff89 + urn:smpte:ul:060e2b34.0401010d.03020106.00000000 + urn:uuid:7321ecae-3485-4332-9c0b-fd4d878cf9ba + chRs + Right Surround + 6 + en + urn:uuid:0a9385b8-bb6f-41a4-b79e-7bb6c6e6a197 + + + 2 + 48000/1 + 4798000 + urn:smpte:ul:060e2b34.04010101.0d010301.02060200 + 48000/1 + True + 6 + 24 + 18 + 864000 + urn:smpte:ul:060e2b34.0401010d.04020210.04010000 + + + + + 0 + 24 + 00:00:00:00 + + 24 1 + + + + en + + + us + + + + http://www.mpaa.org/2003-ratings + NR + + + + + + http://www.smpte-ra.org/ns/2067-21/5ED + 595 + 139 + + + + urn:uuid:8f9eea78-1f72-4da0-b2b8-06b0f48b0423 + + + urn:uuid:859de402-3609-46c1-870d-7cc605d1635d + urn:uuid:27a967ef-fd63-4c2a-a391-accff6d5b00c + + + urn:uuid:61667160-1232-4b39-b8e3-b5afef43cdcc + ASC-STEM2_HTJ2K_12BIT_LOSSLESS_01.mxf + 24 1 + 2399 + 0 + 2399 + urn:uuid:3bd1e2d9-76d6-417a-881c-38c23b537319 + urn:uuid:958716b7-4793-4888-b6cb-816e021d4c6b + m0CHzn2PlD3vMkrlmQbV7nVVOl4= + + + + + + urn:uuid:fd4a0605-c2aa-448c-bd01-b38494556c23 + urn:uuid:929f9891-3ac8-43de-b7c2-9f50e8b3a472 + + + urn:uuid:b1c9a4e1-3636-435e-b06c-307a09a23142 + ASC-STEM2_HTJ2K_12BIT_LOSSLESS_01_EN_51_A.mxf + 48000 1 + 4798000 + 0 + 4798000 + urn:uuid:c8268a8a-c3bf-456a-bdca-d8f267c87210 + urn:uuid:be3a2417-4016-404e-9706-4406b8969c31 + vQPLH3Uo5yNea3z0xYU4ZyiVLdQ= + + + + + + + + diff --git a/src/test/resources/TestIMP/Application2E5ED/CPL_ASC-STEM2_J2K_12BIT_LOSSLESS.xml b/src/test/resources/TestIMP/Application2E5ED/CPL_ASC-STEM2_J2K_12BIT_LOSSLESS.xml new file mode 100644 index 00000000..781418af --- /dev/null +++ b/src/test/resources/TestIMP/Application2E5ED/CPL_ASC-STEM2_J2K_12BIT_LOSSLESS.xml @@ -0,0 +1,340 @@ + + + urn:uuid:9c00e5a0-472b-4526-ab96-3923d067f30f + ASC-STEM2_J2K_12BIT_LOSSLESS + 2025-08-25T12:15:07-00:00 + Colorfront + Colorfront Transkoder Dev build66261 + Colorfront + ASC-STEM2_J2K_12BIT_LOSSLESS + feature + + + urn:uuid:f1c6bb63-a212-406c-a424-49892c6f35ed + ASC-STEM2_J2K_12BIT_LOSSLESS + + + + + urn:uuid:4624b9a7-3880-49f5-9074-369d17ae002e + + urn:uuid:27bcb251-87da-4e93-9feb-e456c8d3353c + + + urn:uuid:af0ead72-68f2-422a-bd43-dbed1731a9c7 + 2311 + 7680 + 4320 + 0 + 0 + 7680 + 4320 + 0 + 0 + 3 + + + 11 + 1 + 1 + + + 11 + 1 + 1 + + + 11 + 1 + 1 + + + 0104000100060303000177888888888888 + 4060686870686870686870686870686870686870 + + + CompRed + 12 + + + CompGreen + 12 + + + CompBlue + 12 + + + CompNull + 0 + + + CompNull + 0 + + + CompNull + 0 + + + CompNull + 0 + + + CompNull + 0 + + + + + 2 + 24/1 + 2399 + urn:smpte:ul:060e2b34.0401010d.0d010301.020c0600 + FullFrame + 7680 + 4320 + 7680 + 4320 + 0 + 0 + 4320 + 7680 + 0 + 0 + 0 + 7680/4320 + urn:smpte:ul:060e2b34.0401010d.04010101.010a0000 + urn:smpte:ul:060e2b34.0401010d.04010202.03010714 + urn:smpte:ul:060e2b34.0401010d.04010101.02060000 + urn:smpte:ul:060e2b34.0401010d.04010101.03040000 + 7680 + 4320 + 0 + 0 + + 42 + 0 + + + + 35400 + 14600 + + + 8500 + 39850 + + + 6550 + 2300 + + + + 15635 + 16450 + + 10000000 + 50 + 4095 + 0 + ScanningDirection_LeftToRightTopToBottom + + + CompRed + 12 + + + CompGreen + 12 + + + CompBlue + 12 + + + CompNull + 0 + + + CompNull + 0 + + + CompNull + 0 + + + CompNull + 0 + + + CompNull + 0 + + + + + + urn:uuid:6bf766be-9678-4e8e-b579-2370306bad7c + + urn:uuid:4a79c691-4cf2-4ba0-a0ec-b45897fe08e8 + + + urn:uuid:46d8877a-6a03-417f-9d48-3bd9791a6d6e + urn:smpte:ul:060e2b34.0401010d.03020201.00000000 + urn:uuid:24f18b77-eb9e-4e84-bd39-cb98bd8b2ee3 + sg51 + 5.1 + en + ASC-STEM2 + ASC-STEM2 + PRM + Sound of the program + + + urn:uuid:d9c39186-d1c5-4f4d-8922-81b64ee5f83e + urn:smpte:ul:060e2b34.0401010d.03020101.00000000 + urn:uuid:3c49a04d-d06e-4c6a-9b42-0fde7261a870 + chL + Left + 1 + en + urn:uuid:24f18b77-eb9e-4e84-bd39-cb98bd8b2ee3 + + + urn:uuid:51d35234-3d6b-4e72-8e8e-003708d7adad + urn:smpte:ul:060e2b34.0401010d.03020102.00000000 + urn:uuid:7cc2ee7d-54ab-48c8-8219-951c67e915b6 + chR + Right + 2 + en + urn:uuid:24f18b77-eb9e-4e84-bd39-cb98bd8b2ee3 + + + urn:uuid:2f8d128d-e42a-4ac3-bca3-10e14d88b47a + urn:smpte:ul:060e2b34.0401010d.03020103.00000000 + urn:uuid:55a3427c-ec9c-400d-b1ca-390a13356720 + chC + Center + 3 + en + urn:uuid:24f18b77-eb9e-4e84-bd39-cb98bd8b2ee3 + + + urn:uuid:a3525fe4-f680-4a3d-bcfb-7d4833c47094 + urn:smpte:ul:060e2b34.0401010d.03020104.00000000 + urn:uuid:3ac7677b-6dda-4338-b7c8-6f3b8fdb674a + chLFE + LFE + 4 + en + urn:uuid:24f18b77-eb9e-4e84-bd39-cb98bd8b2ee3 + + + urn:uuid:b833b52d-b882-42ea-94e3-92883e2f3c4e + urn:smpte:ul:060e2b34.0401010d.03020105.00000000 + urn:uuid:be0bb5b5-2e23-4cc3-8b14-cb8fd90fb021 + chLs + Left Surround + 5 + en + urn:uuid:24f18b77-eb9e-4e84-bd39-cb98bd8b2ee3 + + + urn:uuid:e9a6c239-980c-4f46-a1fc-b8a0b7a7b563 + urn:smpte:ul:060e2b34.0401010d.03020106.00000000 + urn:uuid:aad7a729-e309-41e0-9477-f120681f7851 + chRs + Right Surround + 6 + en + urn:uuid:24f18b77-eb9e-4e84-bd39-cb98bd8b2ee3 + + + 2 + 48000/1 + 4798000 + urn:smpte:ul:060e2b34.04010101.0d010301.02060200 + 48000/1 + True + 6 + 24 + 18 + 864000 + urn:smpte:ul:060e2b34.0401010d.04020210.04010000 + + + + + 0 + 24 + 00:00:00:00 + + 24 1 + + + + en + + + us + + + + http://www.mpaa.org/2003-ratings + NR + + + + + + http://www.smpte-ra.org/ns/2067-21/5ED + 595 + 139 + + + + urn:uuid:2b49e9a1-b350-4646-97ee-df40cdcf698b + + + urn:uuid:e32c000d-4339-4f1c-80ed-1f15d78e0554 + urn:uuid:dba9a38d-1b99-488d-b4d6-548071af566f + + + urn:uuid:f69b2afc-6337-456d-8c2d-f63339e033f5 + ASC-STEM2_J2K_12BIT_LOSSLESS_01.mxf + 24 1 + 2399 + 0 + 2399 + urn:uuid:4624b9a7-3880-49f5-9074-369d17ae002e + urn:uuid:72036db2-2bd9-4dbe-a9d5-d5f394d3d5d3 + /YJXFDLykhAZVxv9i9ZP4GDI3Zw= + + + + + + urn:uuid:8c546eff-f290-44b1-9f9f-50f276f1d74a + urn:uuid:f414d61e-1b32-4f0f-b74a-5a03f5e3631b + + + urn:uuid:ee5abc8c-54ef-48fc-a1c0-bba770f2c851 + ASC-STEM2_J2K_12BIT_LOSSLESS_01_EN_51_A.mxf + 48000 1 + 4798000 + 0 + 4798000 + urn:uuid:6bf766be-9678-4e8e-b579-2370306bad7c + urn:uuid:51371eb6-2a53-4fc6-8310-49329ae1e9c6 + IGjtLIaU/woLN/McY3BXEHUKqH4= + + + + + + + +