org.yamj.core.tools.MetadataTools.java Source code

Java tutorial

Introduction

Here is the source code for org.yamj.core.tools.MetadataTools.java

Source

/*
 *      Copyright (c) 2004-2015 YAMJ Members
 *      https://github.com/organizations/YAMJ/teams
 *
 *      This file is part of the Yet Another Media Jukebox (YAMJ).
 *
 *      YAMJ 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
 *      any later version.
 *
 *      YAMJ 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 YAMJ.  If not, see <http://www.gnu.org/licenses/>.
 *
 *      Web: https://github.com/YAMJ/yamj-v3
 *
 */
package org.yamj.core.tools;

import com.ibm.icu.text.Transliterator;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.lang3.text.WordUtils;
import org.pojava.datetime.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yamj.common.tools.PropertyTools;
import org.yamj.common.tools.StringTools;
import org.yamj.core.database.model.AbstractMetadata;
import org.yamj.core.database.model.MediaFile;
import org.yamj.core.database.model.VideoData;

public final class MetadataTools {

    private static final Logger LOG = LoggerFactory.getLogger(MetadataTools.class);

    private static final Pattern CLEAN_STRING_PATTERN = Pattern.compile("[^a-zA-Z0-9\\-\\(\\)]");
    private static final char[] CLEAN_DELIMITERS = new char[] { '.', ' ', '_', '-' };
    private static final Pattern DATE_COUNTRY = Pattern.compile("(.*)(\\s*?\\(\\w*\\))");
    private static final Pattern YEAR_PATTERN = Pattern.compile("(?:.*?)(\\d{4})(?:.*?)");
    private static final Pattern LASTNAME_PATTERN = Pattern
            .compile("((?:(?:d[aeiu]|de la|mac|zu|v[ao]n(?: de[nr])?) *)?[^ ]+) *(.*)");

    private static final String MPPA_RATED = "Rated";
    private static final long KB = 1024;
    private static final long MB = KB * KB;
    private static final long GB = KB * KB * KB;
    private static final Map<Character, Character> CHAR_REPLACEMENT_MAP = new HashMap<>();

    private static final DecimalFormat FILESIZE_FORMAT_0;
    private static final DecimalFormat FILESIZE_FORMAT_1;
    private static final DecimalFormat FILESIZE_FORMAT_2;
    private static final boolean IDENT_TRANSLITERATE;
    private static final boolean IDENT_CLEAN;
    private static final Transliterator TRANSLITERATOR;

    private static final String dateFormat = PropertyTools.getProperty("yamj3.date.format", "yyyy-MM-dd");

    private MetadataTools() {
        throw new UnsupportedOperationException("Class cannot be instantiated");
    }

    static {
        // identifier cleaning
        IDENT_TRANSLITERATE = PropertyTools.getBooleanProperty("yamj3.identifier.transliterate", Boolean.FALSE);
        IDENT_CLEAN = PropertyTools.getBooleanProperty("yamj3.identifier.clean", Boolean.TRUE);

        // Populate the charReplacementMap
        String temp = PropertyTools.getProperty("indexing.character.replacement", "");
        StringTokenizer tokenizer = new StringTokenizer(temp, ",");
        while (tokenizer.hasMoreTokens()) {
            String token = tokenizer.nextToken();
            int idx = token.indexOf('-');
            if (idx > 0) {
                String key = token.substring(0, idx).trim();
                String value = token.substring(idx + 1).trim();
                if (key.length() == 1 && value.length() == 1) {
                    CHAR_REPLACEMENT_MAP.put(Character.valueOf(key.charAt(0)), Character.valueOf(value.charAt(0)));
                }
            }
        }

        DecimalFormatSymbols symbols = new DecimalFormatSymbols();
        // Use the "." as a decimal format separator, ignoring localisation
        symbols.setDecimalSeparator('.');
        FILESIZE_FORMAT_0 = new DecimalFormat("0", symbols);
        FILESIZE_FORMAT_1 = new DecimalFormat("0.#", symbols);
        FILESIZE_FORMAT_2 = new DecimalFormat("0.##", symbols);

        // create a new transliterator
        TRANSLITERATOR = Transliterator.getInstance("NFD; Any-Latin; NFC");
    }

    /**
     * Check the passed character against the replacement list.
     *
     * @param charToReplace
     * @return
     */
    public static String characterMapReplacement(Character charToReplace) {
        Character tempC = CHAR_REPLACEMENT_MAP.get(charToReplace);
        if (tempC == null) {
            return charToReplace.toString();
        }
        return tempC.toString();
    }

