Reduce code duplication and update README
- Extract shared utilities (HIDDEN_FILES, VIDEO_EXTENSIONS, fileApiUrl, thumbnailApiUrl, findFile) into new src/lib/media-utils.ts, removing identical copies from games.ts, movies.ts, tv.ts, files.ts, and scanner.ts - Add comment in files.ts clarifying why its VIDEO_EXTENSIONS set intentionally differs from the media library set (web-playable formats for the mixed browser) - Rewrite README to reflect the current feature set: Movies/TV libraries, auth system, tag system, background scanner, updated project structure, folder conventions for all four library types, and a complete API reference Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,9 +2,10 @@ import fs from 'fs'
|
||||
import path from 'path'
|
||||
import type { DirectoryListing, FileEntry, MediaType } from '@/types'
|
||||
import { resolveAndJail } from '@/lib/libraries'
|
||||
import { HIDDEN_FILES, fileApiUrl, thumbnailApiUrl } from './media-utils'
|
||||
|
||||
const HIDDEN_FILES = /^\./
|
||||
|
||||
// Web-playable video formats for the Mixed Media browser (distinct from the
|
||||
// broader set used by dedicated movie/TV scanners).
|
||||
const VIDEO_EXTENSIONS = new Set(['.mp4', '.mov', '.mkv', '.avi', '.webm', '.m4v'])
|
||||
const IMAGE_EXTENSIONS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.tiff', '.tif'])
|
||||
|
||||
@@ -15,14 +16,6 @@ function getMediaType(filename: string): MediaType {
|
||||
return 'other'
|
||||
}
|
||||
|
||||
function fileApiUrl(libraryId: string, relativePath: string): string {
|
||||
return `/api/file?libraryId=${encodeURIComponent(libraryId)}&path=${encodeURIComponent(relativePath)}`
|
||||
}
|
||||
|
||||
function thumbnailApiUrl(libraryId: string, relativePath: string): string {
|
||||
return `/api/thumbnail?libraryId=${encodeURIComponent(libraryId)}&path=${encodeURIComponent(relativePath)}`
|
||||
}
|
||||
|
||||
export function scanDirectory(
|
||||
libraryRoot: string,
|
||||
libraryId: string,
|
||||
|
||||
@@ -1,33 +1,7 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import type { Game, GameSeries } from '@/types'
|
||||
|
||||
const HIDDEN_FILES = /^\./
|
||||
|
||||
/**
|
||||
* Finds the first file in a directory whose basename (without extension)
|
||||
* matches the given pattern (case-insensitive).
|
||||
*/
|
||||
function findFile(dir: string, pattern: RegExp): string | null {
|
||||
let entries: string[]
|
||||
try {
|
||||
entries = fs.readdirSync(dir)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
const match = entries.find(
|
||||
(entry) => !HIDDEN_FILES.test(entry) && pattern.test(path.basename(entry, path.extname(entry)))
|
||||
)
|
||||
return match ?? null
|
||||
}
|
||||
|
||||
function fileApiUrl(libraryId: string, relativePath: string): string {
|
||||
return `/api/file?libraryId=${encodeURIComponent(libraryId)}&path=${encodeURIComponent(relativePath)}`
|
||||
}
|
||||
|
||||
function thumbnailApiUrl(libraryId: string, relativePath: string): string {
|
||||
return `/api/thumbnail?libraryId=${encodeURIComponent(libraryId)}&path=${encodeURIComponent(relativePath)}`
|
||||
}
|
||||
import { HIDDEN_FILES, fileApiUrl, thumbnailApiUrl, findFile } from './media-utils'
|
||||
|
||||
/**
|
||||
* Attempts to build a Game from a directory.
|
||||
|
||||
31
src/lib/media-utils.ts
Normal file
31
src/lib/media-utils.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
export const HIDDEN_FILES = /^\./
|
||||
|
||||
/** Video extensions for dedicated media libraries (movies, TV). */
|
||||
export const VIDEO_EXTENSIONS = new Set(['.mkv', '.mp4', '.avi', '.mov', '.m4v', '.wmv', '.ts', '.m2ts'])
|
||||
|
||||
export function fileApiUrl(libraryId: string, relativePath: string): string {
|
||||
return `/api/file?libraryId=${encodeURIComponent(libraryId)}&path=${encodeURIComponent(relativePath)}`
|
||||
}
|
||||
|
||||
export function thumbnailApiUrl(libraryId: string, relativePath: string): string {
|
||||
return `/api/thumbnail?libraryId=${encodeURIComponent(libraryId)}&path=${encodeURIComponent(relativePath)}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the first non-hidden file in a directory whose basename (without extension)
|
||||
* matches the given pattern (case-insensitive).
|
||||
*/
|
||||
export function findFile(dir: string, pattern: RegExp): string | null {
|
||||
let entries: string[]
|
||||
try {
|
||||
entries = fs.readdirSync(dir)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
return entries.find(
|
||||
(e) => !HIDDEN_FILES.test(e) && pattern.test(path.basename(e, path.extname(e)))
|
||||
) ?? null
|
||||
}
|
||||
@@ -2,34 +2,7 @@ import fs from 'fs'
|
||||
import path from 'path'
|
||||
import type { Movie } from '@/types'
|
||||
import { parseMovieNfo } from './nfo'
|
||||
|
||||
const HIDDEN_FILES = /^\./
|
||||
|
||||
const VIDEO_EXTENSIONS = new Set(['.mkv', '.mp4', '.avi', '.mov', '.m4v', '.wmv', '.ts', '.m2ts'])
|
||||
|
||||
function fileApiUrl(libraryId: string, relativePath: string): string {
|
||||
return `/api/file?libraryId=${encodeURIComponent(libraryId)}&path=${encodeURIComponent(relativePath)}`
|
||||
}
|
||||
|
||||
function thumbnailApiUrl(libraryId: string, relativePath: string): string {
|
||||
return `/api/thumbnail?libraryId=${encodeURIComponent(libraryId)}&path=${encodeURIComponent(relativePath)}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the first file in a directory whose basename (without extension)
|
||||
* matches the given pattern (case-insensitive).
|
||||
*/
|
||||
function findFile(dir: string, pattern: RegExp): string | null {
|
||||
let entries: string[]
|
||||
try {
|
||||
entries = fs.readdirSync(dir)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
return entries.find(
|
||||
(e) => !HIDDEN_FILES.test(e) && pattern.test(path.basename(e, path.extname(e)))
|
||||
) ?? null
|
||||
}
|
||||
import { HIDDEN_FILES, VIDEO_EXTENSIONS, fileApiUrl, thumbnailApiUrl, findFile } from './media-utils'
|
||||
|
||||
function findVideoFile(dir: string): string | null {
|
||||
let entries: string[]
|
||||
|
||||
@@ -8,8 +8,8 @@ import { scanMoviesLibrary } from './movies'
|
||||
import { scanTvLibrary, scanTvSeasons, scanTvEpisodes } from './tv'
|
||||
import { scanGamesLibrary } from './games'
|
||||
import { getThumbnailPath } from './thumbnails'
|
||||
import { VIDEO_EXTENSIONS } from './media-utils'
|
||||
|
||||
const VIDEO_EXTENSIONS = new Set(['.mkv', '.mp4', '.avi', '.mov', '.m4v', '.wmv', '.ts', '.m2ts'])
|
||||
const IMAGE_EXTENSIONS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp'])
|
||||
|
||||
let scanRunning = false
|
||||
|
||||
@@ -2,39 +2,12 @@ import fs from 'fs'
|
||||
import path from 'path'
|
||||
import type { TvSeries, TvSeason, TvEpisode } from '@/types'
|
||||
import { parseTvShowNfo, parseEpisodeNfo } from './nfo'
|
||||
|
||||
const HIDDEN_FILES = /^\./
|
||||
|
||||
const VIDEO_EXTENSIONS = new Set(['.mkv', '.mp4', '.avi', '.mov', '.m4v', '.wmv', '.ts', '.m2ts'])
|
||||
|
||||
function fileApiUrl(libraryId: string, relativePath: string): string {
|
||||
return `/api/file?libraryId=${encodeURIComponent(libraryId)}&path=${encodeURIComponent(relativePath)}`
|
||||
}
|
||||
|
||||
function thumbnailApiUrl(libraryId: string, relativePath: string): string {
|
||||
return `/api/thumbnail?libraryId=${encodeURIComponent(libraryId)}&path=${encodeURIComponent(relativePath)}`
|
||||
}
|
||||
import { HIDDEN_FILES, VIDEO_EXTENSIONS, fileApiUrl, thumbnailApiUrl, findFile } from './media-utils'
|
||||
|
||||
function isVideoFile(name: string): boolean {
|
||||
return VIDEO_EXTENSIONS.has(path.extname(name).toLowerCase())
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the first file in a directory whose basename (without extension)
|
||||
* matches the given pattern (case-insensitive).
|
||||
*/
|
||||
function findFile(dir: string, pattern: RegExp): string | null {
|
||||
let entries: string[]
|
||||
try {
|
||||
entries = fs.readdirSync(dir)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
return entries.find(
|
||||
(e) => !HIDDEN_FILES.test(e) && pattern.test(path.basename(e, path.extname(e)))
|
||||
) ?? null
|
||||
}
|
||||
|
||||
function readDirs(dir: string): string[] {
|
||||
try {
|
||||
return fs
|
||||
|
||||
Reference in New Issue
Block a user