anipar
Version:
Anime title Parser built for AnimeGarden.
970 lines (963 loc) • 23.2 kB
JavaScript
class Context {
// Left cursor for consumed tokens
left = 0;
// Right cursor for consumed tokens
right;
tokens;
options;
result;
tags;
constructor(tokens, options) {
this.tokens = tokens;
this.options = options;
this.right = tokens.length - 1;
this.tags = [];
this.result = {};
}
update(key, value) {
this.result[key] = value;
}
update2(key1, key2, value) {
if (!this.result[key1]) {
this.result[key1] = {};
}
this.result[key1][key2] = value;
}
update3(key1, key2, key3, value) {
if (!this.result[key1]) {
this.result[key1] = {};
}
if (!this.result[key1][key2]) {
this.result[key1][key2] = {};
}
this.result[key1][key2][key3] = value;
}
get hasEpisode() {
return this.result.episode || this.result.episodes || this.result.episodeRange;
}
validate() {
this.result.tags = this.tags;
return this.result;
}
}
class Token {
text;
left;
right;
constructor(text, left, right) {
this.text = text;
this.left = left;
this.right = right;
}
get isWrapped() {
return this.left && this.right;
}
slice(start, end) {
const text = this.text.slice(start, end);
return new Token(text, this.left, this.right);
}
trim() {
const text = this.text.trim();
return new Token(text, this.left, this.right);
}
toString() {
return `${this.left ?? ""}${this.text}${this.right ?? ""}`;
}
}
const Wrappers = /* @__PURE__ */ new Map([
["[", "]"],
["\u3010", "\u3011"],
["(", ")"],
["\uFF08", "\uFF09"],
["{", "}"]
]);
const RevWrappers = new Map([...Wrappers.entries()].map(([k, v]) => [v, k]));
function tokenize(text) {
const tokens = [];
let cursor = 0;
let cur = "";
let left = void 0;
let right = void 0;
while (cursor < text.length) {
const char = text[cursor];
if (Wrappers.has(char)) {
if (!left) {
if (cur) {
tokens.push(new Token(cur.trim()));
cur = "";
}
left = char;
right = Wrappers.get(char);
} else {
cur += char;
}
} else if (left && right && char === right) {
tokens.push(new Token(cur.trim(), left, right));
cur = "";
left = void 0;
right = void 0;
} else {
cur += char;
}
cursor += 1;
}
if (cur) {
tokens.push(new Token(cur.trim()));
}
return tokens.filter((t) => t.text);
}
const WrappedEpisodeRE = /^(?<ep1>\d+)(?:\.(\d))?(?:[vV](\d+))?$|^第(?<ep2>\d+)[集话話]$/;
const WrappedMovieRE = /^Movie [vV](\d+)$/;
const EpisodesRange1 = /^(\d+)-(\d+)(?:\s*.*)$/;
const EpisodesRange2 = /^全(\d+)集$/;
function matchEpiodes(ctx, text) {
{
const res = WrappedEpisodeRE.exec(text);
if (res) {
const epText = res.groups?.ep1 || res.groups?.ep2;
const ep = +epText;
if (!Number.isNaN(ep)) {
if (1949 <= ep && ep <= 2099 && text === epText && ctx.hasEpisode) {
ctx.update("year", ep);
return true;
}
ctx.update2("episode", "number", ep);
if (res[2] && !Number.isNaN(res[2])) {
ctx.update2("episode", "numberSub", +res[2]);
}
if (res[3] && !Number.isNaN(res[3])) {
ctx.update("version", +res[3]);
}
return true;
}
}
}
{
const res = WrappedMovieRE.exec(text);
if (res) {
if (res[1] && !Number.isNaN(res[1])) {
ctx.update("version", +res[1]);
}
ctx.update("type", "Movie");
return true;
}
}
{
const res = EpisodesRange1.exec(text);
if (res) {
const from = +res[1];
const to = +res[2];
if (!Number.isNaN(from) && !Number.isNaN(to)) {
ctx.update2("episodeRange", "from", from);
ctx.update2("episodeRange", "to", to);
return true;
}
}
}
{
const res = EpisodesRange2.exec(text);
if (res) {
const to = +res[1];
if (!Number.isNaN(to)) {
ctx.update2("episodeRange", "from", 1);
ctx.update2("episodeRange", "to", to);
return true;
}
}
}
return false;
}
const SuffixEpisodeRE = [/- (\d+)(?:\.(\d))?$/, /第(\d+)(?:\.(\d))?[集话話]$/];
function parseSuffixEpisodes(ctx) {
if (ctx.result.episode || ctx.result.episodes || ctx.result.episodeRange) {
return true;
}
const token = ctx.tokens[ctx.right];
const text = token.text;
for (const RE of SuffixEpisodeRE) {
const res = RE.exec(text);
if (res) {
const ep = +res[1];
if (!Number.isNaN(ep)) {
ctx.update2("episode", "number", ep);
if (res[2] && !Number.isNaN(res[2])) {
ctx.update2("episode", "numberSub", +res[2]);
}
ctx.tokens[ctx.right] = token.slice(0, text.length - res[0].length).trim();
return true;
}
}
}
}
const SuffixSeasonOrEpisodesRes = [
[
/(?:S|Season\s?)(\d+)$/,
(res, ctx) => {
const season = +res[1];
if (!Number.isNaN(season)) {
if (!ctx.result.season?.number || ctx.result.season.number === season) {
ctx.update2("season", "number", season);
return true;
}
}
return false;
}
],
[
/(1st|2nd|3rd|[456789]th) Season$/,
(res, ctx) => {
const season = Number.parseInt(res[1]);
if (!Number.isNaN(season)) {
if (!ctx.result.season?.number || ctx.result.season.number === season) {
ctx.update2("season", "number", season);
return true;
}
}
return false;
}
],
[
/第?(\d+)[季期]$/,
(res, ctx) => {
const season = +res[1];
if (!Number.isNaN(season)) {
if (!ctx.result.season?.number || ctx.result.season.number === season) {
ctx.update2("season", "number", season);
return true;
}
}
return false;
}
],
[
/第?((?:[零一二三四五六七八九]十)?[零一二三四五六七八九])[季期]$/,
(res, ctx) => {
const text = res[1];
const map = {
\u96F6: 0,
\u4E00: 1,
\u4E8C: 2,
\u4E09: 3,
\u56DB: 4,
\u4E94: 5,
\u516D: 6,
\u4E03: 7,
\u516B: 8,
\u4E5D: 9
};
const base = text.length === 2 && text[0] === "\u5341" ? 1 : text.length === 3 ? map[text[0]] : 0;
const offset = map[text[text.length - 1]];
const season = base * 10 + offset;
if (!ctx.result.season?.number || ctx.result.season.number === season) {
ctx.update2("season", "number", season);
return true;
}
return false;
}
],
[
/ (\d+)$/,
(res, ctx) => {
const season = +res[1];
if (!Number.isNaN(season) && (ctx.result.episode || ctx.result.episodes || ctx.result.episodeRange)) {
if (/Parts? \d+$/.test(res.input)) {
return false;
}
if (!ctx.result.season?.number || ctx.result.season.number === season) {
ctx.update2("season", "number", season);
return true;
}
}
return false;
}
],
[
/(?<ep1>\d+)(?:\.(\d))?(?:[vV](\d+))?$|^第(?<ep2>\d+)[集话話]$/,
(res, ctx) => {
const ep = +(res.groups?.ep1 || res.groups?.ep2);
if (!Number.isNaN(ep)) {
if (/Parts? \d+$/.test(res.input)) {
return false;
}
ctx.update2("episode", "number", ep);
if (res[2] && !Number.isNaN(res[2])) {
ctx.update2("episode", "numberSub", +res[2]);
}
if (res[3] && !Number.isNaN(res[3])) {
ctx.update("version", +res[3]);
}
return true;
}
return false;
}
],
[
/(日配版)$/,
(res, ctx) => {
ctx.update("language", res[0]);
return true;
}
],
[
/(重播)$/,
(res, ctx) => {
ctx.tags.push(res[0]);
return true;
}
]
];
const AudioTerm = /* @__PURE__ */ new Set([
// Audio channels
"2.0CH",
"2CH",
"5.1",
"5.1CH",
"DTS",
"DTS-ES",
"DTS5.1",
"TRUEHD5.1",
// Audio codec
"AAC",
"AACX2",
"AAC\xD72",
"AACX3",
"AAC\xD73",
"AACX4",
"AAC\xD74",
"AC3",
"EAC3",
"E-AC-3",
"FLAC",
"FLACX2",
"FLAC\xD72",
"FLACX3",
"FLAC\xD73",
"FLACX4",
"FLAC\xD74",
"LOSSLESS",
"MP3",
"WAV",
"OGG",
"VORBIS",
// Audio language
"DUALAUDIO",
"DUAL AUDIO"
]);
const VideoTerm = /* @__PURE__ */ new Set([
// 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",
"HEVC-10BIT",
"HEVC_OPUS",
"DIVX",
"DIVX5",
"DIVX6",
"XVID",
// Video format
"AVI",
"RMVB",
"WMV",
"WMV3",
"WMV9",
// Video quality
"HDR",
"HQ",
"LQ",
// Video resolution
"HD",
"SD"
]);
const VideoResolution = /* @__PURE__ */ new Set([
"480P",
"720P",
"1080P",
"2160P",
"AI2160p",
"1280X720",
"1280\xD7720",
"1920X816",
"1920\xD7816",
"1920X1080",
"1920\xD71080",
"2K",
"4K"
]);
const Source = /* @__PURE__ */ new Set([
"BD",
"BDRIP",
"BLURAY",
"BLU-RAY",
"BDREMUX",
"UHDBDRIP",
"DVD",
"DVD5",
"DVD9",
"DVD-R2J",
"DVDRIP",
"DVD-RIP",
"R2DVD",
"R2J",
"R2JDVD",
"R2JDVDRIP",
"HDTV",
"HDTVRIP",
"TVRIP",
"TV-RIP",
"WEB",
"WEBCAST",
"WEBDL",
"WEB-DL",
"WEBRIP",
"WEB-RIP",
"WEB-MKV"
]);
const Platfrom = /* @__PURE__ */ new Set(["Baha", "Bilibili", "B-Global", "ABEMA", "CR", "ViuTV", "AMZN", "ADN"]);
const Type = /* @__PURE__ */ new Set([
"GEKIJOUBAN",
"MOVIE",
"OAD",
"OAV",
"ONA",
"OVA",
"SPECIAL",
"SPECIALS",
"TV",
"\u7279\u522B\u7BC7",
"\u7279\u5225\u7BC7",
"\u7279\u5225\u7DE8",
"\u7279\u522B\u8BDD",
"\u7279\u5225\u8BDD",
"\u7279\u5225\u8A71",
"\u756A\u5916\u7BC7",
"\u756A\u5916\u7DE8",
"\u5267\u573A\u7248",
"\u5287\u5834\u7248",
//
"SP",
//
"ED",
"ENDING",
"NCED",
"NCOP",
"OP",
"OPENING",
"PREVIEW",
"PV"
]);
const Languages = /* @__PURE__ */ new Set([
"CN",
"CHS",
"CHT",
"YUE",
"JP",
"\u7B80\u4F53",
"\u56FD\u8BED\u4E2D\u5B57",
"\u7E41\u9AD4",
"\u4E2D\u65E5\u53CC\u8BED",
"\u7B80\u65E5\u53CC\u8BED",
"\u7E41\u65E5\u96D9\u8A9E",
"HOY\u7CB5\u8A9E"
]);
const Subtitles = /* @__PURE__ */ new Set([
"ASS",
"GB",
"BIG5",
"DUB",
"DUBBED",
"HARDSUB",
"HARDSUBS",
"RAW",
"SOFTSUB",
"SOFTSUBS",
"SUB",
"SUBBED",
"SUBTITLED",
"SRT"
]);
const LanguagePrefixes = [
"\u7B80\u4F53",
"\u7E41\u9AD4",
"\u7B80\u65E5",
"\u7E41\u65E5",
"\u7B80\u7E41\u65E5\u53CC\u8BED",
"\u7B80\u7E41\u65E5",
"\u7B80\u7E41",
"\u7E41\u7B80\u65E5",
"\u7B80\u7E41\u65E5\u8BED",
"\u7B80\u65E5\u53CC\u8BED"
];
const SubtitlesSufixes = /* @__PURE__ */ new Set(["\u5185\u5D4C", "\u5167\u5D4C", "\u5185\u5C01", "\u5185\u5C01\u5B57\u5E55", "\u5916\u6302", "\u5916\u639B"]);
const PlatformLanguage = /* @__PURE__ */ new Map([["ViuTV\u7CB5\u8A9E", ["ViuTV", "\u7CB5\u8A9E"]]]);
const LanguageSubtitles = /* @__PURE__ */ new Map([
["\u7B80\u4F53\u5B57\u5E55", ["\u7B80\u4F53", void 0]],
["\u7E41\u9AD4\u5B57\u5E55", ["\u7E41\u9AD4", void 0]],
["\u7B80\u65E5\u53CC\u8BED\u5B57\u5E55", ["\u7B80\u65E5\u53CC\u8BED", void 0]],
["TVB\u7CB5\u8A9E", ["\u7CB5\u8A9E", void 0]],
["\u4EE3\u7406\u5546\u7CB5\u8A9E", ["\u7CB5\u8A9E", void 0]],
["\u7CB5\u65E5\u96D9\u8A9E+\u5167\u5C01\u7E41\u9AD4\u4E2D\u6587\u5B57\u5E55", ["\u7E41\u9AD4\u4E2D\u6587", "\u5167\u5C01\u5B57\u5E55"]],
["\u7CB5\u8A9E+\u7121\u5C0D\u767D\u5B57\u5E55", [void 0, "\u7121\u5C0D\u767D\u5B57\u5E55"]]
]);
const Extension = /* @__PURE__ */ new Set([
"3GP",
"AVI",
"DIVX",
"FLV",
"M2TS",
"MKV",
"MOV",
"MP4",
"MPG",
"OGM",
"RM",
"RMVB",
"TS",
"WEBM",
"WMV"
]);
const Tags = /* @__PURE__ */ new Set(["\u56FD\u6F2B", "\u5148\u884C\u7248", "\u5148\u884C\u7248\u672C", "\u6B63\u5F0F\u7248", "\u6B63\u5F0F\u7248\u672C", "Ani-One"]);
const SearchPrefix = ["\u68C0\u7D22\uFF1A", "\u68C0\u7D22\u7528\uFF1A"];
const HiringPrefix = ["\u62DB\u52DF", "\u5B57\u5E55\u793E\u62DB\u4EBA"];
const OtherPrefix = ["\u25B6"];
function matchSingleTag(ctx, text) {
const upper = text.toUpperCase();
if (AudioTerm.has(upper)) {
ctx.update3("file", "audio", "term", text);
return true;
}
if (VideoTerm.has(upper)) {
ctx.update3("file", "video", "term", text);
return true;
}
if (VideoResolution.has(upper)) {
ctx.update3("file", "video", "resolution", text);
return true;
}
if (Source.has(upper)) {
ctx.update("source", text);
return true;
}
if (Platfrom.has(text)) {
ctx.update("platform", text);
return true;
}
if (Type.has(upper)) {
ctx.update("type", text);
return true;
}
if (Extension.has(upper)) {
ctx.update2("file", "extension", text);
return true;
}
if (Tags.has(text)) {
ctx.tags.push(text);
return true;
}
{
if (Languages.has(upper)) {
ctx.update("language", text);
return true;
}
if (Subtitles.has(upper)) {
ctx.update("subtitles", text);
return true;
}
const combined = LanguageSubtitles.get(text);
if (combined) {
ctx.update("language", combined[0]);
ctx.update("subtitles", combined[1]);
return true;
}
const combined2 = PlatformLanguage.get(text);
if (combined2) {
ctx.update("platform", combined2[0]);
ctx.update("language", combined2[1]);
return true;
}
for (const prefix of LanguagePrefixes) {
if (text.startsWith(prefix)) {
const language = prefix;
const subtitles = text.slice(prefix.length);
if (SubtitlesSufixes.has(subtitles)) {
ctx.update("language", language);
ctx.update("subtitles", subtitles);
return true;
}
}
}
}
{
{
const match = /^(\d\d\d\d)年(\d\d?)月新?番$/.exec(text);
if (match) {
const year = +match[1];
const month = +match[2];
if (1949 <= year && year <= 2099) {
ctx.update("year", year);
}
if (1 <= month && month <= 12) {
ctx.update("month", month);
}
return true;
}
}
{
const match = /^★?(\d\d?)月新?番★?$/.exec(text);
if (match) {
const month = +match[1];
if (1 <= month && month <= 12) {
ctx.update("month", month);
}
return true;
}
}
{
const match = /^(\d\d\d\d)\.(\d?\d)\.(\d?\d)$/.exec(text);
if (match) {
const year = +match[1];
const month = +match[2];
if (1949 <= year && year <= 2099) {
ctx.update("year", year);
}
if (1 <= month && month <= 12) {
ctx.update("month", month);
}
return true;
}
}
{
const match = /^(\d\d\d\d)(SP)$/.exec(text);
if (match) {
const year = +match[1];
const type = match[2];
if (1949 <= year && year <= 2099) {
ctx.update("year", year);
}
ctx.update("type", type);
return true;
}
}
{
const match = /^[vV](\d+)$/.exec(text);
if (match) {
const version = +match[1];
ctx.update("version", version);
return true;
}
}
}
{
for (const prefix of SearchPrefix) {
if (text.startsWith(prefix)) {
const title = text.slice(prefix.length).trim();
ctx.tags.push(title);
return true;
}
}
for (const prefix of HiringPrefix) {
if (text.startsWith(prefix)) {
return true;
}
}
for (const prefix of OtherPrefix) {
if (text.startsWith(prefix)) {
ctx.tags.push(text);
return true;
}
}
}
return false;
}
const TagSeperators = [" ", "_"];
function matchMultipleTags(ctx, text) {
for (const sep of TagSeperators) {
const parts = text.split(sep);
if (parts.length <= 1) continue;
let matched = true;
for (let i = 0; i < parts.length; i += 1) {
const part = parts[i];
if (!matchSingleTag(ctx, part)) {
matched = false;
}
}
if (matched) {
return true;
}
}
return false;
}
function parseTag(ctx, cursor) {
const token = ctx.tokens[cursor];
if (token.isWrapped) {
const text = token.text;
if (matchSingleTag(ctx, text)) {
return true;
}
if (matchEpiodes(ctx, text)) {
return true;
}
if (matchMultipleTags(ctx, text)) {
return true;
}
}
return false;
}
function parseRightTags(ctx) {
while (ctx.left < ctx.right) {
if (parseTag(ctx, ctx.right)) {
ctx.right -= 1;
} else {
if (ctx.left + 2 < ctx.right && ctx.right >= ctx.tokens.length - 1) {
ctx.tags.push(ctx.tokens[ctx.right].text);
ctx.right -= 1;
} else {
break;
}
}
}
{
const token = ctx.tokens[ctx.right];
const text = token.text;
const sepearators = [" ", "\u2605"];
for (const sep of sepearators) {
const parts = text.split(sep);
if (parts.length > 1) {
let changed = 0;
while (parts.length > 1) {
const part = parts[parts.length - 1];
if (/^\d+$/.test(part)) {
break;
}
if (matchSingleTag(ctx, part) || matchEpiodes(ctx, part) || matchMultipleTags(ctx, part)) {
changed++;
parts.pop();
} else {
break;
}
}
if (changed > 1) {
const trimmed = parts.join(sep);
ctx.tokens[ctx.right] = new Token(trimmed, token.left, token.right);
}
if (changed > 0) {
break;
}
}
}
}
}
function parseLeftTags(ctx) {
while (ctx.left < ctx.right) {
if (parseTag(ctx, ctx.left)) {
ctx.left += 1;
} else {
break;
}
}
{
const token = ctx.tokens[ctx.left];
const text = token.text;
const match = /^★?(\d\d?)月新?番★?/.exec(text);
if (match) {
const matched = match[0];
const month = +match[1];
ctx.update("month", month);
ctx.tokens[ctx.left] = token.slice(matched.length);
if (ctx.tokens[ctx.left].text.length === 0) {
ctx.left += 1;
}
}
}
{
const token = ctx.tokens[ctx.left];
const text = token.text;
const match = /^★?(剧场版|劇場版)★?/.exec(text);
if (match) {
const matched = match[0];
const type = match[1];
ctx.update("type", type);
ctx.tokens[ctx.left] = token.slice(matched.length);
if (ctx.tokens[ctx.left].text.length === 0) {
ctx.left += 1;
}
}
}
}
function parseSuffixSeasonOrEpisodes(ctx, text) {
while (true) {
let found = false;
if (RevWrappers.has(text[text.length - 1])) {
const leftWrapper = RevWrappers.get(text[text.length - 1]);
const leftIdx = text.lastIndexOf(leftWrapper);
if (leftIdx !== -1) {
const maybe = text.slice(leftIdx + 1, text.length - 1);
if (maybe.length > 0) {
if (matchSingleTag(ctx, maybe) || !ctx.hasEpisode && matchEpiodes(ctx, maybe)) {
text = text.slice(0, text.length - maybe.length - 2).trimEnd();
found = true;
}
}
}
}
for (const [re, fn] of SuffixSeasonOrEpisodesRes) {
const res = re.exec(text);
if (res && fn(res, ctx)) {
text = text.slice(0, text.length - res[0].length).trimEnd();
found = true;
break;
}
}
if (!found) break;
}
return text;
}
function parseFansub(ctx) {
if (ctx.left + 1 > ctx.right) {
return false;
}
const token = ctx.tokens[ctx.left];
if (token.isWrapped) {
const text = token.text;
let found = false;
const seps = ["&", "\xB7"];
for (const sep of seps) {
const [name, ...collab] = text.split(sep);
if (collab.length > 0) {
if (collab.length === 1 && collab[0].endsWith("\u58D3\u5236")) {
break;
}
found = true;
ctx.update2("fansub", "name", name);
ctx.update2("fansub", "collab", collab);
break;
}
}
if (!found) {
ctx.update2("fansub", "name", text);
}
ctx.left += 1;
return true;
}
return false;
}
function parseTitle(ctx) {
parseSuffixEpisodes(ctx);
const rest = ctx.tokens.slice(ctx.left, ctx.right + 1);
if (rest.length === 0) return false;
let found = false;
const matchParts = (parts) => {
const [title, ...other] = parts;
const trimmedTitle = parseSuffixSeasonOrEpisodes(ctx, title);
const trimmedOther = other.map((t) => parseSuffixSeasonOrEpisodes(ctx, t)).map((t) => t.trim()).filter((t) => !!t && t !== trimmedTitle);
ctx.update("title", trimmedTitle);
if (trimmedOther.length > 0) {
ctx.update("titles", trimmedOther);
}
};
if (rest.length > 1 && rest.every((t) => t.isWrapped)) {
matchParts(rest.map((t) => t.text));
found = true;
}
if (!found) {
const text = rest.length === 1 ? rest[0].text : rest.map((t) => t.toString()).join("");
const separators = ["/", "\\"];
for (const sep of separators) {
const parts = splitText(text, sep).map((t) => t.trim()).filter((t) => !!t);
if (parts.length > 1) {
matchParts(parts);
found = true;
break;
}
}
if (!found && ctx.options.fansub === "ANi") {
const parts = splitOnce(text, " - ").map((t) => t.trim()).filter((t) => !!t);
if (parts.length > 1) {
matchParts(parts);
found = true;
}
}
if (!found) {
const trimmed = parseSuffixSeasonOrEpisodes(ctx, text);
ctx.update("title", trimmed);
}
}
return true;
}
function splitText(text, sep) {
const parts = text.split(sep);
const ans = [];
for (let i = 0; i < parts.length; i++) {
if (i === 0) {
ans.push(parts[i]);
} else {
if (sep === "/" && /\d$/.test(parts[i - 1]) && /^\d/.test(parts[i])) {
ans.pop();
ans.push(parts[i - 1] + "/" + parts[i]);
} else {
ans.push(parts[i]);
}
}
}
return ans;
}
function splitOnce(text, separator) {
const found = text.indexOf(separator);
if (found === -1) {
return [text, ""];
}
const first = text.slice(0, found);
const second = text.slice(found + separator.length);
return [first, second];
}
function parse(title, options = {}) {
const tokens = tokenize(title);
if (tokens.length === 0) return void 0;
const context = new Context(tokens, options);
parseRightTags(context);
parseLeftTags(context);
parseFansub(context);
parseLeftTags(context);
if (!parseTitle(context)) {
return void 0;
}
const result = context.validate();
return result ? postprocess(result) : void 0;
}
function postprocess(result) {
if (result.fansub?.name === "ANi") {
if (result.title && result.titles && result.titles.length === 1) {
const t1 = result.title;
const t2 = result.titles[0];
result.title = t2;
result.titles = [t1];
}
}
return result;
}
export { parse };
//# sourceMappingURL=index.mjs.map