Example #1
0
  /**
   * A stateless method that validates an IMFEssenceComponent's header partition and verifies MXF
   * OP1A and IMF compliance. This could be utilized to perform preliminary validation of IMF
   * essences
   *
   * @param essencesHeaderPartitionPayloads - a list of IMF Essence Component header partition
   *     payloads
   * @return a list of errors encountered while performing compliance checks on the IMF Essence
   *     Component Header partition
   * @throws IOException - any I/O related error is exposed through an IOException
   */
  public static List<ErrorLogger.ErrorObject> validateIMFTrackFileHeaderMetadata(
      List<PayloadRecord> essencesHeaderPartitionPayloads) throws IOException {
    IMFErrorLogger imfErrorLogger = new IMFErrorLoggerImpl();
    List<PayloadRecord> essencesHeaderPartition =
        Collections.unmodifiableList(essencesHeaderPartitionPayloads);
    for (PayloadRecord payloadRecord : essencesHeaderPartition) {
      if (payloadRecord.getPayloadAssetType() != PayloadRecord.PayloadAssetType.EssencePartition) {
        imfErrorLogger.addError(
            IMFErrorLogger.IMFErrors.ErrorCodes.IMP_VALIDATOR_PAYLOAD_ERROR,
            IMFErrorLogger.IMFErrors.ErrorLevels.FATAL,
            String.format(
                "Payload asset type is %s, expected asset type %s",
                payloadRecord.getPayloadAssetType(),
                PayloadRecord.PayloadAssetType.EssencePartition.toString()));
        continue;
      }
      HeaderPartition headerPartition = null;
      try {
        headerPartition =
            new HeaderPartition(
                new ByteArrayDataProvider(payloadRecord.getPayload()),
                0L,
                (long) payloadRecord.getPayload().length,
                imfErrorLogger);

        MXFOperationalPattern1A.HeaderPartitionOP1A headerPartitionOP1A =
            MXFOperationalPattern1A.checkOperationalPattern1ACompliance(
                headerPartition, imfErrorLogger);
        IMFConstraints.checkIMFCompliance(headerPartitionOP1A, imfErrorLogger);
      } catch (IMFException | MXFException e) {
        if (headerPartition != null) {
          Preface preface = headerPartition.getPreface();
          GenericPackage genericPackage =
              preface.getContentStorage().getEssenceContainerDataList().get(0).getLinkedPackage();
          SourcePackage filePackage = (SourcePackage) genericPackage;
          UUID packageUUID = filePackage.getPackageMaterialNumberasUUID();
          imfErrorLogger.addError(
              new ErrorLogger.ErrorObject(
                  IMFErrorLogger.IMFErrors.ErrorCodes.IMF_ESSENCE_COMPONENT_ERROR,
                  IMFErrorLogger.IMFErrors.ErrorLevels.FATAL,
                  String.format(
                      "IMFTrackFile with ID %s has fatal errors", packageUUID.toString())));
        }
        if (e instanceof IMFException) {
          IMFException imfException = (IMFException) e;
          imfErrorLogger.addAllErrors(imfException.getErrors());
        } else if (e instanceof MXFException) {
          MXFException mxfException = (MXFException) e;
          imfErrorLogger.addAllErrors(mxfException.getErrors());
        }
      }
    }
    return imfErrorLogger.getErrors();
  }
