package net.pms.dlna;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.pms.configuration.FormatConfiguration;
import net.pms.configuration.RendererConfiguration;
import net.pms.dlna.MediaInfo.StreamType;
import net.pms.formats.Format;
import net.pms.formats.v2.SubtitleType;
import net.pms.image.ImageFormat;
import net.pms.image.ImagesUtil;
import net.pms.image.ImagesUtil.ScaleType;
import net.pms.util.FileUtil;
import net.pms.util.UnknownFormatException;
import org.apache.commons.codec.binary.Base64;
import static org.apache.commons.lang3.StringUtils.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LibMediaInfoParser {
private static final Logger LOGGER = LoggerFactory.getLogger(LibMediaInfoParser.class);
// Regular expression to parse a 4 digit year number from a string
private static final String YEAR_REGEX = ".*([\\d]{4}).*";
// Pattern to parse the year from a string
private static final Pattern yearPattern = Pattern.compile(YEAR_REGEX);
private static MediaInfo MI;
static {
MI = new MediaInfo();
if (MI.isValid()) {
MI.Option("Internet", "No"); // avoid MediaInfoLib to try to connect to an Internet server for availability of newer software, anonymous statistics and retrieving information about a file
MI.Option("Complete", "1");
MI.Option("Language", "raw");
MI.Option("File_TestContinuousFileNames", "0");
LOGGER.debug("Option 'File_TestContinuousFileNames' is set to: " + MI.Option("File_TestContinuousFileNames_Get"));
MI.Option("ParseSpeed", "0");
LOGGER.debug("Option 'ParseSpeed' is set to: " + MI.Option("ParseSpeed_Get"));
// LOGGER.debug(MI.Option("Info_Parameters_CSV")); // It can be used to export all current MediaInfo parameters
}
}
public static boolean isValid() {
return MI.isValid();
}
public static void close() {
try {
MI.finalize();
} catch (Throwable e) {
LOGGER.debug("Caught exception", e);
}
}
@Deprecated
public synchronized static void parse(DLNAMediaInfo media, InputFile inputFile, int type) {
parse(media, inputFile, type, null);
}
/**
* Parse media via MediaInfo.
*/
public synchronized static void parse(DLNAMediaInfo media, InputFile inputFile, int type, RendererConfiguration renderer) {
File file = inputFile.getFile();
if (!media.isMediaparsed() && file != null && MI.isValid() && MI.Open(file.getAbsolutePath()) > 0) {
// try {
StreamType general = StreamType.General;
StreamType video = StreamType.Video;
StreamType audio = StreamType.Audio;
StreamType image = StreamType.Image;
StreamType text = StreamType.Text;
DLNAMediaAudio currentAudioTrack = new DLNAMediaAudio();
DLNAMediaSubtitle currentSubTrack;
media.setSize(file.length());
String value;
// set General
getFormat(general, media, currentAudioTrack, MI.Get(general, 0, "Format"), file);
getFormat(general, media, currentAudioTrack, MI.Get(general, 0, "CodecID").trim(), file);
media.setDuration(getDuration(MI.Get(general, 0, "Duration/String1")));
media.setBitrate(getBitrate(MI.Get(general, 0, "OverallBitRate")));
value = MI.Get(general, 0, "Cover_Data");
if (!value.isEmpty()) {
try {
media.setThumb(DLNAThumbnail.toThumbnail(
new Base64().decode(value.getBytes(StandardCharsets.US_ASCII)),
640,
480,
ScaleType.MAX,
ImageFormat.SOURCE,
false
));
} catch (EOFException e) {
LOGGER.debug(
"Error reading \"{}\" thumbnail from MediaInfo: Unexpected end of stream, probably corrupt or read error.",
file.getName()
);
} catch (UnknownFormatException e) {
LOGGER.debug("Could not read \"{}\" thumbnail from MediaInfo: {}", file.getName(), e.getMessage());
} catch (IOException e) {
LOGGER.error("Error reading \"{}\" thumbnail from MediaInfo: {}", file.getName(), e.getMessage());
LOGGER.trace("", e);
}
}
value = MI.Get(general, 0, "Title");
if (!value.isEmpty()) {
media.setFileTitleFromMetadata(value);
}
// set Video
media.setVideoTrackCount(MI.Count_Get(video));
if (media.getVideoTrackCount() > 0) {
for (int i = 0; i < media.getVideoTrackCount(); i++) {
// check for DXSA and DXSB subtitles (subs in video format)
if (MI.Get(video, i, "Title").startsWith("Subtitle")) {
currentSubTrack = new DLNAMediaSubtitle();
// First attempt to detect subtitle track format
currentSubTrack.setType(SubtitleType.valueOfLibMediaInfoCodec(MI.Get(video, i, "Format")));
// Second attempt to detect subtitle track format (CodecID usually is more accurate)
currentSubTrack.setType(SubtitleType.valueOfLibMediaInfoCodec(MI.Get(video, i, "CodecID")));
currentSubTrack.setId(media.getSubtitleTracksList().size());
addSub(currentSubTrack, media);
} else {
getFormat(video, media, currentAudioTrack, MI.Get(video, i, "Format"), file);
getFormat(video, media, currentAudioTrack, MI.Get(video, i, "Format_Version"), file);
getFormat(video, media, currentAudioTrack, MI.Get(video, i, "CodecID"), file);
media.setWidth(getPixelValue(MI.Get(video, i, "Width")));
media.setHeight(getPixelValue(MI.Get(video, i, "Height")));
media.setMatrixCoefficients(MI.Get(video, i, "matrix_coefficients"));
media.setStereoscopy(MI.Get(video, i, "MultiView_Layout"));
media.setAspectRatioContainer(MI.Get(video, i, "DisplayAspectRatio/String"));
media.setAspectRatioVideoTrack(MI.Get(video, i, "DisplayAspectRatio_Original/String"));
media.setFrameRate(getFPSValue(MI.Get(video, i, "FrameRate")));
media.setFrameRateOriginal(MI.Get(video, i, "FrameRate_Original"));
media.setFrameRateMode(getFrameRateModeValue(MI.Get(video, i, "FrameRate_Mode")));
media.setFrameRateModeRaw(MI.Get(video, i, "FrameRate_Mode"));
media.setReferenceFrameCount(getReferenceFrameCount(MI.Get(video, i, "Format_Settings_RefFrames/String")));
media.setVideoTrackTitleFromMetadata(MI.Get(video, i, "Title"));
value = MI.Get(video, i, "Format_Settings_QPel");
if (!value.isEmpty()) {
media.putExtra(FormatConfiguration.MI_QPEL, value);
}
value = MI.Get(video, i, "Format_Settings_GMC");
if (!value.isEmpty()) {
media.putExtra(FormatConfiguration.MI_GMC, value);
}
value = MI.Get(video, i, "Format_Settings_GOP");
if (!value.isEmpty()) {
media.putExtra(FormatConfiguration.MI_GOP, value);
}
media.setMuxingMode(MI.Get(video, i, "MuxingMode"));
if (!media.isEncrypted()) {
media.setEncrypted("encrypted".equals(MI.Get(video, i, "Encryption")));
}
value = MI.Get(video, i, "BitDepth");
if (!value.isEmpty()) {
try {
media.setVideoBitDepth(Integer.parseInt(value));
} catch (NumberFormatException nfe) {
LOGGER.debug("Could not parse bits per sample \"" + value + "\"");
}
}
}
value = MI.Get(video, i, "Format_Profile");
if (!value.isEmpty() && media.getCodecV() != null && media.getCodecV().equals(FormatConfiguration.H264)) {
media.setAvcLevel(getAvcLevel(value));
}
}
}
// set Audio
int audioTracks = MI.Count_Get(audio);
if (audioTracks > 0) {
for (int i = 0; i < audioTracks; i++) {
currentAudioTrack = new DLNAMediaAudio();
getFormat(audio, media, currentAudioTrack, MI.Get(audio, i, "Format"), file);
getFormat(audio, media, currentAudioTrack, MI.Get(audio, i, "Format_Version"), file);
getFormat(audio, media, currentAudioTrack, MI.Get(audio, i, "Format_Profile"), file);
getFormat(audio, media, currentAudioTrack, MI.Get(audio, i, "CodecID"), file);
currentAudioTrack.setLang(getLang(MI.Get(audio, i, "Language/String")));
currentAudioTrack.setAudioTrackTitleFromMetadata((MI.Get(audio, i, "Title")).trim());
currentAudioTrack.getAudioProperties().setNumberOfChannels(MI.Get(audio, i, "Channel(s)"));
currentAudioTrack.setSampleFrequency(getSampleFrequency(MI.Get(audio, i, "SamplingRate")));
currentAudioTrack.setBitRate(getBitrate(MI.Get(audio, i, "BitRate")));
currentAudioTrack.setSongname(MI.Get(general, 0, "Track"));
if (
renderer.isPrependTrackNumbers() &&
currentAudioTrack.getTrack() > 0 &&
currentAudioTrack.getSongname() != null &&
currentAudioTrack.getSongname().length() > 0
) {
currentAudioTrack.setSongname(currentAudioTrack.getTrack() + ": " + currentAudioTrack.getSongname());
}
currentAudioTrack.setAlbum(MI.Get(general, 0, "Album"));
currentAudioTrack.setArtist(MI.Get(general, 0, "Performer"));
currentAudioTrack.setGenre(MI.Get(general, 0, "Genre"));
// Try to parse the year from the stored date
String recordedDate = MI.Get(general, 0, "Recorded_Date");
Matcher matcher = yearPattern.matcher(recordedDate);
if (matcher.matches()) {
try {
currentAudioTrack.setYear(Integer.parseInt(matcher.group(1)));
} catch (NumberFormatException nfe) {
LOGGER.debug("Could not parse year from recorded date \"" + recordedDate + "\"");
}
}
// Special check for OGM: MediaInfo reports specific Audio/Subs IDs (0xn) while mencoder does not
value = MI.Get(audio, i, "ID/String");
if (!value.isEmpty()) {
if (value.contains("(0x") && !FormatConfiguration.OGG.equals(media.getContainer())) {
currentAudioTrack.setId(getSpecificID(value));
} else {
currentAudioTrack.setId(media.getAudioTracksList().size());
}
}
value = MI.Get(general, i, "Track/Position");
if (!value.isEmpty()) {
try {
currentAudioTrack.setTrack(Integer.parseInt(value));
} catch (NumberFormatException nfe) {
LOGGER.debug("Could not parse track \"" + value + "\"");
}
}
value = MI.Get(audio, i, "BitDepth");
if (!value.isEmpty()) {
try {
currentAudioTrack.setBitsperSample(Integer.parseInt(value));
} catch (NumberFormatException nfe) {
LOGGER.debug("Could not parse bits per sample \"" + value + "\"");
}
}
addAudio(currentAudioTrack, media);
}
}
// set Image
media.setImageCount(MI.Count_Get(image));
if (media.getImageCount() > 0 || type == Format.IMAGE) {
boolean parseByMediainfo = false;
// For images use our own parser instead of MediaInfo which doesn't provide enough information
try {
ImagesUtil.parseImage(file, media);
// This is a little hack. MediaInfo only recognizes a few image formats
// so that MI.Count_Get(image) might return 0 even if there is an image.
if (media.getImageCount() == 0) {
media.setImageCount(1);
}
} catch (IOException e) {
if (media.getImageCount() > 0) {
LOGGER.debug("Error parsing image ({}), switching to MediaInfo: {}", file.getAbsolutePath(), e.getMessage());
LOGGER.trace("", e);
parseByMediainfo = true;
} else {
LOGGER.warn("Image parsing for \"{}\" failed both with MediaInfo and internally: {}", file.getAbsolutePath(), e.getMessage());
LOGGER.trace("", e);
media.setImageCount(1);
}
}
if (parseByMediainfo) {
getFormat(image, media, currentAudioTrack, MI.Get(image, 0, "Format"), file);
media.setWidth(getPixelValue(MI.Get(image, 0, "Width")));
media.setHeight(getPixelValue(MI.Get(image, 0, "Height")));
}
}
// set Subs in text format
int subTracks = MI.Count_Get(text);
if (subTracks > 0) {
for (int i = 0; i < subTracks; i++) {
currentSubTrack = new DLNAMediaSubtitle();
currentSubTrack.setType(SubtitleType.valueOfLibMediaInfoCodec(MI.Get(text, i, "Format")));
currentSubTrack.setType(SubtitleType.valueOfLibMediaInfoCodec(MI.Get(text, i, "CodecID")));
currentSubTrack.setLang(getLang(MI.Get(text, i, "Language/String")));
currentSubTrack.setSubtitlesTrackTitleFromMetadata((MI.Get(text, i, "Title")).trim());
// Special check for OGM: MediaInfo reports specific Audio/Subs IDs (0xn) while mencoder does not
value = MI.Get(text, i, "ID/String");
if (!value.isEmpty()) {
if (value.contains("(0x") && !FormatConfiguration.OGG.equals(media.getContainer())) {
currentSubTrack.setId(getSpecificID(value));
} else {
currentSubTrack.setId(media.getSubtitleTracksList().size());
}
}
addSub(currentSubTrack, media);
}
}
/**
* Native M4A/AAC streaming bug: http://www.ps3mediaserver.org/forum/viewtopic.php?f=6&t=16691
* Some M4A files have generic codec id "mp42" instead of "M4A". For example:
*
* General
* Format : MPEG-4
* Format profile : Apple audio with iTunes info
* Codec ID : M4A
*
* vs
*
* General
* Format : MPEG-4
* Format profile : Base Media / Version 2
* Codec ID : mp42
*
* As a workaround, set container type to AAC for MP4 files that have a single AAC audio track and no video.
*/
if (
FormatConfiguration.MP4.equals(media.getContainer()) &&
isBlank(media.getCodecV()) &&
media.getAudioTracksList() != null &&
media.getAudioTracksList().size() == 1 &&
FormatConfiguration.AAC.equals(media.getAudioTracksList().get(0).getCodecA())
) {
media.setContainer(FormatConfiguration.AAC);
}
/**
* Recognize 3D layout from the filename.
*
* First we check for our custom naming convention, for which the filename
* either has to start with "3DSBSLF" or "3DSBSRF" for side-by-side layout
* or "3DOULF" or "3DOURF" for over-under layout.
* For anaglyph 3D video can be used following combination:
* 3DARCG anaglyph_red_cyan_gray
* 3DARCH anaglyph_red_cyan_half_color
* 3DARCC anaglyph_red_cyan_color
* 3DARCD anaglyph_red_cyan_dubois
* 3DAGMG anaglyph_green_magenta_gray
* 3DAGMH anaglyph_green_magenta_half_color
* 3DAGMC anaglyph_green_magenta_color
* 3DAGMD anaglyph_green_magenta_dubois
* 3DAYBG anaglyph_yellow_blue_gray
* 3DAYBH anaglyph_yellow_blue_half_color
* 3DAYBC anaglyph_yellow_blue_color
* 3DAYBD anaglyph_yellow_blue_dubois
*
* Next we check for common naming conventions.
*/
if (!media.is3d()) {
String upperCaseFileName = file.getName().toUpperCase();
if (upperCaseFileName.startsWith("3DSBS")) {
LOGGER.debug("3D format SBS detected for " + file.getName());
media.setStereoscopy(file.getName().substring(2, 7));
} else if (upperCaseFileName.startsWith("3DOU")) {
LOGGER.debug("3D format OU detected for " + file.getName());
media.setStereoscopy(file.getName().substring(2, 6));
} else if (upperCaseFileName.startsWith("3DA")) {
LOGGER.debug("3D format Anaglyph detected for " + file.getName());
media.setStereoscopy(file.getName().substring(2, 6));
} else if (upperCaseFileName.matches(".*[\\s\\.](H-|H|HALF-|HALF.)SBS[\\s\\.].*")) {
LOGGER.debug("3D format HSBS detected for " + file.getName());
media.setStereoscopy("half side by side (left eye first)");
} else if (upperCaseFileName.matches(".*[\\s\\.](H-|H|HALF-|HALF.)(OU|TB)[\\s\\.].*")) {
LOGGER.debug("3D format HOU detected for " + file.getName());
media.setStereoscopy("half top-bottom (left eye first)");
} else if (upperCaseFileName.matches(".*[\\s\\.]SBS[\\s\\.].*")) {
if (media.getWidth() > 1920) {
LOGGER.debug("3D format SBS detected for " + file.getName());
media.setStereoscopy("side by side (left eye first)");
} else {
LOGGER.debug("3D format HSBS detected based on width for " + file.getName());
media.setStereoscopy("half side by side (left eye first)");
}
} else if (upperCaseFileName.matches(".*[\\s\\.](OU|TB)[\\s\\.].*")) {
if (media.getHeight() > 1080) {
LOGGER.debug("3D format OU detected for " + file.getName());
media.setStereoscopy("top-bottom (left eye first)");
} else {
LOGGER.debug("3D format HOU detected based on height for " + file.getName());
media.setStereoscopy("half top-bottom (left eye first)");
}
}
}
media.postParse(type, inputFile);
// } catch (Exception e) {
// LOGGER.error("Error in MediaInfo parsing:", e);
// } finally {
MI.Close();
if (media.getContainer() == null) {
media.setContainer(DLNAMediaLang.UND);
}
if (media.getCodecV() == null) {
media.setCodecV(DLNAMediaLang.UND);
}
media.setMediaparsed(true);
// }
}
}
public static void addAudio(DLNAMediaAudio currentAudioTrack, DLNAMediaInfo media) {
if (isBlank(currentAudioTrack.getLang())) {
currentAudioTrack.setLang(DLNAMediaLang.UND);
}
if (isBlank(currentAudioTrack.getCodecA())) {
currentAudioTrack.setCodecA(DLNAMediaLang.UND);
}
media.getAudioTracksList().add(currentAudioTrack);
}
public static void addSub(DLNAMediaSubtitle currentSubTrack, DLNAMediaInfo media) {
if (currentSubTrack.getType() == SubtitleType.UNSUPPORTED) {
return;
}
if (isBlank(currentSubTrack.getLang())) {
currentSubTrack.setLang(DLNAMediaLang.UND);
}
media.getSubtitleTracksList().add(currentSubTrack);
}
@Deprecated
// FIXME this is obsolete (replaced by the private method below) and isn't called from anywhere outside this class
public static void getFormat(MediaInfo.StreamType streamType, DLNAMediaInfo media, DLNAMediaAudio audio, String value) {
getFormat(streamType, media, audio, value, null);
}
/**
* Sends the correct information to media.setContainer(),
* media.setCodecV() or media.setCodecA, depending on streamType.
*
* TODO: Rename to something like setFormat - this is not a getter.
*
* @param streamType
* @param media
* @param audio
* @param value
* @param file
*/
private static void getFormat(StreamType streamType, DLNAMediaInfo media, DLNAMediaAudio audio, String value, File file) {
if (value.isEmpty()) {
return;
}
value = value.toLowerCase();
String format = null;
if (isBlank(value)) {
return;
} else if (value.startsWith("3g2")) {
format = FormatConfiguration.THREEGPP2;
} else if (value.startsWith("3gp")) {
format = FormatConfiguration.THREEGPP;
} else if (value.startsWith("matroska")) {
format = FormatConfiguration.MATROSKA;
} else if (value.equals("avi") || value.equals("opendml")) {
format = FormatConfiguration.AVI;
} else if (value.startsWith("cinepack")) {
format = FormatConfiguration.CINEPACK;
} else if (value.startsWith("flash")) {
format = FormatConfiguration.FLV;
} else if (value.equals("webm")) {
format = FormatConfiguration.WEBM;
} else if (value.equals("qt") || value.equals("quicktime")) {
format = FormatConfiguration.MOV;
} else if (value.equals("isom") || value.startsWith("mp4") || value.equals("20") || value.equals("m4v") || value.startsWith("mpeg-4")) {
format = FormatConfiguration.MP4;
} else if (value.contains("mpeg-ps")) {
format = FormatConfiguration.MPEGPS;
} else if (value.contains("mpeg-ts") || value.equals("bdav")) {
format = FormatConfiguration.MPEGTS;
} else if (value.contains("aiff")) {
format = FormatConfiguration.AIFF;
} else if (value.startsWith("atmos") || value.equals("131")) {
format = FormatConfiguration.ATMOS;
} else if (value.contains("ogg")) {
format = FormatConfiguration.OGG;
} else if (value.contains("opus")) {
format = FormatConfiguration.OPUS;
} else if (value.contains("realmedia") || value.startsWith("rv")) {
format = FormatConfiguration.RM;
} else if (value.startsWith("theora")) {
format = FormatConfiguration.THEORA;
} else if (value.contains("windows media") || value.equals("wmv1") || value.equals("wmv2") || value.equals("wmv7") || value.equals("wmv8")) {
format = FormatConfiguration.WMV;
} else if (value.contains("mjpg") || value.contains("m-jpeg")) {
format = FormatConfiguration.MJPEG;
} else if (value.startsWith("h263") || value.startsWith("s263") || value.startsWith("u263")) {
format = FormatConfiguration.H263;
} else if (value.startsWith("avc") || value.startsWith("h264")) {
format = FormatConfiguration.H264;
} else if (value.startsWith("hevc")) {
format = FormatConfiguration.H265;
} else if (value.startsWith("sorenson")) {
format = FormatConfiguration.SORENSON;
} else if (value.startsWith("vp6")) {
format = FormatConfiguration.VP6;
} else if (value.startsWith("vp7")) {
format = FormatConfiguration.VP7;
} else if (value.startsWith("vp8")) {
format = FormatConfiguration.VP8;
} else if (value.startsWith("vp9")) {
format = FormatConfiguration.VP9;
} else if (value.contains("xvid")) {
format = FormatConfiguration.MP4;
} else if (value.contains("mjpg") || value.contains("m-jpeg")) {
format = FormatConfiguration.MJPEG;
} else if (value.contains("div") || value.contains("dx")) {
format = FormatConfiguration.DIVX;
} else if (value.matches("(?i)(dv)|(cdv.?)|(dc25)|(dcap)|(dvc.?)|(dvs.?)|(dvrs)|(dv25)|(dv50)|(dvan)|(dvh.?)|(dvis)|(dvl.?)|(dvnm)|(dvp.?)|(mdvf)|(pdvc)|(r411)|(r420)|(sdcc)|(sl25)|(sl50)|(sldv)")) {
format = FormatConfiguration.DV;
} else if (value.contains("mpeg video")) {
format = FormatConfiguration.MPEG2;
} else if (value.equals("vc-1") || value.equals("vc1") || value.equals("wvc1") || value.equals("wmv3") || value.equals("wmv9") || value.equals("wmva")) {
format = FormatConfiguration.VC1;
} else if (value.startsWith("version 1")) {
if (media.getCodecV() != null && media.getCodecV().equals(FormatConfiguration.MPEG2) && audio.getCodecA() == null) {
format = FormatConfiguration.MPEG1;
}
} else if (value.equals("au") || value.equals("uLaw/AU Audio File")) {
format = FormatConfiguration.AU;
} else if (value.equals("layer 3")) {
if (audio.getCodecA() != null && audio.getCodecA().equals(FormatConfiguration.MPA)) {
format = FormatConfiguration.MP3;
// special case:
if (media.getContainer() != null && media.getContainer().equals(FormatConfiguration.MPA)) {
media.setContainer(FormatConfiguration.MP3);
}
}
} else if (value.equals("layer 2") && audio.getCodecA() != null && media.getContainer() != null &&
audio.getCodecA().equals(FormatConfiguration.MPA) && media.getContainer().equals(FormatConfiguration.MPA)) {
// only for audio files:
format = FormatConfiguration.MP2;
media.setContainer(FormatConfiguration.MP2);
} else if (value.equals ("ma") || value.equals("ma / core") || value.equals("134")) {
if (audio.getCodecA() != null && audio.getCodecA().equals(FormatConfiguration.DTS)) {
format = FormatConfiguration.DTSHD;
}
} else if (value.equals("vorbis") || value.equals("a_vorbis")) {
format = FormatConfiguration.VORBIS;
} else if (value.equals("adts")) {
format = FormatConfiguration.ADTS;
} else if (value.startsWith("amr")) {
format = FormatConfiguration.AMR;
} else if (value.equals("ac-3") || value.equals("a_ac3") || value.equals("2000")) {
format = FormatConfiguration.AC3;
} else if (value.startsWith("cook")) {
format = FormatConfiguration.COOK;
} else if (value.startsWith("qdesign")) {
format = FormatConfiguration.QDESIGN;
} else if (value.equals("realaudio lossless")) {
format = FormatConfiguration.REALAUDIO_LOSSLESS;
} else if (value.equals("e-ac-3")) {
format = FormatConfiguration.EAC3;
} else if (value.contains("truehd")) {
format = FormatConfiguration.TRUEHD;
} else if (value.equals("tta")) {
format = FormatConfiguration.TTA;
} else if (value.equals("55") || value.equals("a_mpeg/l3")) {
format = FormatConfiguration.MP3;
} else if (value.equals("lc")) {
format = FormatConfiguration.AAC;
} else if (value.contains("he-aac")) {
format = FormatConfiguration.AAC_HE;
} else if (value.startsWith("adpcm")) {
format = FormatConfiguration.ADPCM;
} else if (value.equals("pcm") || (value.equals("1") && (audio.getCodecA() == null || !audio.getCodecA().equals(FormatConfiguration.DTS)))) {
format = FormatConfiguration.LPCM;
} else if (value.equals("alac")) {
format = FormatConfiguration.ALAC;
} else if (value.equals("wave")) {
format = FormatConfiguration.WAV;
} else if (value.equals("shorten")) {
format = FormatConfiguration.SHORTEN;
} else if (
(
value.equals("dts") ||
value.equals("a_dts") ||
value.equals("8")
) &&
(
audio.getCodecA() == null ||
!audio.getCodecA().equals(FormatConfiguration.DTSHD)
)
) {
format = FormatConfiguration.DTS;
} else if (value.equals("mpeg audio")) {
format = FormatConfiguration.MPA;
} else if (value.startsWith("wma")) {
format = FormatConfiguration.WMA;
if (media.getCodecV() == null) {
media.setContainer(format);
}
} else if (
streamType == StreamType.Audio && media.getCodecV() == null && audio != null && audio.getCodecA() != null &&
audio.getCodecA() == FormatConfiguration.WMA &&
(value.equals("161") || value.equals("162") || value.equals("163") || value.equalsIgnoreCase("A"))
) {
if (value.equals("161")) {
format = FormatConfiguration.WMA;
} else if (value.equals("162")) {
format = FormatConfiguration.WMAPRO;
} else if (value.equals("163")) {
format = FormatConfiguration.WMALOSSLESS;
} else if (value.equalsIgnoreCase("A")) {
format = FormatConfiguration.WMAVOICE;
}
} else if (value.equals("flac")) {
format = FormatConfiguration.FLAC;
} else if (value.equals("monkey's audio")) {
format = FormatConfiguration.MONKEYS_AUDIO;
} else if (value.contains("musepack")) {
format = FormatConfiguration.MPC;
} else if (value.contains("wavpack")) {
format = FormatConfiguration.WAVPACK;
} else if (value.contains("mlp")) {
format = FormatConfiguration.MLP;
} else if (value.contains("atrac3")) {
format = FormatConfiguration.ATRAC;
if (media.getCodecV() == null) {
media.setContainer(FormatConfiguration.ATRAC);
}
} else if (value.equals("jpeg")) {
format = FormatConfiguration.JPG;
} else if (value.equals("png")) {
format = FormatConfiguration.PNG;
} else if (value.equals("gif")) {
format = FormatConfiguration.GIF;
} else if (value.equals("bitmap")) {
format = FormatConfiguration.BMP;
} else if (value.equals("tiff")) {
format = FormatConfiguration.TIFF;
} else if (containsIgnoreCase(value, "@l") && streamType == StreamType.Video) {
media.setAvcLevel(getAvcLevel(value));
media.setH264Profile(getAvcProfile(value));
}
if (format != null) {
if (streamType == StreamType.General) {
media.setContainer(format);
} else if (streamType == StreamType.Video) {
media.setCodecV(format);
} else if (streamType == StreamType.Audio) {
audio.setCodecA(format);
}
// format not found so set container type based on the file extension. It will be overwritten when the correct type will be found
} else if (streamType == StreamType.General && media.getContainer() == null) {
media.setContainer(FileUtil.getExtension(file.getAbsolutePath()));
}
}
public static int getPixelValue(String value) {
if (value.contains("pixel")) {
value = value.substring(0, value.indexOf("pixel"));
}
value = value.trim();
// Value can look like "512 / 512" at this point
if (value.contains("/")) {
value = value.substring(0, value.indexOf('/')).trim();
}
int pixels = Integer.parseInt(value);
return pixels;
}
/**
* @param value {@code Format_Settings_RefFrames/String} value to parse.
* @return reference frame count or {@code -1} if could not parse.
*/
public static byte getReferenceFrameCount(String value) {
if (isBlank(value)) {
return -1;
}
try {
// Values like "16 frame3"
return Byte.parseByte(substringBefore(value, " "));
} catch (NumberFormatException ex) {
// Not parsed
LOGGER.warn("Could not parse ReferenceFrameCount value {}." , value);
LOGGER.warn("Exception: ", ex);
return -1;
}
}
/**
* @param value {@code Format_Profile} value to parse.
* @return AVC level or {@code null} if could not parse.
*/
public static String getAvcLevel(String value) {
// Example values:
// High@L3.0
// High@L4.0
// High@L4.1
final String avcLevel = substringAfterLast(lowerCase(value), "@l");
if (isNotBlank(avcLevel)) {
return avcLevel;
} else {
LOGGER.warn("Could not parse AvcLevel value {}." , value);
return null;
}
}
public static String getAvcProfile(String value) {
String profile = substringBefore(lowerCase(value), "@l");
if (isNotBlank(profile)) {
return profile;
} else {
LOGGER.warn("Could not parse AvcProfile value {}." , value);
return null;
}
}
public static int getBitrate(String value) {
if (value.isEmpty()) {
return 0;
}
if (value.contains("/")) {
value = value.substring(0, value.indexOf('/')).trim();
}
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
LOGGER.trace("Could not parse bitrate \"{}\": ", value, e.getMessage());
return 0;
}
}
public static int getSpecificID(String value) {
// If ID is given as 'streamID-substreamID' use the second (which is hopefully unique).
// For example in vob audio ID can be '189 (0xBD)-32 (0x80)' and text ID '189 (0xBD)-128 (0x20)'
int end = value.lastIndexOf("(0x");
if (end > -1) {
int start = value.lastIndexOf('-') + 1;
value = value.substring(start > end ? 0 : start, end);
}
value = value.trim();
int id = Integer.parseInt(value);
return id;
}
public static String getSampleFrequency(String value) {
/**
* Some tracks show several values, e.g. "48000 / 48000 / 24000" for HE-AAC
* We store only the first value
*/
if (value.indexOf('/') > -1) {
value = value.substring(0, value.indexOf('/'));
}
if (value.contains("khz")) {
value = value.substring(0, value.indexOf("khz"));
}
value = value.trim();
return value;
}
public static String getFPSValue(String value) {
if (value.contains("fps")) {
value = value.substring(0, value.indexOf("fps"));
}
value = value.trim();
return value;
}
public static String getFrameRateModeValue(String value) {
if (value.indexOf('/') > -1) {
value = value.substring(0, value.indexOf('/'));
}
value = value.trim();
return value;
}
public static String getLang(String value) {
if (value.indexOf('(') > -1) {
value = value.substring(0, value.indexOf('('));
}
if (value.indexOf('/') > -1) {
value = value.substring(0, value.indexOf('/'));
}
value = value.trim();
return value;
}
/**
* @deprecated use trim()
*/
@Deprecated
public static String getFlavor(String value) {
value = value.trim();
return value;
}
private static double getDuration(String value) {
int h = 0, m = 0, s = 0;
StringTokenizer st = new StringTokenizer(value, " ");
while (st.hasMoreTokens()) {
String token = st.nextToken();
int hl = token.indexOf('h');
if (hl > -1) {
h = Integer.parseInt(token.substring(0, hl).trim());
}
int mnl = token.indexOf("mn");
if (mnl > -1) {
m = Integer.parseInt(token.substring(0, mnl).trim());
}
int msl = token.indexOf("ms");
if (msl == -1) {
// Only check if ms was not found
int sl = token.indexOf('s');
if (sl > -1) {
s = Integer.parseInt(token.substring(0, sl).trim());
}
}
}
return (h * 3600) + (m * 60) + s;
}
}