From d3cc2eefe06e0c885892c5f73ef13d100df8b36f Mon Sep 17 00:00:00 2001 From: danielhdz13-netflix <117850928+danielhdz13-netflix@users.noreply.github.com> Date: Mon, 7 Oct 2024 13:38:10 -0400 Subject: [PATCH] Refactoring 'J2KHeaderParameters' into its own class. (#385) * Refactoring 'J2KHeaderParameters' into its own class. Added methods to obtain an instance of a 'J2KHeaderParameters' class from a CPL descriptor, or an MXF descriptor. Added testing to ensure that these methods produce equivalent results. * Adding comments back in * Addressing nits: renaming validateHT to validateHTConstraints, and adding one more comment --- .../imflibrary/J2KHeaderParameters.java | 367 ++++++++++++++++++ .../header/J2KExtendedCapabilities.java | 8 + .../header/JPEG2000PictureComponent.java | 13 + .../header/JPEG2000PictureSubDescriptor.java | 73 +++- .../st2067_2/Application2E2021.java | 8 +- ...ompositionImageEssenceDescriptorModel.java | 212 +--------- .../Application2ExtendedCompositionTest.java | 2 +- .../st2067_2/J2KHeaderParametersTest.java | 51 +++ 8 files changed, 510 insertions(+), 224 deletions(-) create mode 100644 src/main/java/com/netflix/imflibrary/J2KHeaderParameters.java create mode 100644 src/test/java/com/netflix/imflibrary/st2067_2/J2KHeaderParametersTest.java diff --git a/src/main/java/com/netflix/imflibrary/J2KHeaderParameters.java b/src/main/java/com/netflix/imflibrary/J2KHeaderParameters.java new file mode 100644 index 00000000..b524074b --- /dev/null +++ b/src/main/java/com/netflix/imflibrary/J2KHeaderParameters.java @@ -0,0 +1,367 @@ +package com.netflix.imflibrary; + +import com.netflix.imflibrary.st0377.header.J2KExtendedCapabilities; +import com.netflix.imflibrary.st0377.header.JPEG2000PictureComponent; +import com.netflix.imflibrary.st0377.header.JPEG2000PictureSubDescriptor; +import com.netflix.imflibrary.utils.DOMNodeObjectModel; + +import javax.xml.bind.DatatypeConverter; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +public class J2KHeaderParameters { + + public static class CSiz { + public short ssiz; + public short xrsiz; + public short yrsiz; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CSiz cSiz = (CSiz) o; + return ssiz == cSiz.ssiz && xrsiz == cSiz.xrsiz && yrsiz == cSiz.yrsiz; + } + + @Override + public int hashCode() { + return Objects.hash(ssiz, xrsiz, yrsiz); + } + } + + public static class CAP { + public long pcap; + public int[] ccap; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CAP cap = (CAP) o; + return pcap == cap.pcap && Arrays.equals(ccap, cap.ccap); + } + + @Override + public int hashCode() { + int result = Objects.hash(pcap); + result = 31 * result + Arrays.hashCode(ccap); + return result; + } + } + + public static class COD { + public short scod; + public short progressionOrder; + public int numLayers; + public short multiComponentTransform; + public short numDecompLevels; + public short xcb; + public short ycb; + public short cbStyle; + public short transformation; + public short precinctSizes[]; + + public short getScod() { return scod; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + COD cod = (COD) o; + return scod == cod.scod && + progressionOrder == cod.progressionOrder && + numLayers == cod.numLayers && + multiComponentTransform == cod.multiComponentTransform && + numDecompLevels == cod.numDecompLevels && + xcb == cod.xcb && + ycb == cod.ycb && + cbStyle == cod.cbStyle && + transformation == cod.transformation && + Arrays.equals(precinctSizes, cod.precinctSizes); + } + + @Override + public int hashCode() { + int result = Objects.hash(scod, progressionOrder, numLayers, multiComponentTransform, numDecompLevels, xcb, ycb, cbStyle, transformation); + result = 31 * result + Arrays.hashCode(precinctSizes); + return result; + } + } + + public static class QCD { + public short sqcd; + public int spqcd[]; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + QCD qcd = (QCD) o; + return sqcd == qcd.sqcd && Arrays.equals(spqcd, qcd.spqcd); + } + + @Override + public int hashCode() { + int result = Objects.hash(sqcd); + result = 31 * result + Arrays.hashCode(spqcd); + return result; + } + } + + public Integer rsiz; + public Long xsiz; + public Long ysiz; + public Long xosiz; + public Long yosiz; + public Long xtsiz; + public Long ytsiz; + public Long xtosiz; + public Long ytosiz; + public CSiz[] csiz; + public COD cod; + public QCD qcd; + public CAP cap; + + public J2KHeaderParameters() {} + + // From CPL Descriptor to common J2KHeaderParameters + public static J2KHeaderParameters fromDOMNode(DOMNodeObjectModel imageEssencedescriptorDOMNode) { + J2KHeaderParameters p = new J2KHeaderParameters(); + + DOMNodeObjectModel sdNode = imageEssencedescriptorDOMNode.getDOMNode("SubDescriptors"); + if (sdNode == null) { + return null; + } + + DOMNodeObjectModel j2kNode = sdNode.getDOMNode("JPEG2000SubDescriptor"); + if (j2kNode == null) { + return null; + } + + p.rsiz = j2kNode.getFieldAsInteger("Rsiz"); + if (p.rsiz == null) return null; + + p.xsiz = j2kNode.getFieldAsLong("Xsiz"); + if (p.xsiz == null) return null; + + p.ysiz = j2kNode.getFieldAsLong("Ysiz"); + if (p.ysiz == null) return null; + + p.xosiz = j2kNode.getFieldAsLong("XOsiz"); + if (p.xosiz == null) return null; + + p.yosiz = j2kNode.getFieldAsLong("YOsiz"); + if (p.yosiz == null) return null; + + p.xtsiz = j2kNode.getFieldAsLong("XTsiz"); + if (p.xtsiz == null) return null; + + p.ytsiz = j2kNode.getFieldAsLong("YTsiz"); + if (p.ytsiz == null) return null; + + p.xtosiz = j2kNode.getFieldAsLong("XTOsiz"); + if (p.xtosiz == null) return null; + + p.ytosiz = j2kNode.getFieldAsLong("YTOsiz"); + if (p.ytosiz == null) return null; + + // CSiz + DOMNodeObjectModel csiziNode = j2kNode.getDOMNode("PictureComponentSizing"); + if (csiziNode == null) return null; + + List csizi = csiziNode.getDOMNodes("J2KComponentSizing"); + p.csiz = new CSiz[csizi.size()]; + for (int i = 0; i < p.csiz.length; i++) { + p.csiz[i] = new CSiz(); + + Short ssiz = csizi.get(i).getFieldAsShort("Ssiz"); + if (ssiz == null) return null; + p.csiz[i].ssiz = ssiz; + + Short xrsiz = csizi.get(i).getFieldAsShort("XRSiz"); + if (xrsiz == null) return null; + p.csiz[i].xrsiz = xrsiz; + + Short yrsiz = csizi.get(i).getFieldAsShort("YRSiz"); + if (yrsiz == null) return null; + p.csiz[i].yrsiz = yrsiz; + } + + Integer csiz = j2kNode.getFieldAsInteger("Csiz"); + if (csiz != p.csiz.length) return null; + + // CAP + DOMNodeObjectModel capNode = j2kNode.getDOMNode("J2KExtendedCapabilities"); + if (capNode != null) { + Integer pcap = capNode.getFieldAsInteger("Pcap"); + if (pcap != null) { + p.cap = new CAP(); + p.cap.pcap = pcap; + + DOMNodeObjectModel ccapiNode = capNode.getDOMNode("Ccapi"); + if (ccapiNode != null) { + List values = ccapiNode.getFieldsAsInteger("UInt16"); + + p.cap.ccap = new int[values.size()]; + for (int i = 0; i < p.cap.ccap.length; i++) { + if (values.get(i) == null) return null; + p.cap.ccap[i] = values.get(i); + } + } + + int ccapLength = Long.bitCount(p.cap.pcap); + if (ccapLength > 0 && (p.cap.ccap == null || p.cap.ccap.length != ccapLength)) + return null; + if (ccapLength == 0 && (p.cap.ccap != null && p.cap.ccap.length != 0)) + return null; + } else { + return null; + } + } + + // COD + String codString = j2kNode.getFieldAsString("CodingStyleDefault"); + if (codString != null && codString.length() >= 20 && (codString.length() % 2 == 0)) { + p.cod = new COD(); + p.cod.scod = (short) Integer.parseInt(codString.substring(0, 2), 16); + p.cod.progressionOrder = (short) Integer.parseInt(codString.substring(2, 4), 16); + p.cod.numLayers = Integer.parseInt(codString.substring(4, 8), 16); + p.cod.multiComponentTransform = (short) Integer.parseInt(codString.substring(8, 10), 16); + p.cod.numDecompLevels = (short) Integer.parseInt(codString.substring(10, 12), 16); + p.cod.xcb = (short) (Integer.parseInt(codString.substring(12, 14), 16) + 2); + p.cod.ycb = (short) (Integer.parseInt(codString.substring(14, 16), 16) + 2); + p.cod.cbStyle = (short) Integer.parseInt(codString.substring(16, 18), 16); + p.cod.transformation = (short) Integer.parseInt(codString.substring(18, 20), 16); + + p.cod.precinctSizes = new short[(codString.length() - 20) / 2]; + for (int i = 0; i < p.cod.precinctSizes.length; i++) { + p.cod.precinctSizes[i] = (short) Integer.parseInt(codString.substring(20 + 2 * i, 22 + 2 * i), 16); + } + } + + // QCD + String qcdString = j2kNode.getFieldAsString("QuantizationDefault"); + if (qcdString != null && qcdString.length() >= 2 && (qcdString.length() % 2 == 0)) { + p.qcd = new QCD(); + p.qcd.sqcd = (short) Integer.parseInt(qcdString.substring(0, 2), 16); + + int spqcdSize = (p.qcd.sqcd & 0b11111) == 0 ? 1 : 2; + p.qcd.spqcd = new int[(qcdString.length() - 2) / (2 * spqcdSize)]; + for (int i = 0; i < p.qcd.spqcd.length; i++) { + p.qcd.spqcd[i] = Integer.parseInt(qcdString.substring(2 + 2 * spqcdSize * i, 4 + 2 * spqcdSize * i), 16); + } + } + + return p; + } + + // From MXF Descriptor to common J2KHeaderParameters + public static J2KHeaderParameters fromJPEG2000PictureSubDescriptorBO(JPEG2000PictureSubDescriptor.JPEG2000PictureSubDescriptorBO jpeg2000PictureSubDescriptorBO) { + J2KHeaderParameters p = new J2KHeaderParameters(); + + p.rsiz = jpeg2000PictureSubDescriptorBO.getRSiz().intValue(); + p.xsiz = jpeg2000PictureSubDescriptorBO.getXSiz().longValue(); + p.ysiz = jpeg2000PictureSubDescriptorBO.getYSiz().longValue(); + p.xosiz = jpeg2000PictureSubDescriptorBO.getXoSiz().longValue(); + p.yosiz = jpeg2000PictureSubDescriptorBO.getYoSiz().longValue(); + p.xtsiz = jpeg2000PictureSubDescriptorBO.getXtSiz().longValue(); + p.ytsiz = jpeg2000PictureSubDescriptorBO.getYtSiz().longValue(); + p.xtosiz = jpeg2000PictureSubDescriptorBO.getXtoSiz().longValue(); + p.ytosiz = jpeg2000PictureSubDescriptorBO.getYtoSiz().longValue(); + + // CSiz + List subDescriptorCsizi = jpeg2000PictureSubDescriptorBO.getPictureComponentSizing().getEntries(); + p.csiz = new CSiz[subDescriptorCsizi.size()]; + for (int i = 0; i < p.csiz.length; i++) { + p.csiz[i] = new CSiz(); + p.csiz[i].ssiz = subDescriptorCsizi.get(i).getSSiz(); + p.csiz[i].xrsiz = subDescriptorCsizi.get(i).getXrSiz(); + p.csiz[i].yrsiz = subDescriptorCsizi.get(i).getYrSiz(); + } + + if (p.csiz.length != jpeg2000PictureSubDescriptorBO.getCSiz()) { + return null; + } + + // CAP + J2KExtendedCapabilities j2KExtendedCapabilities = jpeg2000PictureSubDescriptorBO.getJ2kExtendedCapabilities(); + List subDescriptorcCap = j2KExtendedCapabilities.getcCap().getEntries(); + p.cap = new CAP(); + p.cap.pcap = j2KExtendedCapabilities.getpCap(); + p.cap.ccap = new int[subDescriptorcCap.size()]; + for (int i = 0; i < p.cap.ccap.length; i++) { + p.cap.ccap[i] = subDescriptorcCap.get(i); + } + + int cCapLength = Long.bitCount(p.cap.pcap); + if (cCapLength > 0 && p.cap.ccap.length != cCapLength) { + return null; + } + + // COD + String codString = jpeg2000PictureSubDescriptorBO.getCodingStyleDefaultString(); + if (codString != null && codString.length() >= 20 && (codString.length() % 2 == 0)) { + p.cod = new COD(); + p.cod.scod = (short) Integer.parseInt(codString.substring(0, 2), 16); + p.cod.progressionOrder = (short) Integer.parseInt(codString.substring(2, 4), 16); + p.cod.numLayers = Integer.parseInt(codString.substring(4, 8), 16); + p.cod.multiComponentTransform = (short) Integer.parseInt(codString.substring(8, 10), 16); + p.cod.numDecompLevels = (short) Integer.parseInt(codString.substring(10, 12), 16); + p.cod.xcb = (short) (Integer.parseInt(codString.substring(12, 14), 16) + 2); + p.cod.ycb = (short) (Integer.parseInt(codString.substring(14, 16), 16) + 2); + p.cod.cbStyle = (short) Integer.parseInt(codString.substring(16, 18), 16); + p.cod.transformation = (short) Integer.parseInt(codString.substring(18, 20), 16); + + p.cod.precinctSizes = new short[(codString.length() - 20) / 2]; + for (int i = 0; i < p.cod.precinctSizes.length; i++) { + p.cod.precinctSizes[i] = (short) Integer.parseInt(codString.substring(20 + 2 * i, 22 + 2 * i), 16); + } + } else { + return null; + } + + // QCD + String qcdString = jpeg2000PictureSubDescriptorBO.getQuantisationDefaultString(); + if (qcdString != null && qcdString.length() >= 2 && (qcdString.length() % 2 == 0)) { + p.qcd = new QCD(); + p.qcd.sqcd = (short) Integer.parseInt(qcdString.substring(0, 2), 16); + + int spqcdSize = (p.qcd.sqcd & 0b11111) == 0 ? 1 : 2; + p.qcd.spqcd = new int[(qcdString.length() - 2) / (2 * spqcdSize)]; + for (int i = 0; i < p.qcd.spqcd.length; i++) { + p.qcd.spqcd[i] = Integer.parseInt(qcdString.substring(2 + 2 * spqcdSize * i, 4 + 2 * spqcdSize * i), 16); + } + } + + return p; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + J2KHeaderParameters that = (J2KHeaderParameters) o; + return Objects.equals(rsiz, that.rsiz) && + Objects.equals(xsiz, that.xsiz) && + Objects.equals(ysiz, that.ysiz) && + Objects.equals(xosiz, that.xosiz) && + Objects.equals(yosiz, that.yosiz) && + Objects.equals(xtsiz, that.xtsiz) && + Objects.equals(ytsiz, that.ytsiz) && + Objects.equals(xtosiz, that.xtosiz) && + Objects.equals(ytosiz, that.ytosiz) && + Arrays.equals(csiz, that.csiz) && + cod.equals(that.cod) && + qcd.equals(that.qcd) && + cap.equals(that.cap); + } + + @Override + public int hashCode() { + int result = Objects.hash(rsiz, xsiz, ysiz, xosiz, yosiz, xtsiz, ytsiz, xtosiz, ytosiz, cod, qcd, cap); + result = 31 * result + Arrays.hashCode(csiz); + return result; + } +} diff --git a/src/main/java/com/netflix/imflibrary/st0377/header/J2KExtendedCapabilities.java b/src/main/java/com/netflix/imflibrary/st0377/header/J2KExtendedCapabilities.java index 002b9e32..48b329a2 100644 --- a/src/main/java/com/netflix/imflibrary/st0377/header/J2KExtendedCapabilities.java +++ b/src/main/java/com/netflix/imflibrary/st0377/header/J2KExtendedCapabilities.java @@ -20,6 +20,14 @@ public final class J2KExtendedCapabilities { @MXFProperty(size=4) protected final Integer pCap = null; @MXFProperty(size=0, depends=true) protected final CompoundDataTypes.MXFCollections.MXFCollection cCap = null; + public Integer getpCap() { + return pCap; + } + + public CompoundDataTypes.MXFCollections.MXFCollection getcCap() { + return cCap; + } + /** * Instantiates a new parsed J2KExtendedCapabilities object * diff --git a/src/main/java/com/netflix/imflibrary/st0377/header/JPEG2000PictureComponent.java b/src/main/java/com/netflix/imflibrary/st0377/header/JPEG2000PictureComponent.java index f014de7f..c6e5c15e 100644 --- a/src/main/java/com/netflix/imflibrary/st0377/header/JPEG2000PictureComponent.java +++ b/src/main/java/com/netflix/imflibrary/st0377/header/JPEG2000PictureComponent.java @@ -48,12 +48,25 @@ public static final class JPEG2000PictureComponentBO{ @MXFProperty(size=1) protected final Short xrSiz; @MXFProperty(size=1) protected final Short yrSiz; + public Short getSSiz() { + return sSiz; + } + + public Short getXrSiz() { + return xrSiz; + } + + public Short getYrSiz() { + return yrSiz; + } + /** * Instantiates a new parsed JPEG2000PictureComponent object * * @param bytes the byte array corresponding to the 3 fields * @throws IOException - any I/O related error will be exposed through an IOException */ + public JPEG2000PictureComponentBO(byte[] bytes) throws IOException { diff --git a/src/main/java/com/netflix/imflibrary/st0377/header/JPEG2000PictureSubDescriptor.java b/src/main/java/com/netflix/imflibrary/st0377/header/JPEG2000PictureSubDescriptor.java index d3fa5f1b..1fd83052 100644 --- a/src/main/java/com/netflix/imflibrary/st0377/header/JPEG2000PictureSubDescriptor.java +++ b/src/main/java/com/netflix/imflibrary/st0377/header/JPEG2000PictureSubDescriptor.java @@ -26,6 +26,7 @@ import com.netflix.imflibrary.utils.ByteProvider; import javax.annotation.concurrent.Immutable; +import javax.xml.bind.DatatypeConverter; import java.io.IOException; import java.util.Map; @@ -59,7 +60,8 @@ public String toString() * Object corresponding to a parsed JPEG2000PictureSubDescriptor as defined in st429-4-2006 */ @Immutable - public static final class JPEG2000PictureSubDescriptorBO extends SubDescriptorBO{ + public static final class JPEG2000PictureSubDescriptorBO extends SubDescriptorBO { + @MXFProperty(size=16) protected final byte[] generation_uid = null; @MXFProperty(size=2) protected final Short rSiz = null; @MXFProperty(size=4) protected final Integer xSiz = null; @@ -85,28 +87,83 @@ public static final class JPEG2000PictureSubDescriptorBO extends SubDescriptorBO * @param imfErrorLogger the imf error logger * @throws IOException - any I/O related error will be exposed through an IOException */ + public JPEG2000PictureSubDescriptorBO(KLVPacket.Header header, ByteProvider byteProvider, Map localTagToUIDMap, IMFErrorLogger imfErrorLogger) - throws IOException - { + throws IOException { super(header); long numBytesToRead = this.header.getVSize(); - StructuralMetadata.populate(this, byteProvider, numBytesToRead, localTagToUIDMap); - if (this.instance_uid == null) - { + if (this.instance_uid == null) { imfErrorLogger.addError(IMFErrorLogger.IMFErrors.ErrorCodes.IMF_ESSENCE_METADATA_ERROR, IMFErrorLogger.IMFErrors.ErrorLevels.NON_FATAL, JPEG2000PictureSubDescriptor.ERROR_DESCRIPTION_PREFIX + "instance_uid is null"); } } + public Short getRSiz() { + return rSiz; + } + + public Integer getXSiz() { + return xSiz; + } + + public Integer getYSiz() { + return ySiz; + } + + public Integer getXoSiz() { + return xoSiz; + } + + public Integer getYoSiz() { + return yoSiz; + } + + public Integer getXtSiz() { + return xtSiz; + } + + public Integer getYtSiz() { + return ytSiz; + } + + public Integer getXtoSiz() { + return xtoSiz; + } + + public Integer getYtoSiz() { + return ytoSiz; + } + + public Short getCSiz() { + return cSiz; + } + + public CompoundDataTypes.MXFCollections.MXFCollection getPictureComponentSizing() { + return picture_component_sizing; + } + + public String getCodingStyleDefaultString() { + return DatatypeConverter.printHexBinary(coding_style_default); + } + + public String getQuantisationDefaultString() { + return DatatypeConverter.printHexBinary(quantisation_default); + } + + public J2KExtendedCapabilities getJ2kExtendedCapabilities() { + return j2k_extended_capabilities; + } + /** * A method that returns a string representation of a JPEG2000PictureSubDescriptorBO object * * @return string representing the object */ - public String toString() - { + + @Override + public String toString() { StringBuilder sb = new StringBuilder(); sb.append("================== JPEG2000PictureSubDescriptor ======================\n"); sb.append(this.header.toString()); diff --git a/src/main/java/com/netflix/imflibrary/st2067_2/Application2E2021.java b/src/main/java/com/netflix/imflibrary/st2067_2/Application2E2021.java index a6ce6e45..29ad4e38 100644 --- a/src/main/java/com/netflix/imflibrary/st2067_2/Application2E2021.java +++ b/src/main/java/com/netflix/imflibrary/st2067_2/Application2E2021.java @@ -8,7 +8,7 @@ import com.netflix.imflibrary.st0377.header.UL; import com.netflix.imflibrary.IMFErrorLogger; import com.netflix.imflibrary.st2067_2.ApplicationCompositionFactory.ApplicationCompositionType; -import com.netflix.imflibrary.st2067_2.CompositionImageEssenceDescriptorModel.J2KHeaderParameters; +import com.netflix.imflibrary.J2KHeaderParameters; import com.netflix.imflibrary.st2067_2.CompositionImageEssenceDescriptorModel.ProgressionOrder; import com.netflix.imflibrary.utils.Fraction; import com.netflix.imflibrary.JPEG2000; @@ -274,12 +274,10 @@ public Application2E2021(@Nonnull IMFCompositionPlaylistType imfCompositionPlayl /* Validate codestream parameters against constraints listed in SMPTE ST 2067-21:2023 Annex I */ - private static boolean validateHT(CompositionImageEssenceDescriptorModel imageDescriptor, + public static boolean validateHTConstraints(J2KHeaderParameters p, IMFErrorLogger logger) { boolean isValid = true; - J2KHeaderParameters p = imageDescriptor.getJ2KHeaderParameters(); - if (p == null) { logger.addError( IMFErrorLogger.IMFErrors.ErrorCodes.APPLICATION_COMPOSITION_ERROR, @@ -556,7 +554,7 @@ public static boolean isValidJ2KProfile(CompositionImageEssenceDescriptorModel i Integer height = imageDescriptor.getStoredHeight(); if (JPEG2000.isAPP2HT(essenceCoding)) - return validateHT(imageDescriptor, logger); + return validateHTConstraints(imageDescriptor.getJ2KHeaderParameters(), logger); if (JPEG2000.isIMF4KProfile(essenceCoding)) return width > 2048 && width <= 4096 && height > 0 && height <= 3112; diff --git a/src/main/java/com/netflix/imflibrary/st2067_2/CompositionImageEssenceDescriptorModel.java b/src/main/java/com/netflix/imflibrary/st2067_2/CompositionImageEssenceDescriptorModel.java index c068fa8c..dca946cd 100644 --- a/src/main/java/com/netflix/imflibrary/st2067_2/CompositionImageEssenceDescriptorModel.java +++ b/src/main/java/com/netflix/imflibrary/st2067_2/CompositionImageEssenceDescriptorModel.java @@ -9,6 +9,7 @@ import com.netflix.imflibrary.Colorimetry.TransferCharacteristic; import com.netflix.imflibrary.IMFErrorLogger; import com.netflix.imflibrary.IMFErrorLoggerImpl; +import com.netflix.imflibrary.J2KHeaderParameters; import com.netflix.imflibrary.st0377.header.GenericPictureEssenceDescriptor; import com.netflix.imflibrary.st0377.header.UL; import com.netflix.imflibrary.st0377.header.GenericPictureEssenceDescriptor.FrameLayoutType; @@ -196,7 +197,7 @@ public CompositionImageEssenceDescriptorModel(@Nonnull UUID imageEssencedescript this.sampling = parseSampling(this.colorModel); } - this.j2kParameters = parseJ2KParameters(); + this.j2kParameters = J2KHeaderParameters.fromDOMNode(imageEssencedescriptorDOMNode); } else { this.pixelBitDepth = null; @@ -416,217 +417,8 @@ public short value() { } } - static public class J2KHeaderParameters { - - static public class CSiz { - short ssiz; - short xrsiz; - short yrsiz; - } - - static public class CAP { - long pcap; - int[] ccap; - } - - static public class COD { - short scod; - short progressionOrder; - int numLayers; - short multiComponentTransform; - short numDecompLevels; - short xcb; - short ycb; - short cbStyle; - short transformation; - short precinctSizes[]; - } - - static public class QCD { - short sqcd; - int spqcd[]; - } - - Integer rsiz; - Long xsiz; - Long ysiz; - Long xosiz; - Long yosiz; - Long xtsiz; - Long ytsiz; - Long xtosiz; - Long ytosiz; - CSiz[] csiz; - COD cod; - QCD qcd; - CAP cap; - - } - J2KHeaderParameters j2kParameters; - private J2KHeaderParameters parseJ2KParameters() { - J2KHeaderParameters params = new J2KHeaderParameters(); - - DOMNodeObjectModel sdNode = imageEssencedescriptorDOMNode.getDOMNode("SubDescriptors"); - if (sdNode == null) { - /* missing SubDescriptors */ - return null; - } - - DOMNodeObjectModel j2kNode = sdNode.getDOMNode("JPEG2000SubDescriptor"); - if (j2kNode == null) { - /* missing JPEG2000SubDescriptor */ - return null; - } - - params.rsiz = j2kNode.getFieldAsInteger("Rsiz"); - if (params.rsiz == null) - return null; - - params.xsiz = j2kNode.getFieldAsLong("Xsiz"); - if (params.xsiz == null) - return null; - - params.ysiz = j2kNode.getFieldAsLong("Ysiz"); - if (params.ysiz == null) - return null; - - params.xosiz = j2kNode.getFieldAsLong("XOsiz"); - if (params.xosiz == null) - return null; - - params.yosiz = j2kNode.getFieldAsLong("YOsiz"); - if (params.yosiz == null) - return null; - - params.xtsiz = j2kNode.getFieldAsLong("XTsiz"); - if (params.xtsiz == null) - return null; - - params.ytsiz = j2kNode.getFieldAsLong("YTsiz"); - if (params.ytsiz == null) - return null; - - params.xtosiz = j2kNode.getFieldAsLong("XTOsiz"); - if (params.xtosiz == null) - return null; - - params.ytosiz = j2kNode.getFieldAsLong("YTOsiz"); - if (params.ytosiz == null) - return null; - - /* CSizi */ - DOMNodeObjectModel csiziNode = j2kNode.getDOMNode("PictureComponentSizing"); - if (csiziNode == null) - return null; - - List csizi = csiziNode.getDOMNodes("J2KComponentSizing"); - - params.csiz = new J2KHeaderParameters.CSiz[csizi.size()]; - for (int i = 0; i < params.csiz.length; i++) { - params.csiz[i] = new J2KHeaderParameters.CSiz(); - - Short ssiz = csizi.get(i).getFieldAsShort("Ssiz"); - if (ssiz == null) - return null; - params.csiz[i].ssiz = ssiz; - - Short xrsiz = csizi.get(i).getFieldAsShort("XRSiz"); - if (xrsiz == null) - return null; - params.csiz[i].xrsiz = xrsiz; - - Short yrsiz = csizi.get(i).getFieldAsShort("YRSiz"); - if (yrsiz == null) - return null; - params.csiz[i].yrsiz = yrsiz; - } - - Integer csiz = j2kNode.getFieldAsInteger("Csiz"); - if (csiz != params.csiz.length) - return null; - - /* CAP */ - DOMNodeObjectModel capNode = j2kNode.getDOMNode("J2KExtendedCapabilities"); - if (capNode != null) { - - Integer pcap = capNode.getFieldAsInteger("Pcap"); - - if (pcap != null) { - - params.cap = new J2KHeaderParameters.CAP(); - params.cap.pcap = pcap; - - DOMNodeObjectModel ccapiNode = capNode.getDOMNode("Ccapi"); - if (ccapiNode != null) { - List values = ccapiNode.getFieldsAsInteger("UInt16"); - - params.cap.ccap = new int[values.size()]; - for (int i = 0; i < params.cap.ccap.length; i++) { - if (values.get(i) == null) - return null; - params.cap.ccap[i] = values.get(i); - } - - } - - int ccapLength = Long.bitCount(params.cap.pcap); - if (ccapLength > 0 && (params.cap.ccap == null || params.cap.ccap.length != ccapLength)) - return null; - if (ccapLength == 0 && (params.cap.ccap != null && params.cap.ccap.length != 0)) - return null; - - } else { - /* pcap is missing */ - return null; - } - - } - - /* COD */ - String codString = j2kNode.getFieldAsString("CodingStyleDefault"); - - if (codString != null && codString.length() >= 20 && (codString.length() % 2 == 0)) { - - params.cod = new J2KHeaderParameters.COD(); - params.cod.scod = (short) Integer.parseInt(codString.substring(0, 2), 16); - params.cod.progressionOrder = (short) Integer.parseInt(codString.substring(2, 4), 16); - params.cod.numLayers = (int) Integer.parseInt(codString.substring(4, 8), 16); - params.cod.multiComponentTransform = (short) Integer.parseInt(codString.substring(8, 10), 16); - params.cod.numDecompLevels = (short) Integer.parseInt(codString.substring(10, 12), 16); - params.cod.xcb = (short) (Integer.parseInt(codString.substring(12, 14), 16) + 2); - params.cod.ycb = (short) (Integer.parseInt(codString.substring(14, 16), 16) + 2); - params.cod.cbStyle = (short) Integer.parseInt(codString.substring(16, 18), 16); - params.cod.transformation = (short) Integer.parseInt(codString.substring(18, 20), 16); - - params.cod.precinctSizes = new short[(codString.length() - 20)/2]; - for (int i = 0; i < params.cod.precinctSizes.length; i++) { - params.cod.precinctSizes[i] = (short) Integer.parseInt(codString.substring(20 + 2 * i, 22 + 2 * i), 16); - } - - } - - /* QCD */ - String qcdString = j2kNode.getFieldAsString("QuantizationDefault"); - - if (qcdString != null && qcdString.length() >= 2 && (qcdString.length() % 2 == 0)) { - - params.qcd = new J2KHeaderParameters.QCD(); - - params.qcd.sqcd = (short) Integer.parseInt(qcdString.substring(0, 2), 16); - - int spqcdSize = (params.qcd.sqcd & 0b11111) == 0 ? 1 : 2; - params.qcd.spqcd = new int[(qcdString.length() - 2)/(2 * spqcdSize)]; - for (int i = 0; i < params.qcd.spqcd.length; i++) { - params.qcd.spqcd[i] = (int) Integer.parseInt(qcdString.substring(2 + 2 * spqcdSize * i, 4 + 2 * spqcdSize * i), 16); - } - - } - - return params; - } - private @Nonnull Integer parsePixelBitDepth(@Nonnull ColorModel colorModel) { Integer refPixelBitDepth = null; DOMNodeObjectModel subDescriptors = imageEssencedescriptorDOMNode.getDOMNode(regXMLLibDictionary.getSymbolNameFromURN(subdescriptorsUL)); diff --git a/src/test/java/com/netflix/imflibrary/st2067_2/Application2ExtendedCompositionTest.java b/src/test/java/com/netflix/imflibrary/st2067_2/Application2ExtendedCompositionTest.java index 54a684e0..fa1047b5 100644 --- a/src/test/java/com/netflix/imflibrary/st2067_2/Application2ExtendedCompositionTest.java +++ b/src/test/java/com/netflix/imflibrary/st2067_2/Application2ExtendedCompositionTest.java @@ -2,7 +2,7 @@ import com.netflix.imflibrary.IMFErrorLogger; import com.netflix.imflibrary.IMFErrorLoggerImpl; -import com.netflix.imflibrary.st2067_2.CompositionImageEssenceDescriptorModel.J2KHeaderParameters; +import com.netflix.imflibrary.J2KHeaderParameters; import com.netflix.imflibrary.utils.ErrorLogger; import com.netflix.imflibrary.utils.FileByteRangeProvider; diff --git a/src/test/java/com/netflix/imflibrary/st2067_2/J2KHeaderParametersTest.java b/src/test/java/com/netflix/imflibrary/st2067_2/J2KHeaderParametersTest.java new file mode 100644 index 00000000..6dda0d4f --- /dev/null +++ b/src/test/java/com/netflix/imflibrary/st2067_2/J2KHeaderParametersTest.java @@ -0,0 +1,51 @@ +package com.netflix.imflibrary.st2067_2; + +import com.netflix.imflibrary.IMFErrorLogger; +import com.netflix.imflibrary.IMFErrorLoggerImpl; +import com.netflix.imflibrary.J2KHeaderParameters; +import com.netflix.imflibrary.st0377.HeaderPartition; +import com.netflix.imflibrary.st0377.header.GenericPictureEssenceDescriptor; +import com.netflix.imflibrary.st0377.header.InterchangeObject; +import com.netflix.imflibrary.st0377.header.JPEG2000PictureSubDescriptor; +import com.netflix.imflibrary.st0377.header.SourcePackage; +import com.netflix.imflibrary.st2067_2.Application2E2021; +import com.netflix.imflibrary.st2067_2.CompositionImageEssenceDescriptorModel; +import com.netflix.imflibrary.st2067_2.IMFCompositionPlaylistType; +import com.netflix.imflibrary.utils.FileByteRangeProvider; +import org.testng.Assert; +import org.testng.annotations.Test; +import testUtils.TestHelper; + +import java.io.File; +import java.io.IOException; + +@Test(groups = "unit") +public class J2KHeaderParametersTest { + + @Test + public void testParsedMXFJ2KParametersMatchParsedCPLParameters() throws IOException { + // First, get the parsed MXF JPEG2000PictureSubDescriptor... + File inputMXFFile = TestHelper.findResourceByPath("TestIMP/HT/IMP/VIDEO_6ed567b7-c030-46d6-9c1c-0f09bab4b962.mxf"); + IMFErrorLogger imfErrorLogger = new IMFErrorLoggerImpl(); + HeaderPartition headerPartition = HeaderPartition.fromFile(inputMXFFile, imfErrorLogger); + GenericPictureEssenceDescriptor pictureEssenceDescriptor = ((GenericPictureEssenceDescriptor)((SourcePackage) headerPartition.getSourcePackages().get(0)).getGenericDescriptor()); + GenericPictureEssenceDescriptor.GenericPictureEssenceDescriptorBO descriptorBO = (GenericPictureEssenceDescriptor.GenericPictureEssenceDescriptorBO) TestHelper.getValue(pictureEssenceDescriptor, "rgbaPictureEssenceDescriptorBO"); + InterchangeObject.InterchangeObjectBO jpeg2000SubDescriptor = headerPartition.getSubDescriptors(descriptorBO).get(0); + JPEG2000PictureSubDescriptor.JPEG2000PictureSubDescriptorBO jpeg2000PictureSubDescriptorBO = (JPEG2000PictureSubDescriptor.JPEG2000PictureSubDescriptorBO) jpeg2000SubDescriptor; + + // Next, get the parsed CPL Descriptor... + File inputCPLFile = TestHelper.findResourceByPath("TestIMP/HT/IMP/CPL_67be5fc8-87f1-4172-8d52-819ca14c7a20.xml"); + FileByteRangeProvider resourceByteRangeProvider = new FileByteRangeProvider(inputCPLFile); + IMFErrorLogger logger = new IMFErrorLoggerImpl(); + IMFCompositionPlaylistType imfCompositionPlaylistType = IMFCompositionPlaylistType.getCompositionPlayListType(resourceByteRangeProvider, logger); + Application2E2021 app = new Application2E2021(imfCompositionPlaylistType); + CompositionImageEssenceDescriptorModel image = app.getCompositionImageEssenceDescriptorModel(); // Calls 'J2KHeaderParameters.fromDOMNode(...) + + J2KHeaderParameters fromMXF = J2KHeaderParameters.fromJPEG2000PictureSubDescriptorBO(jpeg2000PictureSubDescriptorBO); + J2KHeaderParameters fromCPL = image.getJ2KHeaderParameters(); + + // ...and validate that they are the same. + Assert.assertTrue(fromCPL.equals(fromMXF)); + } + +}