Files
meta-fuse/packages/meta-fuse-core/src/vfs/RenamingRule.ts
T
Worph d35a64a744
Build and Push Docker Image / build (push) Failing after 15s
align with metadata standard
2026-09-13 15:57:12 +02:00

126 lines
4.3 KiB
TypeScript

/**
* Renaming Rule - Defines folder organization logic
*
* Migrated from meta-mesh to meta-fuse for virtual path computation.
* Organizes files into structured folders based on metadata:
* - TV Shows/{title}/S01/{filename} for series
* - Movies/{title} (year)/{filename} for movies
*/
/**
* Minimal metadata interface for folder organization
* Contains only the fields needed for virtual path computation
*/
export interface MediaMeta {
// Title information
originalTitle?: string;
title?: string;
// Series metadata
season?: number;
episode?: number;
extra?: boolean;
// Movie metadata
movieYear?: number;
year?: number;
// File type info
fileType?: string; // 'video', 'subtitle', 'torrent'
extension?: string;
// Version/variant
version?: string;
subtitleLanguage?: string;
// Allow additional fields
[key: string]: unknown;
}
/**
* Compute the virtual path for a file based on its metadata
* Returns the path relative to the VFS root
*
* @param metadata File metadata with title, season, episode, year, etc.
* @param filepath Original file path (used for warnings)
* @returns Virtual path (e.g., "TV Shows/Show Name/S01/Episode.mkv") or null if unsupported
*/
export function renamingRule(metadata: Partial<MediaMeta>, filepath?: string): string | null {
const supported = ['video', 'subtitle', 'torrent'];
// Check if filetype is one of the supported ones
if (!metadata.fileType || !supported.includes(metadata.fileType)) {
return null;
}
// Extract the file extension
const extension = metadata.extension;
if (!extension) {
console.warn(`[RenamingRule] No extension found for file: ${filepath}`);
return null;
}
// Get title - try various fields
// The display name — never the `titles/*` name set (METADATA_KEYS.md §3).
const title = metadata.title || metadata.originalTitle;
if (!title) {
if (metadata.fileType !== 'torrent') {
// Don't warn for torrent files because they don't always have a title
console.warn(`[RenamingRule] No title found for file: ${filepath}`);
}
return null;
}
let newPath = '';
// Build filename components
const season = metadata.season ? ('S' + String(metadata.season).padStart(2, '0')) : '';
const seasonSpace = metadata.season ? ` ${season}` : '';
const episode = metadata.episode ? ('E' + String(metadata.episode).padStart(2, '0')) : '';
const version = metadata.version ? ` ${metadata.version}` : '';
const year = metadata.movieYear ? ` (${metadata.movieYear})` : '';
const subtitle = metadata.fileType === 'subtitle' && metadata.subtitleLanguage
? `.${metadata.subtitleLanguage}`
: '';
const fileName = `${title}${seasonSpace}${episode}${year}${subtitle}${version}.${extension}`;
// Determine structure: TV Shows for series, Movies for standalone content
// Note: season can be 0 (special episodes), so check for null/undefined explicitly
const hasSeason = metadata.season !== null && metadata.season !== undefined;
const hasEpisode = metadata.episode !== null && metadata.episode !== undefined;
if (metadata.extra) {
newPath = `TV Shows/${title}/extra/${fileName}`;
} else if (hasSeason && hasEpisode) {
newPath = `TV Shows/${title}/${season}/${fileName}`;
} else {
newPath = `Movies/${title}${year}/${fileName}`;
}
return newPath;
}
/**
* Sanitize a path to remove illegal characters
* Works for both Windows and Unix-like systems
*/
export function sanitizePath(newPath: string): string {
// Isolate any potential Windows drive letter at the start
const driveMatch = newPath.match(/^[a-zA-Z]:/);
const drive = driveMatch ? driveMatch[0] : '';
// Define illegal characters for Windows and potentially problematic ones for Unix
const illegalChars = /[<>:"|?*]/g;
// Replace illegal characters in the path, excluding the drive part
const pathWithoutDrive = newPath.slice(drive.length);
const sanitizedPath = pathWithoutDrive.replace(illegalChars, '');
// Combine the drive (if any) and the sanitized path
const cleanPath = drive + sanitizedPath;
// Normalize to forward slashes (works cross-platform)
return cleanPath.replace(/\\/g, '/');
}