Example #2
0
  /**
   * A stateless method that returns the RFC-5646 Spoken Language Tag present in the Header
   * Partition of an Audio Essence
   *
   * @param essencesHeaderPartition - a list of payloads corresponding to the Header Partitions of
   *     TrackFiles that are a part of an Audio VirtualTrack
   * @param audioVirtualTrack - the audio virtual track whose spoken language needs to be
   *     ascertained
   * @return string corresponding to the RFC-5646 language tag present in the header partition of
   *     the Audio Essence
   * @throws IOException - any I/O related error is exposed through an IOException
   */
  @Nullable
  public static String getAudioTrackSpokenLanguage(
      VirtualTrack audioVirtualTrack, List<PayloadRecord> essencesHeaderPartition)
      throws IOException {
    IMFErrorLogger imfErrorLogger = new IMFErrorLoggerImpl();
    if (audioVirtualTrack.getSequenceTypeEnum() != Composition.SequenceTypeEnum.MainAudioSequence) {
      throw new IMFException(
          String.format(
              "Virtual track that was passed in is of type %s, spoken language is "
                  + "currently supported for only %s tracks",
              audioVirtualTrack.getSequenceTypeEnum().toString(),
              Composition.SequenceTypeEnum.MainAudioSequence.toString()));
    }
    List<VirtualTrack> virtualTracks = new ArrayList<>();
    virtualTracks.add(audioVirtualTrack);
    imfErrorLogger.addAllErrors(
        checkVirtualTrackAndEssencesHeaderPartitionPayloadRecords(
            virtualTracks, essencesHeaderPartition));
    if (imfErrorLogger.hasFatalErrors()) {
      throw new IMFException(
          String.format(
              "Fatal Errors were detected when trying to verify the Virtual Track and Essence Header Partition payloads %s",
              Utilities.serializeObjectCollectionToString(imfErrorLogger.getErrors())));
    }
    Set<String> audioLanguageSet = new HashSet<>();
    for (PayloadRecord payloadRecord : essencesHeaderPartition) {
      if (payloadRecord.getPayloadAssetType() != PayloadRecord.PayloadAssetType.EssencePartition) {
        throw new IMFException(
            String.format(
                "Payload asset type is %s, expected asset type %s",
                payloadRecord.getPayloadAssetType(),
                PayloadRecord.PayloadAssetType.EssencePartition.toString()),
            imfErrorLogger);
      }
      HeaderPartition headerPartition =
          new HeaderPartition(
              new ByteArrayDataProvider(payloadRecord.getPayload()),
              0L,
              (long) payloadRecord.getPayload().length,
              imfErrorLogger);
      audioLanguageSet.add(headerPartition.getAudioEssenceSpokenLanguage());
    }

    if (audioLanguageSet.size() > 1) {
      throw new IMFException(
          String.format(
              "It seems that RFC-5646 spoken language is not consistent across "
                  + "resources of this Audio Virtual Track, found references to %s languages in the HeaderPartition",
              Utilities.serializeObjectCollectionToString(audioLanguageSet)),
          imfErrorLogger);
    }
    return audioLanguageSet.iterator().next();
  }
Example #3
0
 /**
  * A stateless method that will return the size of the RandomIndexPack present within a MXF file.
  * In a typical IMF workflow this would be the first method that would need to be invoked to
  * perform IMF essence component level validation
  *
  * @param essenceFooter4Bytes - the last 4 bytes of the MXF file used to infer the size of the
  *     RandomIndexPack
  * @return a long integer value representing the size of the RandomIndexPack
  */
 public static Long getRandomIndexPackSize(PayloadRecord essenceFooter4Bytes) {
   IMFErrorLogger imfErrorLogger = new IMFErrorLoggerImpl();
   if (essenceFooter4Bytes.getPayloadAssetType()
       != PayloadRecord.PayloadAssetType.EssenceFooter4Bytes) {
     throw new IMFException(
         String.format(
             "Payload asset type is %s, expected asset type %s",
             essenceFooter4Bytes.getPayloadAssetType(),
             PayloadRecord.PayloadAssetType.EssenceFooter4Bytes.toString()),
         imfErrorLogger);
   }
   return (long) (ByteBuffer.wrap(essenceFooter4Bytes.getPayload()).getInt());
 }
Example #4
0
 /**
  * A stateless method that will read and parse the RandomIndexPack within a MXF file and return a
  * list of byte offsets corresponding to the partitions of the MXF file. In a typical IMF workflow
  * this would be the second method after {@link #getRandomIndexPackSize(PayloadRecord)} that would
  * need to be invoked to perform IMF essence component level validation
  *
  * @param randomIndexPackPayload - a payload containing the raw bytes corresponding to the
  *     RandomIndexPack of the MXF file
  * @param randomIndexPackSize - size of the RandomIndexPack of the MXF file
  * @return list of long integer values representing the byte offsets of the partitions in the MXF
  *     file
  * @throws IOException - any I/O related error is exposed through an IOException
  */
 public static List<Long> getEssencePartitionOffsets(
     PayloadRecord randomIndexPackPayload, Long randomIndexPackSize) throws IOException {
   if (randomIndexPackPayload.getPayload().length != randomIndexPackSize) {
     throw new IllegalArgumentException(
         String.format(
             "RandomIndexPackSize passed in is = %d, RandomIndexPack payload size = %d, they should be equal",
             randomIndexPackSize, randomIndexPackPayload.getPayload().length));
   }
   RandomIndexPack randomIndexPack =
       new RandomIndexPack(
           new ByteArrayDataProvider(randomIndexPackPayload.getPayload()),
           0L,
           randomIndexPackSize);
   return randomIndexPack.getAllPartitionByteOffsets();
 }
