Skip to content

Commit

Permalink
feat: improve movies crunchyroll detection
Browse files Browse the repository at this point in the history
  • Loading branch information
Ziedelth committed Oct 26, 2024
1 parent ae901f0 commit 0167ffb
Show file tree
Hide file tree
Showing 8 changed files with 9,144 additions and 17 deletions.
2 changes: 1 addition & 1 deletion src/main/kotlin/fr/shikkanime/Application.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ fun main() {
// Every 20 seconds
JobManager.scheduleJob("*/20 * * * * ?", FetchEpisodesJob::class.java)
// Every 10 minutes
JobManager.scheduleJob("0 */10 * * * ?", UpdateEpisodeJob::class.java)
JobManager.scheduleJob("0 */10 * * * ?", UpdateEpisodeMappingJob::class.java)
// Every hour
JobManager.scheduleJob("0 0 * * * ?", SavingImageCacheJob::class.java, UpdateAnimeJob::class.java)
// Every day at midnight
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import java.time.Duration
import java.time.ZonedDateTime
import java.util.concurrent.atomic.AtomicBoolean

class UpdateEpisodeJob : AbstractJob {
class UpdateEpisodeMappingJob : AbstractJob {
private val logger = LoggerFactory.getLogger(javaClass)

@Inject
Expand Down Expand Up @@ -100,8 +100,8 @@ class UpdateEpisodeJob : AbstractJob {
val variants = episodeVariantService.findAllByMapping(mapping)
val mappingIdentifier = "${StringUtils.getShortName(mapping.anime!!.name!!)} - S${mapping.season} ${mapping.episodeType} ${mapping.number}"
logger.info("Updating episode $mappingIdentifier...")
val episodes =
variants.flatMap { variant -> runBlocking { retrievePlatformEpisode(mapping, variant) } }
val episodes = variants.flatMap { variant -> runBlocking { retrievePlatformEpisode(mapping, variant) } }
.sortedBy { it.platform.sortIndex }

saveAnimePlatformIfNotExists(episodes, mapping)

Expand Down
35 changes: 29 additions & 6 deletions src/main/kotlin/fr/shikkanime/platforms/CrunchyrollPlatform.kt
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@ class CrunchyrollPlatform :
}
}

private val seasonInfoCache = MapCache<CountryCodeIdKeyCache, CrunchyrollWrapper.Season?>(Duration.ofDays(1)) {
try {
val token = identifiers[it.countryCode]!!
return@MapCache HttpRequest.retry(3) { CrunchyrollWrapper.getSeason(it.countryCode.locale, token, it.id) }
} catch (e: Exception) {
logger.log(Level.SEVERE, "Error on fetching season info", e)
return@MapCache null
}
}

override fun getPlatform(): Platform = Platform.CRUN

override fun getConfigurationClass() = CrunchyrollConfiguration::class.java
Expand Down Expand Up @@ -206,10 +216,15 @@ class CrunchyrollPlatform :
?: throw AnimeException("Anime not found")
val isConfigurationSimulcast = configuration!!.simulcasts.any { it.name.lowercase() == animeName.lowercase() }

if (needSimulcast && !(isConfigurationSimulcast || crunchyrollAnimeContent.simulcast || isDubbed))
throw AnimeNotSimulcastedException("\"$animeName\" is not simulcasted")
val season = seasonInfoCache[CountryCodeIdKeyCache(countryCode, browseObject.episodeMetadata.seasonId)] ?: run {
logger.warning("Season not found for ${browseObject.episodeMetadata.seasonId}")
throw AnimeException("Anime season not found")
}

val (number, episodeType) = getNumberAndEpisodeType(browseObject.episodeMetadata)
val (number, episodeType) = getNumberAndEpisodeType(browseObject.episodeMetadata, season)

if (needSimulcast && !(isConfigurationSimulcast || crunchyrollAnimeContent.simulcast || isDubbed || episodeType == EpisodeType.FILM))
throw AnimeNotSimulcastedException("\"$animeName\" is not simulcasted")

var original = true

Expand Down Expand Up @@ -238,17 +253,25 @@ class CrunchyrollPlatform :
audioLocale = browseObject.episodeMetadata.audioLocale,
id = browseObject.id,
url = CrunchyrollWrapper.buildUrl(countryCode, browseObject.id, browseObject.slugTitle),
uncensored = false,
uncensored = browseObject.episodeMetadata.matureBlocked,
original = original
)
}

