From e9a5fe16314c1d4b5df7620833a03944b82fae3d Mon Sep 17 00:00:00 2001 From: George Woodall Date: Tue, 28 Jul 2026 18:23:18 +0100 Subject: [PATCH 1/3] feat: replace IntroDB with SkipDB --- Strings HR.xml.txt | 16 ++-- composeApp/build.gradle.kts | 13 --- .../player/PlayerSettingsStorage.android.kt | 12 +-- .../IntegrationLogoPainter.android.kt | 4 +- ...introdb_favicon.png => skipdb_favicon.png} | Bin .../composeResources/values-bg/strings.xml | 14 ++-- .../composeResources/values-cs/strings.xml | 16 ++-- .../composeResources/values-de/strings.xml | 4 +- .../composeResources/values-el/strings.xml | 12 +-- .../composeResources/values-es/strings.xml | 14 ++-- .../composeResources/values-fr/strings.xml | 14 ++-- .../composeResources/values-he/strings.xml | 2 +- .../composeResources/values-hu/strings.xml | 14 ++-- .../composeResources/values-id/strings.xml | 10 +-- .../composeResources/values-in/strings.xml | 10 +-- .../composeResources/values-it/strings.xml | 12 +-- .../composeResources/values-ja/strings.xml | 16 ++-- .../composeResources/values-nb/strings.xml | 10 +-- .../composeResources/values-nl/strings.xml | 16 ++-- .../composeResources/values-pl/strings.xml | 12 +-- .../values-pt-rBR/strings.xml | 10 +-- .../composeResources/values-pt/strings.xml | 14 ++-- .../composeResources/values-ro/strings.xml | 14 ++-- .../composeResources/values-sk/strings.xml | 16 ++-- .../composeResources/values-tr/strings.xml | 8 +- .../composeResources/values-vi/strings.xml | 14 ++-- .../composeResources/values/strings.xml | 16 ++-- .../features/player/PlayerScreenModalHosts.kt | 2 + .../player/PlayerScreenRuntimeEffects.kt | 2 + .../features/player/PlayerScreenRuntimeUi.kt | 3 +- .../player/PlayerSettingsRepository.kt | 18 ++-- .../features/player/PlayerSettingsStorage.kt | 4 +- .../app/features/player/skip/SkipIntroApi.kt | 47 +++++------ .../player/skip/SkipIntroRepository.kt | 79 +++++++++--------- .../app/features/player/skip/SkipModels.kt | 31 ++++--- .../features/player/skip/SubmitIntroDialog.kt | 8 +- .../settings/IntegrationLogoPainter.kt | 2 +- .../settings/LicensesAttributionsPage.kt | 10 +-- .../features/settings/PlaybackSettingsPage.kt | 32 +++---- .../app/features/settings/SettingsSearch.kt | 4 +- .../player/PlayerSettingsStorage.ios.kt | 12 +-- .../settings/IntegrationLogoPainter.ios.kt | 4 +- gradle/gradle-daemon-jvm.properties | 12 +++ 43 files changed, 297 insertions(+), 286 deletions(-) rename composeApp/src/commonMain/composeResources/drawable/{introdb_favicon.png => skipdb_favicon.png} (100%) create mode 100644 gradle/gradle-daemon-jvm.properties diff --git a/Strings HR.xml.txt b/Strings HR.xml.txt index 09c5f459..b8467bd4 100644 --- a/Strings HR.xml.txt +++ b/Strings HR.xml.txt @@ -471,8 +471,8 @@ Nuvio se povezuje s TorBoxom radi autentifikacije računa, pristupa biblioteci u oblaku, provjere predmemorije i značajki reprodukcije iz oblaka. Nuvio nije povezan s TorBoxom niti ga TorBox podržava. MDBList Nuvio koristi MDBList za ocjene i podatke vanjskih pružatelja informacija o ocjenama. Nuvio nije povezan s uslugom MDBList niti ona podržava Nuvio. - IntroDB - Nuvio koristi IntroDB API za vremenske oznake koje osigurava zajednica, a odnose se na uvodne špice, sažetke, odjavne špice i najave koje koriste kontrole za preskakanje. Nuvio nije povezan s uslugom IntroDB niti ona podržava Nuvio. + SkipDB + Nuvio koristi SkipDB API za vremenske oznake koje osigurava zajednica, a odnose se na uvodne špice, sažetke, odjavne špice i najave koje koriste kontrole za preskakanje. Nuvio nije povezan s uslugom SkipDB niti ona podržava Nuvio. MPVKit Koristi se za reprodukciju na iOS sustavima. Izvorni kod samog MPVKita licenciran je pod licencom LGPL v3.0. Paketi MPVKita, uključujući biblioteke libmpv i FFmpeg, također su licencirani pod licencom LGPL v3.0. @@ -818,8 +818,8 @@ Zadajte ID klienta API AnimeSkip. Nabavite ga na anime-skip.com. Omogući slanje uvoda Prikaži gumb za slanje vremenskih oznaka za uvod i odjavu u bazu podataka zajednice. - API ključ IntroDB - Unesite svoj IntroDB API ključ za slanje vremenskih oznaka. On je obavezan za slanje. + API ključ SkipDB + Unesite svoj SkipDB API ključ za slanje vremenskih oznaka. On je obavezan za slanje. Također pretražite AnimeSkip za vremenske oznake za preskakanje (potreban je ID klijenta). Automatski reproduciraj sljedeću epizodu Automatski pokreni sljedeću epizodu kada se pojavi upit. @@ -930,7 +930,7 @@ Dajte prednost prisilnim titlovima kada se podudaraju s postavkama jezika titlova. Okomiti pomak Preskoči intro - Koristite introdb.app za prepoznavanje uvodnih špica i sažetaka prethodnih događaja. + Koristite skipdb.tv za prepoznavanje uvodnih špica i sažetaka prethodnih događaja. Opseg izvora za automatsku reprodukciju Svi instalirani dodaci Automatska reprodukcija uzima u obzir samo prijenose iz vaših instaliranih dodataka. @@ -1737,8 +1737,8 @@ Serije Knjižnica u oblaku je isključena. - - Neispravan API ključ ili neuspjela veza + + Neispravan API ključ ili neuspjela veza "%1$s" nedostaje u manifestu. TMDB Kolekcija %1$s TMDB Redatelj %1$s @@ -1840,7 +1840,7 @@ Rezultati testa (%1$d) Plugin-ovima pružatelja usluga potreban je TMDB API ključ. Konfigurirajte ga na zaslonu TMDB; u protivnom plugin-ovi pružatelja usluga neće ispravno raditi. Nema rezultata pretraživanja za %1$s. - Neispravan API ključ ili neuspjela veza + Neispravan API ključ ili neuspjela veza Repozitorij plugin-ova Pošalji Intro Pošalji diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index b1b36fd3..94ceab87 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -108,19 +108,6 @@ abstract class GenerateRuntimeConfigsTask : DefaultTask() { ) } - outDir.resolve("com/nuvio/app/features/player/skip").apply { - mkdirs() - resolve("IntroDbConfig.kt").writeText( - """ - |package com.nuvio.app.features.player.skip - | - |object IntroDbConfig { - | const val URL = "${props.getProperty("INTRODB_API_URL", "")}" - |} - """.trimMargin() - ) - } - outDir.resolve("com/nuvio/app/features/details").apply { mkdirs() resolve("ImdbEpisodeRatingsConfig.kt").writeText( diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.android.kt b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.android.kt index 74d31347..a5893ee6 100644 --- a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.android.kt +++ b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.android.kt @@ -62,7 +62,7 @@ actual object PlayerSettingsStorage { private const val skipIntroEnabledKey = "skip_intro_enabled" private const val animeSkipEnabledKey = "animeskip_enabled" private const val animeSkipClientIdKey = "animeskip_client_id" - private const val introDbApiKeyKey = "introdb_api_key" + private const val skipDbApiKeyKey = "skipdb_api_key" private const val introSubmitEnabledKey = "intro_submit_enabled" private const val streamAutoPlayNextEpisodeEnabledKey = "stream_auto_play_next_episode_enabled" private const val streamAutoPlayPreferBingeGroupKey = "stream_auto_play_prefer_binge_group" @@ -802,13 +802,13 @@ actual object PlayerSettingsStorage { ?.apply() } - actual fun loadIntroDbApiKey(): String? = - preferences?.getString(ProfileScopedKey.of(introDbApiKeyKey), null) + actual fun loadSkipDbApiKey(): String? = + preferences?.getString(ProfileScopedKey.of(skipDbApiKeyKey), null) - actual fun saveIntroDbApiKey(apiKey: String) { + actual fun saveSkipDbApiKey(apiKey: String) { preferences ?.edit() - ?.putString(ProfileScopedKey.of(introDbApiKeyKey), apiKey) + ?.putString(ProfileScopedKey.of(skipDbApiKeyKey), apiKey) ?.apply() } @@ -1192,7 +1192,7 @@ actual object PlayerSettingsStorage { payload.decodeSyncBoolean(skipIntroEnabledKey)?.let(::saveSkipIntroEnabled) payload.decodeSyncBoolean(animeSkipEnabledKey)?.let(::saveAnimeSkipEnabled) payload.decodeSyncString(animeSkipClientIdKey)?.let(::saveAnimeSkipClientId) - payload.decodeSyncString(introDbApiKeyKey)?.let(::saveIntroDbApiKey) + payload.decodeSyncString(skipDbApiKeyKey)?.let(::saveSkipDbApiKey) payload.decodeSyncBoolean(introSubmitEnabledKey)?.let(::saveIntroSubmitEnabled) payload.decodeSyncBoolean(streamAutoPlayNextEpisodeEnabledKey)?.let(::saveStreamAutoPlayNextEpisodeEnabled) payload.decodeSyncBoolean(streamAutoPlayPreferBingeGroupKey)?.let(::saveStreamAutoPlayPreferBingeGroup) diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/settings/IntegrationLogoPainter.android.kt b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/settings/IntegrationLogoPainter.android.kt index a2140bde..c918b213 100644 --- a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/settings/IntegrationLogoPainter.android.kt +++ b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/settings/IntegrationLogoPainter.android.kt @@ -5,7 +5,7 @@ import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource import com.nuvio.app.R import nuvio.composeapp.generated.resources.Res -import nuvio.composeapp.generated.resources.introdb_favicon +import nuvio.composeapp.generated.resources.skipdb_favicon import nuvio.composeapp.generated.resources.rating_tmdb import org.jetbrains.compose.resources.painterResource as composePainterResource @@ -15,5 +15,5 @@ internal actual fun integrationLogoPainter(logo: IntegrationLogo): Painter = IntegrationLogo.Tmdb -> composePainterResource(Res.drawable.rating_tmdb) IntegrationLogo.Trakt -> painterResource(id = R.drawable.trakt_tv_favicon) IntegrationLogo.MdbList -> painterResource(id = R.drawable.mdblist_logo) - IntegrationLogo.IntroDb -> composePainterResource(Res.drawable.introdb_favicon) + IntegrationLogo.SkipDb -> composePainterResource(Res.drawable.skipdb_favicon) } diff --git a/composeApp/src/commonMain/composeResources/drawable/introdb_favicon.png b/composeApp/src/commonMain/composeResources/drawable/skipdb_favicon.png similarity index 100% rename from composeApp/src/commonMain/composeResources/drawable/introdb_favicon.png rename to composeApp/src/commonMain/composeResources/drawable/skipdb_favicon.png diff --git a/composeApp/src/commonMain/composeResources/values-bg/strings.xml b/composeApp/src/commonMain/composeResources/values-bg/strings.xml index d68d2cdd..01c238c8 100644 --- a/composeApp/src/commonMain/composeResources/values-bg/strings.xml +++ b/composeApp/src/commonMain/composeResources/values-bg/strings.xml @@ -474,8 +474,8 @@ Nuvio се свързва с TorBox за удостоверяване на акаунт, достъп до облачна библиотека, проверки на кеша и функции за облачно възпроизвеждане. Nuvio не е свързан с или одобрен от TorBox. MDBList Nuvio използва MDBList за оценки и данни от доставчици на външни резултати. Nuvio не е свързан с или одобрен от MDBList. - IntroDB - Nuvio използва IntroDB API за предоставени от общността времеви маркери за въведения, резюмета, надписи и прегледи, използвани от контролите за пропускане. Nuvio не е свързан с или одобрен от IntroDB. + SkipDB + Nuvio използва SkipDB API за предоставени от общността времеви маркери за въведения, резюмета, надписи и прегледи, използвани от контролите за пропускане. Nuvio не е свързан с или одобрен от SkipDB. MPVKit Използва се за възпроизвеждане на iOS версии. Самият изходен код на MPVKit е лицензиран под LGPL v3.0. Пакетите MPVKit, включително библиотеките libmpv и FFmpeg, също са лицензирани под LGPL v3.0. @@ -812,8 +812,8 @@ Въведете вашето AnimeSkip API клиентско ID. Вземете го от anime-skip.com. Активирай изпращане на въведения Показвай бутон за изпращане на времеви маркери за въведения/финали към общностната база данни. - IntroDB API ключ - Въведете вашия IntroDB API ключ, за да изпращате времеви маркери. Необходим за изпращане. + SkipDB API ключ + Въведете вашия SkipDB API ключ, за да изпращате времеви маркери. Необходим за изпращане. Търси също в AnimeSkip за времеви маркери за пропускане (изисква клиентско ID). Автоматично пускане на следващия епизод Стартирай следващия епизод автоматично, когато се появи подкана. @@ -922,7 +922,7 @@ Предпочитай принудителни субтитри при съответствие с настройките ви за субтитри. Вертикално отместване Пропусни въведението - Използвай introdb.app за засичане на въведения и резюмета. + Използвай skipdb.tv за засичане на въведения и резюмета. Обхват на автоматичното пускане Всички инсталирани добавки Автоматичното пускане разглежда само потоци от вашите инсталирани добавки. @@ -1691,7 +1691,7 @@ Филми Сериали Облачната библиотека е деактивирана. - Невалиден API ключ или неуспешна връзка + Невалиден API ключ или неуспешна връзка В манифеста липсва „%1$s" TMDB колекция %1$s TMDB режисьор %1$s @@ -1793,7 +1793,7 @@ Резултати от теста (%1$d) Доставчиците на плъгини изискват TMDB API ключ. Задайте го на TMDB екрана или доставчиците на плъгини няма да работят правилно. Не са върнати резултати от търсенето за %1$s. - Невалиден API ключ или неуспешна връзка + Невалиден API ключ или неуспешна връзка Хранилище с плъгини Изпрати въведение Изпрати diff --git a/composeApp/src/commonMain/composeResources/values-cs/strings.xml b/composeApp/src/commonMain/composeResources/values-cs/strings.xml index b8a0769e..18d8de97 100644 --- a/composeApp/src/commonMain/composeResources/values-cs/strings.xml +++ b/composeApp/src/commonMain/composeResources/values-cs/strings.xml @@ -485,8 +485,8 @@ Nuvio se připojuje k TorBox pro ověřování účtu, přístup do cloudové knihovny, kontrolu mezipaměti a funkce cloudového přehrávání. Nuvio není přidruženo k TorBox. MDBList Nuvio používá MDBList pro hodnocení a data externích poskytovatelů skóre. Nuvio není přidruženo k MDBList. - IntroDB - Nuvio používá IntroDB API pro komunitní časové značky intra, rekapitulací, závěrečných titulků a ukázek, které se používají při přeskakování. Nuvio není přidruženo k IntroDB. + SkipDB + Nuvio používá SkipDB API pro komunitní časové značky intra, rekapitulací, závěrečných titulků a ukázek, které se používají při přeskakování. Nuvio není přidruženo k SkipDB. MPVKit Používá se pro přehrávání na iOS buildech. Samotný zdrojový kód MPVKit je licencován pod LGPL v3.0. Balíčky MPVKit, včetně knihoven libmpv a FFmpeg, jsou rovněž licencovány pod LGPL v3.0. @@ -833,8 +833,8 @@ Zadejte své klientské ID pro AnimeSkip API. Získejte jej na anime-skip.com. Povolit odesílání intra Zobrazit tlačítko pro odeslání časových značek intra/outra do komunitní databáze. - API klíč k IntroDB - Pro odeslání časových značek zadejte svůj API klíč IntroDB. Vyžadováno pro odeslání. + API klíč k SkipDB + Pro odeslání časových značek zadejte svůj API klíč SkipDB. Vyžadováno pro odeslání. Hledat časové značky k přeskočení také na AnimeSkip (vyžaduje Client ID). Automaticky přehrát další epizodu Jakmile se zobrazí výzva, automaticky spustit další epizodu. @@ -959,7 +959,7 @@ Upřednostnit vynucené (forced) titulky při shodě s nastavením jazyka titulků. Svislý posun Přeskočit intro - Použije introdb.app pro detekci inter a rekapitulací. + Použije skipdb.tv pro detekci inter a rekapitulací. Rozsah zdroje u automatického přehrávání Všechny nainstalované doplňky Automatické přehrávání zohledňuje pouze streamy, které pocházejí z vašich nainstalovaných doplňků. @@ -1769,8 +1769,8 @@ Seriály Cloudová knihovna je vypnutá. - - Neplatný API klíč nebo selhalo připojení + + Neplatný API klíč nebo selhalo připojení V manifestu chybí \"%1$s\" Sbírka TMDB %1$s Režisér TMDB %1$s @@ -1872,7 +1872,7 @@ Výsledky testu (%1$d) Pluginy poskytovatelů vyžadují API klíč k TMDB. Nastavte jej na obrazovce TMDB, jinak nebudou pluginy správně fungovat. Pro %1$s nebyly vráceny žádné výsledky vyhledávání. - Neplatný API klíč nebo selhalo připojení + Neplatný API klíč nebo selhalo připojení Repozitář pluginů Odeslat intro Odeslat diff --git a/composeApp/src/commonMain/composeResources/values-de/strings.xml b/composeApp/src/commonMain/composeResources/values-de/strings.xml index cfc55c3b..e93abda5 100644 --- a/composeApp/src/commonMain/composeResources/values-de/strings.xml +++ b/composeApp/src/commonMain/composeResources/values-de/strings.xml @@ -634,8 +634,8 @@ Gib deine AnimeSkip-API-Client-ID ein. Erhältlich unter anime-skip.com. Intro-Übermittlung aktivieren Eine Schaltfläche anzeigen, um Intro-/Outro-Zeitstempel an die Community-Datenbank zu übermitteln. - IntroDB-API-Schlüssel - Gib deinen IntroDB-API-Schlüssel ein, um Zeitstempel zu übermitteln. Für die Übermittlung erforderlich. + SkipDB-API-Schlüssel + Gib deinen SkipDB-API-Schlüssel ein, um Zeitstempel zu übermitteln. Für die Übermittlung erforderlich. Auch AnimeSkip nach Skip-Zeitstempeln durchsuchen (erfordert Client-ID). Nächste Episode automatisch abspielen Automatisch die nächste Episode finden und abspielen, wenn der Schwellenwert erreicht ist. diff --git a/composeApp/src/commonMain/composeResources/values-el/strings.xml b/composeApp/src/commonMain/composeResources/values-el/strings.xml index 6faf80ac..9febf981 100644 --- a/composeApp/src/commonMain/composeResources/values-el/strings.xml +++ b/composeApp/src/commonMain/composeResources/values-el/strings.xml @@ -1729,8 +1729,8 @@ AndroidX Media3 ExoPlayer 1.8.0 Το Nuvio χρησιμοποιεί τα IMDb Non-Commercial Datasets, συμπεριλαμβανομένου του title.ratings.tsv.gz, για βαθμολογίες και αριθμό ψήφων IMDb. Πληροφορίες με ευγενική παραχώρηση του IMDb (https://www.imdb.com). Χρησιμοποιείται κατόπιν άδειας. Τα δεδομένα IMDb προορίζονται για προσωπική και μη εμπορική χρήση σύμφωνα με τους όρους του IMDb. IMDb Non-Commercial Datasets - Το Nuvio χρησιμοποιεί το API του IntroDB για δεδομένα intro, ανακεφαλαίωσης, τίτλων τέλους και προεπισκόπησης χρονοσημάνσεων από την κοινότητα, τα οποία χρησιμοποιούνται από τα χειριστήρια παράλειψης. Το Nuvio δεν σχετίζεται με το IntroDB ούτε έχει την υποστήριξή του. - IntroDB + Το Nuvio χρησιμοποιεί το API του SkipDB για δεδομένα intro, ανακεφαλαίωσης, τίτλων τέλους και προεπισκόπησης χρονοσημάνσεων από την κοινότητα, τα οποία χρησιμοποιούνται από τα χειριστήρια παράλειψης. Το Nuvio δεν σχετίζεται με το SkipDB ούτε έχει την υποστήριξή του. + SkipDB Το Nuvio χρησιμοποιεί το MDBList για βαθμολογίες και δεδομένα από εξωτερικούς παρόχους βαθμολόγησης. Το Nuvio δεν σχετίζεται με το MDBList ούτε έχει την υποστήριξή του. MDBList Χρησιμοποιείται για αναπαραγωγή σε εκδόσεις iOS. @@ -1789,10 +1789,10 @@ Προώθηση των επιλυμένων χρονοσημάνσεων intro και outro στον εξωτερικό player για αυτόματη παράλειψη. Λειτουργεί μόνο σε players που το υποστηρίζουν· άλλοι players το αγνοούν. Ενεργοποίηση υποβολής intro Εμφάνιση κουμπιού για υποβολή χρονοσημάνσεων intro/outro στη βάση δεδομένων της κοινότητας. - Κλειδί API IntroDB - Εισαγάγετε το κλειδί API του IntroDB για να υποβάλλετε χρονοσημάνσεις. Απαιτείται για υποβολή. - Μη έγκυρο κλειδί API ή η σύνδεση απέτυχε - Μη έγκυρο κλειδί API ή η σύνδεση απέτυχε + Κλειδί API SkipDB + Εισαγάγετε το κλειδί API του SkipDB για να υποβάλλετε χρονοσημάνσεις. Απαιτείται για υποβολή. + Μη έγκυρο κλειδί API ή η σύνδεση απέτυχε + Μη έγκυρο κλειδί API ή η σύνδεση απέτυχε Ήχος output Χρήση της παλαιότερης εξόδου AudioUnit. Χρήση AudioUnit ενώ η έξοδος AVFoundation είναι προσωρινά απενεργοποιημένη. diff --git a/composeApp/src/commonMain/composeResources/values-es/strings.xml b/composeApp/src/commonMain/composeResources/values-es/strings.xml index 670243b1..12af258a 100644 --- a/composeApp/src/commonMain/composeResources/values-es/strings.xml +++ b/composeApp/src/commonMain/composeResources/values-es/strings.xml @@ -485,8 +485,8 @@ Nuvio se conecta a TorBox para autenticación de cuentas, acceso a la biblioteca en la nube, comprobaciones de caché y funciones de reproducción en la nube. Nuvio no está afiliado ni respaldado por TorBox. Lista MDBL Nuvio utiliza MDBList para calificaciones y datos de proveedores de puntuaciones externos. Nuvio no está afiliado ni respaldado por MDBList. - IntroducciónDB - Nuvio utiliza la API IntroDB para la introducción, el resumen, los créditos y las marcas de tiempo de vista previa proporcionadas por la comunidad utilizadas por los controles de omisión. Nuvio no está afiliado ni respaldado por IntroDB. + IntroducciónDB + Nuvio utiliza la API SkipDB para la introducción, el resumen, los créditos y las marcas de tiempo de vista previa proporcionadas por la comunidad utilizadas por los controles de omisión. Nuvio no está afiliado ni respaldado por SkipDB. MPVKit Se utiliza para la reproducción en compilaciones de iOS. La fuente MPVKit por sí sola tiene licencia LGPL v3.0. Los paquetes MPVKit, incluidas las bibliotecas libmpv y FFmpeg, también tienen licencia LGPL v3.0. @@ -833,8 +833,8 @@ Introduce tu ID de cliente API de AnimeSkip. Obtén uno en anime-skip.com. Habilitar envío de introducción Muestre un botón para enviar marcas de tiempo de introducción/final a la base de datos de la comunidad. - Clave API de introducción a DB - Ingrese su clave API de IntroDB para enviar marcas de tiempo. Requerido para la presentación. + Clave API de introducción a DB + Ingrese su clave API de SkipDB para enviar marcas de tiempo. Requerido para la presentación. Buscar también marcas de salto en AnimeSkip (requiere ID de cliente). Reproducción automática del siguiente episodio Buscar y reproducir automáticamente el siguiente episodio cuando se alcance el umbral. @@ -1769,8 +1769,8 @@ Serie La biblioteca en la nube está deshabilitada. - - Clave API no válida o error de conexión + + Clave API no válida o error de conexión Falta el manifiesto \\"%1$s\\" Colección TMDB %1$s Director de TMDB %1$s @@ -1872,7 +1872,7 @@ Resultados de la prueba (%1$d) Los proveedores de complementos requieren una clave API de TMDB. Configúrelo en la pantalla TMDB o los proveedores de complementos no funcionarán correctamente. No se obtuvieron resultados de búsqueda para %1$s. - Clave API no válida o error de conexión + Clave API no válida o error de conexión Repositorio de complementos Enviar introducción Entregar diff --git a/composeApp/src/commonMain/composeResources/values-fr/strings.xml b/composeApp/src/commonMain/composeResources/values-fr/strings.xml index 24d16d5d..73118652 100644 --- a/composeApp/src/commonMain/composeResources/values-fr/strings.xml +++ b/composeApp/src/commonMain/composeResources/values-fr/strings.xml @@ -493,8 +493,8 @@ Nuvio se connecte à TorBox pour l’authentification du compte, l’accès à la cloud library, les vérifications de cache et les fonctionnalités de lecture cloud. Nuvio n’est ni affilié ni approuvé par TorBox. MDBList Nuvio utilise MDBList pour les notes et les données de scores externes. Nuvio n’est ni affilié ni approuvé par MDBList. - IntroDB - Nuvio utilise l’API IntroDB pour les timestamps d’intro, de récap, de générique et d’aperçu fournis par la communauté et utilisés par les contrôles de saut. Nuvio n’est ni affilié ni approuvé par IntroDB. + SkipDB + Nuvio utilise l’API SkipDB pour les timestamps d’intro, de récap, de générique et d’aperçu fournis par la communauté et utilisés par les contrôles de saut. Nuvio n’est ni affilié ni approuvé par SkipDB. MPVKit Utilisé pour la lecture sur les builds iOS. Le code source MPVKit seul est distribué sous LGPL v3.0. Les bundles MPVKit, y compris les bibliothèques libmpv et FFmpeg, sont également distribués sous LGPL v3.0. @@ -851,8 +851,8 @@ Saisissez votre ID client API AnimeSkip. Obtenez-en un sur anime-skip.com. Activer la soumission d’intro Affiche un bouton pour soumettre des timestamps d’intro/outro à la base de données communautaire. - Clé API IntroDB - Saisissez votre clé API IntroDB pour soumettre des timestamps. Requise pour la soumission. + Clé API SkipDB + Saisissez votre clé API SkipDB pour soumettre des timestamps. Requise pour la soumission. Rechercher également des marqueurs de saut sur AnimeSkip (nécessite un ID client). Lecture automatique de l’épisode suivant Rechercher et lire automatiquement l’épisode suivant lorsque le seuil est atteint. @@ -1782,8 +1782,8 @@ Séries La cloud library est désactivée. - - Clé API invalide ou échec de connexion + + Clé API invalide ou échec de connexion US, KR, JP, IN 2020-01-01 @@ -1899,7 +1899,7 @@ Résultats du test (%1$d) Les fournisseurs de plugins nécessitent une clé API TMDB. Configurez-la dans l’écran TMDB, sinon les fournisseurs ne fonctionneront pas correctement. Aucun résultat de recherche pour %1$s. - Clé API invalide ou échec de connexion + Clé API invalide ou échec de connexion Dépôt de plugin Soumettre l’intro Soumettre diff --git a/composeApp/src/commonMain/composeResources/values-he/strings.xml b/composeApp/src/commonMain/composeResources/values-he/strings.xml index f17027ad..eae18bc1 100644 --- a/composeApp/src/commonMain/composeResources/values-he/strings.xml +++ b/composeApp/src/commonMain/composeResources/values-he/strings.xml @@ -390,7 +390,7 @@ גודל כתוביות צבע הטקסט דלג על הקדמה - השתמש ב-introdb.app לזיהוי הקדמות ותקצירים. + השתמש ב-skipdb.tv לזיהוי הקדמות ותקצירים. אחוז סף אוספים קרדיטים diff --git a/composeApp/src/commonMain/composeResources/values-hu/strings.xml b/composeApp/src/commonMain/composeResources/values-hu/strings.xml index 9e334613..316478fd 100644 --- a/composeApp/src/commonMain/composeResources/values-hu/strings.xml +++ b/composeApp/src/commonMain/composeResources/values-hu/strings.xml @@ -429,8 +429,8 @@ A Nuvio a Trakthoz kapcsolódik a fiókhitelesítés, a megtekintési előzmények, a folyamat-szinkronizálás, a könyvtáradatok, az értékelések, a listák és a hozzászólások kezeléséhez. A Nuvio nem áll kapcsolatban a Trakttal, és a Trakt nem támogatja azt. MDBList A Nuvio az MDBList-et használja az értékelésekhez és a külső pontszám-szolgáltatók adataihoz. A Nuvio nem áll kapcsolatban az MDBListtel, és az MDBList nem támogatja azt. - IntroDB - A Nuvio az IntroDB API-t használja a közösség által beküldött intró, összefoglaló (recap), stáblista és előzetes időbélyegekhez, amelyeket a kihagyás vezérlők használnak. A Nuvio nem áll kapcsolatban az IntroDB-vel, és az IntroDB nem támogatja azt. + SkipDB + A Nuvio az SkipDB API-t használja a közösség által beküldött intró, összefoglaló (recap), stáblista és előzetes időbélyegekhez, amelyeket a kihagyás vezérlők használnak. A Nuvio nem áll kapcsolatban az SkipDB-vel, és az SkipDB nem támogatja azt. MPVKit Lejátszáshoz használatos az iOS buildjelemeken. Az MPVKit forráskódja önmagában az LGPL v3.0 szerint licencelt. Az MPVKit csomagok, beleértve a libmpv és FFmpeg könyvtárakat, szintén az LGPL v3.0 szerint licenceltek. @@ -715,8 +715,8 @@ Add meg az AnimeSkip API ügyfél-azonosítódat (Client ID). Az anime-skip.com oldalon igényelhetsz egyet. Intró beküldés engedélyezése Gomb megjelenítése az intró/outró időbélyegek közösségi adatbázisba történő beküldéséhez. - IntroDB API-kulcs - Add meg az IntroDB API-kulcsodat az időbélyegek beküldéséhez. A beküldéshez kötelező. + SkipDB API-kulcs + Add meg az SkipDB API-kulcsodat az időbélyegek beküldéséhez. A beküldéshez kötelező. Keresés az AnimeSkip adatbázisában is a kihagyási időbélyegekért (ügyfél-azonosító szükséges). Következő epizód automatikus lejátszása A következő epizód automatikus indítása, amikor a felhívás megjelenik. @@ -796,7 +796,7 @@ Töltőképernyő megjelenítése Töltőképernyő mutatása az első videokocka megjelenéséig. Intró kihagyása - Az introdb.app használata az intrók és összefoglalók felismeréséhez. + Az skipdb.tv használata az intrók és összefoglalók felismeréséhez. Automatikus lejátszás forrásköre Minden telepített kiegészítő Az automatikus lejátszás csak a telepített kiegészítőkből származó streameket veszi figyelembe. @@ -1752,7 +1752,7 @@ Filmek Sorozatok A Felhő könyvtár le van tiltva. - Érvénytelen API kulcs vagy a kapcsolódás meghiúsult + Érvénytelen API kulcs vagy a kapcsolódás meghiúsult A manifestből hiányzik a(z) „%1$s” mező %1$s. TMDB gyűjtemény %1$s. TMDB rendező @@ -1854,7 +1854,7 @@ Teszt eredmények (%1$d) A szolgáltatói bővítményekhez TMDB API kulcs szükséges. Állítsd be a TMDB képernyőn, különben a bővítmények nem fognak megfelelően működni. Nincs találat a(z) %1$s katalógusban. - Érvénytelen API kulcs vagy a kapcsolódás meghiúsult + Érvénytelen API kulcs vagy a kapcsolódás meghiúsult Bővítmény-tároló Időbélyegek beküldése Beküldés diff --git a/composeApp/src/commonMain/composeResources/values-id/strings.xml b/composeApp/src/commonMain/composeResources/values-id/strings.xml index 660f670c..ae04b6cd 100644 --- a/composeApp/src/commonMain/composeResources/values-id/strings.xml +++ b/composeApp/src/commonMain/composeResources/values-id/strings.xml @@ -451,8 +451,8 @@ Nuvio menhubungi TorBox untuk autentikasi akun, akses pustaka cloud, pemeriksaan cache,dan fitur pemutaran cloud. Nuvio tidak berafiliasi dengan atau diendorse oleh TorBox. MDBList Nuvio menggunakan MDBList untuk rating dan data penyedia skor eksternal. Nuvio tidak berafiliasi atau didukung oleh MDBList. - IntroDB - Nuvio menggunakan API IntroDB untuk intro, rekap, kredit, dan pratinjau cap waktu yang disediakan komunitas yang digunakan oleh kontrol lewati. Nuvio tidak berafiliasi atau didukung oleh IntroDB. + SkipDB + Nuvio menggunakan API SkipDB untuk intro, rekap, kredit, dan pratinjau cap waktu yang disediakan komunitas yang digunakan oleh kontrol lewati. Nuvio tidak berafiliasi atau didukung oleh SkipDB. MPVKit Digunakan untuk pemutaran pada build iOS. Sumber MPVKit saja dilisensikan di bawah LGPL v3.0. Bundle MPVKit, termasuk libmpv dan pustaka FFmpeg, juga dilisensikan di bawah LGPL v3.0. @@ -758,8 +758,8 @@ Masukkan ID klien API AnimeSkip Anda. Dapatkan di anime-skip.com. Aktifkan Pengiriman Intro Tampilkan tombol untuk mengirim cap waktu intro/outro ke database komunitas. - Kunci API IntroDB - Masukkan kunci API IntroDB Anda untuk mengirim cap waktu. Diperlukan untuk pengiriman. + Kunci API SkipDB + Masukkan kunci API SkipDB Anda untuk mengirim cap waktu. Diperlukan untuk pengiriman. Juga cari AnimeSkip untuk cap waktu lewati (memerlukan ID klien). Putar Otomatis Episode Berikutnya Mulai episode berikutnya secara otomatis ketika prompt muncul. @@ -862,7 +862,7 @@ Utamakan forced subtitles ketika mencocokkan pengaturan bahasa terjemahan. Batas Vertikal Lewati Intro - Gunakan introdb.app untuk mendeteksi intro dan rekap. + Gunakan skipdb.tv untuk mendeteksi intro dan rekap. Cakupan Sumber Putar Otomatis Semua addon terpasang Putar otomatis hanya mempertimbangkan stream dari addon yang terpasang. diff --git a/composeApp/src/commonMain/composeResources/values-in/strings.xml b/composeApp/src/commonMain/composeResources/values-in/strings.xml index 8fe51ffa..89a69c2f 100644 --- a/composeApp/src/commonMain/composeResources/values-in/strings.xml +++ b/composeApp/src/commonMain/composeResources/values-in/strings.xml @@ -451,8 +451,8 @@ Nuvio menhubungi TorBox untuk autentikasi akun, akses pustaka cloud, pemeriksaan cache,dan fitur pemutaran cloud. Nuvio tidak berafiliasi dengan atau diendorse oleh TorBox. MDBList Nuvio menggunakan MDBList untuk rating dan data penyedia skor eksternal. Nuvio tidak berafiliasi atau didukung oleh MDBList. - IntroDB - Nuvio menggunakan API IntroDB untuk intro, rekap, kredit, dan pratinjau cap waktu yang disediakan komunitas yang digunakan oleh kontrol lewati. Nuvio tidak berafiliasi atau didukung oleh IntroDB. + SkipDB + Nuvio menggunakan API SkipDB untuk intro, rekap, kredit, dan pratinjau cap waktu yang disediakan komunitas yang digunakan oleh kontrol lewati. Nuvio tidak berafiliasi atau didukung oleh SkipDB. MPVKit Digunakan untuk pemutaran pada build iOS. Sumber MPVKit saja dilisensikan di bawah LGPL v3.0. Bundle MPVKit, termasuk libmpv dan pustaka FFmpeg, juga dilisensikan di bawah LGPL v3.0. @@ -758,8 +758,8 @@ Masukkan ID klien API AnimeSkip Anda. Dapatkan di anime-skip.com. Aktifkan Pengiriman Intro Tampilkan tombol untuk mengirim cap waktu intro/outro ke database komunitas. - Kunci API IntroDB - Masukkan kunci API IntroDB Anda untuk mengirim cap waktu. Diperlukan untuk pengiriman. + Kunci API SkipDB + Masukkan kunci API SkipDB Anda untuk mengirim cap waktu. Diperlukan untuk pengiriman. Juga cari AnimeSkip untuk cap waktu lewati (memerlukan ID klien). Putar Otomatis Episode Berikutnya Mulai episode berikutnya secara otomatis ketika prompt muncul. @@ -862,7 +862,7 @@ Utamakan forced subtitles ketika mencocokkan pengaturan bahasa terjemahan. Batas Vertikal Lewati Intro - Gunakan introdb.app untuk mendeteksi intro dan rekap. + Gunakan skipdb.tv untuk mendeteksi intro dan rekap. Cakupan Sumber Putar Otomatis Semua addon terpasang Putar otomatis hanya mempertimbangkan stream dari addon yang terpasang. diff --git a/composeApp/src/commonMain/composeResources/values-it/strings.xml b/composeApp/src/commonMain/composeResources/values-it/strings.xml index 999a8f8d..3d05c45a 100644 --- a/composeApp/src/commonMain/composeResources/values-it/strings.xml +++ b/composeApp/src/commonMain/composeResources/values-it/strings.xml @@ -1303,8 +1303,8 @@ Nuvio si connette a TorBox per l'autenticazione dell'account, l'accesso alla libreria cloud, i controlli della cache e le funzioni di riproduzione in cloud. Nuvio non è affiliato né approvato da TorBox. MDBList Nuvio utilizza MDBList per valutazioni e dati di fornitori di punteggi esterni. Nuvio non è affiliato né approvato da MDBList. - IntroDB - Nuvio utilizza l'API di IntroDB per i timestamp di intro, riassunti, titoli di coda e anteprime forniti dalla community e utilizzati dai controlli di salto. Nuvio non è affiliato né approvato da IntroDB. + SkipDB + Nuvio utilizza l'API di SkipDB per i timestamp di intro, riassunti, titoli di coda e anteprime forniti dalla community e utilizzati dai controlli di salto. Nuvio non è affiliato né approvato da SkipDB. MPVKit Utilizzato per la riproduzione sulle build iOS. Il solo sorgente di MPVKit è rilasciato sotto licenza LGPL v3.0. Anche i pacchetti di MPVKit, incluse le librerie libmpv e FFmpeg, sono rilasciati sotto licenza LGPL v3.0. @@ -1406,8 +1406,8 @@ Sfoca le miniature degli episodi finché non vengono visti per evitare spoiler. Abilita invio intro Mostra un pulsante per inviare i timestamp di intro/sigle finali al database della community. - Chiave API IntroDB - Inserisci la tua chiave API IntroDB per inviare i timestamp. Richiesta per l'invio. + Chiave API SkipDB + Inserisci la tua chiave API SkipDB per inviare i timestamp. Richiesta per l'invio. Lettore esterno App lettore esterno Apri la nuova riproduzione con l'app video predefinita di Android o con il selettore di sistema. @@ -1807,7 +1807,7 @@ Film Serie La libreria cloud è disabilitata. - Chiave API non valida o connessione non riuscita + Chiave API non valida o connessione non riuscita Nel manifest manca "%1$s" Collezione TMDB %1$s Regista TMDB %1$s @@ -1905,7 +1905,7 @@ Risultati del test (%1$d) I provider dei plugin richiedono una chiave API TMDB. Impostala nella schermata TMDB, altrimenti i provider non funzioneranno correttamente. Nessun risultato di ricerca restituito per %1$s. - Chiave API non valida o connessione non riuscita + Chiave API non valida o connessione non riuscita Repository di plugin Invia intro Invia diff --git a/composeApp/src/commonMain/composeResources/values-ja/strings.xml b/composeApp/src/commonMain/composeResources/values-ja/strings.xml index 349f8c06..2fadff6a 100644 --- a/composeApp/src/commonMain/composeResources/values-ja/strings.xml +++ b/composeApp/src/commonMain/composeResources/values-ja/strings.xml @@ -485,8 +485,8 @@ Nuvioはアカウント認証、クラウドライブラリアクセス、キャッシュ確認、クラウド再生機能のためにTorBoxと連携しています。NuvioはTorBoxと提携・承認されたものではありません。 MDBList NuvioはMDBListを使用して評価と外部スコアプロバイダーデータを取得しています。NuvioはMDBListと提携・承認されたものではありません。 - IntroDB - NuvioはIntroDB APIを使用して、スキップコントロールに使用するイントロ・振り返り・エンドクレジット・プレビューのタイムスタンプ(コミュニティ提供)を取得しています。NuvioはIntroDBと提携・承認されたものではありません。 + SkipDB + NuvioはSkipDB APIを使用して、スキップコントロールに使用するイントロ・振り返り・エンドクレジット・プレビューのタイムスタンプ(コミュニティ提供)を取得しています。NuvioはSkipDBと提携・承認されたものではありません。 MPVKit iOSビルドの再生に使用。 MPVKit単体はLGPL v3.0でライセンスされています。libmpvおよびFFmpegライブラリを含むMPVKitバンドルも同様にLGPL v3.0でライセンスされています。 @@ -833,8 +833,8 @@ AnimeSkip APIのクライアントIDを入力してください。anime-skip.comで取得できます。 イントロ投稿を有効にする イントロ・アウトロのタイムスタンプをコミュニティデータベースに投稿するボタンを表示します。 - IntroDB APIキー - タイムスタンプを投稿するためのIntroDB APIキーを入力してください。投稿に必要です。 + SkipDB APIキー + タイムスタンプを投稿するためのSkipDB APIキーを入力してください。投稿に必要です。 スキップタイムスタンプをAnimeSkipでも検索します(クライアントIDが必要)。 次のエピソードを自動再生 プロンプトが表示されたら自動的に次のエピソードを開始します。 @@ -957,7 +957,7 @@ 字幕言語の設定に一致する場合、強制字幕を優先します。 垂直オフセット イントロをスキップ - introdb.appを使用してイントロと振り返りを検出します。 + skipdb.tvを使用してイントロと振り返りを検出します。 自動再生のソース範囲 インストール済みのすべてのアドオン 自動再生はインストール済みアドオンのストリームのみを対象とします。 @@ -1764,8 +1764,8 @@ シリーズ クラウドライブラリが無効です。 - - APIキーが無効か接続に失敗しました + + APIキーが無効か接続に失敗しました マニフェストに「%1$s」がありません TMDBコレクション %1$s TMDB監督 %1$s @@ -1867,7 +1867,7 @@ テスト結果 (%1$d 件) プラグインプロバイダーにはTMDB APIキーが必要です。TMDB画面でAPIキーを設定しないと、プラグインプロバイダーが正常に動作しない場合があります。 %1$s の検索結果が返されませんでした。 - APIキーが無効か接続に失敗しました + APIキーが無効か接続に失敗しました プラグインリポジトリ イントロを投稿 投稿 diff --git a/composeApp/src/commonMain/composeResources/values-nb/strings.xml b/composeApp/src/commonMain/composeResources/values-nb/strings.xml index fa0b8468..0c4edc33 100644 --- a/composeApp/src/commonMain/composeResources/values-nb/strings.xml +++ b/composeApp/src/commonMain/composeResources/values-nb/strings.xml @@ -435,8 +435,8 @@ Nuvio kobler til Trakt for autentisering, seerhistorikk, synkronisering osv. Nuvio er ikke tilknyttet eller godkjent av Trakt. MDBList Nuvio bruker MDBList for eksterne vurderinger. Nuvio er ikke tilknyttet eller godkjent av MDBList. - IntroDB - Nuvio bruker IntroDB API for intro-, recap-, credits- og preview-tidsstempler. Nuvio er ikke tilknyttet eller godkjent av IntroDB. + SkipDB + Nuvio bruker SkipDB API for intro-, recap-, credits- og preview-tidsstempler. Nuvio er ikke tilknyttet eller godkjent av SkipDB. MPVKit Brukes for avspilling på iOS. Lisensiert under LGPL v3.0. @@ -738,8 +738,8 @@ Skriv inn din AnimeSkip API client ID. Hent en på anime-skip.com. Aktiver intro-innsending Vis en knapp for å sende intro/outro-tidsstempler til fellesskapsdatabasen. - IntroDB API-nøkkel - Skriv inn din IntroDB API-nøkkel for å sende tidsstempler. + SkipDB API-nøkkel + Skriv inn din SkipDB API-nøkkel for å sende tidsstempler. Søk også i AnimeSkip etter hopp-tidsstempler (krever client ID). Auto-spill neste episode Start neste episode automatisk når prompt vises. @@ -819,7 +819,7 @@ Laste-overlay Vis lasts skjerm til første bilde vises. Hopp intro - Bruk introdb.app for å oppdage introer og recaps. + Bruk skipdb.tv for å oppdage introer og recaps. Auto-play kildeomfang Alle installerte tillegg Auto-play vurderer kun strømmer fra installerte tillegg. diff --git a/composeApp/src/commonMain/composeResources/values-nl/strings.xml b/composeApp/src/commonMain/composeResources/values-nl/strings.xml index 30963d88..da2d4e2a 100644 --- a/composeApp/src/commonMain/composeResources/values-nl/strings.xml +++ b/composeApp/src/commonMain/composeResources/values-nl/strings.xml @@ -501,8 +501,8 @@ Nuvio maakt verbinding met TorBox voor accountauthenticatie, toegang tot cloudbibliotheek, cachecontroles en cloud-afspeelfuncties. Nuvio is niet gelieerd aan of goedgekeurd door TorBox. MDBList Nuvio gebruikt MDBList voor beoordelingen en gegevens van externe scoreproviders. Nuvio is niet gelieerd aan of goedgekeurd door MDBList. - IntroDB - Nuvio gebruikt de IntroDB API voor door de community aangeleverde tijdstempels van intro, samenvatting, aftiteling en preview die worden gebruikt door de overslaanbediening. Nuvio is niet gelieerd aan of goedgekeurd door IntroDB. + SkipDB + Nuvio gebruikt de SkipDB API voor door de community aangeleverde tijdstempels van intro, samenvatting, aftiteling en preview die worden gebruikt door de overslaanbediening. Nuvio is niet gelieerd aan of goedgekeurd door SkipDB. MPVKit Gebruikt voor afspelen op iOS-builds. De MPVKit-broncode zelf is gelicentieerd onder LGPL v3.0. MPVKit-bundels, inclusief libmpv- en FFmpeg-bibliotheken, zijn eveneens gelicentieerd onder LGPL v3.0. @@ -859,8 +859,8 @@ Voer je AnimeSkip API-clientID in. Verkrijg er een op anime-skip.com. Intro-inzending inschakelen Toon een knop om intro-/outrotijdstempels naar de communitydatabase te versturen. - IntroDB API-sleutel - Voer je IntroDB API-sleutel in om tijdstempels in te dienen. Vereist voor inzending. + SkipDB API-sleutel + Voer je SkipDB API-sleutel in om tijdstempels in te dienen. Vereist voor inzending. Zoek ook op AnimeSkip naar overslaantijdstempels (vereist clientID). Volgende aflevering automatisch afspelen Start de volgende aflevering automatisch wanneer de prompt verschijnt. @@ -985,7 +985,7 @@ Geef de voorkeur aan geforceerde ondertitels bij het matchen van je taalinstellingen voor ondertiteling. Verticale offset Intro overslaan - Gebruik introdb.app om intro's en samenvattingen te detecteren. + Gebruik skipdb.tv om intro's en samenvattingen te detecteren. Bronbereik voor automatisch afspelen Alle geïnstalleerde addons Automatisch afspelen houdt alleen rekening met streams van je geïnstalleerde addons. @@ -1795,8 +1795,8 @@ Series Cloudbibliotheek is uitgeschakeld. - - Ongeldige API-sleutel of verbinding mislukt + + Ongeldige API-sleutel of verbinding mislukt Manifest mist \"%1$s\" TMDB-collectie %1$s TMDB-regisseur %1$s @@ -1898,7 +1898,7 @@ Testresultaten (%1$d) Providerplug-ins vereisen een TMDB API-sleutel. Stel deze in op het TMDB-scherm, anders werken providerplug-ins niet correct. Geen zoekresultaten voor %1$s. - Ongeldige API-sleutel of verbinding mislukt + Ongeldige API-sleutel of verbinding mislukt Plug-inrepository Intro indienen Indienen diff --git a/composeApp/src/commonMain/composeResources/values-pl/strings.xml b/composeApp/src/commonMain/composeResources/values-pl/strings.xml index 10adfca3..55e30af9 100644 --- a/composeApp/src/commonMain/composeResources/values-pl/strings.xml +++ b/composeApp/src/commonMain/composeResources/values-pl/strings.xml @@ -451,8 +451,8 @@ Nuvio łączy się z TorBox w celu uwierzytelniania konta, dostępu do biblioteki w chmurze, sprawdzania pamięci podręcznej i odtwarzania z chmury. Nuvio nie jest powiązane z TorBox ani przez nie wspierane. MDBList Nuvio korzysta z MDBList do ocen i danych zewnętrznych dostawców ocen. Nuvio nie jest powiązane z MDBList ani przez nie wspierane. - IntroDB - Nuvio korzysta z API IntroDB do dostarczanych przez społeczność znaczników intro, podsumowań, napisów końcowych i podglądów używanych przez kontrolki pomijania. Nuvio nie jest powiązane z IntroDB ani przez nie wspierane. + SkipDB + Nuvio korzysta z API SkipDB do dostarczanych przez społeczność znaczników intro, podsumowań, napisów końcowych i podglądów używanych przez kontrolki pomijania. Nuvio nie jest powiązane z SkipDB ani przez nie wspierane. MPVKit Używany do odtwarzania w wersjach na iOS. Kod źródłowy MPVKit jest licencjonowany na podstawie LGPL v3.0. Pakiety MPVKit, w tym biblioteki libmpv i FFmpeg, są również licencjonowane na podstawie LGPL v3.0. @@ -762,8 +762,8 @@ Wprowadź ID klienta API AnimeSkip. Pobierz je na anime-skip.com. Włącz przesyłanie intro Pokaż przycisk do przesyłania znaczników intro/outro do bazy społeczności. - Klucz API IntroDB - Wprowadź klucz API IntroDB, aby przesyłać znaczniki czasowe. Wymagany do przesyłania. + Klucz API SkipDB + Wprowadź klucz API SkipDB, aby przesyłać znaczniki czasowe. Wymagany do przesyłania. Wyszukuj również w AnimeSkip znaczniki pomijania (wymaga ID klienta). Automatyczne odtwarzanie następnego odcinka Automatycznie znajdź i odtwórz następny odcinek po osiągnięciu progu. @@ -1771,8 +1771,8 @@ Zezwalaj na strumienie peer-to-peer (torrent) Strumieniowanie P2P STRUMIENIOWANIE P2P - Nieprawidłowy klucz API lub błąd połączenia - Nieprawidłowy klucz API lub błąd połączenia + Nieprawidłowy klucz API lub błąd połączenia + Nieprawidłowy klucz API lub błąd połączenia Wyjście audio Użyj starszego wyjścia AudioUnit. Użyj AudioUnit, gdy wyjście AVFoundation jest tymczasowo wyłączone. diff --git a/composeApp/src/commonMain/composeResources/values-pt-rBR/strings.xml b/composeApp/src/commonMain/composeResources/values-pt-rBR/strings.xml index 21c1a2fe..598e1a1c 100644 --- a/composeApp/src/commonMain/composeResources/values-pt-rBR/strings.xml +++ b/composeApp/src/commonMain/composeResources/values-pt-rBR/strings.xml @@ -436,8 +436,8 @@ O Nuvio conecta-se ao Trakt para autenticação de conta, histórico de assistidos, sincronização de progresso, dados da biblioteca, classificações, listas e comentários. O Nuvio não é afiliado ou endossado pelo Trakt. MDBList O Nuvio usa o MDBList para classificações e dados de provedores externos de pontuação. O Nuvio não é afiliado ou endossado pelo MDBList. - IntroDB - O Nuvio usa a API do IntroDB para timestamps de intro, recapitulação, créditos e prévia fornecidos pela comunidade, usados pelos controles de pular. O Nuvio não é afiliado ou endossado pelo IntroDB. + SkipDB + O Nuvio usa a API do SkipDB para timestamps de intro, recapitulação, créditos e prévia fornecidos pela comunidade, usados pelos controles de pular. O Nuvio não é afiliado ou endossado pelo SkipDB. MPVKit Usado para reprodução em builds para iOS. O código-fonte do MPVKit isolado é licenciado sob LGPL v3.0. Os bundles do MPVKit, incluindo bibliotecas libmpv e FFmpeg, também são licenciados sob LGPL v3.0. @@ -725,8 +725,8 @@ Insira seu client ID da API do AnimeSkip. Obtenha um em anime-skip.com. Ativar Envio de Intro Mostrar um botão para enviar timestamps de intro/outro para o banco de dados da comunidade. - Chave de API do IntroDB - Insira sua chave de API do IntroDB para enviar timestamps. Necessário para envio. + Chave de API do SkipDB + Insira sua chave de API do SkipDB para enviar timestamps. Necessário para envio. Também buscar no AnimeSkip por timestamps de pular (requer client ID). Reproduzir Próximo Episódio Automaticamente Iniciar próximo episódio automaticamente quando o prompt aparecer. @@ -808,7 +808,7 @@ Overlay de Carregamento Mostrar tela de carregamento até o primeiro quadro de vídeo aparecer. Pular Intro - Usar introdb.app para detectar intros e recapitulações. + Usar skipdb.tv para detectar intros e recapitulações. Escopo de Fonte para Reprodução Automática Todos os addons instalados Reprodução automática considera apenas streams vindas de seus addons instalados. diff --git a/composeApp/src/commonMain/composeResources/values-pt/strings.xml b/composeApp/src/commonMain/composeResources/values-pt/strings.xml index 990d1895..41425111 100644 --- a/composeApp/src/commonMain/composeResources/values-pt/strings.xml +++ b/composeApp/src/commonMain/composeResources/values-pt/strings.xml @@ -480,8 +480,8 @@ O Nuvio liga-se ao TorBox para autenticação de conta, acesso à biblioteca na nuvem, verificações de cache e funcionalidades de reprodução na nuvem. O Nuvio não é afiliado ao TorBox nem apoiado por ele. MDBList O Nuvio utiliza o MDBList para classificações e dados de fornecedores de pontuações externas. O Nuvio não é afiliado ao MDBList nem apoiado por ele. - IntroDB - O Nuvio utiliza a API do IntroDB para marcas de tempo de introduções, resumos, créditos e antevisões fornecidas pela comunidade, utilizadas pelos controlos de salto. O Nuvio não é afiliado ao IntroDB nem apoiado por ele. + SkipDB + O Nuvio utiliza a API do SkipDB para marcas de tempo de introduções, resumos, créditos e antevisões fornecidas pela comunidade, utilizadas pelos controlos de salto. O Nuvio não é afiliado ao SkipDB nem apoiado por ele. MPVKit Utilizado para a reprodução em versões iOS. O código-fonte do MPVKit, por si só, está licenciado sob a LGPL v3.0. Os pacotes do MPVKit, incluindo as bibliotecas libmpv e FFmpeg, também estão licenciados sob a LGPL v3.0. @@ -819,8 +819,8 @@ Introduz o teu ID de cliente da API do AnimeSkip. Obtém um em anime-skip.com. Ativar envio de introduções Mostra um botão para enviares marcas de tempo de introduções/finais para a base de dados da comunidade. - Chave API do IntroDB - Introduz a tua chave API do IntroDB para enviares marcas de tempo. Obrigatório para a submissão. + Chave API do SkipDB + Introduz a tua chave API do SkipDB para enviares marcas de tempo. Obrigatório para a submissão. Pesquisa também no AnimeSkip por marcas de tempo para saltar conteúdo (requer ID de cliente). Reproduzir automaticamente o próximo episódio Inicia o próximo episódio automaticamente assim que o aviso aparecer. @@ -931,7 +931,7 @@ Dá preferência a legendas forçadas quando corresponderem às definições de idioma das legendas. Afastamento vertical Saltar introdução - Utiliza o introdb.app para detetar introduções e resumos. + Utiliza o skipdb.tv para detetar introduções e resumos. Âmbito da reprodução automática Todos os addons instalados A reprodução automática apenas considera streams provenientes dos teus addons instalados. @@ -1700,7 +1700,7 @@ Filmes Séries A biblioteca na nuvem está desativada. - Chave API inválida ou falha na ligação + Chave API inválida ou falha na ligação Campo em falta no manifesto: \"%1$s\" Coleção TMDB %1$s Realização TMDB %1$s @@ -1802,7 +1802,7 @@ Resultados do teste (%1$d) Os fornecedores de plugins precisam de uma chave API do TMDB. Define-a na página do TMDB; caso contrário, podem não funcionar corretamente. Nenhum resultado de pesquisa devolvido por %1$s. - Chave API inválida ou falha na ligação + Chave API inválida ou falha na ligação Repositório de plugins Enviar introdução Enviar diff --git a/composeApp/src/commonMain/composeResources/values-ro/strings.xml b/composeApp/src/commonMain/composeResources/values-ro/strings.xml index 22b50658..90952d92 100644 --- a/composeApp/src/commonMain/composeResources/values-ro/strings.xml +++ b/composeApp/src/commonMain/composeResources/values-ro/strings.xml @@ -501,8 +501,8 @@ Nuvio se conectează la TorBox pentru autentificarea contului, accesul la biblioteca din cloud, verificarea cache-ului și funcții de redare în cloud. Nuvio nu este afiliat cu și nici aprobat de TorBox. MDBList Nuvio utilizează MDBList pentru evaluări și date de la furnizori externi de scoruri. Nuvio nu este afiliat cu și nici aprobat de MDBList. - IntroDB - Nuvio folosește API-ul IntroDB pentru marcajele de timp (intro, recapitulare, credite și previzualizări) oferite de comunitate și utilizate de comenzile de salt. Nuvio nu este afiliat cu și nici aprobat de IntroDB. + SkipDB + Nuvio folosește API-ul SkipDB pentru marcajele de timp (intro, recapitulare, credite și previzualizări) oferite de comunitate și utilizate de comenzile de salt. Nuvio nu este afiliat cu și nici aprobat de SkipDB. MPVKit Utilizat pentru redare pe build-urile de iOS. Codul sursă MPVKit în sine este licențiat sub LGPL v3.0. Pachetele MPVKit, inclusiv bibliotecile libmpv și FFmpeg, sunt de asemenea licențiate sub LGPL v3.0. @@ -859,8 +859,8 @@ Introdu ID-ul tău de client API AnimeSkip. Poți obține unul pe anime-skip.com. Activează trimiterea de intro-uri Afișează un buton pentru a trimite marcajele de timp pentru intro/outro în baza de date a comunității. - Cheie API IntroDB - Introdu cheia ta API IntroDB pentru a trimite marcaje de timp. Este necesară pentru trimitere. + Cheie API SkipDB + Introdu cheia ta API SkipDB pentru a trimite marcaje de timp. Este necesară pentru trimitere. Caută marcaje de timp de salt și pe AnimeSkip (necesită ID de client). Redă automat episodul următor Pornește automat episodul următor când apare mesajul. @@ -985,7 +985,7 @@ Preferă subtitrările forțate atunci când corespund setărilor tale de limbă. Decalaj vertical Sari peste intro - Folosește introdb.app pentru a detecta intro-urile și recapitulările. + Folosește skipdb.tv pentru a detecta intro-urile și recapitulările. Domeniu redare automată surse Toate extensiile instalate Redarea automată ia în considerare doar fluxurile care provin din extensiile tale instalate. @@ -1767,7 +1767,7 @@ Filme Seriale Biblioteca în cloud este dezactivată. - Cheie API nevalidă sau conexiunea a eșuat + Cheie API nevalidă sau conexiunea a eșuat Din manifest lipsește câmpul „%1$s” Colecție TMDB %1$s Regizor TMDB %1$s @@ -1869,7 +1869,7 @@ Rezultate test (%1$d) Furnizorii de module necesită o cheie API TMDB. Seteaz-o în ecranul TMDB, altfel modulele furnizorilor nu vor funcționa corect. Nu s-au returnat rezultate de căutare pentru %1$s. - Cheie API nevalidă sau conexiunea a eșuat + Cheie API nevalidă sau conexiunea a eșuat Depozit de module Trimite Intro Trimite diff --git a/composeApp/src/commonMain/composeResources/values-sk/strings.xml b/composeApp/src/commonMain/composeResources/values-sk/strings.xml index b707e5b5..b0fdb9ed 100644 --- a/composeApp/src/commonMain/composeResources/values-sk/strings.xml +++ b/composeApp/src/commonMain/composeResources/values-sk/strings.xml @@ -475,8 +475,8 @@ Nuvio sa pripája k TorBox pre autentifikáciu účtu, prístup ku cloudovej knižnici, kontroly vyrovnávacej pamäte a funkcie cloudového prehrávania. Nuvio nie je pridružený k TorBox ani ním schválený. MDBList Nuvio používa MDBList pre hodnotenia a dáta externých poskytovateľov skóre. Nuvio nie je pridružený k MDBList ani ním schválený. - IntroDB - Nuvio používa API IntroDB pre komunitou poskytované časové pečiatky intro, rekapitulácie, kreditov a náhľadov používaných ovládacími prvkami preskočenia. Nuvio nie je pridružený k IntroDB ani ním schválený. + SkipDB + Nuvio používa API SkipDB pre komunitou poskytované časové pečiatky intro, rekapitulácie, kreditov a náhľadov používaných ovládacími prvkami preskočenia. Nuvio nie je pridružený k SkipDB ani ním schválený. MPVKit Používa sa na prehrávanie v zostavách iOS. Samotný zdrojový kód MPVKit je licencovaný pod LGPL v3.0. Balíky MPVKit vrátane knižníc libmpv a FFmpeg sú tiež licencované pod LGPL v3.0. @@ -821,8 +821,8 @@ Zadajte ID klienta API AnimeSkip. Získajte ho na anime-skip.com. Povoliť odosielanie intro Zobraziť tlačidlo na odosielanie časových pečiatok intro/outro do komunitnej databázy. - API kľúč IntroDB - Zadajte váš API kľúč IntroDB na odosielanie časových pečiatok. Vyžaduje sa na odosielanie. + API kľúč SkipDB + Zadajte váš API kľúč SkipDB na odosielanie časových pečiatok. Vyžaduje sa na odosielanie. Tiež vyhľadávať v AnimeSkip časové pečiatky preskočenia (vyžaduje ID klienta). Automaticky prehrať ďalšiu epizódu Spustiť ďalšiu epizódu automaticky, keď sa zobrazí výzva. @@ -933,7 +933,7 @@ Uprednostniť vynútené titulky pri zhode s nastaveniami jazyka titulkov. Vertikálny posun Preskočiť intro - Použiť introdb.app na detekciu intro a rekapitulácií. + Použiť skipdb.tv na detekciu intro a rekapitulácií. Rozsah zdrojov automatického prehrávania Všetky nainštalované doplnky Automatické prehrávanie zohľadňuje iba streamy z vašich nainštalovaných doplnkov. @@ -1740,8 +1740,8 @@ Seriály Cloudová knižnica je vypnutá. - - Neplatný API kľúč alebo zlyhalo pripojenie + + Neplatný API kľúč alebo zlyhalo pripojenie V manifeste chýba \"%1$s\" TMDB Kolekcia %1$s TMDB Režisér %1$s @@ -1843,7 +1843,7 @@ Výsledky testu (%1$d) Pluginy poskytovateľov vyžadujú TMDB API kľúč. Nastavte ho na obrazovke TMDB, inak pluginy poskytovateľov nebudú fungovať správne. Žiadne výsledky vyhľadávania pre %1$s. - Neplatný API kľúč alebo zlyhalo pripojenie + Neplatný API kľúč alebo zlyhalo pripojenie Repozitár pluginov Odoslať Intro Odoslať diff --git a/composeApp/src/commonMain/composeResources/values-tr/strings.xml b/composeApp/src/commonMain/composeResources/values-tr/strings.xml index 2c450f87..249dde85 100644 --- a/composeApp/src/commonMain/composeResources/values-tr/strings.xml +++ b/composeApp/src/commonMain/composeResources/values-tr/strings.xml @@ -441,8 +441,8 @@ Nuvio; hesap doğrulama, bulut kitaplığı erişimi, önbellek kontrolleri ve bulut oynatma özellikleri için TorBox'a bağlanır. Nuvio'nun TorBox ile hiçbir bağı yoktur ve TorBox tarafından onaylanmamıştır. MDBList Nuvio; puanlar ve dış kaynak puan verileri için MDBList'i kullanır. Nuvio'nun MDBList ile hiçbir bağı yoktur ve MDBList tarafından onaylanmamıştır. - IntroDB - Nuvio; jenerik ve geçme kontrolleri tarafından kullanılan, topluluk tarafından sağlanan giriş, özet ve önizleme zaman damgaları için IntroDB API'sini kullanır. Nuvio'nun IntroDB ile hiçbir bağı yoktur ve IntroDB tarafından onaylanmamıştır. + SkipDB + Nuvio; jenerik ve geçme kontrolleri tarafından kullanılan, topluluk tarafından sağlanan giriş, özet ve önizleme zaman damgaları için SkipDB API'sini kullanır. Nuvio'nun SkipDB ile hiçbir bağı yoktur ve SkipDB tarafından onaylanmamıştır. MPVKit iOS sürümlerinde oynatma için kullanılır. Sadece MPVKit kaynak kodu LGPL v3.0 kapsamında lisanslanmıştır. libmpv ve FFmpeg kütüphanelerini içeren MPVKit paketleri de LGPL v3.0 kapsamında lisanslanmıştır. @@ -748,8 +748,8 @@ AnimeSkip API client ID'ni gir. anime-skip.com üzerinden alabilirsin. Giriş Geçme Gönderimini Aç Giriş/jenerik zaman damgalarını topluluk veritabanına göndermek için bir buton göster. - IntroDB API Anahtarı - Zaman damgası göndermek için IntroDB API anahtarını gir. Gönderim için zorunludur. + SkipDB API Anahtarı + Zaman damgası göndermek için SkipDB API anahtarını gir. Gönderim için zorunludur. Geçme zamanları için AnimeSkip'te de ara (client ID gerekir). Sonraki bölümü otomatik oynat Eşik değere ulaşılınca sonraki bölümü otomatik bulup oynat. diff --git a/composeApp/src/commonMain/composeResources/values-vi/strings.xml b/composeApp/src/commonMain/composeResources/values-vi/strings.xml index 08f3bc69..fbc609d1 100644 --- a/composeApp/src/commonMain/composeResources/values-vi/strings.xml +++ b/composeApp/src/commonMain/composeResources/values-vi/strings.xml @@ -482,8 +482,8 @@ Nuvio kết nối với TorBox để xác thực tài khoản, truy cập thư viện đám mây, kiểm tra bộ nhớ đệm và phát nội dung từ đám mây. Nuvio không liên kết hoặc được TorBox bảo trợ MDBList Nuvio dùng MDBList để lấy điểm đánh giá và dữ liệu từ các dịch vụ xếp hạng khác. Nuvio không liên kết hoặc được MDBList bảo trợ - IntroDB - Nuvio dùng API IntroDB để lấy dữ liệu cộng đồng về thời điểm mở đầu, tóm tắt, phần credit và đoạn xem trước cho tính năng bỏ qua. Nuvio không liên kết hoặc được IntroDB bảo trợ + SkipDB + Nuvio dùng API SkipDB để lấy dữ liệu cộng đồng về thời điểm mở đầu, tóm tắt, phần credit và đoạn xem trước cho tính năng bỏ qua. Nuvio không liên kết hoặc được SkipDB bảo trợ MPVKit Dùng để phát nội dung trên iOS MPVKit được cấp phép theo LGPL v3.0. Các thành phần đi kèm như libmpv và FFmpeg cũng được cấp phép theo LGPL v3.0 @@ -838,8 +838,8 @@ Nhập Client ID API Anime Skip. Tạo tại anime-skip.com Cho phép gửi mốc mở đầu Hiển thị nút gửi mốc Đoạn mở đầu/Đoạn kết thúc lên cơ sở dữ liệu cộng đồng - Khóa API IntroDB - Nhập khóa API IntroDB để gửi mốc thời gian. Bắt buộc để dùng tính năng này + Khóa API SkipDB + Nhập khóa API SkipDB để gửi mốc thời gian. Bắt buộc để dùng tính năng này Tìm thêm mốc bỏ qua từ Anime Skip (yêu cầu Client ID) Tự động phát tập tiếp theo Tự động phát tập tiếp theo khi thông báo xuất hiện @@ -966,7 +966,7 @@ Tự động áp dụng phụ đề khớp với ngôn ngữ đã chọn Căn chỉnh dọc Tự động bỏ qua Đoạn mở đầu - Dùng IntroDB để phát hiện Đoạn mở đầu và Tóm tắt + Dùng SkipDB để phát hiện Đoạn mở đầu và Tóm tắt Phạm vi chọn nguồn phát tự động Tất cả addon đã cài Chỉ tự động phát từ nguồn do addon đã cài cung cấp @@ -1746,7 +1746,7 @@ Phim Loạt phim Thư viện đám mây đang tắt - Khóa API không hợp lệ hoặc không thể kết nối + Khóa API không hợp lệ hoặc không thể kết nối Manifest thiếu trường "%1$s" Bộ sưu tập TMDB %1$s Đạo diễn TMDB %1$s @@ -1848,7 +1848,7 @@ Kết quả kiểm tra (%1$d) Plugin yêu cầu khóa API TMDB. Vui lòng thiết lập trong màn hình TMDB để đảm bảo tính năng hoạt động chính xác Không tìm thấy kết quả trong %1$s - Khóa API không hợp lệ hoặc lỗi kết nối + Khóa API không hợp lệ hoặc lỗi kết nối Kho Plugin Gửi mốc Đoạn mở đầu Gửi diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index affcc835..0878fd79 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -504,8 +504,8 @@ Nuvio connects to TorBox for account authentication, cloud library access, cache checks, and cloud playback features. Nuvio is not affiliated with or endorsed by TorBox. MDBList Nuvio uses MDBList for ratings and external score provider data. Nuvio is not affiliated with or endorsed by MDBList. - IntroDB - Nuvio uses the IntroDB API for community-provided intro, recap, credits, and preview timestamps used by skip controls. Nuvio is not affiliated with or endorsed by IntroDB. + SkipDB + Nuvio uses the SkipDB API for community-provided intro, recap, credits, and preview timestamps used by skip controls. Nuvio is not affiliated with or endorsed by SkipDB. MPVKit Used for playback on iOS builds. MPVKit source alone is licensed under LGPL v3.0. MPVKit bundles, including libmpv and FFmpeg libraries, are also licensed under LGPL v3.0. @@ -904,8 +904,8 @@ Enter your AnimeSkip API client ID. Get one at anime-skip.com. Enable Intro Submission Show a button to submit intro/outro timestamps to the community database. - IntroDB API Key - Enter your IntroDB API key to submit timestamps. Required for submission. + SkipDB API Key + Enter your SkipDB API key to submit timestamps. Required for submission. Also search AnimeSkip for skip timestamps (requires client ID). Auto-play Next Episode Start next episode automatically when prompt appears. @@ -1032,7 +1032,7 @@ Prefer forced subtitles when audio matches the subtitle language; if unavailable, select nothing. Vertical Offset Skip Intro - Use introdb.app to detect intros and recaps. + Use skipdb.tv to detect intros and recaps. Auto-play Source Scope All installed addons Auto-play only considers streams coming from your installed addons. @@ -1862,8 +1862,8 @@ Series Cloud library is disabled. - - Invalid API Key or connection failed + + Invalid API Key or connection failed Manifest missing \"%1$s\" TMDB Collection %1$s TMDB Director %1$s @@ -1965,7 +1965,7 @@ Test results (%1$d) Plugin providers require a TMDB API key. Set it on the TMDB screen or plugin providers will not work correctly. No search results returned for %1$s. - Invalid API Key or connection failed + Invalid API Key or connection failed Plugin repository Submit Intro Submit diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenModalHosts.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenModalHosts.kt index b3665232..ad6af040 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenModalHosts.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenModalHosts.kt @@ -81,6 +81,7 @@ internal fun PlayerScreenModalHosts( activeVideoId: String?, metaUiState: MetaDetailsUiState, displayedPositionMs: Long, + durationMs: Long, submitIntroSegmentType: String, onSubmitIntroSegmentTypeChanged: (String) -> Unit, submitIntroStartTimeStr: String, @@ -209,6 +210,7 @@ internal fun PlayerScreenModalHosts( season = season, episode = episode, currentTimeSec = displayedPositionMs / 1000.0, + durationMs = durationMs, segmentType = submitIntroSegmentType, onSegmentTypeChange = onSubmitIntroSegmentTypeChanged, startTimeStr = submitIntroStartTimeStr, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenRuntimeEffects.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenRuntimeEffects.kt index 02df155a..c3903662 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenRuntimeEffects.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenRuntimeEffects.kt @@ -398,10 +398,12 @@ private fun PlayerScreenRuntime.BindPlayerMetadataAndSkipEffects() { launch { val imdbId = vid.split(":").firstOrNull()?.takeIf { it.startsWith("tt") } + val durationSec = playbackSnapshot.durationMs.takeIf { it > 0L }?.let { it / 1000.0 } val intervals = SkipIntroRepository.getSkipIntervals( imdbId = imdbId, season = season, episode = episode, + durationSec = durationSec, ) skipIntervals = intervals } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenRuntimeUi.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenRuntimeUi.kt index d83c22fd..150a730b 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenRuntimeUi.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenRuntimeUi.kt @@ -318,7 +318,7 @@ private fun PlayerScreenRuntime.RenderPlayerControls(displayedPositionMs: Long, onSubmitIntroClick = if ( isSeries && playerSettingsUiState.introSubmitEnabled && - playerSettingsUiState.introDbApiKey.isNotBlank() + playerSettingsUiState.skipDbApiKey.isNotBlank() ) { { showSubmitIntroModal = true } } else { @@ -565,6 +565,7 @@ private fun PlayerScreenRuntime.RenderPlayerModals(displayedPositionMs: Long) { activeVideoId = activeVideoId, metaUiState = metaUiState, displayedPositionMs = displayedPositionMs, + durationMs = playbackSnapshot.durationMs, submitIntroSegmentType = submitIntroSegmentType, onSubmitIntroSegmentTypeChanged = { submitIntroSegmentType = it }, submitIntroStartTimeStr = submitIntroStartTimeStr, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsRepository.kt index c0f4c429..f1257224 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsRepository.kt @@ -66,7 +66,7 @@ data class PlayerSettingsUiState( val skipIntroEnabled: Boolean = true, val animeSkipEnabled: Boolean = false, val animeSkipClientId: String = "", - val introDbApiKey: String = "", + val skipDbApiKey: String = "", val introSubmitEnabled: Boolean = false, val streamAutoPlayNextEpisodeEnabled: Boolean = false, val streamAutoPlayPreferBingeGroup: Boolean = true, @@ -132,7 +132,7 @@ object PlayerSettingsRepository { private var skipIntroEnabled = true private var animeSkipEnabled = false private var animeSkipClientId = "" - private var introDbApiKey = "" + private var skipDbApiKey = "" private var introSubmitEnabled = false private var streamAutoPlayNextEpisodeEnabled = false private var streamAutoPlayPreferBingeGroup = true @@ -203,7 +203,7 @@ object PlayerSettingsRepository { skipIntroEnabled = true animeSkipEnabled = false animeSkipClientId = "" - introDbApiKey = "" + skipDbApiKey = "" introSubmitEnabled = false streamAutoPlayNextEpisodeEnabled = false streamAutoPlayPreferBingeGroup = true @@ -326,7 +326,7 @@ object PlayerSettingsRepository { skipIntroEnabled = PlayerSettingsStorage.loadSkipIntroEnabled() ?: true animeSkipEnabled = PlayerSettingsStorage.loadAnimeSkipEnabled() ?: false animeSkipClientId = PlayerSettingsStorage.loadAnimeSkipClientId() ?: "" - introDbApiKey = PlayerSettingsStorage.loadIntroDbApiKey() ?: "" + skipDbApiKey = PlayerSettingsStorage.loadSkipDbApiKey() ?: "" introSubmitEnabled = PlayerSettingsStorage.loadIntroSubmitEnabled() ?: false streamAutoPlayNextEpisodeEnabled = PlayerSettingsStorage.loadStreamAutoPlayNextEpisodeEnabled() ?: false streamAutoPlayPreferBingeGroup = PlayerSettingsStorage.loadStreamAutoPlayPreferBingeGroup() ?: true @@ -663,12 +663,12 @@ object PlayerSettingsRepository { PlayerSettingsStorage.saveAnimeSkipClientId(clientId) } - fun setIntroDbApiKey(apiKey: String) { + fun setSkipDbApiKey(apiKey: String) { ensureLoaded() - if (introDbApiKey == apiKey) return - introDbApiKey = apiKey + if (skipDbApiKey == apiKey) return + skipDbApiKey = apiKey publish() - PlayerSettingsStorage.saveIntroDbApiKey(apiKey) + PlayerSettingsStorage.saveSkipDbApiKey(apiKey) } fun setIntroSubmitEnabled(enabled: Boolean) { @@ -944,7 +944,7 @@ object PlayerSettingsRepository { skipIntroEnabled = skipIntroEnabled, animeSkipEnabled = animeSkipEnabled, animeSkipClientId = animeSkipClientId, - introDbApiKey = introDbApiKey, + skipDbApiKey = skipDbApiKey, introSubmitEnabled = introSubmitEnabled, streamAutoPlayNextEpisodeEnabled = streamAutoPlayNextEpisodeEnabled, streamAutoPlayPreferBingeGroup = streamAutoPlayPreferBingeGroup, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.kt index 312a0764..2a0dcc38 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.kt @@ -90,8 +90,8 @@ internal expect object PlayerSettingsStorage { fun loadAnimeSkipClientId(): String? fun saveAnimeSkipClientId(clientId: String) - fun loadIntroDbApiKey(): String? - fun saveIntroDbApiKey(apiKey: String) + fun loadSkipDbApiKey(): String? + fun saveSkipDbApiKey(apiKey: String) fun loadIntroSubmitEnabled(): Boolean? fun saveIntroSubmitEnabled(enabled: Boolean) fun loadStreamAutoPlayNextEpisodeEnabled(): Boolean? diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/skip/SkipIntroApi.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/skip/SkipIntroApi.kt index 25438fde..5d16a725 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/skip/SkipIntroApi.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/skip/SkipIntroApi.kt @@ -11,33 +11,37 @@ internal object SkipIntroApi { private const val ANISKIP_BASE = "https://api.aniskip.com/v2/" private const val ARM_BASE = "https://arm.haglund.dev/api/v2/" private const val ANIMESKIP_BASE = "https://api.anime-skip.com/" + private const val SKIPDB_BASE = "https://api.skipdb.tv" - // --- IntroDb --- + // --- SkipDB --- - suspend fun getIntroDbSegments( + suspend fun getSkipDbSegments( imdbId: String, season: Int, episode: Int, - ): IntroDbSegmentsResponse? { - val baseUrl = IntroDbConfig.URL.trimEnd('/') - if (baseUrl.isBlank()) return null - val url = "$baseUrl/segments?imdb_id=$imdbId&season=$season&episode=$episode" + durationSec: Double? = null, + ): SkipDbSegmentsResponse? { + val baseUrl = SKIPDB_BASE.trimEnd('/') + var url = "$baseUrl/api/segments?imdb_id=$imdbId&season=$season&episode=$episode" + if (durationSec != null && durationSec > 0) { + url += "&duration=${durationSec.toLong()}" + } return try { val text = httpGetText(url) - json.decodeFromString(text) + json.decodeFromString(text) } catch (_: Exception) { null } } - suspend fun submitIntro( + suspend fun submitSegment( apiKey: String, - request: SubmitIntroRequest, + request: SubmitSegmentRequest, ): Boolean { - val baseUrl = IntroDbConfig.URL.trimEnd('/') - if (baseUrl.isBlank() || apiKey.isBlank()) return false - val url = "$baseUrl/submit" - val body = json.encodeToString(SubmitIntroRequest.serializer(), request) + val baseUrl = SKIPDB_BASE.trimEnd('/') + if (apiKey.isBlank()) return false + val url = "$baseUrl/api/segments" + val body = json.encodeToString(SubmitSegmentRequest.serializer(), request) val headers = mapOf( "Authorization" to "Bearer $apiKey", "Content-Type" to "application/json" @@ -55,10 +59,10 @@ internal object SkipIntroApi { } } - suspend fun verifyIntroDbApiKey(apiKey: String): Boolean { - val baseUrl = IntroDbConfig.URL.trimEnd('/') - if (baseUrl.isBlank() || apiKey.isBlank()) return false - val url = "$baseUrl/submit" + suspend fun verifySkipDbApiKey(apiKey: String): Boolean { + val baseUrl = SKIPDB_BASE.trimEnd('/') + if (apiKey.isBlank()) return false + val url = "$baseUrl/api/segments" val headers = mapOf( "Authorization" to "Bearer $apiKey", "Content-Type" to "application/json" @@ -70,16 +74,9 @@ internal object SkipIntroApi { headers = headers, body = "{}" ) - - // 400 means Auth passed but payload was empty/invalid -> Key is Valid - if (response.status == 400) return true - - // 200/201 would also mean valid (though unexpected with empty body) + if (response.status == 400 || response.status == 422) return true if (response.status == 200 || response.status == 201) return true - - // Explicitly handle auth failures if (response.status == 401 || response.status == 403) return false - false } catch (_: Exception) { false diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/skip/SkipIntroRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/skip/SkipIntroRepository.kt index d99dc978..27358dac 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/skip/SkipIntroRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/skip/SkipIntroRepository.kt @@ -11,14 +11,12 @@ object SkipIntroRepository { private val animeSkipShowIdCache = HashMap() private const val NO_ID = "__none__" - private val introDbConfigured: Boolean - get() = IntroDbConfig.URL.isNotBlank() - suspend fun getSkipIntervals( imdbId: String?, season: Int, episode: Int, requireSkipIntroEnabled: Boolean = true, + durationSec: Double? = null, ): List = coroutineScope { if (imdbId == null) return@coroutineScope emptyList() val settings = PlayerSettingsRepository.uiState.value @@ -27,8 +25,8 @@ object SkipIntroRepository { val cacheKey = "$imdbId:$season:$episode" cache[cacheKey]?.let { return@coroutineScope it } - val introDbDeferred = async { - if (introDbConfigured) fetchFromIntroDb(imdbId, season, episode) else emptyList() + val skipDbDeferred = async { + fetchFromSkipDb(imdbId, season, episode, durationSec) } val entriesDeferred = async { resolveImdbEntries(imdbId) } val entries = entriesDeferred.await() @@ -40,7 +38,7 @@ object SkipIntroRepository { } return@coroutineScope mergeByPriority( - introDbDeferred.await(), + skipDbDeferred.await(), animeSkipDeferred.await(), aniSkipDeferred.await(), ).also { cache[cacheKey] = it } @@ -50,6 +48,7 @@ object SkipIntroRepository { malId: String, episode: Int, requireSkipIntroEnabled: Boolean = true, + durationSec: Double? = null, ): List = coroutineScope { val settings = PlayerSettingsRepository.uiState.value if (requireSkipIntroEnabled && !settings.skipIntroEnabled) return@coroutineScope emptyList() @@ -65,17 +64,17 @@ object SkipIntroRepository { } catch (_: Exception) { null } } - var introDb = emptyList() + var skipDb = emptyList() var animeSkip = emptyList() val imdbId = imdbIdDeferred.await() if (imdbId != null) { val entries = resolveImdbEntries(imdbId) val season = entries.indexOfFirst { it.myanimelist == malId.toIntOrNull() } + 1 - val introDbDeferred = async { - if (introDbConfigured) fetchFromIntroDb(imdbId, season, episode) else emptyList() + val skipDbDeferred = async { + fetchFromSkipDb(imdbId, season, episode, durationSec) } val animeSkipDeferred = async { fetchAnimeSkipForEntries(entries, season, episode) } - introDb = introDbDeferred.await() + skipDb = skipDbDeferred.await() animeSkip = animeSkipDeferred.await() } else { val anilistId = try { @@ -84,13 +83,14 @@ object SkipIntroRepository { if (anilistId != null) animeSkip = fetchFromAnimeSkip(anilistId, episode, season = null) } - return@coroutineScope mergeByPriority(introDb, animeSkip, aniSkipDeferred.await()).also { cache[cacheKey] = it } + return@coroutineScope mergeByPriority(skipDb, animeSkip, aniSkipDeferred.await()).also { cache[cacheKey] = it } } suspend fun getSkipIntervalsForKitsu( kitsuId: String, episode: Int, requireSkipIntroEnabled: Boolean = true, + durationSec: Double? = null, ): List = coroutineScope { val settings = PlayerSettingsRepository.uiState.value if (requireSkipIntroEnabled && !settings.skipIntroEnabled) return@coroutineScope emptyList() @@ -112,17 +112,17 @@ object SkipIntroRepository { malIdDeferred.await()?.let { fetchFromAniSkip(it, episode) } ?: emptyList() } - var introDb = emptyList() + var skipDb = emptyList() var animeSkip = emptyList() val imdbId = imdbIdDeferred.await() if (imdbId != null) { val entries = resolveImdbEntries(imdbId) val season = entries.indexOfFirst { it.kitsu == kitsuId.toIntOrNull() } + 1 - val introDbDeferred = async { - if (introDbConfigured) fetchFromIntroDb(imdbId, season, episode) else emptyList() + val skipDbDeferred = async { + fetchFromSkipDb(imdbId, season, episode, durationSec) } val animeSkipDeferred = async { fetchAnimeSkipForEntries(entries, season, episode) } - introDb = introDbDeferred.await() + skipDb = skipDbDeferred.await() animeSkip = animeSkipDeferred.await() } else { val anilistId = try { @@ -131,13 +131,13 @@ object SkipIntroRepository { if (anilistId != null) animeSkip = fetchFromAnimeSkip(anilistId, episode, season = null) } - return@coroutineScope mergeByPriority(introDb, animeSkip, aniSkipDeferred.await()).also { cache[cacheKey] = it } + return@coroutineScope mergeByPriority(skipDb, animeSkip, aniSkipDeferred.await()).also { cache[cacheKey] = it } } /** * Merge provider results into one best-of: fill each segment category (opening / ending / * recap) from the highest-priority provider that has it. Arguments MUST be passed in priority - * order (IntroDB has the broadest coverage, then Anime-Skip, then AniSkip), so a partial + * order (SkipDB has the broadest coverage, then Anime-Skip, then AniSkip), so a partial * result from one provider never shadows a complete segment from another. */ private fun mergeByPriority(vararg providerResults: List): List { @@ -155,6 +155,7 @@ object SkipIntroRepository { "intro", "op", "mixed-op" -> "opening" "outro", "ed", "mixed-ed", "credits", "ending" -> "ending" "recap" -> "recap" + "preview" -> "preview" else -> null } @@ -176,26 +177,28 @@ object SkipIntroRepository { return emptyList() } - private suspend fun fetchFromIntroDb(imdbId: String, season: Int, episode: Int): List { + private suspend fun fetchFromSkipDb(imdbId: String, season: Int, episode: Int, durationSec: Double? = null): List { return try { - val data = SkipIntroApi.getIntroDbSegments(imdbId, season, episode) + val data = SkipIntroApi.getSkipDbSegments(imdbId, season, episode, durationSec) if (data == null) return emptyList() + val segments = data.segments ?: return emptyList() listOfNotNull( - data.intro.toSkipIntervalOrNull("intro"), - data.recap.toSkipIntervalOrNull("recap"), - data.outro.toSkipIntervalOrNull("outro"), + segments.intro.toSkipIntervalOrNull("intro"), + segments.recap.toSkipIntervalOrNull("recap"), + segments.outro.toSkipIntervalOrNull("outro"), + segments.preview.toSkipIntervalOrNull("preview"), ) } catch (_: Exception) { emptyList() } } - private fun IntroDbSegment?.toSkipIntervalOrNull(type: String): SkipInterval? { + private fun SkipDbSegment?.toSkipIntervalOrNull(type: String): SkipInterval? { if (this == null) return null - val start = startSec ?: startMs?.let { it / 1000.0 } - val end = endSec ?: endMs?.let { it / 1000.0 } + val start = startMs?.let { it / 1000.0 } + val end = endMs?.let { it / 1000.0 } if (start == null || end == null || end <= start) return null - return SkipInterval(startTime = start, endTime = end, type = type, provider = "introdb") + return SkipInterval(startTime = start, endTime = end, type = type, provider = "skipdb") } private suspend fun fetchFromAniSkip(malId: String, episode: Int): List { @@ -277,34 +280,34 @@ object SkipIntroRepository { } catch (_: Exception) { emptyList() }.also { imdbEntriesCache[imdbId] = it } } - suspend fun submitIntro( + suspend fun submitSegment( imdbId: String, season: Int, episode: Int, - startSec: Double, - endSec: Double, + startMs: Long, + endMs: Long, segmentType: String, + durationMs: Long? = null, ): Boolean { val settings = PlayerSettingsRepository.uiState.value - val apiKey = settings.introDbApiKey.trim() + val apiKey = settings.skipDbApiKey.trim() if (!settings.introSubmitEnabled || apiKey.isBlank()) return false - val request = SubmitIntroRequest( + val request = SubmitSegmentRequest( imdbId = imdbId, season = season, episode = episode, - startSec = startSec, - endSec = endSec, - startMs = (startSec * 1000).toLong(), - endMs = (endSec * 1000).toLong(), + startMs = startMs, + endMs = endMs, segmentType = segmentType, + durationMs = durationMs, ) - return SkipIntroApi.submitIntro(apiKey, request) + return SkipIntroApi.submitSegment(apiKey, request) } - suspend fun verifyIntroDbApiKey(apiKey: String): Boolean { - return SkipIntroApi.verifyIntroDbApiKey(apiKey) + suspend fun verifySkipDbApiKey(apiKey: String): Boolean { + return SkipIntroApi.verifySkipDbApiKey(apiKey) } fun clearCache() { diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/skip/SkipModels.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/skip/SkipModels.kt index 0e996a97..45eb420c 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/skip/SkipModels.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/skip/SkipModels.kt @@ -27,39 +27,44 @@ enum class NextEpisodeThresholdMode { MINUTES_BEFORE_END, } -// --- IntroDb API response models --- +// --- SkipDB API response models --- @Serializable -data class IntroDbSegmentsResponse( +data class SkipDbSegmentsResponse( @SerialName("imdb_id") val imdbId: String? = null, @SerialName("season") val season: Int? = null, @SerialName("episode") val episode: Int? = null, - @SerialName("intro") val intro: IntroDbSegment? = null, - @SerialName("recap") val recap: IntroDbSegment? = null, - @SerialName("outro") val outro: IntroDbSegment? = null, + @SerialName("segments") val segments: SkipDbSegments? = null, + @SerialName("intro_length_estimate_ms") val introLengthEstimateMs: Long? = null, ) @Serializable -data class IntroDbSegment( - @SerialName("start_sec") val startSec: Double? = null, - @SerialName("end_sec") val endSec: Double? = null, +data class SkipDbSegments( + @SerialName("intro") val intro: SkipDbSegment? = null, + @SerialName("recap") val recap: SkipDbSegment? = null, + @SerialName("outro") val outro: SkipDbSegment? = null, + @SerialName("preview") val preview: SkipDbSegment? = null, +) + +@Serializable +data class SkipDbSegment( @SerialName("start_ms") val startMs: Long? = null, @SerialName("end_ms") val endMs: Long? = null, + @SerialName("match") val match: String? = null, + @SerialName("adjusted") val adjusted: Boolean? = null, + @SerialName("offset_ms") val offsetMs: Long? = null, @SerialName("confidence") val confidence: Double? = null, - @SerialName("submission_count") val submissionCount: Int? = null, - @SerialName("updated_at") val updatedAt: String? = null, ) @Serializable -data class SubmitIntroRequest( +data class SubmitSegmentRequest( @SerialName("imdb_id") val imdbId: String, @SerialName("season") val season: Int, @SerialName("episode") val episode: Int, - @SerialName("start_sec") val startSec: Double, - @SerialName("end_sec") val endSec: Double, @SerialName("start_ms") val startMs: Long, @SerialName("end_ms") val endMs: Long, @SerialName("segment_type") val segmentType: String, + @SerialName("duration_ms") val durationMs: Long? = null, ) // --- AniSkip API response models --- diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/skip/SubmitIntroDialog.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/skip/SubmitIntroDialog.kt index b173d239..ed4c623a 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/skip/SubmitIntroDialog.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/skip/SubmitIntroDialog.kt @@ -73,6 +73,7 @@ fun SubmitIntroDialog( season: Int, episode: Int, currentTimeSec: Double, + durationMs: Long, segmentType: String, onSegmentTypeChange: (String) -> Unit, startTimeStr: String, @@ -210,13 +211,14 @@ fun SubmitIntroDialog( if (start != null && end != null && end > start) { isSubmitting = true scope.launch { - val result = SkipIntroRepository.submitIntro( + val result = SkipIntroRepository.submitSegment( imdbId = imdbId, season = season, episode = episode, - startSec = start, - endSec = end, + startMs = (start * 1000).toLong(), + endMs = (end * 1000).toLong(), segmentType = segmentType, + durationMs = durationMs.takeIf { it > 0L }, ) isSubmitting = false if (result) { diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/IntegrationLogoPainter.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/IntegrationLogoPainter.kt index 8bf7971d..2f460102 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/IntegrationLogoPainter.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/IntegrationLogoPainter.kt @@ -7,7 +7,7 @@ internal enum class IntegrationLogo { Tmdb, Trakt, MdbList, - IntroDb, + SkipDb, } @Composable diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/LicensesAttributionsPage.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/LicensesAttributionsPage.kt index d86eb0a6..13516255 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/LicensesAttributionsPage.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/LicensesAttributionsPage.kt @@ -45,7 +45,7 @@ private const val TraktUrl = "https://trakt.tv" private const val PremiumizeUrl = "https://www.premiumize.me" private const val TorboxUrl = "https://torbox.app" private const val MdbListUrl = "https://mdblist.com" -private const val IntroDbUrl = "https://introdb.app/" +private const val SkipDbUrl = "https://skipdb.tv/" private const val NuvioRepositoryUrl = "https://github.com/NuvioMedia/NuvioMobile" private const val MpvKitUrl = "https://github.com/mpvkit/MPVKit" private const val ApacheLicenseUrl = "https://www.apache.org/licenses/LICENSE-2.0" @@ -351,10 +351,10 @@ private fun attributionItems(): List = listOf( link = MdbListUrl, ), AttributionItem( - titleRes = Res.string.settings_licenses_attributions_introdb_title, - bodyRes = Res.string.settings_licenses_attributions_introdb_body, - logo = IntegrationLogo.IntroDb, - link = IntroDbUrl, + titleRes = Res.string.settings_licenses_attributions_skipdb_title, + bodyRes = Res.string.settings_licenses_attributions_skipdb_body, + logo = IntegrationLogo.SkipDb, + link = SkipDbUrl, ), AttributionItem( titleRes = Res.string.settings_licenses_attributions_imdb_title, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/PlaybackSettingsPage.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/PlaybackSettingsPage.kt index 3dcf5a16..49da3735 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/PlaybackSettingsPage.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/PlaybackSettingsPage.kt @@ -1066,22 +1066,22 @@ private fun PlaybackSettingsSection( ) if (autoPlayPlayerSettings.introSubmitEnabled) { SettingsGroupDivider(isTablet = isTablet) - var showIntroDbApiKeyDialog by remember { mutableStateOf(false) } + var showSkipDbApiKeyDialog by remember { mutableStateOf(false) } val notSetLabel = stringResource(Res.string.settings_playback_not_set) SettingsNavigationRow( - title = stringResource(Res.string.settings_playback_introdb_api_key), - description = autoPlayPlayerSettings.introDbApiKey.ifBlank { notSetLabel }, + title = stringResource(Res.string.settings_playback_skipdb_api_key), + description = autoPlayPlayerSettings.skipDbApiKey.ifBlank { notSetLabel }, isTablet = isTablet, - onClick = { showIntroDbApiKeyDialog = true }, + onClick = { showSkipDbApiKeyDialog = true }, ) - if (showIntroDbApiKeyDialog) { - IntroDbApiKeyDialog( - initialValue = autoPlayPlayerSettings.introDbApiKey, + if (showSkipDbApiKeyDialog) { + SkipDbApiKeyDialog( + initialValue = autoPlayPlayerSettings.skipDbApiKey, onSave = { - PlayerSettingsRepository.setIntroDbApiKey(it) - showIntroDbApiKeyDialog = false + PlayerSettingsRepository.setSkipDbApiKey(it) + showSkipDbApiKeyDialog = false }, - onDismiss = { showIntroDbApiKeyDialog = false }, + onDismiss = { showSkipDbApiKeyDialog = false }, ) } } @@ -3351,7 +3351,7 @@ private fun AnimeSkipClientIdDialog( @Composable @OptIn(ExperimentalMaterial3Api::class) -private fun IntroDbApiKeyDialog( +private fun SkipDbApiKeyDialog( initialValue: String, onSave: (String) -> Unit, onDismiss: () -> Unit, @@ -3360,7 +3360,7 @@ private fun IntroDbApiKeyDialog( var value by remember { mutableStateOf(initialValue) } var isVerifying by remember { mutableStateOf(false) } var errorMessage by remember { mutableStateOf(null) } - val invalidKeyMessage = stringResource(Res.string.settings_playback_introdb_invalid_key) + val invalidKeyMessage = stringResource(Res.string.settings_playback_skipdb_invalid_key) BasicAlertDialog(onDismissRequest = { if (!isVerifying) onDismiss() }) { Surface( @@ -3373,13 +3373,13 @@ private fun IntroDbApiKeyDialog( verticalArrangement = Arrangement.spacedBy(12.dp), ) { Text( - text = stringResource(Res.string.settings_playback_introdb_api_key), + text = stringResource(Res.string.settings_playback_skipdb_api_key), style = MaterialTheme.typography.titleLarge, color = MaterialTheme.colorScheme.onSurface, fontWeight = FontWeight.SemiBold, ) Text( - text = stringResource(Res.string.settings_playback_introdb_api_key_description), + text = stringResource(Res.string.settings_playback_skipdb_api_key_description), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -3389,7 +3389,7 @@ private fun IntroDbApiKeyDialog( value = it errorMessage = null }, - label = stringResource(Res.string.settings_playback_introdb_api_key), + label = stringResource(Res.string.settings_playback_skipdb_api_key), modifier = Modifier.fillMaxWidth(), isError = errorMessage != null, ) @@ -3424,7 +3424,7 @@ private fun IntroDbApiKeyDialog( isVerifying = true errorMessage = null scope.launch { - val isValid = com.nuvio.app.features.player.skip.SkipIntroRepository.verifyIntroDbApiKey(trimmed) + val isValid = com.nuvio.app.features.player.skip.SkipIntroRepository.verifySkipDbApiKey(trimmed) isVerifying = false if (isValid) { onSave(trimmed) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsSearch.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsSearch.kt index 9ed104d7..79bd7b95 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsSearch.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsSearch.kt @@ -289,7 +289,7 @@ internal fun settingsSearchEntries( PlaybackSearchRow("premiumize-attribution", stringResource(Res.string.settings_licenses_attributions_premiumize_title), stringResource(Res.string.settings_licenses_attributions_premiumize_body)), PlaybackSearchRow("torbox-attribution", stringResource(Res.string.settings_licenses_attributions_torbox_title), stringResource(Res.string.settings_licenses_attributions_torbox_body)), PlaybackSearchRow("mdblist-attribution", stringResource(Res.string.settings_licenses_attributions_mdblist_title), stringResource(Res.string.settings_licenses_attributions_mdblist_body)), - PlaybackSearchRow("introdb-attribution", stringResource(Res.string.settings_licenses_attributions_introdb_title), stringResource(Res.string.settings_licenses_attributions_introdb_body)), + PlaybackSearchRow("skipdb-attribution", stringResource(Res.string.settings_licenses_attributions_skipdb_title), stringResource(Res.string.settings_licenses_attributions_skipdb_body)), PlaybackSearchRow("imdb-datasets", stringResource(Res.string.settings_licenses_attributions_imdb_title), stringResource(Res.string.settings_licenses_attributions_imdb_body)), PlaybackSearchRow( if (isIos) "mpvkit-license" else "exoplayer-license", @@ -640,7 +640,7 @@ internal fun settingsSearchEntries( PlaybackSearchRow("anime-skip", stringResource(Res.string.settings_playback_anime_skip), stringResource(Res.string.settings_playback_anime_skip_description)), PlaybackSearchRow("anime-skip-client", stringResource(Res.string.settings_playback_anime_skip_client_id), stringResource(Res.string.settings_playback_anime_skip_client_id_description)), PlaybackSearchRow("intro-submit", stringResource(Res.string.settings_playback_intro_submit_enabled), stringResource(Res.string.settings_playback_intro_submit_enabled_description)), - PlaybackSearchRow("introdb-key", stringResource(Res.string.settings_playback_introdb_api_key), stringResource(Res.string.settings_playback_introdb_api_key_description)), + PlaybackSearchRow("skipdb-key", stringResource(Res.string.settings_playback_skipdb_api_key), stringResource(Res.string.settings_playback_skipdb_api_key_description)), ), ) addPlaybackRows( diff --git a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.ios.kt b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.ios.kt index 4eeb657a..ae43bd7e 100644 --- a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/player/PlayerSettingsStorage.ios.kt @@ -60,7 +60,7 @@ actual object PlayerSettingsStorage { private const val skipIntroEnabledKey = "skip_intro_enabled" private const val animeSkipEnabledKey = "animeskip_enabled" private const val animeSkipClientIdKey = "animeskip_client_id" - private const val introDbApiKeyKey = "introdb_api_key" + private const val skipDbApiKeyKey = "skipdb_api_key" private const val introSubmitEnabledKey = "intro_submit_enabled" private const val streamAutoPlayNextEpisodeEnabledKey = "stream_auto_play_next_episode_enabled" private const val streamAutoPlayPreferBingeGroupKey = "stream_auto_play_prefer_binge_group" @@ -680,14 +680,14 @@ actual object PlayerSettingsStorage { NSUserDefaults.standardUserDefaults.setObject(clientId, forKey = ProfileScopedKey.of(animeSkipClientIdKey)) } - actual fun loadIntroDbApiKey(): String? { + actual fun loadSkipDbApiKey(): String? { val defaults = NSUserDefaults.standardUserDefaults - val key = ProfileScopedKey.of(introDbApiKeyKey) + val key = ProfileScopedKey.of(skipDbApiKeyKey) return defaults.stringForKey(key) } - actual fun saveIntroDbApiKey(apiKey: String) { - NSUserDefaults.standardUserDefaults.setObject(apiKey, forKey = ProfileScopedKey.of(introDbApiKeyKey)) + actual fun saveSkipDbApiKey(apiKey: String) { + NSUserDefaults.standardUserDefaults.setObject(apiKey, forKey = ProfileScopedKey.of(skipDbApiKeyKey)) } actual fun loadIntroSubmitEnabled(): Boolean? { @@ -1011,7 +1011,7 @@ actual object PlayerSettingsStorage { payload.decodeSyncBoolean(skipIntroEnabledKey)?.let(::saveSkipIntroEnabled) payload.decodeSyncBoolean(animeSkipEnabledKey)?.let(::saveAnimeSkipEnabled) payload.decodeSyncString(animeSkipClientIdKey)?.let(::saveAnimeSkipClientId) - payload.decodeSyncString(introDbApiKeyKey)?.let(::saveIntroDbApiKey) + payload.decodeSyncString(skipDbApiKeyKey)?.let(::saveSkipDbApiKey) payload.decodeSyncBoolean(streamAutoPlayNextEpisodeEnabledKey)?.let(::saveStreamAutoPlayNextEpisodeEnabled) payload.decodeSyncBoolean(streamAutoPlayPreferBingeGroupKey)?.let(::saveStreamAutoPlayPreferBingeGroup) payload.decodeSyncBoolean(streamAutoPlayReuseBingeGroupKey)?.let(::saveStreamAutoPlayReuseBingeGroup) diff --git a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/settings/IntegrationLogoPainter.ios.kt b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/settings/IntegrationLogoPainter.ios.kt index 6aa94391..0eebc060 100644 --- a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/settings/IntegrationLogoPainter.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/settings/IntegrationLogoPainter.ios.kt @@ -3,7 +3,7 @@ package com.nuvio.app.features.settings import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.painter.Painter import nuvio.composeapp.generated.resources.Res -import nuvio.composeapp.generated.resources.introdb_favicon +import nuvio.composeapp.generated.resources.skipdb_favicon import nuvio.composeapp.generated.resources.mdblist_logo import nuvio.composeapp.generated.resources.rating_tmdb import nuvio.composeapp.generated.resources.trakt_tv_favicon @@ -15,5 +15,5 @@ internal actual fun integrationLogoPainter(logo: IntegrationLogo): Painter = IntegrationLogo.Tmdb -> painterResource(Res.drawable.rating_tmdb) IntegrationLogo.Trakt -> painterResource(Res.drawable.trakt_tv_favicon) IntegrationLogo.MdbList -> painterResource(Res.drawable.mdblist_logo) - IntegrationLogo.IntroDb -> painterResource(Res.drawable.introdb_favicon) + IntegrationLogo.SkipDb -> painterResource(Res.drawable.skipdb_favicon) } diff --git a/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties new file mode 100644 index 00000000..6c1139ec --- /dev/null +++ b/gradle/gradle-daemon-jvm.properties @@ -0,0 +1,12 @@ +#This file is generated by updateDaemonJvm +toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/ec7520a1e057cd116f9544c42142a16b/redirect +toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/4c4f879899012ff0a8b2e2117df03b0e/redirect +toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/ec7520a1e057cd116f9544c42142a16b/redirect +toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/4c4f879899012ff0a8b2e2117df03b0e/redirect +toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/73bcfb608d1fde9fb62e462f834a3299/redirect +toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/846ee0d876d26a26f37aa1ce8de73224/redirect +toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/ec7520a1e057cd116f9544c42142a16b/redirect +toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/4c4f879899012ff0a8b2e2117df03b0e/redirect +toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/9482ddec596298c84656d31d16652665/redirect +toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/39701d92e1756bb2f141eb67cd4c660e/redirect +toolchainVersion=21 From 56b1ac8b5b8ae0cdca2b83b701b5bbb692b99f33 Mon Sep 17 00:00:00 2001 From: George Woodall Date: Tue, 28 Jul 2026 23:03:14 +0100 Subject: [PATCH 2/3] fix: use official SkipDB favicon from skipdb.tv --- .../drawable/skipdb_favicon.png | Bin 22040 -> 43407 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/composeApp/src/commonMain/composeResources/drawable/skipdb_favicon.png b/composeApp/src/commonMain/composeResources/drawable/skipdb_favicon.png index e3e357b9a3fa035c64711d43d24f16788e057ed5..0f3faf043ef6e2f627a183a20250eae014ed2041 100644 GIT binary patch literal 43407 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

XH%J8bH&q(-KGuouq$ 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 literal 22040 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 Date: Thu, 30 Jul 2026 18:09:35 +0100 Subject: [PATCH 3/3] fix: wait for duration before fetching SkipDB segments --- .../app/features/player/PlayerScreenRuntimeEffects.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenRuntimeEffects.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenRuntimeEffects.kt index c3903662..493afa3f 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenRuntimeEffects.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/player/PlayerScreenRuntimeEffects.kt @@ -398,7 +398,15 @@ private fun PlayerScreenRuntime.BindPlayerMetadataAndSkipEffects() { launch { val imdbId = vid.split(":").firstOrNull()?.takeIf { it.startsWith("tt") } - val durationSec = playbackSnapshot.durationMs.takeIf { it > 0L }?.let { it / 1000.0 } + var durationSec: Double? = null + for (i in 1..20) { + val d = playbackSnapshot.durationMs + if (d > 0L) { + durationSec = d / 1000.0 + break + } + kotlinx.coroutines.delay(250) + } val intervals = SkipIntroRepository.getSkipIntervals( imdbId = imdbId, season = season,