    /**
     * Change all the characters in a string to the safe replacements
     *
     * @param input
     * @return
     */
    public static String stringMapReplacement(String input) {
        if (input == null || CHAR_REPLACEMENT_MAP.isEmpty()) {
            return input;
        }

        Character tempC;
        StringBuilder sb = new StringBuilder();

        for (Character c : input.toCharArray()) {
            tempC = CHAR_REPLACEMENT_MAP.get(c);
            if (tempC == null) {
                sb.append(c);
            } else {
                sb.append(tempC);
            }
        }
        return sb.toString();
    }

    /**
     * Format the date into short format
     *
     * @param date
     * @return
     */
    public static String formatDateShort(Date date) {
        if (date == null) {
            return null;
        }

        SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
        return sdf.format(date);
    }

    /**
     * Format the date into short format
     *
     * @param date
     * @return
     */
    public static String formatDateLong(Date date) {
        if (date == null) {
            return null;
        }

        SimpleDateFormat sdf = new SimpleDateFormat(dateFormat + " HH:mm:ss");
        return sdf.format(date);
    }

    /**
     * Format the file size
     *
     * @param fileSize
     * @return
     */
    public static String formatFileSize(long fileSize) {
        String returnSize;
        if (fileSize < 0) {
            returnSize = null;
        } else if (fileSize < KB) {
            returnSize = fileSize + " Bytes";
        } else {
            String appendText;
            long divider;

            // resolve text to append and divider
            if (fileSize < MB) {
                appendText = " KB";
                divider = KB;
            } else if (fileSize < GB) {
                appendText = " MB";
                divider = MB;
            } else {
                appendText = " GB";
                divider = GB;
            }

            // resolve decimal format
            DecimalFormat df;
            long checker = (fileSize / divider);
            if (checker < 10) {
                df = FILESIZE_FORMAT_2;
            } else if (checker < 100) {
                df = FILESIZE_FORMAT_1;
            } else {
                df = FILESIZE_FORMAT_0;
            }

            // build string
            returnSize = df.format((float) fileSize / (float) divider) + appendText;
        }

        return returnSize;
    }

    /**
     * Format the duration passed as ?h ?m format
     *
     * @param runtime duration in seconds
     * @return
     */
    public static String formatRuntime(final int runtime) {
        if (runtime < 0) {
            return null;
        }
        int fixed = runtime / 1000;
        StringBuilder returnString = new StringBuilder();

        int nbHours = fixed / 3600;
        if (nbHours != 0) {
            returnString.append(nbHours).append("h");
        }

        int nbMinutes = (fixed - (nbHours * 3600)) / 60;
        if (nbMinutes != 0) {
            if (nbHours != 0) {
                returnString.append(" ");
            }
            returnString.append(nbMinutes).append("m");
        }

        return returnString.toString();
    }

    public static int toYear(String string) {
        int year;

        if (StringUtils.isNotBlank(string) && StringUtils.isNumeric(string)) {
            try {
                year = Integer.parseInt(string);
            } catch (NumberFormatException ex) {
                year = -1;
            }
        } else {
            year = -1;
        }
        return year;
    }

    /**
     * Take a string runtime in various formats and try to output this in minutes
     *
     * @param runtime
     * @return
     */
    public static int processRuntime(String runtime) {
        return processRuntime(runtime, -1);
    }

    /**
     * Take a string runtime in various formats and try to output this in minutes
     *
     * @param runtime
     * @param defaultValue
     * @return
     */
    public static int processRuntime(String runtime, int defaultValue) {
        if (StringUtils.isBlank(runtime)) {
            // No string to parse
            return defaultValue;
        }

        int returnValue;
        // See if we can convert this to a number and assume it's correct if we can
        returnValue = NumberUtils.toInt(runtime, defaultValue);

        if (returnValue < 0) {
            // This is for the format xx(hour/hr/min)yy(min), e.g. 1h30, 90mins, 1h30m
            Pattern hrmnPattern = Pattern.compile("(?i)(\\d+)(\\D*)(\\d*)(.*?)");

            Matcher matcher = hrmnPattern.matcher(runtime);
            if (matcher.find()) {
                int first = NumberUtils.toInt(matcher.group(1), -1);
                String divide = matcher.group(2);
                int second = NumberUtils.toInt(matcher.group(3), -1);

                if (first > -1 && second > -1) {
                    returnValue = (first > -1 ? first * 60 : 0) + (second > -1 ? second : 0);
                } else if (StringUtils.isBlank(divide)) {
                    // No divider value, so assume this is a straight minute value
                    returnValue = first;
                } else if (second > -1 && StringUtils.isNotBlank(divide)) {
                    // this is xx(text) so we need to work out what the (text) is
                    if (divide.toLowerCase().contains("h")) {
                        // Assume it is a form of "hours", so convert to minutes
                        returnValue = first * 60;
                    } else {
                        // Assume it's a form of "minutes"
                        returnValue = first;
                    }
                }
            }
        }

        return returnValue;
    }