private fun getNumberAndEpisodeType(episode: CrunchyrollWrapper.Episode): Pair<Int, EpisodeType> {
private fun getNumberAndEpisodeType(
episode: CrunchyrollWrapper.Episode,
season: CrunchyrollWrapper.Season
): Pair<Int, EpisodeType> {
var number = episode.number ?: -1
val specialEpisodeRegex = "SP(\\d*)".toRegex()

var episodeType = when {
episode.seasonSlugTitle?.contains("movie", true) == true -> EpisodeType.FILM
episode.seasonSlugTitle?.contains("movie", true) == true || season.keywords.any {
it.contains(
"movie",
true
)
} -> EpisodeType.FILM
number == -1 -> EpisodeType.SPECIAL
else -> EpisodeType.EPISODE
}
Expand Down
17 changes: 17 additions & 0 deletions src/main/kotlin/fr/shikkanime/wrappers/CrunchyrollWrapper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ object CrunchyrollWrapper {
val id: String,
@SerializedName("subtitle_locales")
val subtitleLocales: List<String>,
val keywords: List<String>,
)

data class Episode(
Expand Down Expand Up @@ -92,6 +93,8 @@ object CrunchyrollWrapper {
@SerializedName("duration_ms")
val durationMs: Long,
val description: String?,
@SerializedName("mature_blocked")
val matureBlocked: Boolean,
val versions: List<Version>?,
@SerializedName("next_episode_id")
val nextEpisodeId: String?,
Expand Down Expand Up @@ -203,7 +206,21 @@ object CrunchyrollWrapper {
val asJsonArray = ObjectParser.fromJson(response.bodyAsText()).getAsJsonArray("data")
?: throw Exception("Failed to get seasons")
return ObjectParser.fromJson(asJsonArray, Array<Season>::class.java)
}

suspend fun getSeason(locale: String, accessToken: String, seasonId: String): Season {
val response = httpRequest.get(
"${BASE_URL}content/v2/cms/seasons/$seasonId?locale=$locale",
headers = mapOf(
"Authorization" to "Bearer $accessToken",
),
)

require(response.status == HttpStatusCode.OK) { "Failed to get seasons (${response.status})" }

val asJsonArray = ObjectParser.fromJson(response.bodyAsText()).getAsJsonArray("data")
?: throw Exception("Failed to get seasons")
return ObjectParser.fromJson(asJsonArray.first(), Season::class.java)
}

@JvmStatic
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.time.ZonedDateTime

class UpdateEpisodeJobTest {
class UpdateEpisodeMappingJobTest {
@Inject
private lateinit var updateEpisodeJob: UpdateEpisodeJob
private lateinit var updateEpisodeMappingJob: UpdateEpisodeMappingJob

@Inject
private lateinit var animeService: AnimeService
Expand Down Expand Up @@ -86,7 +86,7 @@ class UpdateEpisodeJobTest {
url = "https://www.crunchyroll.com/fr/watch/GZ7UV8KWZ/rent-a-girlfriend"
)
)
updateEpisodeJob.run()
updateEpisodeMappingJob.run()

val animes = animeService.findAll()
assertEquals(1, animes.size)
Expand Down Expand Up @@ -142,7 +142,7 @@ class UpdateEpisodeJobTest {
url = "https://animationdigitalnetwork.fr/video/the-eminence-in-shadow/20568-episode-1-un-camarade-detestable"
)
)
updateEpisodeJob.run()
updateEpisodeMappingJob.run()

val animes = animeService.findAll()
assertEquals(1, animes.size)
Expand Down Expand Up @@ -192,7 +192,7 @@ class UpdateEpisodeJobTest {
)

val now = ZonedDateTime.now()
updateEpisodeJob.run()
updateEpisodeMappingJob.run()

val animes = animeService.findAll()
assertEquals(1, animes.size)
Expand Down
23 changes: 23 additions & 0 deletions src/test/kotlin/fr/shikkanime/platforms/CrunchyrollPlatformTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ class CrunchyrollPlatformTest {
null,
1440000L,
null,
false,
null,
"nextId",
)
Expand Down Expand Up @@ -310,6 +311,7 @@ class CrunchyrollPlatformTest {
null,
1440000L,
null,
false,
null,
null,
)
Expand Down Expand Up @@ -382,6 +384,7 @@ class CrunchyrollPlatformTest {
null,
1440000L,
null,
false,
null,
null,
)
Expand Down Expand Up @@ -438,4 +441,24 @@ class CrunchyrollPlatformTest {
assertEquals(12, episodes.size)
assertEquals("BOCCHI THE ROCK!", episodes[0].anime)
}

@Test
fun `fetchEpisodes for 2024-10-25`() {
val s = "2024-10-25T18:15:00Z"
val zonedDateTime = ZonedDateTime.parse(s)

val episodes = platform.fetchEpisodes(
zonedDateTime,
File(
ClassLoader.getSystemClassLoader().getResource("crunchyroll/api-${s.replace(':', '-')}.json")?.file
?: throw Exception("File not found")
)
).toMutableList()
episodes.removeAll { it.anime != "Gridman Universe" }

assertEquals(true, episodes.isNotEmpty())
assertEquals(1, episodes.size)
assertEquals("Gridman Universe", episodes[0].anime)
assertEquals(EpisodeType.FILM, episodes[0].episodeType)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import fr.shikkanime.utils.Constant
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Assumptions.assumeTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.time.ZonedDateTime
Expand All @@ -34,8 +35,8 @@ class PrimeVideoPlatformTest {
})
val episodes = runBlocking { primeVideoPlatform.fetchApiContent(key, zonedDateTime) }

assertTrue(episodes.isNotEmpty())
assertTrue(episodes.size == 13)
assumeTrue(episodes.isNotEmpty())
assumeTrue(episodes.size == 13)

episodes.forEach {
assertTrue(it.image.startsWith("https://m.media-amazon.com"))
Expand Down
Loading

0 comments on commit 0167ffb

Please sign in to comment.