Java tutorial
/* * 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() { if (metadataXmlProvider.isCustomized()) { return; } // get locale from context or use fallback-locale, if can't get from context String locale = resolveContextLocale(); if (locale == null) { locale = resolveFallbackLocale(); } LocaleValidator.validateLocale(locale); metadataXmlProvider.setLocale(LocaleHelper.fromITunesLocale(locale)); } private void resolveLocaleAfterAudiomapInit() { if (!audioMapXmlProvider.isCustomized()) { return; } if (!metadataXmlProvider.isCustomized()) { metadataXmlProvider.setLocale(audioMapXmlProvider.getLocale()); return; } if (!Objects.equals(metadataXmlProvider.getLocale(), audioMapXmlProvider.getLocale())) { logger.warn("Locale set in metadata.xml doesn't match locale in audiomap.xml."); } } private String resolveContextLocale() { SequenceTemplateParameterContext sequenceContext = contextProvider.getSequenceContext(); return sequenceContext.getUuids(SequenceType.AUDIO).stream() .map(uuid -> new ContextInfoBuilder().setSequenceType(SequenceType.AUDIO).setSequenceUuid(uuid) .build()) .findFirst().filter(contextInfo -> sequenceContext.hasSequenceParameter(LANGUAGE, contextInfo)) .map(contextInfo -> sequenceContext.getParameterValue(LANGUAGE, contextInfo)).orElse(null); } private String resolveFallbackLocale() { String fallbackLocale = iTunesInputParameters.getCmdLineArgs().getFallbackLocale(); return StringUtils.isBlank(fallbackLocale) ? DEFAULT_LOCALE : fallbackLocale; } private void initMetadata() { File metadataFile = iTunesInputParameters.getMetadataFile(); String vendorId = iTunesInputParameters.getCmdLineArgs().getVendorId(); try { metadataXmlProvider = MetadataXmlProviderFactory.createProvider(metadataFile, resolvePackageType()); } catch (XmlParsingException | IOException e) { throw new RuntimeException(e); } metadataXmlProvider.updateVendorId(vendorId); } private ITunesPackageType resolvePackageType() { ITunesPackageType packageType = iTunesInputParameters.getCmdLineArgs().getPackageType(); if (packageType == null) { packageType = ITunesPackageType .fromName(contextProvider.getDestContext().getParameterValue(DEST_PARAM_VIDEO_SPECIFIED_FOR)); } return packageType != null ? packageType : ITunesPackageType.film; } private void initAudioMap() throws IOException, XmlParsingException { File audiomapFile = iTunesInputParameters.getAudiomapFile(); audioMapXmlProvider = new AudioMapXmlProvider(audiomapFile, contextProvider, LocaleHelper.toITunesLocale(metadataXmlProvider.getLocale())); audioMapXmlProvider.initAudio(); } // Audio processing private void buildAudiomapParameters() throws XmlParsingException, FileNotFoundException { int[] i = { 0 }; // add dynamic parameters // mainAudio contextProvider.getDynamicContext().addParameter(DYNAMIC_MAIN_AUDIO, audioMapXmlProvider.getMainAudioFileName(), true); // mainAudioTracks contextProvider.getDynamicContext().addParameter(DYNAMIC_MAIN_AUDIO_TRACKS, String.valueOf(audioMapXmlProvider.getMainAudioTracks())); // additionalAudioCount contextProvider.getDynamicContext().addParameter(DYNAMIC_ADDITIONAL_AUDIO_COUNT, String.valueOf(audioMapXmlProvider.getAdditionalAudioCount())); // additionalAudioTracks%{i} i[0] = 0; audioMapXmlProvider.getAdditionalAudioTracks().forEach((p) -> { contextProvider.getDynamicContext().addParameter(DYNAMIC_ADDITIONAL_AUDIO_TRACKS_PREFIX + i[0], p.toString()); i[0]++; }); // panParameter%{i} i[0] = 0; audioMapXmlProvider.getPanParameters().forEach((p) -> { contextProvider.getDynamicContext().addParameter(DYNAMIC_PAN_PARAMETER_PREFIX + i[0], p); i[0]++; }); // additionalAudio%{i} i[0] = 0; audioMapXmlProvider.getAdditionalAudioFileNames().forEach((p) -> { contextProvider.getDynamicContext().addParameter(DYNAMIC_ADDITIONAL_AUDIO_PREFIX + i[0], p); i[0]++; }); } private void buildSilenceExprParameters() { contextProvider.getSequenceContext().getUuids(SequenceType.AUDIO).forEach(this::buildSilenceExprParameter); } private void buildSilenceExprParameter(SequenceUUID seqUuid) { ContextInfo contextInfo = new ContextInfoBuilder().setSequenceType(SequenceType.AUDIO) .setSequenceUuid(seqUuid).build(); SequenceTemplateParameterContext sequenceContext = contextProvider.getSequenceContext(); String seqNum = sequenceContext.getParameterValue(SequenceContextParameters.NUM, contextInfo); String channelsNum = sequenceContext.getParameterValue(SequenceContextParameters.CHANNELS_NUM, contextInfo); contextProvider.getDynamicContext().addParameter(DYNAMIC_PARAM_AUDIO_SILENCE_EXPR_PREFIX + seqNum, computeSilenceExprParameter(channelsNum)); } private String computeSilenceExprParameter(String channelsNum) { return StringUtils.repeat("0:", Integer.parseInt(channelsNum)); } // Subtitle processing private void buildSubtitleInputParameters() { if (iTunesInputParameters.getSubFiles() == null) { return; } iTunesInputParameters.getSubFiles().forEach(this::buildSubtitleInputParameter); int count = iTunesInputParameters.getSubFiles().size(); contextProvider.getDynamicContext().addParameter(DYNAMIC_PARAM_SUBTITLE_TTML_COUNT, String.valueOf(count)); } private void buildSubtitleInputParameter(File subtitles) { int num = iTunesInputParameters.getSubFiles().indexOf(subtitles); contextProvider.getDynamicContext().addParameter(DYNAMIC_PARAM_SUBTITLE_TTML_PREFIX + num, subtitles.getAbsolutePath()); } // Asset processing // Additional assets (poster, trailer, chapters) private void processAdditionalAssets() throws XmlParsingException, IOException { processPoster(); processCaptions(); if (metadataXmlProvider.getDescriptor().getPackageType() == ITunesPackageType.film) { processTrailer(); processChapters(); } else { if (iTunesInputParameters.getTrailerFile() != null) { logger.info("A trailer is not required for TV package and can not be processed."); } if (iTunesInputParameters.getChaptersFile() != null) { logger.info("Chapters is not required for TV package and can not be processed."); } } } private void processPoster() throws IOException { File poster = iTunesInputParameters.getPosterFile(); if (poster == null) { return; } new PosterAssetProcessor(metadataXmlProvider, itmspDir) .setVendorId(iTunesInputParameters.getCmdLineArgs().getVendorId()) .setLocale(metadataXmlProvider.getLocale()).process(poster); } private void processChapters() throws XmlParsingException, IOException { File chaptersFile = iTunesInputParameters.getChaptersFile(); if (chaptersFile == null) { return; } ChaptersXmlProvider chaptersXmlProvider = new ChaptersXmlProvider(chaptersFile); metadataXmlProvider.appendChaptersTimeCode(chaptersXmlProvider.getTimecodeFormat()); ChapterAssetProcessor processor = new ChapterAssetProcessor(metadataXmlProvider, itmspDir) .setAspectRatio(getDestAspectRatio()); int i = 1; for (InputChapterItem chapter : chaptersXmlProvider.getChapters()) { processor.setInputChapterItem(chapter).setChapterIndex(i) .process(chaptersXmlProvider.getChapterFile(chapter)); i++; } } private void processTrailer() throws XmlParsingException, IOException { File trailer = iTunesInputParameters.getTrailerFile(); if (trailer == null) { return; } new TrailerAssetProcessor(metadataXmlProvider, itmspDir) .setVendorId(iTunesInputParameters.getCmdLineArgs().getVendorId()) .setFormat(getTrailerMediaInfo(trailer).getFormat()).setLocale(metadataXmlProvider.getLocale()) .process(trailer); } private void processCaptions() throws IOException { File captions = iTunesInputParameters.getCcFile(); if (captions == null) { if (Objects.equals(metadataXmlProvider.getLocale(), Locale.US)) { logger.warn("Closed captions are required for US deliveries."); } return; } new CaptionsAssetProcessor(metadataXmlProvider, itmspDir) .setVendorId(iTunesInputParameters.getCmdLineArgs().getVendorId()) // only US english captions currently allowed .setLocale(Locale.US).process(captions); } // Main source asset private void processMainSource() throws IOException { DynamicTemplateParameterContext dynamicContext = contextProvider.getDynamicContext(); File destSource = new File(contextProvider.getWorkingDir(), dynamicContext.getParameterValueAsString(DYNAMIC_PARAM_DEST_SOURCE)); new SourceAssetProcessor(metadataXmlProvider, itmspDir).setLocale( audioMapXmlProvider == null ? metadataXmlProvider.getLocale() : audioMapXmlProvider.getLocale()) .process(destSource); } // Additional audio assets private void processAdditionalAudios() { if (audioMapXmlProvider == null) { return; } audioMapXmlProvider.getAlternativesAudio().forEach(this::safeProcessAdditionalAudio); } private void safeProcessAdditionalAudio(AudioOption audioOption) { try { processAdditionalAudio(audioOption); } catch (IOException e) { throw new RuntimeException(e); } } private void processAdditionalAudio(AudioOption audioOption) throws IOException { File audio = new File(inputParameters.getWorkingDirFile(), audioOption.getFileName()); new AudioAssetProcessor(metadataXmlProvider, itmspDir) .setLocale(LocaleHelper.fromITunesLocale(audioOption.getLocale())).process(audio); } // Subtitles processing private void processSubtitles() { int count = iTunesInputParameters.getSubFiles() == null ? contextProvider.getSequenceContext().getSequenceCount(SequenceType.SUBTITLE) : iTunesInputParameters.getSubFiles().size(); if (count == 0) { return; } IntStream.range(0, count) .mapToObj(i -> contextProvider.getDynamicContext() .getParameterValueAsString(DYNAMIC_PARAM_SUBTITLE_ITT_PREFIX + i)) .map(fileName -> new File(inputParameters.getWorkingDirFile(), fileName)) .forEach(this::safeProcessSubtitles); } private void safeProcessSubtitles(File subtitles) { try { processSubtitles(subtitles); } catch (AssetValidationException e) { logger.warn("Subtitles processing failed: {}", e.getMessage()); } catch (IOException e) { throw new RuntimeException(e); } } private void processSubtitles(File subtitles) throws IOException { new SubtitlesAssetProcessor(metadataXmlProvider, itmspDir).setLocale(getLocaleFromTtml(subtitles)) .process(subtitles); } /** * Get iTunes locale based on lang extracted from subtitles itt ot ttml file. * * @param subtitles - a subtitles file (itt or ttml). * @return iTunes locale associated with input subtitles. * @throws IOException */ private Locale getLocaleFromTtml(File subtitles) throws IOException { try { TtEltype ttEl = XmlParser.parse(subtitles, new String[] { TTML_SCHEMA }, TTML_PACKAGES, TtEltype.class); return LocaleHelper.fromITunesLocale(ttEl.getLang()); } catch (XmlParsingException e) { throw new ConversionException("Can't parse subtitles"); } } /** * Get ffprobe media info for trailer. * For correct work <mediaInfoCommandOthers> must contain <mediaInfoCommand> with name 'trailer' in conversion.xml * * @param trailer - a trailer file. * @return media info for input trailer. * @throws XmlParsingException * @throws IOException */ private FfprobeType getTrailerMediaInfo(File trailer) throws XmlParsingException, IOException { try { return new SimpleMediaInfoBuilder(contextProvider, new ConversionEngine().getExecuteStrategyFactory()) .setCommandName(DYNAMIC_PARAM_TRAILER_MEDIAINFO_COMMAND) .setInputDynamicParam(DYNAMIC_PARAM_TRAILER_MEDIAINFO_INPUT) .setOutputDynamicParam(DYNAMIC_PARAM_TRAILER_MEDIAINFO_OUTPUT).build(trailer); } catch (MediaInfoException e) { throw new ConversionException("Conversion aborted cause of MediaInfo failures", e); } } /** * Get dest context aspect ratio and convert to fraction. * * @return fraction corresponds to destination aspect ratio */ private BigFraction getDestAspectRatio() { DestTemplateParameterContext destContext = contextProvider.getDestContext(); return ConversionHelper.parseAspectRatio(destContext.getParameterValue(ASPECT_RATIO)); } }