    /**
     * Convert a string to date to
     *
     * @param dateToParse
     * @return
     */
    public static Date parseToDate(String dateToParse) {
        Date parsedDate = null;

        String parseDate = StringUtils.normalizeSpace(dateToParse);
        if (StringUtils.isNotBlank(parseDate)) {
            try {
                DateTime dateTime;
                if (parseDate.length() == 4 && StringUtils.isNumeric(parseDate)) {
                    // assume just the year an append "-01-01" to the end
                    dateTime = new DateTime(parseDate + "-01-01");
                } else {
                    // look for the date as "dd MMMM yyyy (Country)" and remove the country
                    Matcher m = DATE_COUNTRY.matcher(dateToParse);
                    if (m.find()) {
                        parseDate = m.group(1);
                    }
                    dateTime = new DateTime(parseDate);
                }
                parsedDate = dateTime.toDate();
            } catch (Exception ex) {
                LOG.debug("Failed to parse date '{}', error: {}", dateToParse, ex.getMessage());
                LOG.trace("Error", ex);
            }
        }

        return parsedDate;
    }

    /**
     * Get the year as string from given date.
     *
     * @param date
     * @return
     */
    public static String extractYearAsString(Date date) {
        if (date == null) {
            return null;
        }

        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        return String.valueOf(cal.get(Calendar.YEAR));
    }

    /**
     * Get the year as integer from given date.
     *
     * @param date
     * @return
     */
    public static int extractYearAsInt(Date date) {
        if (date == null) {
            return -1;
        }

        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        return cal.get(Calendar.YEAR);
    }

    /**
     * Locate a 4 digit year in a date string.
     *
     * @param date
     * @return
     */
    public static int extractYearAsInt(String date) {
        if (StringUtils.isBlank(date)) {
            return -1;
        }
        if (StringUtils.isNumeric(date) && (date.length() == 4)) {
            return NumberUtils.toInt(date, -1);
        }

        int year = -1;
        Matcher m = YEAR_PATTERN.matcher(date);
        if (m.find()) {
            year = NumberUtils.toInt(m.group(1));
        }

        return year;
    }

    /**
     * Parse a string value and convert it into an integer rating
     *
     * The rating should be between 0 and 10 inclusive.<br/>
     * Invalid values or values less than 0 will return -1
     *
     * @param rating the converted rating or -1 if there was an error
     * @return
     */
    public static int parseRating(String rating) {
        if (StringUtils.isBlank(rating)) {
            // Rating isn't valid, so skip it
            return -1;
        }
        return parseRating(NumberUtils.toFloat(rating.replace(',', '.'), -1));
    }

    /**
     * Parse a float rating into an integer
     *
     * The rating should be between 0 and 10 inclusive.<br/>
     * Invalid values or values less than 0 will return -1
     *
     * @param rating the converted rating or -1 if there was an error
     * @return
     */
    public static int parseRating(float rating) {
        if (rating > 0.0f) {
            if (rating <= 10.0f) {
                return Math.round(rating * 10f);
            }
            return Math.round(rating * 1f);
        }
        return -1;
    }

    /**
     * Checks if all media files have been watched.
     *
     * @param videoData
     * @param apiCall
     * @return
     */
    public static boolean allMediaFilesWatched(VideoData videoData, boolean apiCall) {
        boolean onlyExtras = true;
        for (MediaFile stored : videoData.getMediaFiles()) {
            if (stored.isExtra()) {
                continue;
            }
            onlyExtras = false;
            if (apiCall) {
                if (!stored.isWatchedApi()) {
                    return false;
                }
            } else if (!stored.isWatchedFile()) {
                return false;
            }
        }

        return !onlyExtras;
    }

    /**
     * Remove alternate name from role
     *
     * @param role
     * @return
     */
    public static String fixActorRole(final String role) {
        if (role == null) {
            return null;
        }
        String fixed = role;

        // (as = alternate name)
        int idx = StringUtils.indexOfIgnoreCase(fixed, "(as ");
        if (idx > 0) {
            fixed = fixed.substring(0, idx);
        }

        // double characters
        idx = StringUtils.indexOf(fixed, "/");
        if (idx > 0) {
            List<String> characters = StringTools.splitList(fixed, "/");
            fixed = StringUtils.join(characters.toArray(), " / ");
        }

        return fixed;
    }

