Nachsehen, was in den Tonspuren wirklich steht
Die Sprachwahl vergleicht Kürzel, und Kürzel lügen — beim ZDF-Fall ist die als deutsch ausgezeichnete Spur die mit Audiodeskription. Ob der **Name** der Spur das rettet, sieht man nur, wenn man beides nebeneinander legt. Alles andere wäre geraten, und davon hatten wir heute genug. `--spuren <host> <suchbegriff>` sucht einen Titel und druckt zu jeder Tonspur: Kürzel, Name aus der Datei, wie Jellyfin es zusammensetzt, Codec, Kanäle und die Kennzeichen. Dafür holt der Decoder jetzt auch `Title`, `IsHearingImpaired` und `IsOriginal`. `Title` ist das zweite Signal neben dem Kürzel und manchmal das einzige richtige: wo „eng" steht, kann im Namen „Deutsch" stehen. Wir haben es bisher weggeworfen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
41cd7331a9
commit
946de29893
@@ -446,3 +446,80 @@ private func anfrage(_ url: URL, _ kopfzeilen: [String: String]) -> URLRequest {
|
||||
for (name, wert) in kopfzeilen { request.setValue(wert, forHTTPHeaderField: name) }
|
||||
return request
|
||||
}
|
||||
|
||||
/// Zeigt, was der Server über die Tonspuren eines Titels wirklich sagt.
|
||||
///
|
||||
/// **Der Grund:** Die Sprachwahl vergleicht Kürzel, und Kürzel lügen. Ob bei
|
||||
/// einer Datei der Titel das rettet — „eng" im Kürzel, „Deutsch" im Namen —,
|
||||
/// sieht man nur, wenn man beides nebeneinander legt. Alles andere wäre
|
||||
/// geraten.
|
||||
func zeigeSpuren(host: String, suche: String) async {
|
||||
guard let basis = URL(string: host.contains("://") ? host : "http://\(host)") else { return }
|
||||
|
||||
print("Benutzername: ", terminator: "")
|
||||
guard let name = readLine(), !name.isEmpty else { return }
|
||||
guard let passwort = String(validatingCString: getpass("Passwort: ")), !passwort.isEmpty else { return }
|
||||
|
||||
let identity = JellyfinIdentity(
|
||||
client: "Flixy-Probe",
|
||||
device: "Mac",
|
||||
deviceID: "flixy-probe-" + UUID().uuidString,
|
||||
version: "1.0"
|
||||
)
|
||||
let client = JellyfinClient(baseURL: basis, identity: identity)
|
||||
|
||||
guard let konto = try? await JellyfinAuthenticator(client: client).signIn(
|
||||
userName: name,
|
||||
password: passwort
|
||||
) else {
|
||||
print("Anmeldung gescheitert.")
|
||||
return
|
||||
}
|
||||
print("Angemeldet als \(konto.userName).\n")
|
||||
|
||||
// Suchen, damit man nicht mit Kennungen hantieren muss.
|
||||
let treffer = try? await client.send(
|
||||
JellyfinRequest(path: "/Users/\(konto.userID)/Items", query: [
|
||||
URLQueryItem(name: "searchTerm", value: suche),
|
||||
URLQueryItem(name: "Recursive", value: "true"),
|
||||
URLQueryItem(name: "IncludeItemTypes", value: "Movie,Episode"),
|
||||
URLQueryItem(name: "Limit", value: "5"),
|
||||
URLQueryItem(name: "Fields", value: "MediaSources"),
|
||||
]),
|
||||
as: JellyfinItemPage.self
|
||||
)
|
||||
|
||||
guard let gefunden = treffer?.items, !gefunden.isEmpty else {
|
||||
print("Nichts gefunden zu „\(suche)“.")
|
||||
return
|
||||
}
|
||||
|
||||
for eintrag in gefunden {
|
||||
print("── \(eintrag.name ?? "—")")
|
||||
let quellen = eintrag.mediaSources ?? []
|
||||
guard let streams = quellen.first?.mediaStreams else {
|
||||
print(" keine Spurangaben\n")
|
||||
continue
|
||||
}
|
||||
|
||||
for spur in streams where spur.type == "Audio" {
|
||||
let kürzel = spur.language ?? "—"
|
||||
let name = spur.title ?? "—"
|
||||
var merkmale: [String] = []
|
||||
if spur.isDefault == true { merkmale.append("Standard") }
|
||||
if spur.isForced == true { merkmale.append("erzwungen") }
|
||||
if spur.isHearingImpaired == true { merkmale.append("hörgeschädigt") }
|
||||
if spur.isOriginal == true { merkmale.append("Original") }
|
||||
|
||||
print(" [\(spur.index ?? -1)] Kürzel \(kürzel)")
|
||||
print(" Name in der Datei: \(name)")
|
||||
print(" Wie Jellyfin es zeigt: \(spur.displayTitle ?? "—")")
|
||||
print(" \(spur.codec ?? "—"), \(spur.channels ?? 0) Kanäle"
|
||||
+ (merkmale.isEmpty ? "" : ", " + merkmale.joined(separator: ", ")))
|
||||
}
|
||||
print("")
|
||||
}
|
||||
|
||||
print("Zu lesen ist das so: stimmt das Kürzel nicht mit dem Namen überein,")
|
||||
print("lügt die Datei — und dann hilft nur der Name oder das Merken.")
|
||||
}
|
||||
|
||||
@@ -27,6 +27,13 @@ if let index = CommandLine.arguments.firstIndex(of: "--playlist"),
|
||||
exit(0)
|
||||
}
|
||||
|
||||
if let index = CommandLine.arguments.firstIndex(of: "--spuren"),
|
||||
let host = CommandLine.arguments.dropFirst(index + 1).first,
|
||||
let suche = CommandLine.arguments.dropFirst(index + 2).first {
|
||||
await zeigeSpuren(host: host, suche: suche)
|
||||
exit(0)
|
||||
}
|
||||
|
||||
if let index = CommandLine.arguments.firstIndex(of: "--jellyfin"),
|
||||
let host = CommandLine.arguments.dropFirst(index + 1).first {
|
||||
await jellyfinLeiter(host: host)
|
||||
|
||||
@@ -168,6 +168,16 @@ public struct JellyfinMediaStream: Decodable, Sendable {
|
||||
public let codec: String?
|
||||
public let language: String?
|
||||
public let displayTitle: String?
|
||||
/// Was **in der Datei** steht — im Gegensatz zu `displayTitle`, das
|
||||
/// Jellyfin aus Sprache, Codec und Kennzeichen zusammensetzt.
|
||||
///
|
||||
/// Das zweite Signal neben dem Sprachkürzel, und manchmal das einzige
|
||||
/// richtige: wo „eng" steht, kann im Titel „Deutsch" stehen.
|
||||
public let title: String?
|
||||
/// Für Hörgeschädigte. **Nicht** Audiodeskription — dafür hat Jellyfin
|
||||
/// kein Kennzeichen.
|
||||
public let isHearingImpaired: Bool?
|
||||
public let isOriginal: Bool?
|
||||
public let channels: Int?
|
||||
public let isDefault: Bool?
|
||||
public let isForced: Bool?
|
||||
@@ -177,6 +187,8 @@ public struct JellyfinMediaStream: Decodable, Sendable {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case index = "Index", type = "Type", codec = "Codec", language = "Language"
|
||||
case displayTitle = "DisplayTitle", channels = "Channels"
|
||||
case title = "Title"
|
||||
case isHearingImpaired = "IsHearingImpaired", isOriginal = "IsOriginal"
|
||||
case isDefault = "IsDefault", isForced = "IsForced"
|
||||
case height = "Height", width = "Width"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user