Java tutorial
// // Copyright (c) 2016 by The President and Fellows of Harvard College // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. You may obtain a copy of the License at: // http://www.apache.org/licenses/LICENSE-2.0 // Unless required by applicable law or agreed to in writing, software distributed under the License is // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permission and limitations under the License. // package edu.harvard.hul.ois.fits.tools.mediainfo; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.jdom.Attribute; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.xpath.XPath; import edu.harvard.hul.ois.fits.exceptions.FitsToolException; import edu.harvard.hul.ois.fits.tools.utils.XmlUtils; /** * This is a helper class for the MediaInfo tool class. The main purpose of * this class is post process and revise XML data where necessary when either * the MediaInfo API does not not return a value in its default XML output, * or we need to do do some normalization based upon things the XSLT process * doesn't handle or can't handle. */ public class MediaInfoUtil { public static final String CODEC_FAMILY_AVID_DNXHD = "Avid DNxHD"; public static final String CODEC_FAMILY_JPG2000 = "JPEG 2000"; public static final String CODEC_FAMILY_DV = "DV"; public static final String CODEC_FAMILY_UNCOMPRESSED = "Uncompressed"; public static final String CODEC_FAMILY_H_264 = "H.264"; public static final String CODEC_FAMILY_PRORES = "Apple ProRes"; public static final String QUICKTIME_MIMETYPE = "video/quicktime"; public static final String QUICKTIME_FORMAT = "Quicktime"; public static final String MPEG4_FORMAT = "MPEG-4"; @SuppressWarnings("serial") private static final Map<String, String> CODEC_4CC_TO_FAMILY = Collections .unmodifiableMap(new HashMap<String, String>() { { put("mjp2", CODEC_FAMILY_JPG2000); put("r210", CODEC_FAMILY_UNCOMPRESSED); put("v210", CODEC_FAMILY_UNCOMPRESSED); put("v216", CODEC_FAMILY_UNCOMPRESSED); put("2vuy", CODEC_FAMILY_UNCOMPRESSED); put("yuv2", CODEC_FAMILY_UNCOMPRESSED); put("v308", CODEC_FAMILY_UNCOMPRESSED); put("v408", CODEC_FAMILY_UNCOMPRESSED); put("v410", CODEC_FAMILY_UNCOMPRESSED); put("R10g", CODEC_FAMILY_UNCOMPRESSED); put("2Vuy", CODEC_FAMILY_UNCOMPRESSED); put("dv1p", CODEC_FAMILY_DV); put("dv1n", CODEC_FAMILY_DV); put("dv5n", CODEC_FAMILY_DV); put("dv5p", CODEC_FAMILY_DV); put("dvc", CODEC_FAMILY_DV); put("dvcp", CODEC_FAMILY_DV); put("dvh2", CODEC_FAMILY_DV); put("dvh3", CODEC_FAMILY_DV); put("dvh5", CODEC_FAMILY_DV); put("dvh6", CODEC_FAMILY_DV); put("dvhp", CODEC_FAMILY_DV); put("dvhq", CODEC_FAMILY_DV); put("dvp", CODEC_FAMILY_DV); put("dvpp", CODEC_FAMILY_DV); put("mp2v", "MPEG-2"); put("m2v1", "MPEG-2"); put("avc1", CODEC_FAMILY_H_264); put("h264", CODEC_FAMILY_H_264); put("v264", CODEC_FAMILY_H_264); put("x264", CODEC_FAMILY_H_264); put("AVdn", CODEC_FAMILY_AVID_DNXHD); put("ap4h", CODEC_FAMILY_PRORES); put("ap4x", CODEC_FAMILY_PRORES); put("apch", CODEC_FAMILY_PRORES); put("apcn", CODEC_FAMILY_PRORES); put("apco", CODEC_FAMILY_PRORES); put("apcs", CODEC_FAMILY_PRORES); } }); @SuppressWarnings("serial") private static final Map<String, String> CODEC_MXF_TO_FAMILY = Collections .unmodifiableMap(new HashMap<String, String>() { { put("0D010301020C0100-040102020301017F", CODEC_FAMILY_JPG2000); put("0D010301020C0100-0401020203010000", CODEC_FAMILY_JPG2000); put("0D010301020C0100-0401020203010100", CODEC_FAMILY_JPG2000); put("0D010301020C0100-0401020203010101", CODEC_FAMILY_JPG2000); put("0D010301020C0100-0401020203010103", CODEC_FAMILY_JPG2000); put("0D010301020C0100-0401020203010104", CODEC_FAMILY_JPG2000); put("0D01030102110100-0401020271010000", CODEC_FAMILY_AVID_DNXHD); put("0D01030102110100-0401020271030000", CODEC_FAMILY_AVID_DNXHD); put("0D01030102110100-0401020271040000", CODEC_FAMILY_AVID_DNXHD); put("0D01030102110100-0401020271070000", CODEC_FAMILY_AVID_DNXHD); put("0D01030102110100-0401020271080000", CODEC_FAMILY_AVID_DNXHD); put("0D01030102110100-0401020271090000", CODEC_FAMILY_AVID_DNXHD); put("0D01030102110100-0401020271100000", CODEC_FAMILY_AVID_DNXHD); put("0D01030102110100-0401020271110000", CODEC_FAMILY_AVID_DNXHD); put("0D01030102110100-0401020271120000", CODEC_FAMILY_AVID_DNXHD); put("0D01030102110100-0401020271130000", CODEC_FAMILY_AVID_DNXHD); put("0D01030102110100-0401020271160000", CODEC_FAMILY_AVID_DNXHD); put("0D01030102110100-0401020271180000", CODEC_FAMILY_AVID_DNXHD); put("0D01030102110100-0401020271190000", CODEC_FAMILY_AVID_DNXHD); put("0D01030102110100-04010202711A0000", CODEC_FAMILY_AVID_DNXHD); } }); private final static String TOOL_NAME = "MediaInfo"; private static final Logger logger = Logger.getLogger(MediaInfoUtil.class); private static MediaInfoNativeWrapper mi = null; protected MediaInfoUtil(MediaInfoNativeWrapper mi) { MediaInfoUtil.mi = mi; } protected Map<String, String> loadGeneralDataMap() { Map<String, String> generalValuesDataMap = new HashMap<String, String>(); generalValuesDataMap.put("dateModified", getMediaInfoString("File_Modified_Date", MediaInfoNativeWrapper.StreamKind.General)); generalValuesDataMap.put("generalBitRate", getMediaInfoString("BitRate", MediaInfoNativeWrapper.StreamKind.General)); generalValuesDataMap.put("timeCodeStart", getMediaInfoString("TimeCode_FirstFrame", MediaInfoNativeWrapper.StreamKind.Other)); generalValuesDataMap.put("generalDuration", getMediaInfoString("Duration", MediaInfoNativeWrapper.StreamKind.General)); generalValuesDataMap.put("generalFileSize", getMediaInfoString("FileSize", MediaInfoNativeWrapper.StreamKind.General)); // // // // TODO: bitRate_Maximum never seems to appear in MediaInfo // // in the general section // // // //String generalBitRateMax = getMediaInfoString( // // "BitRate_Maximum", MediaInfoNativeWrapper.StreamKind.General); // // // Empty ??? // String dateCreated = getMediaInfoString( // "File_Created_Date", MediaInfoNativeWrapper.StreamKind.General); // //"Created_Date", MediaInfoNativeWrapper.StreamKind.General); // // String dateEncoded = getMediaInfoString( // "Encoded_Date", MediaInfoNativeWrapper.StreamKind.General); // // // Empty ??? // String encodedLibraryVersion = getMediaInfoString( // //"File_Encoded_Library_Version", MediaInfoNativeWrapper.StreamKind.General); // "Encoded_Library_Version",MediaInfoNativeWrapper.StreamKind.General); return generalValuesDataMap; } /** * Helper method to add values to the map within the trackValuesMap * * @param trackValuesMap The map to be updated * @param id The key to trackValuesMap * @param key The key to the map contained within trackValuesMap * @param value The value to set in the map contained within trackValuesMap */ protected void addDataToMap(Map<String, Map<String, String>> trackValuesMap, String id, String key, String value) { if (!StringUtils.isEmpty(value)) { if (trackValuesMap.get(id) == null) trackValuesMap.put(id, new HashMap<String, String>()); trackValuesMap.get(id).put(key, value); } } protected Map<String, Map<String, String>> loadVideoDataMap(Map<String, String> generalValuesDataMap) { Map<String, Map<String, String>> videoTrackValuesMap = new HashMap<String, Map<String, String>>(); int numVideoTracks = mi.Count_Get(MediaInfoNativeWrapper.StreamKind.Video); for (int ndx = 0; ndx < numVideoTracks; ndx++) { String id = getMediaInfoString(ndx, "ID", MediaInfoNativeWrapper.StreamKind.Video); if (StringUtils.isEmpty(id)) { // If we only have one video track, we can retrieve data with // the index 0 if (numVideoTracks == 1) id = "0"; else { // TODO: Throw error and/or log? logger.error("Error retrieving the ID of the video track from MediaInfo: " + ndx); continue; } } String duration = getMediaInfoString(ndx, "Duration", MediaInfoNativeWrapper.StreamKind.Video); addDataToMap(videoTrackValuesMap, id, "duration", duration); String videoDelay = getMediaInfoString(ndx, "Delay", MediaInfoNativeWrapper.StreamKind.Video); addDataToMap(videoTrackValuesMap, id, "delay", videoDelay); String frameCount = getMediaInfoString(ndx, "FrameCount", MediaInfoNativeWrapper.StreamKind.Video); addDataToMap(videoTrackValuesMap, id, "frameCount", frameCount); String bitRate = getMediaInfoString(ndx, "BitRate", MediaInfoNativeWrapper.StreamKind.Video); addDataToMap(videoTrackValuesMap, id, "bitRate", bitRate); // // TODO: bitRate_Maximum never seems to appear in MediaInfo // in the video section // // bitRateMax and bitRateMode, are both used to update // bitRate, when bitRateMode is variable (VBR) // String bitRateMax = getMediaInfoString(ndx, "BitRate_Maximum", MediaInfoNativeWrapper.StreamKind.Video); addDataToMap(videoTrackValuesMap, id, "bitRateMax", bitRateMax); String bitRateMode = getMediaInfoString(ndx, "BitRate_Mode", MediaInfoNativeWrapper.StreamKind.Video); addDataToMap(videoTrackValuesMap, id, "bitRateMode", bitRateMode); // ---------------------------------------------------------------- String trackSize = getMediaInfoString(ndx, "StreamSize", MediaInfoNativeWrapper.StreamKind.Video); addDataToMap(videoTrackValuesMap, id, "trackSize", trackSize); // // TODO: frameRate_Maximum never seems to appear in MediaInfo // in the video section // // frameRateMax and frameRateMode, are both used to update // frameRate, when frameRateMode is variable (VFR) // String frameRateMax = getMediaInfoString(ndx, "FrameRate_Maximum", MediaInfoNativeWrapper.StreamKind.Video); addDataToMap(videoTrackValuesMap, id, "frameRateMax", frameRateMax); String frameRateMode = getMediaInfoString(ndx, "FrameRate_Mode", MediaInfoNativeWrapper.StreamKind.Video); addDataToMap(videoTrackValuesMap, id, "frameRateMode", frameRateMode); String frameRate = getMediaInfoString(ndx, "FrameRate", MediaInfoNativeWrapper.StreamKind.Video); addDataToMap(videoTrackValuesMap, id, "frameRate", frameRate); // ---------------------------------------------------------------- String scanningOrder = getMediaInfoString(ndx, "ScanOrder", MediaInfoNativeWrapper.StreamKind.Video); addDataToMap(videoTrackValuesMap, id, "scanningOrder", scanningOrder); // Additional Codec stuff: String codecId = getMediaInfoString(ndx, "CodecID", MediaInfoNativeWrapper.StreamKind.Video); addDataToMap(videoTrackValuesMap, id, "codecId", codecId); String codecCC = getMediaInfoString(ndx, "Codec/CC", MediaInfoNativeWrapper.StreamKind.Video); if (codecCC != null) { codecCC = codecCC.trim(); } addDataToMap(videoTrackValuesMap, id, "codecCC", codecCC); String codecName = getMediaInfoString(ndx, "Codec", MediaInfoNativeWrapper.StreamKind.Video); addDataToMap(videoTrackValuesMap, id, "codecName", codecName); String codecVersion = getMediaInfoString(ndx, "Codec_Profile", MediaInfoNativeWrapper.StreamKind.Video); addDataToMap(videoTrackValuesMap, id, "codecVersion", codecVersion); // By design - if the 4CC code is not one expected, then don't // generate a Code Family. // NOTE for all but MXF, codecCC is used for the 4CC code. // for MXF, we use the codecId. // String codecFamily = getMediaInfoString(ndx, "Codec/Family", // MediaInfoNativeWrapper.StreamKind.Video); String codecFamily = CODEC_4CC_TO_FAMILY.get(codecCC); // Now try for MXF files, if we haven't gotten a Codec Family if (codecFamily == null) { codecFamily = CODEC_MXF_TO_FAMILY.get(codecId); } // If we got a Codec Family, set it in the map if (codecFamily != null) { addDataToMap(videoTrackValuesMap, id, "codecFamily", codecFamily); } String codecInfo = getMediaInfoString(ndx, "Codec/Info", MediaInfoNativeWrapper.StreamKind.Video); addDataToMap(videoTrackValuesMap, id, "codecInfo", codecInfo); // NOTE: // formatProfile goes in the FITS XML general section, but // sometimes is missing from the MediaInfo general section and // present in video section. generalValuesDataMap.put("generalFormatProfileFromVideo", getMediaInfoString(ndx, "Format_Profile", MediaInfoNativeWrapper.StreamKind.Video)); } return videoTrackValuesMap; } protected Map<String, Map<String, String>> loadAudioDataMap() { Map<String, Map<String, String>> audioTrackValuesMap = new HashMap<String, Map<String, String>>(); int numAudioTracks = mi.Count_Get(MediaInfoNativeWrapper.StreamKind.Audio); for (int ndx = 0; ndx < numAudioTracks; ndx++) { String id = getMediaInfoString(ndx, "ID", MediaInfoNativeWrapper.StreamKind.Audio); if (StringUtils.isEmpty(id)) { // If we only have one audio track, we can retrieve data with // the index 0 if (numAudioTracks == 1) id = "0"; else { // TODO: Throw error and/or log? logger.error("Error retrieving the ID of the audio track from MediaInfo: " + ndx); continue; } } String audioDelay = getMediaInfoString(ndx, "Delay", MediaInfoNativeWrapper.StreamKind.Audio); addDataToMap(audioTrackValuesMap, id, "delay", audioDelay); String audioSamplesCount = getMediaInfoString(ndx, "SamplingCount", MediaInfoNativeWrapper.StreamKind.Audio); addDataToMap(audioTrackValuesMap, id, "numSamples", audioSamplesCount); String bitRate = getMediaInfoString(ndx, "BitRate", MediaInfoNativeWrapper.StreamKind.Audio); addDataToMap(audioTrackValuesMap, id, "bitRate", bitRate); // // TODO: bitRate_Maximum never seems to appear in MediaInfo // in the video section // // bitRateMax and bitRateMode, are both used to update // bitRate, when bitRateMode is variable (VBR) // String bitRateMax = getMediaInfoString(ndx, "BitRate_Maximum", MediaInfoNativeWrapper.StreamKind.Audio); addDataToMap(audioTrackValuesMap, id, "bitRateMax", bitRateMax); String bitRateMode = getMediaInfoString(ndx, "BitRate_Mode", MediaInfoNativeWrapper.StreamKind.Audio); addDataToMap(audioTrackValuesMap, id, "bitRateMode", bitRateMode); // ---------------------------------------------------------------- String duration = getMediaInfoString(ndx, "Duration", MediaInfoNativeWrapper.StreamKind.Audio); ; addDataToMap(audioTrackValuesMap, id, "duration", duration); String trackSize = getMediaInfoString(ndx, "StreamSize", MediaInfoNativeWrapper.StreamKind.Audio); addDataToMap(audioTrackValuesMap, id, "trackSize", trackSize); String samplingRate = getMediaInfoString(ndx, "SamplingRate", MediaInfoNativeWrapper.StreamKind.Audio); addDataToMap(audioTrackValuesMap, id, "samplingRate", samplingRate); String channels = getMediaInfoString(ndx, "Channels", MediaInfoNativeWrapper.StreamKind.Audio); addDataToMap(audioTrackValuesMap, id, "channels", channels); // Additional Codec stuff: String codecId = getMediaInfoString(ndx, "CodecID", MediaInfoNativeWrapper.StreamKind.Audio); addDataToMap(audioTrackValuesMap, id, "codecId", codecId); String codecFamily = getMediaInfoString(ndx, "Codec/Family", MediaInfoNativeWrapper.StreamKind.Audio); addDataToMap(audioTrackValuesMap, id, "codecFamily", codecFamily); //String codecInfo = getMediaInfoString(ndx, "Codec/Info", // MediaInfoNativeWrapper.StreamKind.Audio); //addDataToMap(audioTrackValuesMap, id, "codecInfo", codecInfo); } return audioTrackValuesMap; } /** * Revises the XML to include element data that was not returned either via * the MediaInfo XML or require revision for granularity using various data * maps * * @param fitsXml * @param videoTrackValuesMap * @param audioTrackValuesMap * @param generalValuesDataMap * @throws FitsToolException */ protected void reviseXmlData(Document fitsXml, Map<String, Map<String, String>> videoTrackValuesMap, Map<String, Map<String, String>> audioTrackValuesMap, Map<String, String> generalValuesDataMap) throws FitsToolException { try { reviseIdentification(fitsXml); XPath xpathFits = XPath.newInstance("//x:fits/x:metadata/x:video"); // NOTE: We need to add a namespace to xpath, because JDom XPath // does not support default namespaces. It requires you to add a // fake namespace to the XPath instance. xpathFits.addNamespace("x", fitsXml.getRootElement().getNamespaceURI()); Element videoElement = (Element) xpathFits.selectSingleNode(fitsXml); List<Element> elementList = videoElement.getContent(); // -------------------------------------------- // We need to normalize the format for files with MIME Type of // "video/quicktime" to Format of "Quicktime" in some cases String mimeType = null; String format = null; for (Element element : elementList) { if (element.getName().equals("mimeType")) { mimeType = element.getText(); } else if (element.getName().equals("format")) { format = element.getText(); } } if (mimeType != null && format != null) { if (mimeType.equals(QUICKTIME_MIMETYPE) && format.equals(MPEG4_FORMAT)) { generalValuesDataMap.put("format", QUICKTIME_FORMAT); } } // -------------------------------------------- for (Element element : elementList) { // First revise the general data right off the video element reviseGeneralSection(element, generalValuesDataMap); // Tracks if (element.getName().equals("track")) { String id = element.getAttributeValue("id"); // In some cases, the track ID returned by MediaInfo in the // XML used for XSLT transformation is not fully numerical, // and does NOT match the ID returned by calling the // MediaInfo API, so we need to strip off the additional // information contained in the ID that the XSLT transformation // // For example, if the track ID is 1666406348 (0x635357CC) // or even 189 (0xBD)-128 (0x80) // we need to remove all of the text between, and including // the parenthesis to match what is returned by MediaInfo // and set the ID to the 1st value if (!XmlUtils.isNumeric(id)) { id = convertIdToMediaInfoFormat(id); element.setAttribute("id", id); } // HACK: In some cases a track ID is not given, in those cases, use the index if (StringUtils.isEmpty(id)) { id = "0"; element.setAttribute("id", id); } // video track data if (videoTrackValuesMap.containsKey(id)) { reviseVideoSection(element, id, videoTrackValuesMap); } // audio track data if (audioTrackValuesMap.containsKey(id)) { reviseAudioSection(element, id, audioTrackValuesMap); } } // "track" } // for (Element element : elementList) { } catch (JDOMException e) { throw new FitsToolException("Error revising xml node values " + TOOL_NAME); } } private String convertIdToMediaInfoFormat(String id) { return id.replaceAll("\\s*\\([^\\)]*\\)\\s*", ""); } /** * Helper method to revise text in the identification element. * @param fitsXml * @throws JDOMException */ private void reviseIdentification(Document fitsXml) throws JDOMException { // // Get the format and formatProfile from the video track element // String videoFormat = ""; String videoFormatProfile = ""; XPath xpathFits = XPath.newInstance("//x:fits/x:metadata/x:video"); // NOTE: We need to add a namespace to xpath, because JDom XPath // does not support default namespaces. It requires you to add a // fake namespace to the XPath instance. xpathFits.addNamespace("x", fitsXml.getRootElement().getNamespaceURI()); Element videoElement = (Element) xpathFits.selectSingleNode(fitsXml); List<Element> elementList = videoElement.getContent(); for (Element element : elementList) { if (element.getName().equals("format")) { videoFormat = element.getText(); } else if (element.getName().equals("formatProfile")) { videoFormatProfile = element.getText(); } } // // Normalize the format and mimetype to "video/quicktime" and // Quicktime // XPath xpathFitsIdentity = XPath.newInstance("//x:fits/x:identification"); // NOTE: We need to add a namespace to xpath, because JDom XPath // does not support default namespaces. It requires you to add a // fake namespace to the XPath instance. xpathFitsIdentity.addNamespace("x", fitsXml.getRootElement().getNamespaceURI()); Element identityElement = (Element) xpathFitsIdentity.selectSingleNode(fitsXml); List<Element> elementIdentityList = identityElement.getContent(); for (Element elementIdentity : elementIdentityList) { Attribute formatAttrib = elementIdentity.getAttribute("format"); Attribute mimeAttrib = elementIdentity.getAttribute("mimetype"); // Only Reset the format and mimetype if they are the format and // formatProfile from the video section are the required types if (formatAttrib != null && mimeAttrib != null) { if (videoFormat.toUpperCase().contains(MPEG4_FORMAT) && videoFormatProfile.toUpperCase().equals(QUICKTIME_FORMAT.toUpperCase())) { formatAttrib.setValue(QUICKTIME_FORMAT); mimeAttrib.setValue(QUICKTIME_MIMETYPE); } else if (videoFormat.toUpperCase().contains(MPEG4_FORMAT) && mimeAttrib.getValue().equals(QUICKTIME_MIMETYPE)) { formatAttrib.setValue(QUICKTIME_FORMAT); } break; } } } /** * Helper method to revise text in the element passed in. If the element * is found to have an associated value in the map, the current text of * the element is revised. * * @param element jdom element to be revised * @param generalValuesDataMap map holding video elements to revise */ private void reviseGeneralSection(Element element, Map<String, String> generalValuesDataMap) { // General size if (element.getName().equals("size")) { String generalFileSize = generalValuesDataMap.get("generalFileSize"); if (!StringUtils.isEmpty(generalFileSize)) { element.setText(generalFileSize); } } // General Section dateModified else if (element.getName().equals("dateModified")) { String dateModified = generalValuesDataMap.get("dateModified"); if (!StringUtils.isEmpty(dateModified)) { element.setText(dateModified); } } // General Section timecodeStart else if (element.getName().equals("timecodeStart")) { String timeCodeStart = generalValuesDataMap.get("timeCodeStart"); if (!StringUtils.isEmpty(timeCodeStart)) { element.setText(timeCodeStart); } } // General Section bit rate else if (element.getName().equals("bitRate")) { String generalBitRate = generalValuesDataMap.get("generalBitRate"); if (!StringUtils.isEmpty(generalBitRate)) { element.setText(generalBitRate); } } // General Section duration else if (element.getName().equals("duration")) { String generalDuration = generalValuesDataMap.get("generalDuration"); if (!StringUtils.isEmpty(generalDuration)) { element.setText(generalDuration); } } // For now, the MD5 will be put in Ebucore manually AFTER FITS generates it. //// General Section MD5 //else if(element.getName().equals("filemd5")) { // String generalMd5 = generalValuesDataMap.get("md5Hash"); // if (!StringUtils.isEmpty(generalMd5)) { // element.setText(generalMd5); // } //} // General Section formatProfile - If missing, use value from // video section, which was set above else if (element.getName().equals("formatProfile")) { String formatProfileFromElement = element.getValue(); // if value for element is missing or empty, we need to update it if (StringUtils.isEmpty(formatProfileFromElement)) { String generalFormatProfileFromVideo = generalValuesDataMap.get("formatProfileFromElement"); if (generalFormatProfileFromVideo != null && generalFormatProfileFromVideo.length() > 0) { element.setText(generalFormatProfileFromVideo); } } } else if (element.getName().equals("format")) { String format = generalValuesDataMap.get("format"); if (!StringUtils.isEmpty(format)) { element.setText(format); } } } /** * Wrapper to the MediaInfo Get method to retrieve a MediaInfo String from * stream number 0, of info type text, using a named field. * * @param fieldName MediaInfo Field * @param streamType MediaInfoNativeWrapper.InfoKind * @return The data as a String */ private String getMediaInfoString(String fieldName, MediaInfoNativeWrapper.StreamKind streamType) { return getMediaInfoString(0, fieldName, streamType); } /** * Wrapper to the MediaInfo Get method to retrieve a MediaInfo String from * the stream number passed in, of info type text, using a named field. * * @param streamNumber MediaInfo stream number * @param fieldName MediaInfo Field * @param streamType MediaInfoNativeWrapper.InfoKind * @return The data as a String */ private String getMediaInfoString(int streamNumber, String fieldName, MediaInfoNativeWrapper.StreamKind streamType) { return mi.Get(streamType, streamNumber, fieldName, MediaInfoNativeWrapper.InfoKind.Text, MediaInfoNativeWrapper.InfoKind.Name); } private String convertToNormalizedData(String originalString, String labelToPreserve) { return StringUtils.deleteWhitespace(originalString.replace(labelToPreserve, "")) + labelToPreserve; } /** * Helper method to revise text in the video track element passed in. The * id is used as a key to a map of values. If the element's name is found * in the map, the value in the map is used to replace the current text of * the element. * * @param element jdom element to be revised * @param id track key to the map value * @param videoTrackValuesMap map holding video elements to revise */ private void reviseVideoSection(Element element, String id, Map<String, Map<String, String>> videoTrackValuesMap) { // Remove any empty elements // Right now it is only scanning order List<Element> elementsToRemove = new ArrayList<Element>(); List<Element> contents = element.getContent(); for (Element childElement : contents) { String name = childElement.getName(); // If the element name is one which has to be normalized, do so. ElementsToNormalize elementToNomalize = ElementsToNormalize.lookup(name); if (elementToNomalize != null) { String value = childElement.getValue(); if (!StringUtils.isEmpty(value)) { value = convertToNormalizedData(value, elementToNomalize.getUnits()); // If we got here, then we need to update the element's text if (!StringUtils.isEmpty(value)) childElement.setText(value); } continue; } // End normalize // If the "name" is not contained in the enum, continue VideoMethods videoValueEnum = VideoMethods.lookup(name); if (videoValueEnum == null) continue; String value = videoTrackValuesMap.get(id).get(videoValueEnum.getName()); switch (videoValueEnum) { // bitRate // 1) correct format // 2) set to bitRateMax, if mode is variable case bitRate: // NOTE: If the bitRateMode is Variable (VBR), set it to the value for // BitRateMax String bitRateMode = videoTrackValuesMap.get(id).get("bitRateMode"); if (!StringUtils.isEmpty(bitRateMode) && bitRateMode.toUpperCase().equals("VBR")) { String bitRateMax = videoTrackValuesMap.get(id).get("bitRateMax"); if (!StringUtils.isEmpty(bitRateMax)) { childElement.setText(bitRateMax); value = bitRateMax; } } break; case frameRate: // NOTE: If the bitRateMode is Variable (VBR), set it to the value for // BitRateMax String frameRateMode = videoTrackValuesMap.get(id).get("frameRateMode"); if (!StringUtils.isEmpty(frameRateMode) && frameRateMode.toUpperCase().equals("VFR")) { String frameRateMax = videoTrackValuesMap.get(id).get("frameRateMax"); if (!StringUtils.isEmpty(frameRateMax)) { childElement.setText(frameRateMax); value = frameRateMax; } } break; case scanningOrder: // TODO: Fix the below ... // It was noticed that might display BFF (Bottom Filed First), // instead of TFF (Top Field First) for interlaced videos // See: // https://mediaarea.net/en-us/MediaInfo/Support/FAQ // Section: // MediaInfo states video is Bottom Filed First when it is actually Top Field First if (!StringUtils.isEmpty(value)) childElement.setText(value); else { // Remove the child element if it is empty elementsToRemove.add(childElement); } break; default: break; } // switch // If we got here, then we need to update the element's text if (!StringUtils.isEmpty(value)) childElement.setText(value); } // childElement // Remove all elements marked as empty for (Element elementToRemove : elementsToRemove) { elementToRemove.getParent().removeContent(elementToRemove); } } /** * Helper method to revise text in the audio track element passed in. The * id is used as a key to a map of values. If the element's name is found * in the map, the value in the map is used to replace the current text of * the element. * * @param element jdom element to be revised * @param id track key to the map value * @param audioTrackValuesMap map holding audio elements to revise */ private void reviseAudioSection(Element element, String id, Map<String, Map<String, String>> audioTrackValuesMap) { List<Element> contents = element.getContent(); for (Element childElement : contents) { String name = childElement.getName(); // If the "name" is not contained in the enum, continue AudioMethods audioValueEnum = AudioMethods.lookup(name); if (audioValueEnum == null) continue; String value = audioTrackValuesMap.get(id).get(audioValueEnum.getName()); switch (audioValueEnum) { // bitRate // 1) correct format // 2) set to bitRateMax, if mode is variable case bitRate: // NOTE: If the bitRateMode is Variable (VBR), set it to the value for // BitRateMax String bitRateMode = audioTrackValuesMap.get(id).get("bitRateMode"); if (!StringUtils.isEmpty(bitRateMode) && bitRateMode.toUpperCase().equals("VBR")) { String bitRateMax = audioTrackValuesMap.get(id).get("bitRateMax"); if (!StringUtils.isEmpty(bitRateMax)) { childElement.setText(bitRateMax); value = bitRateMax; } } break; default: break; } // switch // If we got here, then we need to update the element's text if (!StringUtils.isEmpty(value)) childElement.setText(value); } // childElement } protected void removeEmptyElements(Document fitsXml) throws FitsToolException { // Remove any empty elements // Right now it is only scanning order List<Element> elementsToRemove = new ArrayList<Element>(); try { XPath xpathFits = XPath.newInstance("//x:fits/x:metadata/x:video"); // NOTE: We need to add a namespace to xpath, because JDom XPath // does not support default namespaces. It requires you to add a // fake namespace to the XPath instance. xpathFits.addNamespace("x", fitsXml.getRootElement().getNamespaceURI()); Element videoElement = (Element) xpathFits.selectSingleNode(fitsXml); List<Element> elementList = videoElement.getContent(); for (Element element : elementList) { // Tracks if (element.getName().equals("track")) { List<Element> contents = element.getContent(); for (Element childElement : contents) { String name = childElement.getName(); String value = childElement.getText(); if (StringUtils.isEmpty(value)) { elementsToRemove.add(childElement); } } } } } catch (JDOMException e) { throw new FitsToolException("Error revising xml node values " + TOOL_NAME); } // Remove all elements marked as empty for (Element elementToRemove : elementsToRemove) { elementToRemove.getParent().removeContent(elementToRemove); } } }