fix: file info bitrate units, multi-track display, tv scrolling

close #840, close #841
This commit is contained in:
edde746
2026-04-11 08:04:12 +02:00
parent c6f2e949a7
commit 91d191b3d8
34 changed files with 230 additions and 55 deletions
+2
View File
@@ -287,6 +287,8 @@
"colorPrimaries": "Farveprimærer",
"chromaSubsampling": "Chroma-subsampling",
"channels": "Kanaler",
"subtitles": "Undertekster",
"overallBitrate": "Samlet bitrate",
"path": "Sti",
"size": "Størrelse",
"container": "Container",
+2
View File
@@ -287,6 +287,8 @@
"colorPrimaries": "Primärfarben",
"chromaSubsampling": "Chroma-Subsampling",
"channels": "Kanäle",
"subtitles": "Untertitel",
"overallBitrate": "Gesamt-Bitrate",
"path": "Pfad",
"size": "Größe",
"container": "Container",
+2
View File
@@ -287,6 +287,8 @@
"colorPrimaries": "Color Primaries",
"chromaSubsampling": "Chroma Subsampling",
"channels": "Channels",
"subtitles": "Subtitles",
"overallBitrate": "Overall Bitrate",
"path": "Path",
"size": "Size",
"container": "Container",
+2
View File
@@ -287,6 +287,8 @@
"colorPrimaries": "Primarias de color",
"chromaSubsampling": "Submuestreo de croma",
"channels": "Canales",
"subtitles": "Subtítulos",
"overallBitrate": "Bitrate total",
"path": "Ruta",
"size": "Tamaño",
"container": "Contenedor",
+2
View File
@@ -287,6 +287,8 @@
"colorPrimaries": "Couleurs primaires",
"chromaSubsampling": "Sous-échantillonnage chromatique",
"channels": "Canaux",
"subtitles": "Sous-titres",
"overallBitrate": "Débit global",
"path": "Chemin",
"size": "Taille",
"container": "Conteneur",
+2
View File
@@ -287,6 +287,8 @@
"colorPrimaries": "Colori primari",
"chromaSubsampling": "Sottocampionamento cromatico",
"channels": "Canali",
"subtitles": "Sottotitoli",
"overallBitrate": "Bitrate complessivo",
"path": "Percorso",
"size": "Dimensione",
"container": "Contenitore",
+2
View File
@@ -287,6 +287,8 @@
"colorPrimaries": "色原色",
"chromaSubsampling": "クロマサブサンプリング",
"channels": "チャンネル",
"subtitles": "字幕",
"overallBitrate": "全体ビットレート",
"path": "パス",
"size": "サイズ",
"container": "コンテナ",
+2
View File
@@ -287,6 +287,8 @@
"colorPrimaries": "색상 원색",
"chromaSubsampling": "채도 서브샘플링",
"channels": "채널",
"subtitles": "자막",
"overallBitrate": "전체 비트레이트",
"path": "경로",
"size": "크기",
"container": "컨테이너",
+2
View File
@@ -287,6 +287,8 @@
"colorPrimaries": "Fargeprimærer",
"chromaSubsampling": "Krominansnedsampling",
"channels": "Kanaler",
"subtitles": "Undertekster",
"overallBitrate": "Total bitrate",
"path": "Sti",
"size": "Størrelse",
"container": "Beholder",
+2
View File
@@ -287,6 +287,8 @@
"colorPrimaries": "Kleurprimaires",
"chromaSubsampling": "Chroma subsampling",
"channels": "Kanalen",
"subtitles": "Ondertitels",
"overallBitrate": "Totale bitrate",
"path": "Pad",
"size": "Grootte",
"container": "Container",
+2
View File
@@ -287,6 +287,8 @@
"colorPrimaries": "Kolory podstawowe",
"chromaSubsampling": "Subsampling chrominancji",
"channels": "Kanały",
"subtitles": "Napisy",
"overallBitrate": "Całkowity bitrate",
"path": "Ścieżka",
"size": "Rozmiar",
"container": "Kontener",
+2
View File
@@ -287,6 +287,8 @@
"colorPrimaries": "Primárias de Cor",
"chromaSubsampling": "Subamostragem de Croma",
"channels": "Canais",
"subtitles": "Legendas",
"overallBitrate": "Taxa de bits total",
"path": "Caminho",
"size": "Tamanho",
"container": "Container",
+2
View File
@@ -287,6 +287,8 @@
"colorPrimaries": "Цветовые первичные",
"chromaSubsampling": "Субдискретизация цветности",
"channels": "Каналы",
"subtitles": "Субтитры",
"overallBitrate": "Общий битрейт",
"path": "Путь",
"size": "Размер",
"container": "Контейнер",
+2 -2
View File
@@ -4,9 +4,9 @@
/// To regenerate, run: `dart run slang`
///
/// Locales: 15
/// Strings: 12945 (863 per locale)
/// Strings: 12975 (865 per locale)
///
/// Built on 2026-04-10 at 16:32 UTC
/// Built on 2026-04-11 at 05:59 UTC
// coverage:ignore-file
// ignore_for_file: type=lint, unused_import
+6 -2
View File
@@ -407,6 +407,8 @@ class _TranslationsFileInfoDa implements TranslationsFileInfoEn {
@override String get colorPrimaries => 'Farveprimærer';
@override String get chromaSubsampling => 'Chroma-subsampling';
@override String get channels => 'Kanaler';
@override String get subtitles => 'Undertekster';
@override String get overallBitrate => 'Samlet bitrate';
@override String get path => 'Sti';
@override String get size => 'Størrelse';
@override String get container => 'Container';
@@ -1629,6 +1631,8 @@ extension on TranslationsDa {
'fileInfo.colorPrimaries' => 'Farveprimærer',
'fileInfo.chromaSubsampling' => 'Chroma-subsampling',
'fileInfo.channels' => 'Kanaler',
'fileInfo.subtitles' => 'Undertekster',
'fileInfo.overallBitrate' => 'Samlet bitrate',
'fileInfo.path' => 'Sti',
'fileInfo.size' => 'Størrelse',
'fileInfo.container' => 'Container',
@@ -1870,10 +1874,10 @@ extension on TranslationsDa {
'about.versionLabel' => ({required Object version}) => 'Version ${version}',
'about.appDescription' => 'En smuk Plex-klient til Flutter',
'about.viewLicensesDescription' => 'Se licenser for tredjepartsbiblioteker',
'serverSelection.allServerConnectionsFailed' => 'Kunne ikke forbinde til nogen servere. Tjek dit netværk og prøv igen.',
'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Ingen servere fundet for ${username} (${email})',
_ => null,
} ?? switch (path) {
'serverSelection.allServerConnectionsFailed' => 'Kunne ikke forbinde til nogen servere. Tjek dit netværk og prøv igen.',
'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Ingen servere fundet for ${username} (${email})',
'serverSelection.failedToLoadServers' => ({required Object error}) => 'Kunne ikke indlæse servere: ${error}',
'hubDetail.title' => 'Titel',
'hubDetail.releaseYear' => 'Udgivelsesår',
+6 -2
View File
@@ -407,6 +407,8 @@ class _TranslationsFileInfoDe implements TranslationsFileInfoEn {
@override String get colorPrimaries => 'Primärfarben';
@override String get chromaSubsampling => 'Chroma-Subsampling';
@override String get channels => 'Kanäle';
@override String get subtitles => 'Untertitel';
@override String get overallBitrate => 'Gesamt-Bitrate';
@override String get path => 'Pfad';
@override String get size => 'Größe';
@override String get container => 'Container';
@@ -1629,6 +1631,8 @@ extension on TranslationsDe {
'fileInfo.colorPrimaries' => 'Primärfarben',
'fileInfo.chromaSubsampling' => 'Chroma-Subsampling',
'fileInfo.channels' => 'Kanäle',
'fileInfo.subtitles' => 'Untertitel',
'fileInfo.overallBitrate' => 'Gesamt-Bitrate',
'fileInfo.path' => 'Pfad',
'fileInfo.size' => 'Größe',
'fileInfo.container' => 'Container',
@@ -1870,10 +1874,10 @@ extension on TranslationsDe {
'about.versionLabel' => ({required Object version}) => 'Version ${version}',
'about.appDescription' => 'Ein schöner Plex-Client für Flutter',
'about.viewLicensesDescription' => 'Lizenzen von Drittanbieter-Bibliotheken anzeigen',
'serverSelection.allServerConnectionsFailed' => 'Verbindung zu allen Servern fehlgeschlagen. Bitte Netzwerk prüfen und erneut versuchen.',
'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Keine Server gefunden für ${username} (${email})',
_ => null,
} ?? switch (path) {
'serverSelection.allServerConnectionsFailed' => 'Verbindung zu allen Servern fehlgeschlagen. Bitte Netzwerk prüfen und erneut versuchen.',
'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Keine Server gefunden für ${username} (${email})',
'serverSelection.failedToLoadServers' => ({required Object error}) => 'Server konnten nicht geladen werden: ${error}',
'hubDetail.title' => 'Titel',
'hubDetail.releaseYear' => 'Erscheinungsjahr',
+10 -2
View File
@@ -912,6 +912,12 @@ class TranslationsFileInfoEn {
/// en: 'Channels'
String get channels => 'Channels';
/// en: 'Subtitles'
String get subtitles => 'Subtitles';
/// en: 'Overall Bitrate'
String get overallBitrate => 'Overall Bitrate';
/// en: 'Path'
String get path => 'Path';
@@ -3362,6 +3368,8 @@ extension on Translations {
'fileInfo.colorPrimaries' => 'Color Primaries',
'fileInfo.chromaSubsampling' => 'Chroma Subsampling',
'fileInfo.channels' => 'Channels',
'fileInfo.subtitles' => 'Subtitles',
'fileInfo.overallBitrate' => 'Overall Bitrate',
'fileInfo.path' => 'Path',
'fileInfo.size' => 'Size',
'fileInfo.container' => 'Container',
@@ -3603,10 +3611,10 @@ extension on Translations {
'about.versionLabel' => ({required Object version}) => 'Version ${version}',
'about.appDescription' => 'A beautiful Plex client for Flutter',
'about.viewLicensesDescription' => 'View licenses of third-party libraries',
'serverSelection.allServerConnectionsFailed' => 'Failed to connect to any servers. Please check your network and try again.',
'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'No servers found for ${username} (${email})',
_ => null,
} ?? switch (path) {
'serverSelection.allServerConnectionsFailed' => 'Failed to connect to any servers. Please check your network and try again.',
'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'No servers found for ${username} (${email})',
'serverSelection.failedToLoadServers' => ({required Object error}) => 'Failed to load servers: ${error}',
'hubDetail.title' => 'Title',
'hubDetail.releaseYear' => 'Release Year',
+6 -2
View File
@@ -407,6 +407,8 @@ class _TranslationsFileInfoEs implements TranslationsFileInfoEn {
@override String get colorPrimaries => 'Primarias de color';
@override String get chromaSubsampling => 'Submuestreo de croma';
@override String get channels => 'Canales';
@override String get subtitles => 'Subtítulos';
@override String get overallBitrate => 'Bitrate total';
@override String get path => 'Ruta';
@override String get size => 'Tamaño';
@override String get container => 'Contenedor';
@@ -1629,6 +1631,8 @@ extension on TranslationsEs {
'fileInfo.colorPrimaries' => 'Primarias de color',
'fileInfo.chromaSubsampling' => 'Submuestreo de croma',
'fileInfo.channels' => 'Canales',
'fileInfo.subtitles' => 'Subtítulos',
'fileInfo.overallBitrate' => 'Bitrate total',
'fileInfo.path' => 'Ruta',
'fileInfo.size' => 'Tamaño',
'fileInfo.container' => 'Contenedor',
@@ -1870,10 +1874,10 @@ extension on TranslationsEs {
'about.versionLabel' => ({required Object version}) => 'Versión ${version}',
'about.appDescription' => 'Un cliente de Plex para Flutter',
'about.viewLicensesDescription' => 'Ver licencias de librerías de terceros',
'serverSelection.allServerConnectionsFailed' => 'No se pudo conectar con ningún servidor. Por favor, comprueba tu conexión e inténtalo de nuevo.',
'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'No se encontraron servidores para ${username} (${email})',
_ => null,
} ?? switch (path) {
'serverSelection.allServerConnectionsFailed' => 'No se pudo conectar con ningún servidor. Por favor, comprueba tu conexión e inténtalo de nuevo.',
'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'No se encontraron servidores para ${username} (${email})',
'serverSelection.failedToLoadServers' => ({required Object error}) => 'Error al cargar servidores: ${error}',
'hubDetail.title' => 'Título',
'hubDetail.releaseYear' => 'Año de lanzamiento',
+6 -2
View File
@@ -407,6 +407,8 @@ class _TranslationsFileInfoFr implements TranslationsFileInfoEn {
@override String get colorPrimaries => 'Couleurs primaires';
@override String get chromaSubsampling => 'Sous-échantillonnage chromatique';
@override String get channels => 'Canaux';
@override String get subtitles => 'Sous-titres';
@override String get overallBitrate => 'Débit global';
@override String get path => 'Chemin';
@override String get size => 'Taille';
@override String get container => 'Conteneur';
@@ -1629,6 +1631,8 @@ extension on TranslationsFr {
'fileInfo.colorPrimaries' => 'Couleurs primaires',
'fileInfo.chromaSubsampling' => 'Sous-échantillonnage chromatique',
'fileInfo.channels' => 'Canaux',
'fileInfo.subtitles' => 'Sous-titres',
'fileInfo.overallBitrate' => 'Débit global',
'fileInfo.path' => 'Chemin',
'fileInfo.size' => 'Taille',
'fileInfo.container' => 'Conteneur',
@@ -1870,10 +1874,10 @@ extension on TranslationsFr {
'about.versionLabel' => ({required Object version}) => 'Version ${version}',
'about.appDescription' => 'Un magnifique client Plex pour Flutter',
'about.viewLicensesDescription' => 'Afficher les licences des bibliothèques tierces',
'serverSelection.allServerConnectionsFailed' => 'Impossible de se connecter à un serveur. Veuillez vérifier votre connexion réseau et réessayer.',
'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Aucun serveur trouvé pour ${username} (${email})',
_ => null,
} ?? switch (path) {
'serverSelection.allServerConnectionsFailed' => 'Impossible de se connecter à un serveur. Veuillez vérifier votre connexion réseau et réessayer.',
'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Aucun serveur trouvé pour ${username} (${email})',
'serverSelection.failedToLoadServers' => ({required Object error}) => 'Échec du chargement des serveurs: ${error}',
'hubDetail.title' => 'Titre',
'hubDetail.releaseYear' => 'Année de sortie',
+6 -2
View File
@@ -407,6 +407,8 @@ class _TranslationsFileInfoIt implements TranslationsFileInfoEn {
@override String get colorPrimaries => 'Colori primari';
@override String get chromaSubsampling => 'Sottocampionamento cromatico';
@override String get channels => 'Canali';
@override String get subtitles => 'Sottotitoli';
@override String get overallBitrate => 'Bitrate complessivo';
@override String get path => 'Percorso';
@override String get size => 'Dimensione';
@override String get container => 'Contenitore';
@@ -1629,6 +1631,8 @@ extension on TranslationsIt {
'fileInfo.colorPrimaries' => 'Colori primari',
'fileInfo.chromaSubsampling' => 'Sottocampionamento cromatico',
'fileInfo.channels' => 'Canali',
'fileInfo.subtitles' => 'Sottotitoli',
'fileInfo.overallBitrate' => 'Bitrate complessivo',
'fileInfo.path' => 'Percorso',
'fileInfo.size' => 'Dimensione',
'fileInfo.container' => 'Contenitore',
@@ -1870,10 +1874,10 @@ extension on TranslationsIt {
'about.versionLabel' => ({required Object version}) => 'Versione ${version}',
'about.appDescription' => 'Un bellissimo client Plex per Flutter',
'about.viewLicensesDescription' => 'Visualizza le licenze delle librerie di terze parti',
'serverSelection.allServerConnectionsFailed' => 'Impossibile connettersi a nessun server. Controlla la tua rete e riprova.',
'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Nessun server trovato per ${username} (${email})',
_ => null,
} ?? switch (path) {
'serverSelection.allServerConnectionsFailed' => 'Impossibile connettersi a nessun server. Controlla la tua rete e riprova.',
'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Nessun server trovato per ${username} (${email})',
'serverSelection.failedToLoadServers' => ({required Object error}) => 'Impossibile caricare i server: ${error}',
'hubDetail.title' => 'Titolo',
'hubDetail.releaseYear' => 'Anno rilascio',
+6 -2
View File
@@ -407,6 +407,8 @@ class _TranslationsFileInfoJa implements TranslationsFileInfoEn {
@override String get colorPrimaries => '色原色';
@override String get chromaSubsampling => 'クロマサブサンプリング';
@override String get channels => 'チャンネル';
@override String get subtitles => '字幕';
@override String get overallBitrate => '全体ビットレート';
@override String get path => 'パス';
@override String get size => 'サイズ';
@override String get container => 'コンテナ';
@@ -1629,6 +1631,8 @@ extension on TranslationsJa {
'fileInfo.colorPrimaries' => '色原色',
'fileInfo.chromaSubsampling' => 'クロマサブサンプリング',
'fileInfo.channels' => 'チャンネル',
'fileInfo.subtitles' => '字幕',
'fileInfo.overallBitrate' => '全体ビットレート',
'fileInfo.path' => 'パス',
'fileInfo.size' => 'サイズ',
'fileInfo.container' => 'コンテナ',
@@ -1870,10 +1874,10 @@ extension on TranslationsJa {
'about.versionLabel' => ({required Object version}) => 'バージョン ${version}',
'about.appDescription' => 'Flutter製の美しいPlexクライアント',
'about.viewLicensesDescription' => 'サードパーティライブラリのライセンスを表示',
'serverSelection.allServerConnectionsFailed' => 'どのサーバーにも接続できませんでした。ネットワークを確認してもう一度お試しください。',
'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => '${username} (${email})のサーバーが見つかりません',
_ => null,
} ?? switch (path) {
'serverSelection.allServerConnectionsFailed' => 'どのサーバーにも接続できませんでした。ネットワークを確認してもう一度お試しください。',
'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => '${username} (${email})のサーバーが見つかりません',
'serverSelection.failedToLoadServers' => ({required Object error}) => 'サーバーの読み込みに失敗しました: ${error}',
'hubDetail.title' => 'タイトル',
'hubDetail.releaseYear' => '公開年',
+6 -2
View File
@@ -407,6 +407,8 @@ class _TranslationsFileInfoKo implements TranslationsFileInfoEn {
@override String get colorPrimaries => '색상 원색';
@override String get chromaSubsampling => '채도 서브샘플링';
@override String get channels => '채널';
@override String get subtitles => '자막';
@override String get overallBitrate => '전체 비트레이트';
@override String get path => '경로';
@override String get size => '크기';
@override String get container => '컨테이너';
@@ -1629,6 +1631,8 @@ extension on TranslationsKo {
'fileInfo.colorPrimaries' => '색상 원색',
'fileInfo.chromaSubsampling' => '채도 서브샘플링',
'fileInfo.channels' => '채널',
'fileInfo.subtitles' => '자막',
'fileInfo.overallBitrate' => '전체 비트레이트',
'fileInfo.path' => '경로',
'fileInfo.size' => '크기',
'fileInfo.container' => '컨테이너',
@@ -1870,10 +1874,10 @@ extension on TranslationsKo {
'about.versionLabel' => ({required Object version}) => '버전 ${version}',
'about.appDescription' => '아름다운 Flutter Plex 클라이언트',
'about.viewLicensesDescription' => '타사 라이브러리 라이선스 보기',
'serverSelection.allServerConnectionsFailed' => '어떤 서버에도 연결할 수 없습니다. 네트워크를 확인하고 다시 시도하세요.',
'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => '${username} (${email})의 서버를 찾을 수 없습니다.',
_ => null,
} ?? switch (path) {
'serverSelection.allServerConnectionsFailed' => '어떤 서버에도 연결할 수 없습니다. 네트워크를 확인하고 다시 시도하세요.',
'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => '${username} (${email})의 서버를 찾을 수 없습니다.',
'serverSelection.failedToLoadServers' => ({required Object error}) => '서버를 로드할 수 없습니다: ${error}',
'hubDetail.title' => '제목',
'hubDetail.releaseYear' => '출시 연도',
+6 -2
View File
@@ -407,6 +407,8 @@ class _TranslationsFileInfoNb implements TranslationsFileInfoEn {
@override String get colorPrimaries => 'Fargeprimærer';
@override String get chromaSubsampling => 'Krominansnedsampling';
@override String get channels => 'Kanaler';
@override String get subtitles => 'Undertekster';
@override String get overallBitrate => 'Total bitrate';
@override String get path => 'Sti';
@override String get size => 'Størrelse';
@override String get container => 'Beholder';
@@ -1629,6 +1631,8 @@ extension on TranslationsNb {
'fileInfo.colorPrimaries' => 'Fargeprimærer',
'fileInfo.chromaSubsampling' => 'Krominansnedsampling',
'fileInfo.channels' => 'Kanaler',
'fileInfo.subtitles' => 'Undertekster',
'fileInfo.overallBitrate' => 'Total bitrate',
'fileInfo.path' => 'Sti',
'fileInfo.size' => 'Størrelse',
'fileInfo.container' => 'Beholder',
@@ -1870,10 +1874,10 @@ extension on TranslationsNb {
'about.versionLabel' => ({required Object version}) => 'Versjon ${version}',
'about.appDescription' => 'En vakker Plex-klient for Flutter',
'about.viewLicensesDescription' => 'Vis lisenser for tredjepartsbiblioteker',
'serverSelection.allServerConnectionsFailed' => 'Kunne ikke koble til noen servere. Sjekk nettverket ditt og prøv igjen.',
'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Ingen servere funnet for ${username} (${email})',
_ => null,
} ?? switch (path) {
'serverSelection.allServerConnectionsFailed' => 'Kunne ikke koble til noen servere. Sjekk nettverket ditt og prøv igjen.',
'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Ingen servere funnet for ${username} (${email})',
'serverSelection.failedToLoadServers' => ({required Object error}) => 'Kunne ikke laste servere: ${error}',
'hubDetail.title' => 'Tittel',
'hubDetail.releaseYear' => 'Utgivelsesår',
+6 -2
View File
@@ -407,6 +407,8 @@ class _TranslationsFileInfoNl implements TranslationsFileInfoEn {
@override String get colorPrimaries => 'Kleurprimaires';
@override String get chromaSubsampling => 'Chroma subsampling';
@override String get channels => 'Kanalen';
@override String get subtitles => 'Ondertitels';
@override String get overallBitrate => 'Totale bitrate';
@override String get path => 'Pad';
@override String get size => 'Grootte';
@override String get container => 'Container';
@@ -1629,6 +1631,8 @@ extension on TranslationsNl {
'fileInfo.colorPrimaries' => 'Kleurprimaires',
'fileInfo.chromaSubsampling' => 'Chroma subsampling',
'fileInfo.channels' => 'Kanalen',
'fileInfo.subtitles' => 'Ondertitels',
'fileInfo.overallBitrate' => 'Totale bitrate',
'fileInfo.path' => 'Pad',
'fileInfo.size' => 'Grootte',
'fileInfo.container' => 'Container',
@@ -1870,10 +1874,10 @@ extension on TranslationsNl {
'about.versionLabel' => ({required Object version}) => 'Versie ${version}',
'about.appDescription' => 'Een mooie Plex client voor Flutter',
'about.viewLicensesDescription' => 'Bekijk licenties van third-party bibliotheken',
'serverSelection.allServerConnectionsFailed' => 'Kon niet verbinden met servers. Controleer je netwerk en probeer opnieuw.',
'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Geen servers gevonden voor ${username} (${email})',
_ => null,
} ?? switch (path) {
'serverSelection.allServerConnectionsFailed' => 'Kon niet verbinden met servers. Controleer je netwerk en probeer opnieuw.',
'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Geen servers gevonden voor ${username} (${email})',
'serverSelection.failedToLoadServers' => ({required Object error}) => 'Kon servers niet laden: ${error}',
'hubDetail.title' => 'Titel',
'hubDetail.releaseYear' => 'Uitgavejaar',
+6 -2
View File
@@ -407,6 +407,8 @@ class _TranslationsFileInfoPl implements TranslationsFileInfoEn {
@override String get colorPrimaries => 'Kolory podstawowe';
@override String get chromaSubsampling => 'Subsampling chrominancji';
@override String get channels => 'Kanały';
@override String get subtitles => 'Napisy';
@override String get overallBitrate => 'Całkowity bitrate';
@override String get path => 'Ścieżka';
@override String get size => 'Rozmiar';
@override String get container => 'Kontener';
@@ -1629,6 +1631,8 @@ extension on TranslationsPl {
'fileInfo.colorPrimaries' => 'Kolory podstawowe',
'fileInfo.chromaSubsampling' => 'Subsampling chrominancji',
'fileInfo.channels' => 'Kanały',
'fileInfo.subtitles' => 'Napisy',
'fileInfo.overallBitrate' => 'Całkowity bitrate',
'fileInfo.path' => 'Ścieżka',
'fileInfo.size' => 'Rozmiar',
'fileInfo.container' => 'Kontener',
@@ -1870,10 +1874,10 @@ extension on TranslationsPl {
'about.versionLabel' => ({required Object version}) => 'Wersja ${version}',
'about.appDescription' => 'Piękny klient Plex na Flutter',
'about.viewLicensesDescription' => 'Zobacz licencje bibliotek zewnętrznych',
'serverSelection.allServerConnectionsFailed' => 'Nie udało się połączyć z żadnym serwerem. Sprawdź sieć i spróbuj ponownie.',
'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Nie znaleziono serwerów dla ${username} (${email})',
_ => null,
} ?? switch (path) {
'serverSelection.allServerConnectionsFailed' => 'Nie udało się połączyć z żadnym serwerem. Sprawdź sieć i spróbuj ponownie.',
'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Nie znaleziono serwerów dla ${username} (${email})',
'serverSelection.failedToLoadServers' => ({required Object error}) => 'Nie udało się załadować serwerów: ${error}',
'hubDetail.title' => 'Tytuł',
'hubDetail.releaseYear' => 'Rok premiery',
+6 -2
View File
@@ -407,6 +407,8 @@ class _TranslationsFileInfoPt implements TranslationsFileInfoEn {
@override String get colorPrimaries => 'Primárias de Cor';
@override String get chromaSubsampling => 'Subamostragem de Croma';
@override String get channels => 'Canais';
@override String get subtitles => 'Legendas';
@override String get overallBitrate => 'Taxa de bits total';
@override String get path => 'Caminho';
@override String get size => 'Tamanho';
@override String get container => 'Container';
@@ -1629,6 +1631,8 @@ extension on TranslationsPt {
'fileInfo.colorPrimaries' => 'Primárias de Cor',
'fileInfo.chromaSubsampling' => 'Subamostragem de Croma',
'fileInfo.channels' => 'Canais',
'fileInfo.subtitles' => 'Legendas',
'fileInfo.overallBitrate' => 'Taxa de bits total',
'fileInfo.path' => 'Caminho',
'fileInfo.size' => 'Tamanho',
'fileInfo.container' => 'Container',
@@ -1870,10 +1874,10 @@ extension on TranslationsPt {
'about.versionLabel' => ({required Object version}) => 'Versão ${version}',
'about.appDescription' => 'Um belo cliente Plex para Flutter',
'about.viewLicensesDescription' => 'Ver licenças de bibliotecas de terceiros',
'serverSelection.allServerConnectionsFailed' => 'Falha ao conectar a qualquer servidor. Verifique sua rede e tente novamente.',
'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Nenhum servidor encontrado para ${username} (${email})',
_ => null,
} ?? switch (path) {
'serverSelection.allServerConnectionsFailed' => 'Falha ao conectar a qualquer servidor. Verifique sua rede e tente novamente.',
'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Nenhum servidor encontrado para ${username} (${email})',
'serverSelection.failedToLoadServers' => ({required Object error}) => 'Falha ao carregar servidores: ${error}',
'hubDetail.title' => 'Título',
'hubDetail.releaseYear' => 'Ano de Lançamento',
+6 -2
View File
@@ -407,6 +407,8 @@ class _TranslationsFileInfoRu implements TranslationsFileInfoEn {
@override String get colorPrimaries => 'Цветовые первичные';
@override String get chromaSubsampling => 'Субдискретизация цветности';
@override String get channels => 'Каналы';
@override String get subtitles => 'Субтитры';
@override String get overallBitrate => 'Общий битрейт';
@override String get path => 'Путь';
@override String get size => 'Размер';
@override String get container => 'Контейнер';
@@ -1629,6 +1631,8 @@ extension on TranslationsRu {
'fileInfo.colorPrimaries' => 'Цветовые первичные',
'fileInfo.chromaSubsampling' => 'Субдискретизация цветности',
'fileInfo.channels' => 'Каналы',
'fileInfo.subtitles' => 'Субтитры',
'fileInfo.overallBitrate' => 'Общий битрейт',
'fileInfo.path' => 'Путь',
'fileInfo.size' => 'Размер',
'fileInfo.container' => 'Контейнер',
@@ -1870,10 +1874,10 @@ extension on TranslationsRu {
'about.versionLabel' => ({required Object version}) => 'Версия ${version}',
'about.appDescription' => 'Красивый клиент Plex на Flutter',
'about.viewLicensesDescription' => 'Просмотр лицензий сторонних библиотек',
'serverSelection.allServerConnectionsFailed' => 'Не удалось подключиться ни к одному серверу. Проверьте сеть и попробуйте снова.',
'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Серверы не найдены для ${username} (${email})',
_ => null,
} ?? switch (path) {
'serverSelection.allServerConnectionsFailed' => 'Не удалось подключиться ни к одному серверу. Проверьте сеть и попробуйте снова.',
'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Серверы не найдены для ${username} (${email})',
'serverSelection.failedToLoadServers' => ({required Object error}) => 'Не удалось загрузить серверы: ${error}',
'hubDetail.title' => 'Название',
'hubDetail.releaseYear' => 'Год выпуска',
+6 -2
View File
@@ -407,6 +407,8 @@ class _TranslationsFileInfoSv implements TranslationsFileInfoEn {
@override String get colorPrimaries => 'Färggrunder';
@override String get chromaSubsampling => 'Kroma-undersampling';
@override String get channels => 'Kanaler';
@override String get subtitles => 'Undertexter';
@override String get overallBitrate => 'Total bithastighet';
@override String get path => 'Sökväg';
@override String get size => 'Storlek';
@override String get container => 'Container';
@@ -1629,6 +1631,8 @@ extension on TranslationsSv {
'fileInfo.colorPrimaries' => 'Färggrunder',
'fileInfo.chromaSubsampling' => 'Kroma-undersampling',
'fileInfo.channels' => 'Kanaler',
'fileInfo.subtitles' => 'Undertexter',
'fileInfo.overallBitrate' => 'Total bithastighet',
'fileInfo.path' => 'Sökväg',
'fileInfo.size' => 'Storlek',
'fileInfo.container' => 'Container',
@@ -1870,10 +1874,10 @@ extension on TranslationsSv {
'about.versionLabel' => ({required Object version}) => 'Version ${version}',
'about.appDescription' => 'En vacker Plex-klient för Flutter',
'about.viewLicensesDescription' => 'Visa licenser för tredjepartsbibliotek',
'serverSelection.allServerConnectionsFailed' => 'Misslyckades att ansluta till servrar. Kontrollera ditt nätverk och försök igen.',
'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Inga servrar hittades för ${username} (${email})',
_ => null,
} ?? switch (path) {
'serverSelection.allServerConnectionsFailed' => 'Misslyckades att ansluta till servrar. Kontrollera ditt nätverk och försök igen.',
'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Inga servrar hittades för ${username} (${email})',
'serverSelection.failedToLoadServers' => ({required Object error}) => 'Misslyckades att ladda servrar: ${error}',
'hubDetail.title' => 'Titel',
'hubDetail.releaseYear' => 'Utgivningsår',
+6 -2
View File
@@ -407,6 +407,8 @@ class _TranslationsFileInfoZh implements TranslationsFileInfoEn {
@override String get colorPrimaries => '颜色原色';
@override String get chromaSubsampling => '色度子采样';
@override String get channels => '声道';
@override String get subtitles => '字幕';
@override String get overallBitrate => '总比特率';
@override String get path => '路径';
@override String get size => '大小';
@override String get container => '容器';
@@ -1629,6 +1631,8 @@ extension on TranslationsZh {
'fileInfo.colorPrimaries' => '颜色原色',
'fileInfo.chromaSubsampling' => '色度子采样',
'fileInfo.channels' => '声道',
'fileInfo.subtitles' => '字幕',
'fileInfo.overallBitrate' => '总比特率',
'fileInfo.path' => '路径',
'fileInfo.size' => '大小',
'fileInfo.container' => '容器',
@@ -1870,10 +1874,10 @@ extension on TranslationsZh {
'about.versionLabel' => ({required Object version}) => '版本 ${version}',
'about.appDescription' => '一款精美的 Flutter Plex 客户端',
'about.viewLicensesDescription' => '查看第三方库的许可证',
'serverSelection.allServerConnectionsFailed' => '无法连接到任何服务器。请检查你的网络并重试。',
'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => '未找到 ${username} (${email}) 的服务器',
_ => null,
} ?? switch (path) {
'serverSelection.allServerConnectionsFailed' => '无法连接到任何服务器。请检查你的网络并重试。',
'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => '未找到 ${username} (${email}) 的服务器',
'serverSelection.failedToLoadServers' => ({required Object error}) => '无法加载服务器: ${error}',
'hubDetail.title' => '标题',
'hubDetail.releaseYear' => '发行年份',
+2
View File
@@ -287,6 +287,8 @@
"colorPrimaries": "Färggrunder",
"chromaSubsampling": "Kroma-undersampling",
"channels": "Kanaler",
"subtitles": "Undertexter",
"overallBitrate": "Total bithastighet",
"path": "Sökväg",
"size": "Storlek",
"container": "Container",
+2
View File
@@ -287,6 +287,8 @@
"colorPrimaries": "颜色原色",
"chromaSubsampling": "色度子采样",
"channels": "声道",
"subtitles": "字幕",
"overallBitrate": "总比特率",
"path": "路径",
"size": "大小",
"container": "容器",
+17 -2
View File
@@ -1,4 +1,5 @@
import '../utils/formatters.dart';
import 'plex_media_info.dart';
class PlexFileInfo {
// Media level properties
@@ -30,8 +31,13 @@ class PlexFileInfo {
final String? chromaSubsampling;
final double? frameRate;
final int? bitDepth;
final int? videoBitrate;
final String? audioChannelLayout;
// Multi-track support
final List<PlexAudioTrack> audioTracks;
final List<PlexSubtitleTrack> subtitleTracks;
PlexFileInfo({
this.container,
this.videoCodec,
@@ -57,7 +63,10 @@ class PlexFileInfo {
this.chromaSubsampling,
this.frameRate,
this.bitDepth,
this.videoBitrate,
this.audioChannelLayout,
this.audioTracks = const [],
this.subtitleTracks = const [],
});
/// Format file size in human-readable format (GB, MB, KB, bytes)
@@ -78,10 +87,16 @@ class PlexFileInfo {
return hours > 0 ? '${hours}h ${minutes}m ${secs}s' : '${minutes}m ${secs}s';
}
/// Format bitrate in Mbps or Kbps
/// Format overall bitrate in Mbps or kbps (Plex API returns kbps)
String get bitrateFormatted {
if (bitrate == null) return 'Unknown';
return ByteFormatter.formatBitrateBps(bitrate!);
return ByteFormatter.formatBitrate(bitrate!);
}
/// Format video stream bitrate in Mbps or kbps
String get videoBitrateFormatted {
if (videoBitrate == null) return 'Unknown';
return ByteFormatter.formatBitrate(videoBitrate!);
}
/// Format resolution as widthxheight
+7 -1
View File
@@ -1266,7 +1266,7 @@ class PlexClient {
final media = metadataJson['Media'][0];
final part = media['Part'] != null && (media['Part'] as List).isNotEmpty ? media['Part'][0] : null;
// Extract video stream details
// Extract video stream details and all audio/subtitle tracks
final streams = part?['Stream'] as List<dynamic>? ?? [];
Map<String, dynamic>? videoStream;
Map<String, dynamic>? audioStream;
@@ -1280,6 +1280,8 @@ class PlexClient {
}
}
final parsedTracks = _parseStreams(streams);
return PlexFileInfo(
// Media level properties
container: media['container'] as String?,
@@ -1308,8 +1310,12 @@ class PlexClient {
chromaSubsampling: videoStream?['chromaSubsampling'] as String?,
frameRate: (videoStream?['frameRate'] as num?)?.toDouble(),
bitDepth: videoStream?['bitDepth'] as int?,
videoBitrate: videoStream?['bitrate'] as int?,
// Audio stream details
audioChannelLayout: audioStream?['audioChannelLayout'] as String?,
// All audio and subtitle tracks
audioTracks: parsedTracks.audio,
subtitleTracks: parsedTracks.subtitles,
);
}
+80 -20
View File
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:material_symbols_icons/symbols.dart';
import '../models/plex_file_info.dart';
import '../i18n/strings.g.dart';
import '../utils/scroll_utils.dart';
import 'bottom_sheet_header.dart';
class FileInfoBottomSheet extends StatefulWidget {
@@ -58,7 +59,8 @@ class _FileInfoBottomSheetState extends State<FileInfoBottomSheet> {
const SizedBox(height: 8),
_buildInfoRow(t.fileInfo.codec, widget.fileInfo.videoCodec ?? t.common.unknown),
_buildInfoRow(t.fileInfo.resolution, widget.fileInfo.resolutionFormatted),
_buildInfoRow(t.fileInfo.bitrate, widget.fileInfo.bitrateFormatted),
if (widget.fileInfo.videoBitrate != null)
_buildInfoRow(t.fileInfo.bitrate, widget.fileInfo.videoBitrateFormatted),
_buildInfoRow(t.fileInfo.frameRate, widget.fileInfo.frameRateFormatted),
_buildInfoRow(t.fileInfo.aspectRatio, widget.fileInfo.aspectRatioFormatted),
if (widget.fileInfo.videoProfile != null)
@@ -76,12 +78,26 @@ class _FileInfoBottomSheetState extends State<FileInfoBottomSheet> {
// Audio Section
_buildSectionHeader(t.fileInfo.audio),
const SizedBox(height: 8),
_buildInfoRow(t.fileInfo.codec, widget.fileInfo.audioCodec ?? t.common.unknown),
_buildInfoRow(t.fileInfo.channels, widget.fileInfo.audioChannelsFormatted),
if (widget.fileInfo.audioProfile != null)
_buildInfoRow(t.fileInfo.profile, widget.fileInfo.audioProfile!),
if (widget.fileInfo.audioTracks.isNotEmpty)
for (int i = 0; i < widget.fileInfo.audioTracks.length; i++)
_buildInfoRow('${i + 1}', widget.fileInfo.audioTracks[i].label),
if (widget.fileInfo.audioTracks.isEmpty) ...[
_buildInfoRow(t.fileInfo.codec, widget.fileInfo.audioCodec ?? t.common.unknown),
_buildInfoRow(t.fileInfo.channels, widget.fileInfo.audioChannelsFormatted),
if (widget.fileInfo.audioProfile != null)
_buildInfoRow(t.fileInfo.profile, widget.fileInfo.audioProfile!),
],
const SizedBox(height: 20),
// Subtitles Section
if (widget.fileInfo.subtitleTracks.isNotEmpty) ...[
_buildSectionHeader(t.fileInfo.subtitles),
const SizedBox(height: 8),
for (int i = 0; i < widget.fileInfo.subtitleTracks.length; i++)
_buildInfoRow('${i + 1}', widget.fileInfo.subtitleTracks[i].label),
const SizedBox(height: 20),
],
// File Section
_buildSectionHeader(t.fileInfo.file),
const SizedBox(height: 8),
@@ -90,6 +106,7 @@ class _FileInfoBottomSheetState extends State<FileInfoBottomSheet> {
_buildInfoRow(t.fileInfo.size, widget.fileInfo.fileSizeFormatted),
_buildInfoRow(t.fileInfo.container, widget.fileInfo.container ?? t.common.unknown),
_buildInfoRow(t.fileInfo.duration, widget.fileInfo.durationFormatted),
_buildInfoRow(t.fileInfo.overallBitrate, widget.fileInfo.bitrateFormatted),
const SizedBox(height: 20),
// Advanced Section
@@ -118,22 +135,65 @@ class _FileInfoBottomSheetState extends State<FileInfoBottomSheet> {
}
Widget _buildInfoRow(String label, String value, {bool isMonospace = false}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 140,
child: Text(label, style: TextStyle(color: Theme.of(context).textTheme.bodySmall?.color, fontSize: 14)),
),
Expanded(
child: Text(
value,
style: TextStyle(fontSize: 14, fontFamily: isMonospace ? 'monospace' : null),
return _FocusableInfoRow(label: label, value: value, isMonospace: isMonospace);
}
}
class _FocusableInfoRow extends StatefulWidget {
final String label;
final String value;
final bool isMonospace;
const _FocusableInfoRow({required this.label, required this.value, this.isMonospace = false});
@override
State<_FocusableInfoRow> createState() => _FocusableInfoRowState();
}
class _FocusableInfoRowState extends State<_FocusableInfoRow> {
late final FocusNode _focusNode;
@override
void initState() {
super.initState();
_focusNode = FocusNode();
_focusNode.addListener(_onFocusChange);
}
@override
void dispose() {
_focusNode.removeListener(_onFocusChange);
_focusNode.dispose();
super.dispose();
}
void _onFocusChange() {
if (_focusNode.hasFocus) {
scrollContextToCenter(context);
}
}
@override
Widget build(BuildContext context) {
return Focus(
focusNode: _focusNode,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 140,
child: Text(widget.label, style: TextStyle(color: Theme.of(context).textTheme.bodySmall?.color, fontSize: 14)),
),
),
],
Expanded(
child: Text(
widget.value,
style: TextStyle(fontSize: 14, fontFamily: widget.isMonospace ? 'monospace' : null),
),
),
],
),
),
);
}