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 \
|
||||||
|
|||||||
+4
-1
@@ -5,6 +5,9 @@
|
|||||||
"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", "iso-639-1": "^3.1.2", "iso-639-3": "^3.0.1" },
|
"dependencies": {
|
||||||
|
"fastify": "^5.0.0",
|
||||||
|
"@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: language
|
||||||
|
*
|
||||||
|
* ============================================================================
|
||||||
|
* 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 (language) only requires mount 2:
|
||||||
|
* - /cache (RW) - to cache language aggregation results
|
||||||
|
* - Note: This plugin aggregates metadata from ffmpeg, doesn't read files
|
||||||
|
*
|
||||||
|
* 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';
|
||||||
|
|||||||
+46
-65
@@ -1,11 +1,16 @@
|
|||||||
/**
|
/**
|
||||||
* Language Plugin
|
* Language Plugin
|
||||||
* Aggregates languages from all streams and determines primary language
|
*
|
||||||
* Uses proper ISO 639 conversion matching @metazla/filename-tools
|
* Aggregates languages from all streams (audio, video, subtitle)
|
||||||
|
* and converts them to ISO 639-3 format.
|
||||||
|
* Determines the primary language based on first audio > video > subtitle stream.
|
||||||
|
*
|
||||||
|
* Matches old LanguageProcessor output:
|
||||||
|
* - languages (add)
|
||||||
|
* - titles/{lang} (set originalTitle)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ISO6391 from 'iso-639-1';
|
import { anyTo_iso_639_3 } from '@metazla/filename-tools';
|
||||||
import { iso6393To2T } from 'iso-639-3';
|
|
||||||
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';
|
||||||
|
|
||||||
@@ -22,45 +27,10 @@ export const manifest: PluginManifest = {
|
|||||||
timeout: 30000,
|
timeout: 30000,
|
||||||
schema: {
|
schema: {
|
||||||
languages: { label: 'Languages', type: 'array', readonly: true },
|
languages: { label: 'Languages', type: 'array', readonly: true },
|
||||||
primaryLanguage: { label: 'Primary Language', type: 'string', readonly: true },
|
|
||||||
},
|
},
|
||||||
config: {},
|
config: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build ISO 639-1 to ISO 639-3 mapping (matching @metazla/filename-tools)
|
|
||||||
const iso6391To3: Record<string, string> = {};
|
|
||||||
for (const key in iso6393To2T) {
|
|
||||||
const value = (iso6393To2T as Record<string, string>)[key];
|
|
||||||
iso6391To3[value] = key;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert any language format to ISO 639-3
|
|
||||||
* Matches anyTo_iso_639_3 from @metazla/filename-tools
|
|
||||||
*/
|
|
||||||
function anyTo_iso_639_3(languageName: string): string | null {
|
|
||||||
if (!languageName) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (languageName.length === 3) {
|
|
||||||
// Could be ISO 639-3 or ISO 639-2T or ISO 639-2B
|
|
||||||
// All 3 are similar, just return the input
|
|
||||||
return languageName;
|
|
||||||
}
|
|
||||||
if (languageName.length === 2) {
|
|
||||||
// Could be ISO 639-1 => validate and convert
|
|
||||||
return iso6391To3[languageName] || 'und';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Full language name - convert to ISO 639-3
|
|
||||||
const code1 = ISO6391.getCode(languageName);
|
|
||||||
if (iso6391To3[code1]) {
|
|
||||||
return iso6391To3[code1];
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function process(
|
export async function process(
|
||||||
request: ProcessRequest,
|
request: ProcessRequest,
|
||||||
sendCallback: (payload: CallbackPayload) => Promise<void>
|
sendCallback: (payload: CallbackPayload) => Promise<void>
|
||||||
@@ -71,56 +41,67 @@ export async function process(
|
|||||||
try {
|
try {
|
||||||
const { cid, existingMeta } = request;
|
const { cid, existingMeta } = request;
|
||||||
|
|
||||||
const languages = new Set<string>();
|
let streamLanguage: string | null = null;
|
||||||
let primaryLanguage: string | null = null;
|
|
||||||
|
|
||||||
// Collect from audio streams (highest priority)
|
// Collect languages from title keys (if existingMeta has titles/*)
|
||||||
|
for (const key of Object.keys(existingMeta || {})) {
|
||||||
|
if (key.startsWith('titles/')) {
|
||||||
|
const langCode = key.substring(7); // Remove 'titles/' prefix
|
||||||
|
const normalized = anyTo_iso_639_3(langCode);
|
||||||
|
if (normalized) {
|
||||||
|
await metaCore.addToSet(cid, 'languages', normalized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect languages from audio streams (highest priority for primary)
|
||||||
for (let i = 0; i < 20; 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) break;
|
if (!lang) break;
|
||||||
const computedLanguage = anyTo_iso_639_3(lang);
|
const computedLanguage = anyTo_iso_639_3(lang);
|
||||||
if (computedLanguage && computedLanguage !== 'und') {
|
if (computedLanguage) {
|
||||||
languages.add(computedLanguage);
|
await metaCore.addToSet(cid, 'languages', computedLanguage);
|
||||||
if (!primaryLanguage) primaryLanguage = computedLanguage;
|
if (!streamLanguage) {
|
||||||
|
streamLanguage = computedLanguage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect from video streams
|
// Collect languages from video streams
|
||||||
for (let i = 0; i < 20; 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) break;
|
if (!lang) break;
|
||||||
const computedLanguage = anyTo_iso_639_3(lang);
|
const computedLanguage = anyTo_iso_639_3(lang);
|
||||||
if (computedLanguage && computedLanguage !== 'und') {
|
if (computedLanguage) {
|
||||||
languages.add(computedLanguage);
|
await metaCore.addToSet(cid, 'languages', computedLanguage);
|
||||||
if (!primaryLanguage) primaryLanguage = computedLanguage;
|
if (!streamLanguage) {
|
||||||
|
streamLanguage = computedLanguage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect from subtitle streams
|
// Collect languages from subtitle streams
|
||||||
for (let i = 0; i < 20; i++) {
|
for (let i = 0; i < 20; i++) {
|
||||||
const lang = existingMeta?.[`fileinfo/streamdetails/subtitle/${i}/language`];
|
const lang = existingMeta?.[`fileinfo/streamdetails/subtitle/${i}/language`];
|
||||||
if (!lang) break;
|
if (!lang) break;
|
||||||
const computedLanguage = anyTo_iso_639_3(lang);
|
const computedLanguage = anyTo_iso_639_3(lang);
|
||||||
if (computedLanguage && computedLanguage !== 'und') {
|
if (computedLanguage) {
|
||||||
languages.add(computedLanguage);
|
await metaCore.addToSet(cid, 'languages', computedLanguage);
|
||||||
if (!primaryLanguage) primaryLanguage = computedLanguage;
|
if (!streamLanguage) {
|
||||||
|
streamLanguage = computedLanguage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add languages to set
|
// Determine the language of the media file based on:
|
||||||
for (const lang of languages) {
|
// 1st audio stream, 2nd video stream, 3rd subtitle stream (first found)
|
||||||
await metaCore.addToSet(cid, 'languages', lang);
|
// Assume original title is one of the stream languages or English if not found
|
||||||
|
const originalTitle = existingMeta?.originalTitle;
|
||||||
|
if (originalTitle) {
|
||||||
|
await metaCore.setProperty(cid, `titles/${streamLanguage || 'eng'}`, originalTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set primary language and title language mapping
|
console.log(`[language] Aggregated languages, primary: ${streamLanguage || 'none'}`);
|
||||||
if (primaryLanguage) {
|
|
||||||
await metaCore.setProperty(cid, 'primaryLanguage', primaryLanguage);
|
|
||||||
|
|
||||||
const originalTitle = existingMeta?.originalTitle;
|
|
||||||
if (originalTitle) {
|
|
||||||
await metaCore.setProperty(cid, `titles/${primaryLanguage}`, originalTitle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await sendCallback({
|
await sendCallback({
|
||||||
taskId: request.taskId,
|
taskId: request.taskId,
|
||||||
|
|||||||
Reference in New Issue
Block a user