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) }
|
for (name, wert) in kopfzeilen { request.setValue(wert, forHTTPHeaderField: name) }
|
||||||
return request
|
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)
|
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"),
|
if let index = CommandLine.arguments.firstIndex(of: "--jellyfin"),
|
||||||
let host = CommandLine.arguments.dropFirst(index + 1).first {
|
let host = CommandLine.arguments.dropFirst(index + 1).first {
|
||||||
await jellyfinLeiter(host: host)
|
await jellyfinLeiter(host: host)
|
||||||
|
|||||||
@@ -168,6 +168,16 @@ public struct JellyfinMediaStream: Decodable, Sendable {
|
|||||||
public let codec: String?
|
public let codec: String?
|
||||||
public let language: String?
|
public let language: String?
|
||||||
public let displayTitle: 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 channels: Int?
|
||||||
public let isDefault: Bool?
|
public let isDefault: Bool?
|
||||||
public let isForced: Bool?
|
public let isForced: Bool?
|
||||||
@@ -177,6 +187,8 @@ public struct JellyfinMediaStream: Decodable, Sendable {
|
|||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
case index = "Index", type = "Type", codec = "Codec", language = "Language"
|
case index = "Index", type = "Type", codec = "Codec", language = "Language"
|
||||||
case displayTitle = "DisplayTitle", channels = "Channels"
|
case displayTitle = "DisplayTitle", channels = "Channels"
|
||||||
|
case title = "Title"
|
||||||
|
case isHearingImpaired = "IsHearingImpaired", isOriginal = "IsOriginal"
|
||||||
case isDefault = "IsDefault", isForced = "IsForced"
|
case isDefault = "IsDefault", isForced = "IsForced"
|
||||||
case height = "Height", width = "Width"
|
case height = "Height", width = "Width"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user