/* * Copyright (c) 2014-2017, Eren Okka * Copyright (c) 2016-2017, Paul Miller * Copyright (c) 2017-2018, Tyler Bratton * * 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/. */ namespace AnitomySharp; /// /// A class to manager the list of known anime keywords. This class is analogous to keyword.cpp of Anitomy, and KeywordManager.java of AnitomyJ /// public static class KeywordManager { private static readonly Dictionary s_keys = new(); private static readonly Dictionary s_extensions = new(); private static readonly List>> s_peekEntries; static KeywordManager() { var optionsDefault = new KeywordOptions(); var optionsInvalid = new KeywordOptions(true, true, false); var optionsUnidentifiable = new KeywordOptions(false, true, true); var optionsUnidentifiableInvalid = new KeywordOptions(false, true, false); var optionsUnidentifiableUnsearchable = new KeywordOptions(false, false, true); Add(ElementCategory.ElementAnimeSeasonPrefix, optionsUnidentifiable, new List {"SAISON", "SEASON"}); Add(ElementCategory.ElementAnimeType, optionsUnidentifiable, new List {"GEKIJOUBAN", "MOVIE", "OAD", "OAV", "ONA", "OVA", "SPECIAL", "SPECIALS", "TV"}); Add(ElementCategory.ElementAnimeType, optionsUnidentifiableUnsearchable, new List {"SP"}); // e.g. "Yumeiro Patissiere SP Professional" Add(ElementCategory.ElementAnimeType, optionsUnidentifiableInvalid, new List {"ED", "ENDING", "NCED", "NCOP", "OP", "OPENING", "PREVIEW", "PV"}); Add(ElementCategory.ElementAudioTerm, optionsDefault, new List { // Audio channels "2.0CH", "2CH", "5.1", "5.1CH", "DTS", "DTS-ES", "DTS5.1", "TRUEHD5.1", // Audio codec "AAC", "AACX2", "AACX3", "AACX4", "AC3", "EAC3", "E-AC-3", "FLAC", "FLACX2", "FLACX3", "FLACX4", "LOSSLESS", "MP3", "OGG", "VORBIS", // Audio language "DUALAUDIO", "DUAL AUDIO" }); Add(ElementCategory.ElementDeviceCompatibility, optionsDefault, new List {"IPAD3", "IPHONE5", "IPOD", "PS3", "XBOX", "XBOX360"}); Add(ElementCategory.ElementDeviceCompatibility, optionsUnidentifiable, new List {"ANDROID"}); Add(ElementCategory.ElementEpisodePrefix, optionsDefault, new List {"EP", "EP.", "EPS", "EPS.", "EPISODE", "EPISODE.", "EPISODES", "CAPITULO", "EPISODIO", "FOLGE"}); Add(ElementCategory.ElementEpisodePrefix, optionsInvalid, new List {"E", "\\x7B2C"}); // single-letter episode keywords are not valid tokens Add(ElementCategory.ElementFileExtension, optionsDefault, new List {"3GP", "AVI", "DIVX", "FLV", "M2TS", "MKV", "MOV", "MP4", "MPG", "OGM", "RM", "RMVB", "TS", "WEBM", "WMV"}); Add(ElementCategory.ElementFileExtension, optionsInvalid, new List {"AAC", "AIFF", "FLAC", "M4A", "MP3", "MKA", "OGG", "WAV", "WMA", "7Z", "RAR", "ZIP", "ASS", "SRT"}); Add(ElementCategory.ElementLanguage, optionsDefault, new List {"ENG", "ENGLISH", "ESPANO", "JAP", "PT-BR", "SPANISH", "VOSTFR"}); Add(ElementCategory.ElementLanguage, optionsUnidentifiable, new List {"ESP", "ITA"}); // e.g. "Tokyo ESP:, "Bokura ga Ita" Add(ElementCategory.ElementOther, optionsDefault, new List {"REMASTER", "REMASTERED", "UNCENSORED", "UNCUT", "TS", "VFR", "WIDESCREEN", "WS"}); Add(ElementCategory.ElementReleaseGroup, optionsDefault, new List {"THORA"}); Add(ElementCategory.ElementReleaseInformation, optionsDefault, new List {"BATCH", "COMPLETE", "PATCH", "REMUX"}); Add(ElementCategory.ElementReleaseInformation, optionsUnidentifiable, new List {"END", "FINAL"}); // e.g. "The End of Evangelion", 'Final Approach" Add(ElementCategory.ElementReleaseVersion, optionsDefault, new List {"V0", "V1", "V2", "V3", "V4"}); Add(ElementCategory.ElementSource, optionsDefault, new List {"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(ElementCategory.ElementSubtitles, optionsDefault, new List {"ASS", "BIG5", "DUB", "DUBBED", "HARDSUB", "HARDSUBS", "RAW", "SOFTSUB", "SOFTSUBS", "SUB", "SUBBED", "SUBTITLED"}); Add(ElementCategory.ElementVideoTerm, optionsDefault, new List { // Frame rate "23.976FPS", "24FPS", "29.97FPS", "30FPS", "60FPS", "120FPS", // Video codec "8BIT", "8-BIT", "10BIT", "10BITS", "10-BIT", "10-BITS", "HI10", "HI10P", "HI444", "HI444P", "HI444PP", "H264", "H265", "H.264", "H.265", "X264", "X265", "X.264", "AVC", "HEVC", "HEVC2", "DIVX", "DIVX5", "DIVX6", "XVID", // Video format "AVI", "RMVB", "WMV", "WMV3", "WMV9", // Video quality "HQ", "LQ", // Video resolution "HD", "SD"}); Add(ElementCategory.ElementVolumePrefix, optionsDefault, new List {"VOL", "VOL.", "VOLUME"}); s_peekEntries = new List>> { Tuple.Create(ElementCategory.ElementAudioTerm, new List { "Dual Audio" }), Tuple.Create(ElementCategory.ElementVideoTerm, new List { "H264", "H.264", "h264", "h.264" }), Tuple.Create(ElementCategory.ElementVideoResolution, new List { "480p", "720p", "1080p" }), Tuple.Create(ElementCategory.ElementSource, new List { "Blu-Ray" }) }; } public static string Normalize(string word) { return string.IsNullOrEmpty(word) ? word : word.ToUpperInvariant(); } public static bool Contains(ElementCategory category, string keyword) { var keys = GetKeywordContainer(category); if (keys.TryGetValue(keyword, out var foundEntry)) { return foundEntry.Category == category; } return false; } /// /// Finds a particular keyword. If found sets category and options to the found search result. /// /// the keyword to search for /// the reference that will be set/changed to the found keyword category /// the reference that will be set/changed to the found keyword options /// if the keyword was found public static bool FindAndSet(string keyword, ref ElementCategory category, ref KeywordOptions options) { Dictionary keys = GetKeywordContainer(category); if (!keys.TryGetValue(keyword, out var foundEntry)) { return false; } if (category == ElementCategory.ElementUnknown) { category = foundEntry.Category; } else if (foundEntry.Category != category) { return false; } options = foundEntry.Options; return true; } /// /// Given a particular filename and range attempt to preidentify the token before we attempt the main parsing logic /// /// the filename /// the search range /// elements array that any pre-identified elements will be added to /// elements array that any pre-identified token ranges will be added to public static void PeekAndAdd(string filename, TokenRange range, List elements, List preIdentifiedTokens) { int endR = range.Offset + range.Size; string search = filename.Substring(range.Offset, endR > filename.Length ? filename.Length - range.Offset : endR - range.Offset); foreach (var entry in s_peekEntries) { foreach (string keyword in entry.Item2) { int foundIdx = search.IndexOf(keyword, StringComparison.CurrentCulture); if (foundIdx == -1) continue; foundIdx += range.Offset; elements.Add(new Element(entry.Item1, keyword)); preIdentifiedTokens.Add(new TokenRange(foundIdx, keyword.Length)); } } } // Private API /** Returns the appropriate keyword container. */ private static Dictionary GetKeywordContainer(ElementCategory category) { return category == ElementCategory.ElementFileExtension ? s_extensions : s_keys; } /// Adds a category, options, and keywords to the internal keywords list. private static void Add(ElementCategory category, KeywordOptions options, IEnumerable keywords) { var keys = GetKeywordContainer(category); foreach (string key in keywords.Where(k => !string.IsNullOrEmpty(k) && !keys.ContainsKey(k))) { keys[key] = new Keyword(category, options); } } } /// /// Keyword options for a particular keyword. /// public class KeywordOptions { public bool Identifiable { get; } public bool Searchable { get; } public bool Valid { get; } public KeywordOptions() : this(true, true, true) {} /// /// Constructs a new keyword options /// /// if the token is identifiable /// if the token is searchable /// if the token is valid public KeywordOptions(bool identifiable, bool searchable, bool valid) { Identifiable = identifiable; Searchable = searchable; Valid = valid; } } /// /// A Keyword /// public struct Keyword { public readonly ElementCategory Category; public readonly KeywordOptions Options; /// /// Constructs a new Keyword /// /// the category of the keyword /// the keyword's options public Keyword(ElementCategory category, KeywordOptions options) { Category = category; Options = options; } }