com.dgtlrepublic.anitomyj.KeywordManager.java Source code

Java tutorial

Introduction

Here is the source code for com.dgtlrepublic.anitomyj.KeywordManager.java

Source

/*
 * Copyright (c) 2014-2016, Eren Okka
 * Copyright (c) 2016, Paul Miller
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

package com.dgtlrepublic.anitomyj;

import static com.dgtlrepublic.anitomyj.Element.ElementCategory.kElementAnimeSeasonPrefix;
import static com.dgtlrepublic.anitomyj.Element.ElementCategory.kElementAnimeType;
import static com.dgtlrepublic.anitomyj.Element.ElementCategory.kElementAudioTerm;
import static com.dgtlrepublic.anitomyj.Element.ElementCategory.kElementDeviceCompatibility;
import static com.dgtlrepublic.anitomyj.Element.ElementCategory.kElementEpisodePrefix;
import static com.dgtlrepublic.anitomyj.Element.ElementCategory.kElementFileExtension;
import static com.dgtlrepublic.anitomyj.Element.ElementCategory.kElementLanguage;
import static com.dgtlrepublic.anitomyj.Element.ElementCategory.kElementOther;
import static com.dgtlrepublic.anitomyj.Element.ElementCategory.kElementReleaseGroup;
import static com.dgtlrepublic.anitomyj.Element.ElementCategory.kElementReleaseInformation;
import static com.dgtlrepublic.anitomyj.Element.ElementCategory.kElementReleaseVersion;
import static com.dgtlrepublic.anitomyj.Element.ElementCategory.kElementSource;
import static com.dgtlrepublic.anitomyj.Element.ElementCategory.kElementSubtitles;
import static com.dgtlrepublic.anitomyj.Element.ElementCategory.kElementUnknown;
import static com.dgtlrepublic.anitomyj.Element.ElementCategory.kElementVideoResolution;
import static com.dgtlrepublic.anitomyj.Element.ElementCategory.kElementVideoTerm;
import static com.dgtlrepublic.anitomyj.Element.ElementCategory.kElementVolumePrefix;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;

import com.dgtlrepublic.anitomyj.Element.ElementCategory;

/**
 * A class to manage the list of known anime keywords. This class is analogous to {@code keyword.cpp} of the original
 * library.
 *
 * @author Paul Miller
 * @author Eren Okka
 */
public class KeywordManager {
    private final Map<String, Keyword> keys = new HashMap<>();
    private final Map<String, Keyword> file_extensions = new HashMap<>();
    private final List<Pair<ElementCategory, List<String>>> peekEntries;
    private static final KeywordManager instance = new KeywordManager();

