Files
terebi/Sources/CLI/TerebiCLI.swift
T
hl-valdemar 31c4759c1d build: bundle playback and YouTube dependencies
Build pinned LGPL playback libraries and package standalone YouTube helpers inside the signed native app. Keep the CLI inside the app and relocate private library links for installation without runtime Homebrew dependencies.

Record adaptive fixture completion in its EXIT trap: cancellation can exit with status 143 before the TERM trap writes its marker, while native pause and quality preservation remain correct.
2026-10-12 01:52:51 +02:00

85 lines
3.8 KiB
Swift

import AppKit
import Darwin
import TerebiConfiguration
@main
@MainActor
enum TerebiCLI {
static func main() async {
let command: PlayerCommand
do {
command = try PlayerCommand.parse(Array(CommandLine.arguments.dropFirst()))
} catch {
fail(error.localizedDescription, code: 64)
}
switch command {
case .help:
print(PlayerCommand.usage)
case .printDefaultConfig:
print(PlayerConfiguration.defaultTOML, terminator: "")
case .launch(let url, let newInstance, let noConfig, let progressFile):
if let progressFile {
var existing = stat()
guard lstat(progressFile.path, &existing) != 0 else {
fail("Progress file already exists: \(progressFile.path). Supply a nonexistent destination.", code: 73)
}
guard errno == ENOENT else {
fail("Could not inspect progress file \(progressFile.path): \(String(cString: strerror(errno))).", code: 73)
}
}
let configURL = PlayerConfiguration.fileURL(environment: ProcessInfo.processInfo.environment,
homeDirectory: FileManager.default.homeDirectoryForCurrentUser)
if !noConfig {
do {
_ = try PlayerConfiguration.load(from: configURL)
} catch {
fail(error.localizedDescription, code: 78)
}
}
var size: UInt32 = 0
_ = _NSGetExecutablePath(nil, &size)
var path = [CChar](repeating: 0, count: Int(size))
guard _NSGetExecutablePath(&path, &size) == 0 else {
fail("Could not locate the CLI executable.", code: 69)
}
let executable = path.withUnsafeBufferPointer {
URL(fileURLWithPath: String(cString: $0.baseAddress!)).resolvingSymlinksInPath()
}
let macOS = executable.deletingLastPathComponent()
let contents = macOS.deletingLastPathComponent()
let app = contents.deletingLastPathComponent()
guard macOS.lastPathComponent == "MacOS", contents.lastPathComponent == "Contents",
app.pathExtension == "app" else {
fail("The CLI must be installed inside terebi.app. Rebuild and reinstall the bundled app.", code: 69)
}
if let error = FFmpegRuntime.error(for: app) {
fail(error, code: 69)
}
let configuration = NSWorkspace.OpenConfiguration()
configuration.createsNewApplicationInstance = newInstance
// LaunchServices does not inherit the invoking shell's XDG environment.
configuration.arguments = noConfig ? ["--no-config"] : ["--config-file", configURL.absoluteString]
if let progressFile {
configuration.arguments += ["--progress-file", progressFile.path]
}
if newInstance, let url {
configuration.arguments += ["--open-media", url.absoluteString]
}
do {
// Launch an application, not a document, so SwiftUI creates its singleton window.
_ = try await NSWorkspace.shared.openApplication(at: app, configuration: configuration)
if !newInstance, let url {
_ = try await NSWorkspace.shared.open([url], withApplicationAt: app, configuration: configuration)
}
} catch {
fail("Could not launch the player: \(error.localizedDescription)", code: 69)
}
}
}
private static func fail(_ message: String, code: Int32) -> Never {
FileHandle.standardError.write(Data("terebi: \(message)\n".utf8))
exit(code)
}
}