    /**
     * Get the certification from the MPAA string
     *
     * @param mpaaCertification
     * @return
     */
    public static String processMpaaCertification(String mpaaCertification) {
        return processMpaaCertification(MPPA_RATED, mpaaCertification);
    }

    /**
     * Get the certification from the MPAA rating string
     *
     * @param mpaaRated
     * @param mpaaCertification
     * @return
     */
    public static String processMpaaCertification(String mpaaRated, String mpaaCertification) {
        // Strip out the "Rated " and extra words at the end of the MPAA certification
        Pattern mpaaPattern = Pattern.compile(
                "(?:" + (StringUtils.isNotBlank(mpaaRated) ? mpaaRated : MPPA_RATED) + "\\s)?(.*?)(?:($|\\s).*?)");
        Matcher m = mpaaPattern.matcher(mpaaCertification);
        if (m.find()) {
            return m.group(1).trim();
        }
        return mpaaCertification.trim();
    }

    public static String cleanIdentifier(final String identifier) {
        String result = identifier;
        if (IDENT_TRANSLITERATE) {
            result = TRANSLITERATOR.transliterate(result);
        }
        if (IDENT_CLEAN) {
            // format  to ss
            result = result.replaceAll("", "ss");
            // remove all accents from letters
            result = StringUtils.stripAccents(result);
            // capitalize first letter
            result = WordUtils.capitalize(result, CLEAN_DELIMITERS);
            // remove punctuation and symbols
            result = result.replaceAll("[\\p{Po}|\\p{S}]", "");
            // just leave characters and digits
            result = CLEAN_STRING_PATTERN.matcher(result).replaceAll(" ").trim();
            // remove double whitespaces
            result = result.replaceAll("^ +| +$|( ){2,}", "$1");
        }
        return result;
    }

    /**
     * Set the sort title.
     *
     * @param metadata the scanned metadata
     * @param prefixes a list with prefixed to strip
     */
    public static void setSortTitle(AbstractMetadata metadata, List<String> prefixes) {
        String sortTitle;
        if (StringUtils.isBlank(metadata.getTitleSort())) {
            sortTitle = StringUtils.stripAccents(metadata.getTitle());

            // strip prefix
            for (String prefix : prefixes) {
                String check = prefix.trim() + " ";
                if (StringUtils.startsWithIgnoreCase(sortTitle, check)) {
                    sortTitle = sortTitle.substring(check.length());
                    break;
                }
            }
        } else {
            sortTitle = StringUtils.stripAccents(metadata.getTitleSort());
        }

        // first char must be a letter or digit
        int idx = 0;
        while (idx < sortTitle.length() && !Character.isLetterOrDigit(sortTitle.charAt(idx))) {
            idx++;
        }

        // replace all non-standard characters in the title sort
        sortTitle = MetadataTools.stringMapReplacement(sortTitle.substring(idx));
        metadata.setTitleSort(sortTitle);
    }

    public static String getExternalSubtitleFormat(String extension) {
        if (extension == null)
            return null;

        String format;
        switch (extension.toLowerCase()) {
        case "ass":
            format = "Advanced SubStation Alpha";
            break;
        case "pgs":
        case "sup":
            format = "Presentation Grapic Stream";
            break;
        case "smi":
        case "sami":
            format = "Synchronized Accessible Media Interchange";
            break;
        case "srt":
            format = "SubRip";
            break;
        case "ssa":
            format = "SubStation Alpha";
            break;
        case "ssf":
            format = "Structured Subtitle Format";
            break;
        case "sub":
            format = "MicroDVD";
            break;
        case "usf":
            format = "Universal Subtitle Format";
            break;
        default:
            format = extension.toUpperCase();
            break;
        }
        return format;
    }

    public static PersonNameDTO splitFullName(String fullName) {
        PersonNameDTO dto = new PersonNameDTO(fullName);

        try {
            String[] result = StringUtils.split(fullName, ' ');
            if (result == null || result.length == 0) {
                // nothing to do
            } else if (result.length == 1) {
                dto.setFirstName(result[0]);
            } else if (result.length == 2) {
                dto.setFirstName(result[0]);
                dto.setLastName(result[1]);
            } else {
                Matcher m = LASTNAME_PATTERN.matcher(fullName);
                if (m.matches()) {
                    dto.setFirstName(m.group(1));
                    dto.setLastName(m.group(2));
                }
            }
        } catch (Exception ex) {
            LOG.trace("Error splitting full person name: " + fullName, ex);
        }

        return dto;
    }
}