Example #5
0
 /**
  * A stateless method that will validate an IMF AssetMap document
  *
  * @param assetMapPayload - a payload record for an AssetMap document
  * @return list of error messages encountered while validating an AssetMap document
  * @throws IOException - any I/O related error is exposed through an IOException
  */
 public static List<ErrorLogger.ErrorObject> validateAssetMap(PayloadRecord assetMapPayload)
     throws IOException {
   if (assetMapPayload.getPayloadAssetType() != PayloadRecord.PayloadAssetType.AssetMap) {
     throw new IMFException(
         String.format(
             "Payload asset type is %s, expected asset type %s",
             assetMapPayload.getPayloadAssetType(),
             PayloadRecord.PayloadAssetType.AssetMap.toString()));
   }
   try {
     AssetMap assetMap =
         new AssetMap(new ByteArrayByteRangeProvider(assetMapPayload.getPayload()));
     return assetMap.getErrors();
   } catch (IMFException e) {
     return e.getErrors();
   }
 }
Example #6
0
  /**
   * A stateless method that will validate an IMF PackingList document
   *
   * @param pkl - a payload record for a Packing List document
   * @return list of error messages encountered while validating a Packing List document
   * @throws IOException - any I/O related error is exposed through an IOException
   */
  public static List<ErrorLogger.ErrorObject> validatePKL(PayloadRecord pkl) throws IOException {
    IMFErrorLogger imfErrorLogger = new IMFErrorLoggerImpl();

    if (pkl.getPayloadAssetType() != PayloadRecord.PayloadAssetType.PackingList) {
      throw new IMFException(
          String.format(
              "Payload asset type is %s, expected asset type %s",
              pkl.getPayloadAssetType(), PayloadRecord.PayloadAssetType.PackingList.toString()),
          imfErrorLogger);
    }
    try {
      PackingList packingList = new PackingList(new ByteArrayByteRangeProvider(pkl.getPayload()));
      imfErrorLogger.addAllErrors(packingList.getErrors());
    } catch (IMFException e) {
      imfErrorLogger.addAllErrors(e.getErrors());
    }
    return imfErrorLogger.getErrors();
  }
Example #7
0
  /**
   * A stateless method that will validate an IMF Composition document
   *
   * @param cpl - a payload record for a Composition document
   * @return list of error messages encountered while validating an AssetMap document
   * @throws IOException - any I/O related error is exposed through an IOException
   */
  public static List<ErrorLogger.ErrorObject> validateCPL(PayloadRecord cpl) throws IOException {
    IMFErrorLogger imfErrorLogger = new IMFErrorLoggerImpl();
    if (cpl.getPayloadAssetType() != PayloadRecord.PayloadAssetType.CompositionPlaylist) {
      throw new IMFException(
          String.format(
              "Payload asset type is %s, expected asset type %s",
              cpl.getPayloadAssetType(),
              PayloadRecord.PayloadAssetType.CompositionPlaylist.toString()));
    }

    try {
      Composition composition = new Composition(new ByteArrayByteRangeProvider(cpl.getPayload()));
      imfErrorLogger.addAllErrors(composition.getErrors());
    } catch (IMFException e) {
      imfErrorLogger.addAllErrors(e.getErrors());
    }
    return imfErrorLogger.getErrors();
  }
Example #8
0
  public static List<ErrorLogger.ErrorObject> conformVirtualTracksInCPL(
      PayloadRecord cplPayloadRecord,
      List<PayloadRecord> essencesHeaderPartitionPayloads,
      boolean conformAllVirtualTracks)
      throws IOException {

    IMFErrorLogger imfErrorLogger = new IMFErrorLoggerImpl();
    List<PayloadRecord> essencesHeaderPartition =
        Collections.unmodifiableList(essencesHeaderPartitionPayloads);

    try {
      imfErrorLogger.addAllErrors(validateCPL(cplPayloadRecord));
      if (imfErrorLogger.hasFatalErrors())
        return Collections.unmodifiableList(imfErrorLogger.getErrors());

      Composition composition =
          new Composition(new ByteArrayByteRangeProvider(cplPayloadRecord.getPayload()));

      imfErrorLogger.addAllErrors(validateIMFTrackFileHeaderMetadata(essencesHeaderPartition));

      List<HeaderPartitionTuple> headerPartitionTuples = new ArrayList<>();
      for (PayloadRecord payloadRecord : essencesHeaderPartition) {
        if (payloadRecord.getPayloadAssetType()
            != PayloadRecord.PayloadAssetType.EssencePartition) {
          imfErrorLogger.addError(
              IMFErrorLogger.IMFErrors.ErrorCodes.IMF_MASTER_PACKAGE_ERROR,
              IMFErrorLogger.IMFErrors.ErrorLevels.FATAL,
              String.format(
                  "Payload asset type is %s, expected asset type %s",
                  payloadRecord.getPayloadAssetType(),
                  PayloadRecord.PayloadAssetType.EssencePartition.toString()));
          continue;
        }
        headerPartitionTuples.add(
            new HeaderPartitionTuple(
                new HeaderPartition(
                    new ByteArrayDataProvider(payloadRecord.getPayload()),
                    0L,
                    (long) payloadRecord.getPayload().length,
                    imfErrorLogger),
                new ByteArrayByteRangeProvider(payloadRecord.getPayload())));
      }

      if (imfErrorLogger.hasFatalErrors()) {
        return imfErrorLogger.getErrors();
      }

      imfErrorLogger.addAllErrors(
          composition.conformVirtualTracksInComposition(
              Collections.unmodifiableList(headerPartitionTuples), conformAllVirtualTracks));

      imfErrorLogger.addAllErrors(composition.getErrors());
    } catch (IMFException e) {
      imfErrorLogger.addAllErrors(e.getErrors());
    }

    return imfErrorLogger.getErrors();
  }
