gt:         if (metadataXmlProvider.isCustomized()) {
context: /*
   * Copyright (C) 2016 Netflix, Inc.
   *
   *     This file is part of IMF Conversion Utility.
   *
   *     IMF Conversion Utility is free software: you can redistribute it and/or modify
   *     it under the terms of the GNU General Public License as published by
   *     the Free Software Foundation, either version 3 of the License, or
   *     (at your option) any later version.
   *
   *     IMF Conversion Utility is distributed in the hope that it will be useful,
   *     but WITHOUT ANY WARRANTY; without even the implied warranty of
   *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   *     GNU General Public License for more details.
   *
   *     You should have received a copy of the GNU General Public License
   *     along with IMF Conversion Utility.  If not, see <http://www.gnu.org/licenses/>.
   */
  package com.netflix.imfutility.itunes;
  
  import com.netflix.imfutility.AbstractFormatBuilder;
  import com.netflix.imfutility.ConversionException;
  import com.netflix.imfutility.conversion.ConversionEngine;
  import com.netflix.imfutility.conversion.templateParameter.ContextInfo;
  import com.netflix.imfutility.conversion.templateParameter.ContextInfoBuilder;
  import com.netflix.imfutility.conversion.templateParameter.context.DestTemplateParameterContext;
  import com.netflix.imfutility.conversion.templateParameter.context.DynamicTemplateParameterContext;
  import com.netflix.imfutility.conversion.templateParameter.context.SequenceTemplateParameterContext;
  import com.netflix.imfutility.conversion.templateParameter.context.parameters.DestContextParameters;
  import com.netflix.imfutility.conversion.templateParameter.context.parameters.SequenceContextParameters;
  import com.netflix.imfutility.cpl.uuid.SequenceUUID;
  import com.netflix.imfutility.generated.conversion.SequenceType;
  import com.netflix.imfutility.generated.itunes.chapters.InputChapterItem;
  import com.netflix.imfutility.generated.mediainfo.FfprobeType;
  import com.netflix.imfutility.itunes.asset.AssetValidationException;
  import com.netflix.imfutility.itunes.asset.AudioAssetProcessor;
  import com.netflix.imfutility.itunes.asset.CaptionsAssetProcessor;
  import com.netflix.imfutility.itunes.asset.ChapterAssetProcessor;
  import com.netflix.imfutility.itunes.asset.PosterAssetProcessor;
  import com.netflix.imfutility.itunes.asset.SourceAssetProcessor;
  import com.netflix.imfutility.itunes.asset.SubtitlesAssetProcessor;
  import com.netflix.imfutility.itunes.asset.TrailerAssetProcessor;
  import com.netflix.imfutility.itunes.audio.AudioMapXmlProvider;
  import com.netflix.imfutility.itunes.audio.AudioMapXmlProvider.AudioOption;
  import com.netflix.imfutility.itunes.chapters.ChaptersXmlProvider;
  import com.netflix.imfutility.itunes.destcontext.DestContextResolveStrategy;
  import com.netflix.imfutility.itunes.destcontext.InputDestContextResolveStrategy;
  import com.netflix.imfutility.itunes.destcontext.NameDestContextResolveStrategy;
  import com.netflix.imfutility.itunes.destcontext.check.DurationChecker;
  import com.netflix.imfutility.itunes.inputparameters.ITunesInputParameters;
  import com.netflix.imfutility.itunes.inputparameters.ITunesInputParametersValidator;
  import com.netflix.imfutility.itunes.locale.LocaleHelper;
  import com.netflix.imfutility.itunes.locale.LocaleValidator;
  import com.netflix.imfutility.itunes.mediainfo.SimpleMediaInfoBuilder;
  import com.netflix.imfutility.itunes.metadata.MetadataXmlProvider;
  import com.netflix.imfutility.itunes.metadata.factory.MetadataXmlProviderFactory;
  import com.netflix.imfutility.mediainfo.MediaInfoException;
  import com.netflix.imfutility.util.ConversionHelper;
  import com.netflix.imfutility.xml.XmlParser;
  import com.netflix.imfutility.xml.XmlParsingException;
  import com.netflix.imfutility.xsd.conversion.DestContextTypeMap;
  import com.netflix.imfutility.xsd.conversion.DestContextsTypeMap;
  import org.apache.commons.io.FileUtils;
  import org.apache.commons.lang3.StringUtils;
  import org.apache.commons.lang3.SystemUtils;
  import org.apache.commons.math3.fraction.BigFraction;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.w3.ns.ttml.TtEltype;
  
  import java.io.File;
  import java.io.FileNotFoundException;
  import java.io.IOException;
  import java.nio.file.Files;
  import java.util.Locale;
  import java.util.Objects;
  import java.util.stream.IntStream;
  
  import static com.netflix.imfutility.conversion.templateParameter.context.parameters.DestContextParameters.ASPECT_RATIO;
  import static com.netflix.imfutility.conversion.templateParameter.context.parameters.DestContextParameters.DAR;
  import static com.netflix.imfutility.conversion.templateParameter.context.parameters.DestContextParameters.FRAME_RATE;
  import static com.netflix.imfutility.conversion.templateParameter.context.parameters.DestContextParameters.INTERLACED;
  import static com.netflix.imfutility.conversion.templateParameter.context.parameters.SequenceContextParameters.LANGUAGE;
  import static com.netflix.imfutility.itunes.ITunesConversionConstants.DEFAULT_LOCALE;
  import static com.netflix.imfutility.itunes.ITunesConversionConstants.DEST_PARAM_VIDEO_IFRAME_RATE;
  import static com.netflix.imfutility.itunes.ITunesConversionConstants.DEST_PARAM_VIDEO_IS_DAR_SPECIFIED;
  import static com.netflix.imfutility.itunes.ITunesConversionConstants.DEST_PARAM_VIDEO_SPECIFIED_FOR;
  import static com.netflix.imfutility.itunes.ITunesConversionConstants.DYNAMIC_ADDITIONAL_AUDIO_COUNT;
  import static com.netflix.imfutility.itunes.ITunesConversionConstants.DYNAMIC_ADDITIONAL_AUDIO_PREFIX;
  import static com.netflix.imfutility.itunes.ITunesConversionConstants.DYNAMIC_ADDITIONAL_AUDIO_TRACKS_PREFIX;
  import static com.netflix.imfutility.itunes.ITunesConversionConstants.DYNAMIC_MAIN_AUDIO;
  import static com.netflix.imfutility.itunes.ITunesConversionConstants.DYNAMIC_MAIN_AUDIO_TRACKS;
  import static com.netflix.imfutility.itunes.ITunesConversionConstants.DYNAMIC_PAN_PARAMETER_PREFIX;
  import static com.netflix.imfutility.itunes.ITunesConversionConstants.DYNAMIC_PARAM_AUDIO_SILENCE_EXPR_PREFIX;
  import static com.netflix.imfutility.itunes.ITunesConversionConstants.DYNAMIC_PARAM_DEST_SOURCE;
  import static com.netflix.imfutility.itunes.ITunesConversionConstants.DYNAMIC_PARAM_IS_OSX;
  import static com.netflix.imfutility.itunes.ITunesConversionConstants.DYNAMIC_PARAM_OUTPUT_ITMSP;
  import static com.netflix.imfutility.itunes.ITunesConversionConstants.DYNAMIC_PARAM_SAME_FPS;
  import static com.netflix.imfutility.itunes.ITunesConversionConstants.DYNAMIC_PARAM_SUBTITLE_IS_CPL_SUB;
  import static com.netflix.imfutility.itunes.ITunesConversionConstants.DYNAMIC_PARAM_SUBTITLE_IS_INPUT_PARAM_SUB;
  import static com.netflix.imfutility.itunes.ITunesConversionConstants.DYNAMIC_PARAM_SUBTITLE_ITT_PREFIX;
  import static com.netflix.imfutility.itunes.ITunesConversionConstants.DYNAMIC_PARAM_SUBTITLE_TTML_COUNT;
  import static com.netflix.imfutility.itunes.ITunesConversionConstants.DYNAMIC_PARAM_SUBTITLE_TTML_PREFIX;
  import static com.netflix.imfutility.itunes.ITunesConversionConstants.DYNAMIC_PARAM_TRAILER_MEDIAINFO_COMMAND;
  import static com.netflix.imfutility.itunes.ITunesConversionConstants.DYNAMIC_PARAM_TRAILER_MEDIAINFO_INPUT;
  import static com.netflix.imfutility.itunes.ITunesConversionConstants.DYNAMIC_PARAM_TRAILER_MEDIAINFO_OUTPUT;
  import static com.netflix.imfutility.itunes.ITunesConversionConstants.DYNAMIC_PARAM_TTML_TO_ITT;
  import static com.netflix.imfutility.itunes.ITunesConversionConstants.DYNAMIC_PARAM_VENDOR_ID;
  import static com.netflix.imfutility.itunes.ITunesConversionConstants.TTML_PACKAGES;
  import static com.netflix.imfutility.itunes.ITunesConversionConstants.TTML_SCHEMA;
  
  /**
   * iTunes format builder (see {@link AbstractFormatBuilder}). It's used for conversion to iTunes format ('convert' iTunes mode).
   */
  public class ITunesFormatBuilder extends AbstractFormatBuilder {
  
      private final Logger logger = LoggerFactory.getLogger(ITunesFormatBuilder.class);
  
      private final ITunesInputParameters iTunesInputParameters;
      private File itmspDir;
      private MetadataXmlProvider metadataXmlProvider;
      private AudioMapXmlProvider audioMapXmlProvider;
  
      public ITunesFormatBuilder(ITunesInputParameters inputParameters) {
          super(new ITunesFormat(), inputParameters);
          this.iTunesInputParameters = inputParameters;
      }
  
      @Override
      protected void doValidateCmdLineArguments() {
          ITunesInputParametersValidator.validateCmdLineArguments(iTunesInputParameters);
      }
  
      @Override
      protected void doBuildDynamicContextPreCpl() {
          DynamicTemplateParameterContext dynamicContext = contextProvider.getDynamicContext();
  
          //  fill ttml-to-itt tool parameter
          dynamicContext.addParameter(DYNAMIC_PARAM_TTML_TO_ITT, iTunesInputParameters.getTtmlToIttTool());
  
          // fill vendorId parameter
          String vendorId = iTunesInputParameters.getCmdLineArgs().getVendorId();
          dynamicContext.addParameter(DYNAMIC_PARAM_VENDOR_ID, vendorId);
  
          // fill output package parameter to [vendor-id].itmsp
          String itmspName = vendorId + ".itmsp";
          dynamicContext.addParameter(DYNAMIC_PARAM_OUTPUT_ITMSP, itmspName, false);
  
          // fill destination main source path
          String destSource = itmspName + File.separator + vendorId + "-source.mov";
          dynamicContext.addParameter(DYNAMIC_PARAM_DEST_SOURCE, destSource, false);
  
          // fill subtitle input source
          boolean isCplSub = iTunesInputParameters.getSubFiles() == null;
          dynamicContext.addParameter(DYNAMIC_PARAM_SUBTITLE_IS_CPL_SUB, Boolean.toString(isCplSub));
          dynamicContext.addParameter(DYNAMIC_PARAM_SUBTITLE_IS_INPUT_PARAM_SUB, Boolean.toString(!isCplSub));
  
          setOSParameters();
      }
  
      @Override
      protected void doBuildDynamicContextPostCpl() throws IOException, XmlParsingException {
          // load, parse and validate metadata.xml (or generate default)
          initMetadata();
          resolveLocaleAfterMetadataInit();
  
          if (resolvePackageType() == ITunesPackageType.tv) {
              new DurationChecker(contextProvider).checkDuration(destContextMap);
          }
  
          // load, parse and validate audiomap.xml (or generate default)  if audio exist
          if (contextProvider.getSequenceContext().getSequenceCount(SequenceType.AUDIO) > 0) {
              initAudioMap();
  
              buildAudiomapParameters();
              buildSilenceExprParameters();
  
              resolveLocaleAfterAudiomapInit();
          }
  
          if (metadataXmlProvider.getDescriptor().getPackageType() == ITunesPackageType.film) {
              buildSubtitleInputParameters();
          } else { // TV package
              if (iTunesInputParameters.getSubFiles() != null && !iTunesInputParameters.getSubFiles().isEmpty()) {
                  logger.info("Subtitles will not be processed for TV package type.");
              }
          }
  
          resolveSameFpsParameter();
      }
  
      @Override
      protected void preConvert() throws IOException, XmlParsingException {
          // creating [vendor-id].itmsp output directory
          createItmspDir();
  
          // process additional assets (poster, chapters, trailer)
          processAdditionalAssets();
      }
  
      @Override
      protected void postConvert() throws IOException, XmlParsingException {
          // process main source
          processMainSource();
  
          // process additional audios (if exists)
          processAdditionalAudios();
  
          // process subtitles (if exists)
          if (metadataXmlProvider.getDescriptor().getPackageType() == ITunesPackageType.film) {
              processSubtitles();
          }
  
          metadataXmlProvider.saveMetadata(itmspDir);
      }
  
      @Override
      protected String getConversionConfiguration() {
          return conversionProvider.getConvertConfiguration().get(0);
      }
  
      @Override
      protected DestContextTypeMap getDestContextMap(DestContextsTypeMap destContexts) {
          logger.info("Resolving destination format...");
  
          String format = iTunesInputParameters.getCmdLineArgs().getFormat();
          ITunesPackageType packageType = iTunesInputParameters.getCmdLineArgs().getPackageType();
  
          DestContextResolveStrategy resolveStrategy = format != null
                  ? new NameDestContextResolveStrategy(format, packageType)
                  : new InputDestContextResolveStrategy(contextProvider, packageType);
          DestContextTypeMap destContextMap = resolveStrategy.resolveContext(destContexts);
  
          logger.info("Destination format defined by {}", format != null
                  ? "cmdline arg"
                  : "source video");
          logger.info("Destination format: {}", destContextMap.getName());
          logger.info("Resolved destination format: OK\n");
  
          return destContextMap;
      }
  
      @Override
      protected void doBuildDestContext() {
          DestTemplateParameterContext destContext = contextProvider.getDestContext();
  
          // set interlaced to false if not specified
          String interlaced = destContext.getParameterValue(INTERLACED);
          destContext.addParameter(INTERLACED, interlaced == null ? Boolean.FALSE.toString() : interlaced);
  
          // define is dar provided
          destContext.addParameter(DEST_PARAM_VIDEO_IS_DAR_SPECIFIED, Boolean.toString(destContext.getParameterValue(DAR) != null));
  
          // set frame rate for interlaced scan
          // for ffmpeg iFrameRate=frameRate*2
          // for prenc iFrameRate=frameRate
          BigFraction iFrameRate = ConversionHelper.parseEditRate(destContext.getParameterValue(FRAME_RATE));
          if (!SystemUtils.IS_OS_MAC_OSX) {
              iFrameRate = iFrameRate.multiply(2);
          }
          destContext.addParameter(DEST_PARAM_VIDEO_IFRAME_RATE, ConversionHelper.toREditRate(iFrameRate));
      }
  
      private void createItmspDir() throws IOException {
          DynamicTemplateParameterContext dynamicContext = contextProvider.getDynamicContext();
          String itmspName = dynamicContext.getParameterValueAsString(DYNAMIC_PARAM_OUTPUT_ITMSP);
  
          logger.info("Creating {} output directory...", itmspName);
  
          itmspDir = new File(contextProvider.getWorkingDir(), itmspName);
          logger.info("Itmsp output directory: {}", itmspDir);
          if (Files.exists(itmspDir.toPath())) {
              FileUtils.cleanDirectory(itmspDir);
          } else {
              if (!itmspDir.mkdir()) {
                  throw new ConversionException(String.format(
                          "Couldn't create %s output directory!", itmspName));
              }
          }
  
          logger.info("Created {} output directory: OK\n", itmspName);
      }
  
      private void setOSParameters() {
          DynamicTemplateParameterContext dynamicContext = contextProvider.getDynamicContext();
          dynamicContext.addParameter(DYNAMIC_PARAM_IS_OSX, Boolean.toString(SystemUtils.IS_OS_MAC_OSX));
      }
  
      private void resolveSameFpsParameter() {
          ContextInfo contextInfo = new ContextInfoBuilder()
                  .setSequenceType(SequenceType.VIDEO)
                  .setSequenceUuid(getVideoSequenceUUID())
                  .build();
  
          BigFraction seqFrameRate = ConversionHelper.parseEditRate(contextProvider.getSequenceContext().getParameterValue(
                  SequenceContextParameters.FRAME_RATE,
                  contextInfo));
          BigFraction destFrameRate = ConversionHelper.parseEditRate(contextProvider.getDestContext().getParameterValue(
                  DestContextParameters.FRAME_RATE));
  
          DynamicTemplateParameterContext dynamicContext = contextProvider.getDynamicContext();
          dynamicContext.addParameter(DYNAMIC_PARAM_SAME_FPS, Boolean.toString(seqFrameRate.equals(destFrameRate)));
      }
  
      private SequenceUUID getVideoSequenceUUID() {
          return contextProvider.getSequenceContext().getUuids(SequenceType.VIDEO).stream()
                  .findFirst()
                  .orElseThrow(() -> new ConversionException("Source must have at least one video sequence"));
      }
  
      // Locales resolving
  
      private void resolveLocaleAfterMetadataInit() {
  