tv-anarchy/Sources/TVAnarchyCore/Metadata/EnrichService.swift

48 lines
2.3 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Foundation
public enum EnrichError: Error, LocalizedError {
case failed(String)
public var errorDescription: String? { if case let .failed(m) = self { return m }; return nil }
}
/// Resolves a title against TMDB + the local IMDb index by shelling into
/// media-recommender's `enrich` module (single source of truth for the
/// TMDB/IMDb pipeline). Runs under a login shell so `uv` is on PATH. The CLI
/// degrades gracefully, so a result with only IMDb fields is normal and useful.
public final class EnrichService: Sendable {
private let projectDir: String
public init(projectDir: String? = nil) {
self.projectDir = projectDir
?? ProcessInfo.processInfo.environment["MEDIA_RECOMMENDER_DIR"]
?? RepoPaths.recommender.path
}
public func enrich(title: String, year: Int?, category: String? = nil) async throws -> EnrichResult {
var inner = ["uv", "run", "python", "-m", "media_rec.enrich", title]
if let year { inner.append(String(year)) }
// Route the keyless provider (anime AniList, tv/cartoons TVmaze).
if let category, !category.isEmpty { inner.append(contentsOf: ["--category", category]) }
let command = "cd \(shq(projectDir)) && " + inner.map(shq).joined(separator: " ")
let dir = projectDir
let r: ProcessResult = await Task.detached(priority: .utility) {
ProcessRunner.runShell(command, timeout: 60, cwd: dir)
}.value
guard r.ok else {
let detail = r.stderr.split(separator: "\n").last.map(String.init) ?? r.stderr
Log.error("enrich \(title) [\(category ?? "-")] failed (exit \(r.status)): \(r.stderr.suffix(300))")
throw EnrichError.failed(detail.isEmpty ? "enrich exited \(r.status)" : detail)
}
Log.info("enrich \(title) [\(category ?? "-")] ok")
guard let data = r.stdout.trimmingCharacters(in: .whitespacesAndNewlines).data(using: .utf8),
!data.isEmpty else {
throw EnrichError.failed("enrich produced no output")
}
do { return try JSONDecoder().decode(EnrichResult.self, from: data) }
catch { throw EnrichError.failed("decode failed: \(error.localizedDescription)") }
}
private func shq(_ s: String) -> String {
"'" + s.replacingOccurrences(of: "'", with: "'\\''") + "'"
}
}