Example #9
0
  /**
   * A stateless method that determines if the Asset type of the payload is an IMF AssetMap,
   * Packinglist or Composition
   *
   * @param payloadRecord - a payload record corresponding to the asset whose type needs to be
   *     confirmed Note: for now this method only supports text/xml documents identified in the PKL
   *     application/mxf asset types cannot be determined.
   * @return asset type of the payload either one of AssetMap, PackingList or Composition
   * @throws IOException - any I/O related error is exposed through an IOException
   */
  public static PayloadRecord.PayloadAssetType getPayloadType(PayloadRecord payloadRecord)
      throws IOException {

    ResourceByteRangeProvider resourceByteRangeProvider =
        new ByteArrayByteRangeProvider(payloadRecord.getPayload());
    if (AssetMap.isFileOfSupportedSchema(resourceByteRangeProvider)) {
      return PayloadRecord.PayloadAssetType.AssetMap;
    } else if (PackingList.isFileOfSupportedSchema(resourceByteRangeProvider)) {
      return PayloadRecord.PayloadAssetType.PackingList;
    } else if (Composition.isCompositionPlaylist(resourceByteRangeProvider)) {
      return PayloadRecord.PayloadAssetType.CompositionPlaylist;
    }
    return PayloadRecord.PayloadAssetType.Unknown;
  }
Example #10
0
  /**
   * A stateless method to retrieve all the VirtualTracks that are a part of a Composition
   *
   * @param cpl - a payload corresponding to the Composition Playlist
   * @return list of VirtualTracks
   * @throws IOException - any I/O related error is exposed through an IOException
   */
  public static List<? extends VirtualTrack> getVirtualTracks(PayloadRecord cpl)
      throws IOException {
    IMFErrorLogger imfErrorLogger = new IMFErrorLoggerImpl();
    List<ErrorLogger.ErrorObject> errorList = validateCPL(cpl);

    imfErrorLogger.addAllErrors(errorList);

    if (imfErrorLogger.hasFatalErrors()) {
      throw new IMFException("Virtual track failed validation", imfErrorLogger);
    }

    Composition composition = new Composition(new ByteArrayByteRangeProvider(cpl.getPayload()));
    return composition.getVirtualTracks();
  }
Example #11
0
  /**
   * A stateless method that can be used to determine if a Composition is conformant. Conformance
   * checks perform deeper inspection of the Composition and the EssenceDescriptors corresponding to
   * all the Virtual Tracks that are a part of the Composition
   *
   * @param cplPayloadRecord a payload record corresponding to the Composition payload
   * @param essencesHeaderPartitionPayloads list of payload records containing the raw bytes of the
   *     HeaderPartitions of the IMF Track files that are a part of the Virtual Track/s in the
   *     Composition
   * @return list of error messages encountered while performing conformance validation of the
   *     Composition document
   * @throws IOException - any I/O related error is exposed through an IOException
   */
  public static List<ErrorLogger.ErrorObject> areAllVirtualTracksInCPLConformed(
      PayloadRecord cplPayloadRecord, List<PayloadRecord> essencesHeaderPartitionPayloads)
      throws IOException {

    IMFErrorLogger imfErrorLogger = new IMFErrorLoggerImpl();
    Composition composition =
        new Composition(new ByteArrayByteRangeProvider(cplPayloadRecord.getPayload()));

    imfErrorLogger.addAllErrors(composition.getErrors());

    List<VirtualTrack> virtualTracks = new ArrayList<>(composition.getVirtualTracks());
    imfErrorLogger.addAllErrors(
        checkVirtualTrackAndEssencesHeaderPartitionPayloadRecords(
            virtualTracks, essencesHeaderPartitionPayloads));
    if (imfErrorLogger.hasFatalErrors()) {
      return imfErrorLogger.getErrors();
    }
    imfErrorLogger.addAllErrors(
        conformVirtualTracksInCPL(cplPayloadRecord, essencesHeaderPartitionPayloads, true));

    return imfErrorLogger.getErrors();
  }
