bugfix
This commit is contained in:
@@ -1,14 +1,20 @@
|
|||||||
FROM node:20-slim AS builder
|
FROM node:20-slim AS builder
|
||||||
|
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY package.json tsconfig.json ./
|
COPY package.json tsconfig.json ./
|
||||||
RUN npm install
|
RUN npm install
|
||||||
|
# Build the git dependency (filename-tools)
|
||||||
|
RUN cd node_modules/@metazla/filename-tools && npm install && npm run build
|
||||||
COPY src/ ./src/
|
COPY src/ ./src/
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
FROM node:20-slim
|
FROM node:20-slim
|
||||||
|
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY package.json ./
|
COPY package.json ./
|
||||||
RUN npm install --omit=dev
|
RUN npm install --omit=dev
|
||||||
|
# Build the git dependency in production too
|
||||||
|
RUN cd node_modules/@metazla/filename-tools && npm install && npm run build
|
||||||
COPY --from=builder /app/dist ./dist
|
COPY --from=builder /app/dist ./dist
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
|
||||||
|
|||||||
+5
-1
@@ -5,6 +5,10 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"scripts": { "build": "tsc", "start": "node dist/index.js" },
|
"scripts": { "build": "tsc", "start": "node dist/index.js" },
|
||||||
"dependencies": { "fastify": "^5.0.0", "wanakana": "^5.3.1" },
|
"dependencies": {
|
||||||
|
"fastify": "^5.0.0",
|
||||||
|
"wanakana": "^5.3.1",
|
||||||
|
"@metazla/filename-tools": "github:worph/filename-tool"
|
||||||
|
},
|
||||||
"devDependencies": { "@types/node": "^20.0.0", "typescript": "^5.3.0" }
|
"devDependencies": { "@types/node": "^20.0.0", "typescript": "^5.3.0" }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* MetaMesh Plugin: anime-detector
|
||||||
|
*
|
||||||
|
* ============================================================================
|
||||||
|
* PLUGIN MOUNT ARCHITECTURE - DO NOT MODIFY WITHOUT AUTHORIZATION
|
||||||
|
* ============================================================================
|
||||||
|
*
|
||||||
|
* Each plugin container has exactly 3 mounts (2 for plugins without output):
|
||||||
|
*
|
||||||
|
* 1. /files (READ-ONLY) - Shared media files, read access only
|
||||||
|
* 2. /cache (READ-WRITE) - Plugin-specific cache folder
|
||||||
|
* 3. /files/plugin/<id> (READ-WRITE) - Plugin output folder (if needed)
|
||||||
|
*
|
||||||
|
* This plugin (anime-detector) only requires mount 2:
|
||||||
|
* - /cache (RW) - to cache detection results
|
||||||
|
* - Note: This plugin analyzes metadata, doesn't read files directly
|
||||||
|
*
|
||||||
|
* SECURITY: Plugins must NEVER write to /files directly.
|
||||||
|
* All write operations go to /cache or /files/plugin/<id> only.
|
||||||
|
*
|
||||||
|
* ============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
import Fastify from 'fastify';
|
import Fastify from 'fastify';
|
||||||
import type { HealthResponse, ProcessRequest, ProcessResponse, CallbackPayload, ConfigureRequest, ConfigureResponse } from './types.js';
|
import type { HealthResponse, ProcessRequest, ProcessResponse, CallbackPayload, ConfigureRequest, ConfigureResponse } from './types.js';
|
||||||
import { manifest, process as processFile } from './plugin.js';
|
import { manifest, process as processFile } from './plugin.js';
|
||||||
|
|||||||
+35
-48
@@ -1,12 +1,27 @@
|
|||||||
/**
|
/**
|
||||||
* Anime Detector Plugin
|
* Anime Detector Plugin
|
||||||
* Detects if content is anime based on keywords and Japanese text
|
*
|
||||||
|
* Detects if a file is likely anime based on:
|
||||||
|
* - Common anime keywords in filename
|
||||||
|
* - Japanese text in title (kana, kanji)
|
||||||
|
* - Japanese audio tracks
|
||||||
|
* - "anime" in file path
|
||||||
|
*
|
||||||
|
* Matches old AnimeDetector output:
|
||||||
|
* - titles/jpn (if kana)
|
||||||
|
* - titles/{romajiIsoCode} (if romaji)
|
||||||
|
* - anime
|
||||||
|
* - genres (add "Anime")
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { isJapanese, isKana } from 'wanakana';
|
import { isJapanese, isKana } from 'wanakana';
|
||||||
|
import { animeKeywords } from '@metazla/filename-tools';
|
||||||
import type { PluginManifest, ProcessRequest, CallbackPayload } from './types.js';
|
import type { PluginManifest, ProcessRequest, CallbackPayload } from './types.js';
|
||||||
import { MetaCoreClient } from './meta-core-client.js';
|
import { MetaCoreClient } from './meta-core-client.js';
|
||||||
|
|
||||||
|
// romajiIsoCode from @metazla/meta-interface is just 'jpl'
|
||||||
|
const romajiIsoCode = 'jpl';
|
||||||
|
|
||||||
export const manifest: PluginManifest = {
|
export const manifest: PluginManifest = {
|
||||||
id: 'anime-detector',
|
id: 'anime-detector',
|
||||||
name: 'Anime Detector',
|
name: 'Anime Detector',
|
||||||
@@ -21,41 +36,11 @@ export const manifest: PluginManifest = {
|
|||||||
schema: {
|
schema: {
|
||||||
anime: { label: 'Is Anime', type: 'boolean', readonly: true },
|
anime: { label: 'Is Anime', type: 'boolean', readonly: true },
|
||||||
'titles/jpn': { label: 'Japanese Title', type: 'string' },
|
'titles/jpn': { label: 'Japanese Title', type: 'string' },
|
||||||
|
[`titles/${romajiIsoCode}`]: { label: 'Romaji Title', type: 'string' },
|
||||||
},
|
},
|
||||||
config: {},
|
config: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Anime keywords from @metazla/filename-tools - fansub group identifiers only
|
|
||||||
// Note: Codec patterns (HEVC, x265, etc.) are NOT anime indicators
|
|
||||||
const ANIME_KEYWORDS = [
|
|
||||||
'[HorribleSubs]',
|
|
||||||
'[DB]', // Dragon Ball fansubs, also generic for various sub groups
|
|
||||||
'[Erai-raws]',
|
|
||||||
'[SubsPlease]',
|
|
||||||
'[GG]', // A known fansub group
|
|
||||||
'[Commie]', // A fansubbing group
|
|
||||||
'[Underwater]', // A fansubbing group
|
|
||||||
'[Nyaa]', // A reference to the Nyaa Torrents site, often included in anime torrents
|
|
||||||
'[FFF]', // A fansubbing group
|
|
||||||
'[Coalgirls]', // A fansubbing group known for high-quality releases
|
|
||||||
'[Chihiro]', // A fansubbing group
|
|
||||||
'[DameDesuYo]', // A fansubbing group
|
|
||||||
'[GJM]', // A fansubbing group (Good Job! Media)
|
|
||||||
'[AhorribleSubs]', // A misspelling of HorribleSubs sometimes used
|
|
||||||
'[AnimeRG]', // A group known for releasing anime torrents
|
|
||||||
'[Judas]', // A group known for their anime releases
|
|
||||||
'[Cleo]', // A group known for their anime encodes
|
|
||||||
'[LostYears]', // A group known for dub and sub releases
|
|
||||||
'[KaiDubs]', // A group known for dubbed anime releases
|
|
||||||
'[AnimeKaizoku]', // A website known for anime downloads, sometimes included in filenames
|
|
||||||
'[AnimeLand]', // A website known for anime downloads, sometimes included in filenames
|
|
||||||
'[Beatrice-Raws]', // A group known for raw anime blu-ray encodes
|
|
||||||
'[BakedFish]', // A less common but recognized release group
|
|
||||||
'[DeadFish]', // A group known for re-encoding fansubs into hardsubbed mp4 format
|
|
||||||
'[Golumpa]', // A group known for dubbed anime releases
|
|
||||||
'[EMBER]', // A group known for their anime releases
|
|
||||||
];
|
|
||||||
|
|
||||||
export async function process(
|
export async function process(
|
||||||
request: ProcessRequest,
|
request: ProcessRequest,
|
||||||
sendCallback: (payload: CallbackPayload) => Promise<void>
|
sendCallback: (payload: CallbackPayload) => Promise<void>
|
||||||
@@ -79,6 +64,7 @@ export async function process(
|
|||||||
|
|
||||||
const originalTitle = existingMeta?.originalTitle || '';
|
const originalTitle = existingMeta?.originalTitle || '';
|
||||||
const fileName = existingMeta?.fileName || '';
|
const fileName = existingMeta?.fileName || '';
|
||||||
|
|
||||||
let isAnime = false;
|
let isAnime = false;
|
||||||
let isJpn = false;
|
let isJpn = false;
|
||||||
|
|
||||||
@@ -88,8 +74,8 @@ export async function process(
|
|||||||
isJpn = true;
|
isJpn = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for anime keywords
|
// Check for anime keywords in filename (from @metazla/filename-tools)
|
||||||
for (const keyword of ANIME_KEYWORDS) {
|
for (const keyword of animeKeywords) {
|
||||||
if (fileName.includes(keyword)) {
|
if (fileName.includes(keyword)) {
|
||||||
isAnime = true;
|
isAnime = true;
|
||||||
isJpn = true;
|
isJpn = true;
|
||||||
@@ -97,45 +83,46 @@ export async function process(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if title is Japanese
|
// Check if title is in Japanese
|
||||||
if (originalTitle && (isKana(originalTitle) || isJapanese(originalTitle))) {
|
if (originalTitle && (isKana(originalTitle) || isJapanese(originalTitle))) {
|
||||||
isJpn = true;
|
isJpn = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check audio streams for Japanese language
|
// Check audio streams for Japanese language
|
||||||
for (let i = 0; i < 10; i++) {
|
for (let i = 0; i < 20; i++) {
|
||||||
const lang = existingMeta?.[`fileinfo/streamdetails/audio/${i}/language`];
|
const lang = existingMeta?.[`fileinfo/streamdetails/audio/${i}/language`];
|
||||||
if (lang === 'jpn' || lang === 'ja') {
|
if (!lang) break;
|
||||||
|
if (lang === 'jpn') {
|
||||||
isJpn = true;
|
isJpn = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!lang) break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check video streams for Japanese language (matching original)
|
// Check video streams for Japanese language
|
||||||
for (let i = 0; i < 10; i++) {
|
for (let i = 0; i < 20; i++) {
|
||||||
const lang = existingMeta?.[`fileinfo/streamdetails/video/${i}/language`];
|
const lang = existingMeta?.[`fileinfo/streamdetails/video/${i}/language`];
|
||||||
if (lang === 'jpn' || lang === 'ja') {
|
if (!lang) break;
|
||||||
|
if (lang === 'jpn') {
|
||||||
isJpn = true;
|
isJpn = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!lang) break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const metadata: Record<string, string> = {};
|
// Set Japanese title appropriately
|
||||||
|
|
||||||
if (isJpn && originalTitle) {
|
if (isJpn && originalTitle) {
|
||||||
if (isKana(originalTitle)) {
|
if (isKana(originalTitle)) {
|
||||||
metadata['titles/jpn'] = originalTitle;
|
await metaCore.setProperty(cid, 'titles/jpn', originalTitle);
|
||||||
} else {
|
} else {
|
||||||
metadata['titles/rom'] = originalTitle;
|
await metaCore.setProperty(cid, `titles/${romajiIsoCode}`, originalTitle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Anything Japanese is considered anime (as per original implementation)
|
||||||
if (isAnime || isJpn) {
|
if (isAnime || isJpn) {
|
||||||
metadata.anime = 'true';
|
await metaCore.setProperty(cid, 'anime', 'true');
|
||||||
await metaCore.mergeMetadata(cid, metadata);
|
// Use add for genres (RecordSet)
|
||||||
await metaCore.addToSet(cid, 'genres', 'Anime');
|
await metaCore.addToSet(cid, 'genres', 'Anime');
|
||||||
|
console.log(`[anime-detector] Detected anime: ${fileName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await sendCallback({
|
await sendCallback({
|
||||||
|
|||||||
Reference in New Issue
Block a user