    @SuppressWarnings("ArraysAsListWithZeroOrOneArgument")
    private KeywordManager() {
        KeywordOptions optionsDefault = new KeywordOptions();
        KeywordOptions optionsInvalid = new KeywordOptions(true, true, false);
        KeywordOptions optionsUnidentifiable = new KeywordOptions(false, true, true);
        KeywordOptions optionsUnidentifiableInvalid = new KeywordOptions(false, true, false);
        KeywordOptions optionsUnidentifiableUnsearchable = new KeywordOptions(false, false, true);

        add(kElementAnimeSeasonPrefix, optionsUnidentifiable, Arrays.asList("SAISON", "SEASON"));

        add(kElementAnimeType, optionsUnidentifiable,
                Arrays.asList("GEKIJOUBAN", "MOVIE", "OAD", "OAV", "ONA", "OVA", "SPECIAL", "SPECIALS", "TV"));

        add(kElementAnimeType, optionsUnidentifiableUnsearchable, Arrays.asList("SP")); // e.g. "Yumeiro Patissiere SP Professional"

        add(kElementAnimeType, optionsUnidentifiableInvalid,
                Arrays.asList("ED", "ENDING", "NCED", "NCOP", "OP", "OPENING", "PREVIEW", "PV"));

        add(kElementAudioTerm, optionsDefault, Arrays.asList(
                // Audio channels
                "2.0CH", "2CH", "5.1", "5.1CH", "DTS", "DTS-ES", "DTS5.1", "TRUEHD5.1",
                // Audio codec
                "AAC", "AACX2", "AACX3", "AACX4", "AC3", "FLAC", "FLACX2", "FLACX3", "FLACX4", "LOSSLESS", "MP3",
                "OGG", "VORBIS",
                // Audio language
                "DUALAUDIO", "DUAL AUDIO"));

        add(kElementDeviceCompatibility, optionsDefault,
                Arrays.asList("IPAD3", "IPHONE5", "IPOD", "PS3", "XBOX", "XBOX360"));

        add(kElementDeviceCompatibility, optionsUnidentifiable, Arrays.asList("ANDROID"));

        add(kElementEpisodePrefix, optionsDefault, Arrays.asList("EP", "EP.", "EPS", "EPS.", "EPISODE", "EPISODE.",
                "EPISODES", "CAPITULO", "EPISODIO", "FOLGE"));

        add(kElementEpisodePrefix, optionsInvalid, Arrays.asList("E", "\\x7B2C")); // single-letter episode keywords are not valid tokens

        add(kElementFileExtension, optionsDefault, Arrays.asList("3GP", "AVI", "DIVX", "FLV", "M2TS", "MKV", "MOV",
                "MP4", "MPG", "OGM", "RM", "RMVB", "WEBM", "WMV"));

        add(kElementFileExtension, optionsInvalid, Arrays.asList("AAC", "AIFF", "FLAC", "M4A", "MP3", "MKA", "OGG",
                "WAV", "WMA", "7Z", "RAR", "ZIP", "ASS", "SRT"));

        add(kElementLanguage, optionsDefault,
                Arrays.asList("ENG", "ENGLISH", "ESPANO", "JAP", "PT-BR", "SPANISH", "VOSTFR"));

        add(kElementLanguage, optionsUnidentifiable, Arrays.asList("ESP", "ITA")); // e.g. "Tokyo ESP", "Bokura ga Ita"

        add(kElementOther, optionsDefault,
                Arrays.asList("REMASTER", "REMASTERED", "UNCENSORED", "UNCUT", "TS", "VFR", "WIDESCREEN", "WS"));

        add(kElementReleaseGroup, optionsDefault, Arrays.asList("THORA"));

        add(kElementReleaseInformation, optionsDefault, Arrays.asList("BATCH", "COMPLETE", "PATCH", "REMUX"));

        add(kElementReleaseInformation, optionsUnidentifiable, Arrays.asList("END", "FINAL")); // e.g. "The End of Evangelion", "Final Approach"

        add(kElementReleaseVersion, optionsDefault, Arrays.asList("V0", "V1", "V2", "V3", "V4"));

        add(kElementSource, optionsDefault,
                Arrays.asList("BD", "BDRIP", "BLURAY", "BLU-RAY", "DVD", "DVD5", "DVD9", "DVD-R2J", "DVDRIP",
                        "DVD-RIP", "R2DVD", "R2J", "R2JDVD", "R2JDVDRIP", "HDTV", "HDTVRIP", "TVRIP", "TV-RIP",
                        "WEBCAST", "WEBRIP"));

        add(kElementSubtitles, optionsDefault, Arrays.asList("ASS", "BIG5", "DUB", "DUBBED", "HARDSUB", "RAW",
                "SOFTSUB", "SOFTSUBS", "SUB", "SUBBED", "SUBTITLED"));

        add(kElementVideoTerm, optionsDefault, Arrays.asList(
                // Frame rate
                "23.976FPS", "24FPS", "29.97FPS", "30FPS", "60FPS", "120FPS",
                // Video codec
                "8BIT", "8-BIT", "10BIT", "10BITS", "10-BIT", "10-BITS", "HI10P", "H264", "H265", "H.264", "H.265",
                "X264", "X265", "X.264", "AVC", "HEVC", "DIVX", "DIVX5", "DIVX6", "XVID",
                // Video format
                "AVI", "RMVB", "WMV", "WMV3", "WMV9",
                // Video quality
                "HQ", "LQ",
                // Video resolution
                "HD", "SD"));

        add(kElementVolumePrefix, optionsDefault, Arrays.asList("VOL", "VOL.", "VOLUME"));

        /** {@link #peekAndAdd(String, TokenRange, List, List)} entries */
        peekEntries = new ArrayList<Pair<ElementCategory, List<String>>>() {
            {
                add(Pair.of(kElementAudioTerm, Arrays.asList("Dual Audio")));
                add(Pair.of(kElementVideoTerm, Arrays.asList("H264", "H.264", "h264", "h.264")));
                add(Pair.of(kElementVideoResolution, Arrays.asList("480p", "720p", "1080p")));
                add(Pair.of(kElementSource, Arrays.asList("Blu-Ray")));
            }
        };
    }

    /** Return singleton instance. */
    public static KeywordManager getInstance() {
        return instance;
    }

    /** Returns a normalized string. */
    public static String normalzie(String word) {
        if (StringUtils.isEmpty(word))
            return word;
        return word.toUpperCase(Locale.ENGLISH);
    }