Example #12
0
  private static List<ErrorLogger.ErrorObject>
      checkVirtualTrackAndEssencesHeaderPartitionPayloadRecords(
          List<VirtualTrack> virtualTracks, List<PayloadRecord> essencesHeaderPartition)
          throws IOException {
    IMFErrorLogger imfErrorLogger = new IMFErrorLoggerImpl();
    Set<UUID> trackFileIDsSet = new HashSet<>();

    for (PayloadRecord payloadRecord : essencesHeaderPartition) {
      if (payloadRecord.getPayloadAssetType() != PayloadRecord.PayloadAssetType.EssencePartition) {
        throw new IMFException(
            String.format(
                "Payload asset type is %s, expected asset type %s",
                payloadRecord.getPayloadAssetType(),
                PayloadRecord.PayloadAssetType.EssencePartition.toString()),
            imfErrorLogger);
      }
      HeaderPartition headerPartition =
          new HeaderPartition(
              new ByteArrayDataProvider(payloadRecord.getPayload()),
              0L,
              (long) payloadRecord.getPayload().length,
              imfErrorLogger);
      try {
        /**
         * Add the Top Level Package UUID to the set of TrackFileIDs, this is required to validate
         * that the essences header partition that were passed in are in fact from the constituent
         * resources of the VirtualTack
         */
        MXFOperationalPattern1A.HeaderPartitionOP1A headerPartitionOP1A =
            MXFOperationalPattern1A.checkOperationalPattern1ACompliance(
                headerPartition, imfErrorLogger);
        IMFConstraints.HeaderPartitionIMF headerPartitionIMF =
            IMFConstraints.checkIMFCompliance(headerPartitionOP1A, imfErrorLogger);
        Preface preface =
            headerPartitionIMF.getHeaderPartitionOP1A().getHeaderPartition().getPreface();
        GenericPackage genericPackage =
            preface.getContentStorage().getEssenceContainerDataList().get(0).getLinkedPackage();
        SourcePackage filePackage = (SourcePackage) genericPackage;
        UUID packageUUID = filePackage.getPackageMaterialNumberasUUID();
        trackFileIDsSet.add(packageUUID);
      } catch (IMFException | MXFException e) {
        Preface preface = headerPartition.getPreface();
        GenericPackage genericPackage =
            preface.getContentStorage().getEssenceContainerDataList().get(0).getLinkedPackage();
        SourcePackage filePackage = (SourcePackage) genericPackage;
        UUID packageUUID = filePackage.getPackageMaterialNumberasUUID();
        imfErrorLogger.addError(
            new ErrorLogger.ErrorObject(
                IMFErrorLogger.IMFErrors.ErrorCodes.IMF_ESSENCE_COMPONENT_ERROR,
                IMFErrorLogger.IMFErrors.ErrorLevels.FATAL,
                String.format("IMFTrackFile with ID %s has fatal errors", packageUUID.toString())));
        if (e instanceof IMFException) {
          IMFException imfException = (IMFException) e;
          imfErrorLogger.addAllErrors(imfException.getErrors());
        } else if (e instanceof MXFException) {
          MXFException mxfException = (MXFException) e;
          imfErrorLogger.addAllErrors(mxfException.getErrors());
        }
      }
    }

    Set<UUID> virtualTrackResourceIDsSet = new HashSet<>();
    for (Composition.VirtualTrack virtualTrack : virtualTracks) {
      if (virtualTrack instanceof IMFEssenceComponentVirtualTrack) {
        virtualTrackResourceIDsSet.addAll(
            IMFEssenceComponentVirtualTrack.class.cast(virtualTrack).getTrackResourceIds());
      } else {
        imfErrorLogger.addError(
            IMFErrorLogger.IMFErrors.ErrorCodes.IMF_ESSENCE_COMPONENT_ERROR,
            IMFErrorLogger.IMFErrors.ErrorLevels.FATAL,
            String.format(
                "VirtualTrack with TrackId %s is not an Essence Component Virtual Track",
                virtualTrack.getTrackID().toString()));
      }
    }
    /**
     * Following check ensures that the Header Partitions corresponding to all the Resources of the
     * VirtualTracks were passed in.
     */
    Set<UUID> unreferencedResourceIDsSet = new HashSet<>();
    for (UUID uuid : virtualTrackResourceIDsSet) {
      if (!trackFileIDsSet.contains(uuid)) {
        unreferencedResourceIDsSet.add(uuid);
      }
    }
    if (unreferencedResourceIDsSet.size() > 0) {
      imfErrorLogger.addError(
          new ErrorLogger.ErrorObject(
              IMFErrorLogger.IMFErrors.ErrorCodes.IMP_VALIDATOR_PAYLOAD_ERROR,
              IMFErrorLogger.IMFErrors.ErrorLevels.FATAL,
              String.format(
                  "It seems that no EssenceHeaderPartition data was passed in for "
                      + "VirtualTrack Resource Ids %s, please verify that the correct Header Partition payloads for the "
                      + "Virtual Track were passed in",
                  Utilities.serializeObjectCollectionToString(unreferencedResourceIDsSet))));
    }

    /**
     * Following check ensures that the Header Partitions corresponding to only the Resource that
     * are a part of the VirtualTracks were passed in.
     */
    Set<UUID> unreferencedTrackFileIDsSet = new HashSet<>();
    for (UUID uuid : trackFileIDsSet) {
      if (!virtualTrackResourceIDsSet.contains(uuid)) {
        unreferencedTrackFileIDsSet.add(uuid);
      }
    }
    if (unreferencedTrackFileIDsSet.size() > 0) {
      imfErrorLogger.addError(
          new ErrorLogger.ErrorObject(
              IMFErrorLogger.IMFErrors.ErrorCodes.IMP_VALIDATOR_PAYLOAD_ERROR,
              IMFErrorLogger.IMFErrors.ErrorLevels.FATAL,
              String.format(
                  "It seems that EssenceHeaderPartition data was passed in for "
                      + "Resource Ids %s which are not part of this virtual track, please verify that only the Header "
                      + "Partition payloads for the Virtual Track were passed in",
                  Utilities.serializeObjectCollectionToString(unreferencedTrackFileIDsSet))));
    }

    return imfErrorLogger.getErrors();
  }
