This commit is contained in:
@@ -1,5 +1,8 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
|
# Locally-built @metazla/filename-tools tarball (vendored file: dep; rebuilt from
|
||||||
|
# packages/filename-tool — see Dockerfile note). Build artifact, not source.
|
||||||
|
vendor/
|
||||||
*.log
|
*.log
|
||||||
.env
|
.env
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
+10
-9
@@ -12,13 +12,16 @@ RUN npm install -g corepack@latest && corepack enable
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Copy package files first for layer caching
|
# Copy package files first for layer caching. vendor/ holds the locally-built
|
||||||
|
# @metazla/filename-tools tarball (file: dep) — replaces github:worph/filename-tool
|
||||||
|
# so local lib changes ship without a GitHub push. The tarball is pre-built
|
||||||
|
# (dist/ included), so no in-place rebuild step is needed. --no-frozen-lockfile
|
||||||
|
# because the committed lockfile still pins the old github tarball; pnpm
|
||||||
|
# reconciles it to the file: dep at build time.
|
||||||
COPY package.json pnpm-lock.yaml ./
|
COPY package.json pnpm-lock.yaml ./
|
||||||
|
COPY vendor/ ./vendor/
|
||||||
RUN corepack install
|
RUN corepack install
|
||||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --no-frozen-lockfile
|
||||||
|
|
||||||
# Build the git dependency (filename-tools)
|
|
||||||
RUN cd node_modules/@metazla/filename-tools && pnpm install && pnpm run build
|
|
||||||
|
|
||||||
# Copy source and build
|
# Copy source and build
|
||||||
COPY tsconfig.json ./
|
COPY tsconfig.json ./
|
||||||
@@ -38,11 +41,9 @@ RUN npm install -g corepack@latest && corepack enable
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY package.json pnpm-lock.yaml ./
|
COPY package.json pnpm-lock.yaml ./
|
||||||
|
COPY vendor/ ./vendor/
|
||||||
RUN corepack install
|
RUN corepack install
|
||||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile --prod
|
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --no-frozen-lockfile --prod
|
||||||
|
|
||||||
# Build the git dependency in production too
|
|
||||||
RUN cd node_modules/@metazla/filename-tools && pnpm install && pnpm run build
|
|
||||||
|
|
||||||
COPY --from=builder /app/dist ./dist
|
COPY --from=builder /app/dist ./dist
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fastify": "^5.0.0",
|
"fastify": "^5.0.0",
|
||||||
"@metazla/filename-tools": "github:worph/filename-tool"
|
"@metazla/filename-tools": "file:vendor/filename-tools.tgz"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.0.0",
|
"@types/node": "^20.0.0",
|
||||||
|
|||||||
Generated
+5
-5
@@ -9,8 +9,8 @@ importers:
|
|||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@metazla/filename-tools':
|
'@metazla/filename-tools':
|
||||||
specifier: github:worph/filename-tool
|
specifier: file:vendor/filename-tools.tgz
|
||||||
version: https://codeload.github.com/worph/filename-tool/tar.gz/8a9c357c8af20bf7af040985993f5f78d1aa5251
|
version: file:vendor/filename-tools.tgz
|
||||||
fastify:
|
fastify:
|
||||||
specifier: ^5.0.0
|
specifier: ^5.0.0
|
||||||
version: 5.7.4
|
version: 5.7.4
|
||||||
@@ -193,8 +193,8 @@ packages:
|
|||||||
'@jridgewell/[email protected]':
|
'@jridgewell/[email protected]':
|
||||||
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
||||||
|
|
||||||
'@metazla/filename-tools@https://codeload.github.com/worph/filename-tool/tar.gz/8a9c357c8af20bf7af040985993f5f78d1aa5251':
|
'@metazla/filename-tools@file:vendor/filename-tools.tgz':
|
||||||
resolution: {tarball: https://codeload.github.com/worph/filename-tool/tar.gz/8a9c357c8af20bf7af040985993f5f78d1aa5251}
|
resolution: {integrity: sha512-S20j5k3Ga2Dn8c2fThKmWsGjPzmb4Ckr/OTFjG5WQX1Ua1ry8X88/Cfql+AYaNGCjTbgIG51T4SbIMgGd285XA==, tarball: file:vendor/filename-tools.tgz}
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|
||||||
'@pinojs/[email protected]':
|
'@pinojs/[email protected]':
|
||||||
@@ -953,7 +953,7 @@ snapshots:
|
|||||||
|
|
||||||
'@jridgewell/[email protected]': {}
|
'@jridgewell/[email protected]': {}
|
||||||
|
|
||||||
'@metazla/filename-tools@https://codeload.github.com/worph/filename-tool/tar.gz/8a9c357c8af20bf7af040985993f5f78d1aa5251':
|
'@metazla/filename-tools@file:vendor/filename-tools.tgz':
|
||||||
dependencies:
|
dependencies:
|
||||||
file-type: 19.6.0
|
file-type: 19.6.0
|
||||||
franc: 6.2.0
|
franc: 6.2.0
|
||||||
|
|||||||
+6
-2
@@ -35,8 +35,12 @@ app.post<{ Body: ConfigureRequest }>('/configure', async (request): Promise<Conf
|
|||||||
return { status: 'ok' };
|
return { status: 'ok' };
|
||||||
});
|
});
|
||||||
app.post<{ Body: ProcessRequest }>('/process', async (request, reply) => {
|
app.post<{ Body: ProcessRequest }>('/process', async (request, reply) => {
|
||||||
const { taskId, cid, filePath, callbackUrl, metaCoreUrl } = request.body;
|
// filePath is optional: the parser works on a name string, which may be
|
||||||
if (!taskId || !cid || !filePath || !callbackUrl || !metaCoreUrl) {
|
// supplied either as filePath or via existingMeta.fileName (the gateway
|
||||||
|
// feeder path, where no file exists on disk). The required wire fields are
|
||||||
|
// the write target (cid) + the callback/meta-core plumbing.
|
||||||
|
const { taskId, cid, callbackUrl, metaCoreUrl } = request.body;
|
||||||
|
if (!taskId || !cid || !callbackUrl || !metaCoreUrl) {
|
||||||
return reply.send({ status: 'rejected', error: 'Missing required fields' } as ProcessResponse);
|
return reply.send({ status: 'rejected', error: 'Missing required fields' } as ProcessResponse);
|
||||||
}
|
}
|
||||||
processFile(request.body, async (payload: CallbackPayload) => {
|
processFile(request.body, async (payload: CallbackPayload) => {
|
||||||
|
|||||||
+45
-1
@@ -4,6 +4,7 @@
|
|||||||
* Uses @metazla/filename-tools library for parsing
|
* Uses @metazla/filename-tools library for parsing
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { env } from 'node:process';
|
||||||
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';
|
||||||
import {
|
import {
|
||||||
@@ -38,6 +39,16 @@ export const manifest: PluginManifest = {
|
|||||||
config: {},
|
config: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit the title-derived release tags (quality / codec / languages) added in the
|
||||||
|
* filename-tools lib. **Opt-in (default off)** because these are title *guesses*:
|
||||||
|
* in meta-sort the authoritative values come from probing the real stream
|
||||||
|
* (ffmpeg / language plugin), and a title-guessed `languages/<iso>` set member
|
||||||
|
* would pollute that. Gateway feeders (no real file) enable it via
|
||||||
|
* `EMIT_TITLE_TAGS=true`; meta-sort leaves it off. (`real-probe > title-guess`.)
|
||||||
|
*/
|
||||||
|
const EMIT_TITLE_TAGS = /^(1|true|yes)$/i.test(env.EMIT_TITLE_TAGS ?? '');
|
||||||
|
|
||||||
// Create extractor instance with default config
|
// Create extractor instance with default config
|
||||||
const extractor = new FileNameVideoMetaExtractor(
|
const extractor = new FileNameVideoMetaExtractor(
|
||||||
[], // watchFolderList - empty for plugin context
|
[], // watchFolderList - empty for plugin context
|
||||||
@@ -60,8 +71,22 @@ export async function process(
|
|||||||
try {
|
try {
|
||||||
const { cid, filePath } = request;
|
const { cid, filePath } = request;
|
||||||
|
|
||||||
|
// The parser operates on a name string. Accept it via filePath or, when
|
||||||
|
// the caller has no file on disk (gateway feeder path), via
|
||||||
|
// existingMeta.fileName. Skip cleanly if neither is present.
|
||||||
|
const nameToParse = filePath || request.existingMeta?.fileName;
|
||||||
|
if (!nameToParse) {
|
||||||
|
await sendCallback({
|
||||||
|
taskId: request.taskId,
|
||||||
|
status: 'skipped',
|
||||||
|
duration: Date.now() - startTime,
|
||||||
|
reason: 'No filePath or existingMeta.fileName to parse',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Use the library to extract metadata
|
// Use the library to extract metadata
|
||||||
const metadata = extractor.extractVideoFileMetadata(filePath);
|
const metadata = extractor.extractVideoFileMetadata(nameToParse);
|
||||||
|
|
||||||
// Convert to record for merging
|
// Convert to record for merging
|
||||||
const result: Record<string, string> = {};
|
const result: Record<string, string> = {};
|
||||||
@@ -73,6 +98,25 @@ export async function process(
|
|||||||
if (metadata.videoType) result.videoType = metadata.videoType;
|
if (metadata.videoType) result.videoType = metadata.videoType;
|
||||||
if (metadata.extra) result.extra = metadata.extra;
|
if (metadata.extra) result.extra = metadata.extra;
|
||||||
|
|
||||||
|
// Title-derived release tags — opt-in (see EMIT_TITLE_TAGS). quality /
|
||||||
|
// codec are scalars; languages is a `languages/<iso> = "true"` key-set
|
||||||
|
// (METADATA_KEYS §1), the same shape gateway feeders + meta-watch use.
|
||||||
|
if (EMIT_TITLE_TAGS) {
|
||||||
|
if (metadata.quality) result.quality = metadata.quality;
|
||||||
|
if (metadata.codec) result.codec = metadata.codec;
|
||||||
|
for (const lang of metadata.languages ?? []) {
|
||||||
|
result[`languages/${lang}`] = 'true';
|
||||||
|
}
|
||||||
|
// contentKind mirrors videoType (movie→movie, tvshow→episode).
|
||||||
|
// Gateway feeders retire their Rust filename port and rely on this
|
||||||
|
// plugin as the sole filename source, so it owns this derivation
|
||||||
|
// too (meta-watch reads contentKind to detect/aggregate episodes).
|
||||||
|
// Gated with the other title-derived tags so meta-sort (the real
|
||||||
|
// file is the authority there, flag off) is unaffected.
|
||||||
|
if (metadata.videoType === 'tvshow') result.contentKind = 'episode';
|
||||||
|
else if (metadata.videoType === 'movie') result.contentKind = 'movie';
|
||||||
|
}
|
||||||
|
|
||||||
if (Object.keys(result).length > 0) {
|
if (Object.keys(result).length > 0) {
|
||||||
await metaCore.mergeMetadata(cid, result);
|
await metaCore.mergeMetadata(cid, result);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -41,7 +41,8 @@ export interface HealthResponse {
|
|||||||
export interface ProcessRequest {
|
export interface ProcessRequest {
|
||||||
taskId: string;
|
taskId: string;
|
||||||
cid: string;
|
cid: string;
|
||||||
filePath: string;
|
/** Optional: falls back to existingMeta.fileName as the name to parse. */
|
||||||
|
filePath?: string;
|
||||||
callbackUrl: string;
|
callbackUrl: string;
|
||||||
metaCoreUrl: string;
|
metaCoreUrl: string;
|
||||||
existingMeta?: Record<string, string>;
|
existingMeta?: Record<string, string>;
|
||||||
|
|||||||
Reference in New Issue
Block a user