    /** Returns whether or not {@code KeywordManager} contains {@code keyword}. */
    public boolean contains(ElementCategory category, String keyword) {
        Map<String, Keyword> keys = getKeywordContainer(category);
        Keyword foundEntry = keys.get(keyword);
        return foundEntry != null && foundEntry.getCategory() == category;
    }

    /**
     * Finds a particular {@code keyword}. If found sets {@code category} and {@code options} to the found search
     * result.
     *
     * @param keyword  the keyword to search for
     * @param category an atomic reference that will be set/changed to the found keyword category
     * @param options  an atomic reference that will be set/changed to the found keyword options
     * @return true if the keyword was found; false otherwise
     */
    public boolean findAndSet(String keyword, AtomicReference<ElementCategory> category,
            AtomicReference<KeywordOptions> options) {
        Map<String, Keyword> keys = getKeywordContainer(category.get());
        Keyword foundEntry = keys.get(keyword);
        if (foundEntry != null) {
            if (category.get() == kElementUnknown) {
                category.set(foundEntry.getCategory());
            } else if (foundEntry.getCategory() != category.get()) {
                return false;
            }

            options.set(foundEntry.getOptions());
            return true;
        }

        return false;
    }

    /**
     * Given a particular {@code filename} and {@code range} attempt to preidentify the token before we attempt the main
     * parsing logic.
     *
     * @param filename            the filename
     * @param range               the search range
     * @param elements            elements array that any pre-identified elements will be added to
     * @param preidentifiedTokens elements array that any pre-identified token ranges will be added to
     */
    @SuppressWarnings("ArraysAsListWithZeroOrOneArgument")
    public void peekAndAdd(String filename, TokenRange range, List<Element> elements,
            List<TokenRange> preidentifiedTokens) {
        int endR = range.getOffset() + range.getSize();
        String search = filename.substring(range.getOffset(), endR > filename.length() ? filename.length() : endR);
        peekEntries.forEach(entry -> entry.getRight().forEach(keyword -> {
            int foundIdx = search.indexOf(keyword);
            if (foundIdx != -1) {
                foundIdx += range.getOffset();
                elements.add(new Element(entry.getKey(), keyword));
                preidentifiedTokens.add(new TokenRange(foundIdx, keyword.length()));
            }
        }));
    }

    /************ P R I V A T E  A P I ********** */

    /** Returns the appropriate keyword container. */
    private Map<String, Keyword> getKeywordContainer(ElementCategory category) {
        return category == kElementFileExtension ? file_extensions : keys;
    }

    /** Adds a {@code category}, {@code options} and {@code keywords} to the internal keywords list. */
    private void add(ElementCategory category, KeywordOptions options, List<String> keywords) {
        Map<String, Keyword> keys = getKeywordContainer(category);
        keywords.stream().filter(StringUtils::isNotEmpty).filter(s -> !keys.containsKey(s))
                .forEach(keyword -> keys.put(keyword, new Keyword(category, options)));
    }

    /**
     * Keyword options for a particular keyword.
     *
     * @author Paul Miller
     */
    public static class KeywordOptions {
        private final boolean identifiable;
        private final boolean searchable;
        private final boolean valid;

        public KeywordOptions() {
            this(true, true, true);
        }

        /**
         * Constructs a new keyword option.
         *
         * @param identifiable if the token is identifiable
         * @param searchable   if the token is searchable
         * @param valid        if the token is valid
         */
        public KeywordOptions(boolean identifiable, boolean searchable, boolean valid) {
            this.identifiable = identifiable;
            this.searchable = searchable;
            this.valid = valid;
        }

        /** Returns whether or not a keyword is identifiable. */
        public boolean isIdentifiable() {
            return identifiable;
        }

        /** Returns whether or not a keyword is searchable */
        public boolean isSearchable() {
            return searchable;
        }

        /** Returns whether or not a keyword is valid */
        public boolean isValid() {
            return valid;
        }
    }

    /**
     * A Keyword
     *
     * @author Paul  Miller
     */
    public static class Keyword {
        private final ElementCategory category;
        private final KeywordOptions options;

        /**
         * Constructs a new Keyword.
         *
         * @param category the category of the keyword
         * @param options  the keyword's options
         */
        public Keyword(ElementCategory category, KeywordOptions options) {
            this.category = category;
            this.options = options;
        }

        /** Returns the keyword category. */
        public ElementCategory getCategory() {
            return category;
        }

        /** Returns the keyword options */
        public KeywordOptions getOptions() {
            return options;
        }
    }
}