Example #13
0
  /**
   * A stateless method that determines if 2 or more Composition documents corresponding to the same
   * title can be inferred to represent the same presentation timeline. This method is present to
   * work around current limitations in the IMF eco system wherein CPL's might not be built
   * incrementally to include all the IMF essences that are a part of the same timeline
   *
   * @param referenceCPLPayloadRecord - a payload record corresponding to a Reference Composition
   *     document, perhaps the first composition playlist document that was delivered for a
   *     particular composition.
   * @param cplPayloads - a list of payload records corresponding to each of the Composition
   *     documents that need to be verified for mergeability
   * @return a boolean indicating if the CPLs can be merged or not
   * @throws IOException - any I/O related error is exposed through an IOException
   */
  public static List<ErrorLogger.ErrorObject> isCPLMergeable(
      PayloadRecord referenceCPLPayloadRecord, List<PayloadRecord> cplPayloads) throws IOException {

    IMFErrorLogger imfErrorLogger = new IMFErrorLoggerImpl();
    List<PayloadRecord> cplPayloadRecords = Collections.unmodifiableList(cplPayloads);
    List<Composition> compositions = new ArrayList<>();
    try {
      compositions.add(
          new Composition(new ByteArrayByteRangeProvider(referenceCPLPayloadRecord.getPayload())));
    } catch (IMFException e) {
      imfErrorLogger.addAllErrors(e.getErrors());
    }

    for (PayloadRecord cpl : cplPayloadRecords) {
      try {
        compositions.add(new Composition(new ByteArrayByteRangeProvider(cpl.getPayload())));
      } catch (IMFException e) {
        imfErrorLogger.addAllErrors(e.getErrors());
      }
    }

    if (imfErrorLogger.hasFatalErrors()) {
      return imfErrorLogger.getErrors();
    }

    VirtualTrack referenceVideoVirtualTrack = compositions.get(0).getVideoVirtualTrack();
    UUID referenceCPLUUID = compositions.get(0).getUUID();
    for (int i = 1; i < compositions.size(); i++) {
      if (!referenceVideoVirtualTrack.equivalent(compositions.get(i).getVideoVirtualTrack())) {
        imfErrorLogger.addError(
            IMFErrorLogger.IMFErrors.ErrorCodes.IMF_CPL_ERROR,
            IMFErrorLogger.IMFErrors.ErrorLevels.WARNING,
            String.format(
                "CPL Id %s can't be merged with Reference CPL Id %s, since the video virtual tracks do not seem to represent the same timeline.",
                compositions.get(i).getUUID(), referenceCPLUUID));
      }
    }

    /**
     * Perform AudioTrack mergeability checks 1) Identify AudioTracks that are the same language 2)
     * Compare language tracks to see if they represent the same timeline
     */
    Boolean bAudioVirtualTrackMapFail = false;
    List<Map<Set<DOMNodeObjectModel>, ? extends VirtualTrack>> audioVirtualTracksMapList =
        new ArrayList<>();
    for (Composition composition : compositions) {
      try {
        audioVirtualTracksMapList.add(composition.getAudioVirtualTracksMap());
      } catch (IMFException e) {
        bAudioVirtualTrackMapFail = false;
        imfErrorLogger.addAllErrors(e.getErrors());
      }
    }

    if (!bAudioVirtualTrackMapFail) {
      Map<Set<DOMNodeObjectModel>, ? extends VirtualTrack> referenceAudioVirtualTracksMap =
          audioVirtualTracksMapList.get(0);
      for (int i = 1; i < audioVirtualTracksMapList.size(); i++) {
        if (!compareAudioVirtualTrackMaps(
            Collections.unmodifiableMap(referenceAudioVirtualTracksMap),
            Collections.unmodifiableMap(audioVirtualTracksMapList.get(i)),
            imfErrorLogger)) {
          imfErrorLogger.addError(
              IMFErrorLogger.IMFErrors.ErrorCodes.IMF_CPL_ERROR,
              IMFErrorLogger.IMFErrors.ErrorLevels.WARNING,
              String.format(
                  "CPL Id %s can't be merged with Reference CPL Id %s, since 2 same language audio tracks do not seem to represent the same timeline.",
                  compositions.get(i).getUUID(), referenceCPLUUID));
        }
      }
    }

    /** Perform MarkerTrack mergeability checks */
    Composition.VirtualTrack referenceMarkerVirtualTrack =
        compositions.get(0).getMarkerVirtualTrack();
    if (referenceMarkerVirtualTrack != null) {
      UUID referenceMarkerCPLUUID = compositions.get(0).getUUID();
      for (int i = 1; i < compositions.size(); i++) {
        if (!referenceVideoVirtualTrack.equivalent(compositions.get(i).getMarkerVirtualTrack())) {
          imfErrorLogger.addError(
              IMFErrorLogger.IMFErrors.ErrorCodes.IMF_CPL_ERROR,
              IMFErrorLogger.IMFErrors.ErrorLevels.WARNING,
              String.format(
                  "CPL Id %s can't be merged with Reference CPL Id %s, since the marker virtual tracks do not seem to represent the same timeline.",
                  compositions.get(i).getUUID(), referenceMarkerCPLUUID));
        }
      }
    }

    return imfErrorLogger.getErrors();
  }
Example #14
0
  /**
   * A stateless method that will validate IMF AssetMap and PackingList documents for all the data
   * that should be cross referenced by both
   *
   * @param assetMapPayload - a payload record for an AssetMap document
   * @param pklPayloads - a list of payload records for Packing List documents referenced by the
   *     AssetMap
   * @return list of error messages encountered while validating an AssetMap document
   * @throws IOException - any I/O related error is exposed through an IOException
   */
  public static List<ErrorLogger.ErrorObject> validatePKLAndAssetMap(
      PayloadRecord assetMapPayload, List<PayloadRecord> pklPayloads) throws IOException {
    IMFErrorLogger imfErrorLogger = new IMFErrorLoggerImpl();
    List<PayloadRecord> packingListPayloadRecords = Collections.unmodifiableList(pklPayloads);

    if (assetMapPayload.getPayloadAssetType() != PayloadRecord.PayloadAssetType.AssetMap) {
      imfErrorLogger.addError(
          IMFErrorLogger.IMFErrors.ErrorCodes.IMF_AM_ERROR,
          IMFErrorLogger.IMFErrors.ErrorLevels.FATAL,
          String.format(
              "Payload asset type is %s, expected asset type %s",
              assetMapPayload.getPayloadAssetType(),
              PayloadRecord.PayloadAssetType.AssetMap.toString()));
    }

    ResourceByteRangeProvider assetMapByteRangeProvider =
        new ByteArrayByteRangeProvider(assetMapPayload.getPayload());
    AssetMap assetMapObjectModel = null;
    try {
      assetMapObjectModel = new AssetMap(assetMapByteRangeProvider);
      imfErrorLogger.addAllErrors(assetMapObjectModel.getErrors());

      if (assetMapObjectModel.getPackingListAssets().size() == 0) {
        imfErrorLogger.addError(
            IMFErrorLogger.IMFErrors.ErrorCodes.IMF_AM_ERROR,
            IMFErrorLogger.IMFErrors.ErrorLevels.FATAL,
            String.format(
                "Asset map should reference atleast one PackingList, %d " + "references found",
                assetMapObjectModel.getPackingListAssets().size()));
      }
    } catch (IMFException e) {
      imfErrorLogger.addAllErrors(e.getErrors());
    }

    List<ResourceByteRangeProvider> packingLists = new ArrayList<>();
    for (PayloadRecord payloadRecord : packingListPayloadRecords) {
      if (payloadRecord.getPayloadAssetType() != PayloadRecord.PayloadAssetType.PackingList) {
        imfErrorLogger.addError(
            IMFErrorLogger.IMFErrors.ErrorCodes.IMF_MASTER_PACKAGE_ERROR,
            IMFErrorLogger.IMFErrors.ErrorLevels.FATAL,
            String.format(
                "Payload asset type is %s, expected asset type %s",
                assetMapPayload.getPayloadAssetType(),
                PayloadRecord.PayloadAssetType.PackingList.toString()));
      } else {
        packingLists.add(new ByteArrayByteRangeProvider(payloadRecord.getPayload()));
      }
    }

    if (packingLists.size() == 0) {
      imfErrorLogger.addError(
          IMFErrorLogger.IMFErrors.ErrorCodes.IMF_MASTER_PACKAGE_ERROR,
          IMFErrorLogger.IMFErrors.ErrorLevels.FATAL,
          String.format(
              "Atleast one PackingList is expected, %d were detected", packingLists.size()));
    }

    if (imfErrorLogger.hasFatalErrors()) {
      return imfErrorLogger.getErrors();
    }

    List<PackingList> packingListObjectModels = new ArrayList<>();
    for (ResourceByteRangeProvider resourceByteRangeProvider : packingLists) {
      try {
        PackingList packingList = new PackingList(resourceByteRangeProvider);
        packingListObjectModels.add(packingList);
        imfErrorLogger.addAllErrors(packingList.getErrors());
      } catch (IMFException e) {
        imfErrorLogger.addAllErrors(e.getErrors());
        return imfErrorLogger.getErrors();
      }
    }
    List<UUID> assetUUIDsAssetMapList = new ArrayList<>();
    for (AssetMap.Asset asset : assetMapObjectModel.getAssetList()) {
      assetUUIDsAssetMapList.add(asset.getUUID());
    }

    /*
            //Sort the UUIDs in the AssetMap
            assetUUIDsAssetMapList.sort(new Comparator<UUID>() {
                                        @Override
                                        public int compare(UUID o1, UUID o2) {
                                            return o1.compareTo(o2);
                                        }
                                    });
    */

    /* Collect all the assets in all of the PKLs that are a part of this IMP delivery */
    List<UUID> assetUUIDsPackingList = new ArrayList<>();
    for (PackingList packingList : packingListObjectModels) {
      assetUUIDsPackingList.add(
          packingList
              .getUUID()); // PKL's UUID is also added to this list since that should be present in
                           // the AssetMap
      for (PackingList.Asset asset : packingList.getAssets()) {
        assetUUIDsPackingList.add(asset.getUUID());
      }
    }

    /*
            //Sort the UUIDs in the PackingList
            assetUUIDsPackingList.sort(new Comparator<UUID>() {
                @Override
                public int compare(UUID o1, UUID o2) {
                    return o1.compareTo(o2);
                }
            });
    */

    /* Check to see if all the Assets referenced in the PKL are also referenced by the Asset Map */
    Set<UUID> assetUUIDsAssetMapSet = new HashSet<>(assetUUIDsAssetMapList);
    Set<UUID> assetUUIDsPKLSet = new HashSet<>(assetUUIDsPackingList);

    StringBuilder unreferencedPKLAssetsUUIDs = new StringBuilder();
    for (UUID uuid : assetUUIDsPKLSet) {
      if (!assetUUIDsAssetMapSet.contains(uuid)) {
        unreferencedPKLAssetsUUIDs.append(uuid.toString());
        unreferencedPKLAssetsUUIDs.append(", ");
      }
    }

    if (!unreferencedPKLAssetsUUIDs.toString().isEmpty()) {
      imfErrorLogger.addError(
          IMFErrorLogger.IMFErrors.ErrorCodes.IMF_AM_ERROR,
          IMFErrorLogger.IMFErrors.ErrorLevels.FATAL,
          String.format(
              "The following UUID/s %s in the Packing list are not referenced by the AssetMap.",
              unreferencedPKLAssetsUUIDs.toString()));
      return imfErrorLogger.getErrors();
    }

    /* Check if all the assets in the AssetMap that are supposed to be PKLs have the same UUIDs as the PKLs themselves */
    Set<UUID> packingListAssetsUUIDsSet = new HashSet<>();
    for (AssetMap.Asset asset : assetMapObjectModel.getPackingListAssets()) {
      packingListAssetsUUIDsSet.add(asset.getUUID());
    }
    StringBuilder unreferencedPKLUUIDs = new StringBuilder();
    for (PackingList packingList : packingListObjectModels) {
      if (!packingListAssetsUUIDsSet.contains(packingList.getUUID())) {
        unreferencedPKLUUIDs.append(packingList.getUUID());
      }
    }
    if (!unreferencedPKLUUIDs.toString().isEmpty()) {
      imfErrorLogger.addError(
          IMFErrorLogger.IMFErrors.ErrorCodes.IMF_AM_ERROR,
          IMFErrorLogger.IMFErrors.ErrorLevels.FATAL,
          String.format(
              "The following Packing lists %s are not referenced in the AssetMap",
              unreferencedPKLUUIDs.toString()));
      return imfErrorLogger.getErrors();
    }
    return imfErrorLogger.getErrors();
  }