Skip to content
Snippets Groups Projects
Commit c7aabee8 authored by Nicolas Pomepuy's avatar Nicolas Pomepuy Committed by Geoffrey Métais
Browse files

Batch identify medias

parent 5508b3f2
No related branches found
No related tags found
1 merge request!288Moviepedia API implementation
Showing
with 320 additions and 165 deletions
......@@ -60,6 +60,7 @@ public abstract class AbstractMediaWrapper extends MediaLibraryItem implements P
public final static int META_SUBTITLE_DELAY = 201;
//Various
public final static int META_APPLICATION_SPECIFIC = 250;
public final static int META_METADATA_RETRIEVED = 251;
// threshold lentgh between song and podcast ep, set to 15 minutes
protected static final long PODCAST_THRESHOLD = 900000L;
......
......@@ -110,7 +110,7 @@
tools:srcCompat="@drawable/ic_album_big"
vlc:layout_constraintDimensionRatio="2:3"
android:src="@{cover}"
android:scaleType="centerCrop"
android:scaleType="@{scaleType}"
vlc:layout_constraintEnd_toEndOf="parent"
vlc:layout_constraintStart_toStartOf="parent"
vlc:layout_constraintTop_toTopOf="parent"
......
......@@ -47,6 +47,7 @@ import org.videolan.medialibrary.interfaces.AbstractMedialibrary
import org.videolan.medialibrary.interfaces.DevicesDiscoveryCb
import org.videolan.vlc.gui.SendCrashActivity
import org.videolan.vlc.gui.helpers.NotificationHelper
import org.videolan.vlc.moviepedia.MoviepediaIndexer
import org.videolan.vlc.repository.DirectoryRepository
import org.videolan.vlc.util.*
import java.io.File
......@@ -379,6 +380,7 @@ class MediaParsingService : LifecycleService(), DevicesDiscoveryCb, LifecycleOwn
private fun exitCommand() {
if (!medialibrary.isWorking && !serviceLock && !discoverTriggered) {
lastNotificationTime = 0L
MoviepediaIndexer.indexMedialib(this@MediaParsingService)
stopSelf()
}
}
......
......@@ -62,6 +62,9 @@ interface MediaMetadataDataFullDao {
@RawQuery(observedEntities = [MediaMetadataWithImages::class])
fun getAllPaged(query: SupportSQLiteQuery): DataSource.Factory<Int, MediaMetadataWithImages>
@Query("select * from media_metadata")
fun getAllLive(): LiveData<List<MediaMetadataWithImages>>
// @Insert(onConflict = OnConflictStrategy.REPLACE)
// fun insert(mediaMetadataFull: MediaMetadataFull)
......
......@@ -77,11 +77,14 @@ data class MediaMetadata(
@ColumnInfo(name = "current_backdrop")
var currentBackdrop: String,
@ColumnInfo(name = "show_id")
var show_id: String?
var show_id: String?,
@ColumnInfo(name = "has_cast")
var hasCast: Boolean
)
fun MediaMetadata.getYear() = SimpleDateFormat("yyyy", Locale.getDefault()).format(releaseDate)
fun MediaMetadata.getYear() = releaseDate?.let { SimpleDateFormat("yyyy", Locale.getDefault()).format(it) }
?: ""
class MediaMetadataWithImages {
@Embedded
lateinit var metadata: MediaMetadata
......
......@@ -154,20 +154,20 @@ class MainTvFragment : BrowseSupportFragment(), OnItemViewSelectedListener, OnIt
}
private fun registerDatasets() {
model.browsers.observe(viewLifecycleOwner, Observer {
model.browsers.observe(requireActivity(), Observer {
browserAdapter.setItems(it, diffCallback)
})
model.audioCategories.observe(viewLifecycleOwner, Observer {
model.audioCategories.observe(requireActivity(), Observer {
categoriesAdapter.setItems(it.toList(), diffCallback)
})
model.videos.observe(viewLifecycleOwner, Observer {
model.videos.observe(requireActivity(), Observer {
videoAdapter.setItems(it, diffCallback)
})
model.nowPlaying.observe(viewLifecycleOwner, Observer {
model.nowPlaying.observe(requireActivity(), Observer {
displayNowPlaying = it.isNotEmpty()
nowPlayingAdapter.setItems(it, diffCallback)
})
model.history.observe(viewLifecycleOwner, Observer {
model.history.observe(requireActivity(), Observer {
displayHistory = it.isNotEmpty()
if (it.isNotEmpty()) {
historyAdapter.setItems(it, diffCallback)
......@@ -175,23 +175,12 @@ class MainTvFragment : BrowseSupportFragment(), OnItemViewSelectedListener, OnIt
resetLines()
})
model.playlist.observe(viewLifecycleOwner, Observer {
model.playlist.observe(requireActivity(), Observer {
displayPlaylist = it.isNotEmpty()
playlistAdapter.setItems(it, diffCallback)
resetLines()
})
model.tvshows.observe(this, Observer {
if (it.size > 0) {
videoAdapter.add(2, GenericCardItem(ID_ALL_TVSHOWS, getString(R.string.header_tvshows), getString(R.string.movie_count, it.size.toString()), R.drawable.ic_browser_video_big_normal, R.color.tv_card_content_dark))
} else {
}
resetLines()
})
model.movies.observe(this, Observer {
videoAdapter.add(1, GenericCardItem(ID_ALL_MOVIES, getString(R.string.header_movies), getString(R.string.movie_count, it.size.toString()), R.drawable.ic_browser_video_big_normal, R.color.tv_card_content_dark))
})
}
private fun resetLines() {
......
......@@ -57,6 +57,7 @@ import androidx.lifecycle.ViewModelProviders
import kotlinx.coroutines.*
import org.videolan.medialibrary.interfaces.media.AbstractMediaWrapper
import org.videolan.vlc.R
import org.videolan.vlc.moviepedia.MoviepediaIndexer
import org.videolan.vlc.moviepedia.models.identify.Media
import org.videolan.vlc.moviepedia.models.identify.getAllResults
import org.videolan.vlc.viewmodels.MoviepediaModel
......@@ -77,7 +78,7 @@ class MoviepediaTvFragment : SearchSupportFragment(), SearchSupportFragment.Sear
if (item is Media) {
launch {
withContext(Dispatchers.IO) {
viewModel.saveMediaMetadata(requireActivity(), media, item)
MoviepediaIndexer.saveMediaMetadata(requireActivity(), media, item)
}
requireActivity().finish()
}
......
......@@ -117,6 +117,8 @@ class MoviepediaTvItemAdapter(type: Long, private val eventsHandler: IEventsHand
}
}
override fun isEmpty() = currentList?.isEmpty() != false
override fun setOnFocusChangeListener(focusListener: FocusableRecyclerView.FocusListener?) {
this.focusListener = focusListener
}
......@@ -185,6 +187,7 @@ class MoviepediaTvItemAdapter(type: Long, private val eventsHandler: IEventsHand
init {
binding.holder = this
if (defaultCover != null) binding.cover = defaultCover
binding.scaleType = ImageView.ScaleType.FIT_CENTER
if (AndroidUtil.isMarshMallowOrLater)
itemView.setOnContextClickListener { v ->
onMoreClick(v)
......@@ -213,6 +216,7 @@ class MoviepediaTvItemAdapter(type: Long, private val eventsHandler: IEventsHand
override fun recycle() {
if (defaultCover != null) binding.cover = defaultCover
binding.scaleType = ImageView.ScaleType.FIT_CENTER
binding.title.text = ""
binding.subtitle.text = ""
binding.mediaCover.resetFade()
......
......@@ -59,7 +59,7 @@ class FileBrowserTvFragment : BaseBrowserTvFragment<MediaLibraryItem>(), PathAda
override fun getColumnNumber() = resources.getInteger(R.integer.tv_songs_col_count)
companion object {
fun newInstance(type: Long, item: MediaLibraryItem?, root : Boolean = false) =
fun newInstance(type: Long, item: MediaLibraryItem?, root: Boolean = false) =
FileBrowserTvFragment().apply {
arguments = Bundle().apply {
this.putLong(CATEGORY, type)
......@@ -125,7 +125,6 @@ class FileBrowserTvFragment : BaseBrowserTvFragment<MediaLibraryItem>(), PathAda
(viewModel as BrowserModel).getDescriptionUpdate().observe(this, Observer { pair ->
if (pair != null) (adapter as RecyclerView.Adapter<*>).notifyItemChanged(pair.first)
})
}
override fun onStart() {
......
......@@ -28,11 +28,11 @@ import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import kotlinx.android.synthetic.main.song_browser.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ObsoleteCoroutinesApi
import kotlinx.coroutines.launch
import org.videolan.medialibrary.interfaces.AbstractMedialibrary
import org.videolan.vlc.R
import org.videolan.vlc.database.models.MediaMetadataType
......@@ -122,7 +122,7 @@ class MoviepediaBrowserTvFragment : BaseBrowserTvFragment<MediaMetadataWithImage
}
else -> {
item.metadata.mlId?.let {
launch {
lifecycleScope.launchWhenStarted {
val media = requireActivity().getFromMl { getMedia(it) }
TvUtil.showMediaDetail(requireActivity(), media)
}
......@@ -141,7 +141,7 @@ class MoviepediaBrowserTvFragment : BaseBrowserTvFragment<MediaMetadataWithImage
}
else -> {
item.metadata.mlId?.let {
launch {
lifecycleScope.launchWhenStarted {
val media = requireActivity().getFromMl { getMedia(it) }
TvUtil.showMediaDetail(requireActivity(), media)
}
......
......@@ -25,6 +25,8 @@
package org.videolan.vlc.moviepedia
import org.videolan.vlc.moviepedia.models.body.ScrobbleBody
import org.videolan.vlc.moviepedia.models.body.ScrobbleBodyBatch
import org.videolan.vlc.moviepedia.models.identify.IdentifyBatchResult
import org.videolan.vlc.moviepedia.models.identify.IdentifyResult
import org.videolan.vlc.moviepedia.models.identify.Media
import org.videolan.vlc.moviepedia.models.media.MoviepediaResults
......@@ -38,6 +40,9 @@ interface IMoviepediaApiService {
@POST("search-media/identify")
suspend fun searchMedia(@Body body: ScrobbleBody): IdentifyResult
@POST("search-media/batchidentify")
suspend fun searchMediaBatch(@Body body: List<ScrobbleBodyBatch>): List<IdentifyBatchResult>
@GET("media/{media}")
suspend fun getMedia(@Path("media") mediaId: String): Media
......
/*
* ************************************************************************
* MoviepediaIndexer.kt
* *************************************************************************
* Copyright © 2019 VLC authors and VideoLAN
* Author: Nicolas POMEPUY
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
* **************************************************************************
*
*
*/
package org.videolan.vlc.moviepedia
import android.content.Context
import android.net.Uri
import android.util.Log
import kotlinx.coroutines.*
import org.videolan.medialibrary.interfaces.AbstractMedialibrary
import org.videolan.medialibrary.interfaces.media.AbstractMediaWrapper
import org.videolan.vlc.BuildConfig
import org.videolan.vlc.database.models.*
import org.videolan.vlc.moviepedia.models.identify.*
import org.videolan.vlc.moviepedia.models.media.cast.image
import org.videolan.vlc.repository.MediaMetadataRepository
import org.videolan.vlc.repository.MediaPersonRepository
import org.videolan.vlc.repository.MoviepediaApiRepository
import org.videolan.vlc.repository.PersonRepository
import org.videolan.vlc.util.getFromMl
import org.videolan.vlc.util.getLocaleLanguages
object MoviepediaIndexer : CoroutineScope by MainScope() {
fun indexMedialib(context: Context) {
launch {
withContext(Dispatchers.IO) {
val medias = context.getFromMl { getPagedVideos(AbstractMedialibrary.SORT_DEFAULT, false, 1000, 0) }
val filesToIndex = HashMap<Long, Uri>()
medias.forEach {
if (it.getMetaLong(AbstractMediaWrapper.META_METADATA_RETRIEVED) != 1L)
filesToIndex[it.id] = it.uri
}
if (BuildConfig.DEBUG) Log.d(this::class.java.simpleName, "Retrieving infos for ${filesToIndex.size} files")
for (filesToIndex in filesToIndex) {
if (BuildConfig.DEBUG) Log.d(this::class.java.simpleName, "Retrieving infos for: ${filesToIndex.value.lastPathSegment}")
}
val repo = MoviepediaApiRepository.getInstance()
val results = repo.searchMediaBatch(filesToIndex)
medias.forEach { media ->
media?.setLongMeta(AbstractMediaWrapper.META_METADATA_RETRIEVED, 1L)
}
results.forEach { result ->
result.lucky?.let {
val media = medias.find { it.id == result.id.toLong() }
saveMediaMetadata(context, media, it, retrieveCast = false, removePersonOrphans = false)
}
}
removePersonOrphans(context)
}
}
}
private fun removePersonOrphans(context: Context) {
//Remove orphans
val allPersons = PersonRepository.getInstance(context).getAll()
val allPersonJoins = MediaPersonRepository.getInstance(context).getAll()
val personsToRemove = allPersons.filter { person -> allPersonJoins.any { personJoin -> person.moviepediaId == personJoin.personId }.not() }
PersonRepository.getInstance(context).deleteAll(personsToRemove)
}
suspend fun saveMediaMetadata(context: Context, media: AbstractMediaWrapper?, item: Media, retrieveCast: Boolean = true, removePersonOrphans: Boolean = true) {
val repo = MoviepediaApiRepository.getInstance()
val type = when (item.mediaType) {
MediaType.TV_EPISODE -> MediaMetadataType.TV_EPISODE
MediaType.MOVIE -> MediaMetadataType.MOVIE
else -> MediaMetadataType.TV_SHOW
}
val mediaMetadataRepository = MediaMetadataRepository.getInstance(context)
val show: String? = when (item.mediaType) {
MediaType.TV_EPISODE -> {
//check if show already exists
var moviepediaId = mediaMetadataRepository.getTvshow(item.showId)?.metadata?.moviepediaId
if (moviepediaId == null) {
//show doesn't exist, let's retrieve it
val tvShowResult = repo.getMedia(item.showId)
saveMediaMetadata(context, null, tvShowResult, retrieveCast, removePersonOrphans)
moviepediaId = item.showId
}
moviepediaId
}
else -> null
}
val languages = context.getLocaleLanguages()
val mediaMetadata = MediaMetadata(
item.mediaId,
media?.id,
type,
item.title,
item.summary ?: "",
item.genre?.joinToString { genre -> genre } ?: "",
item.date,
item.country?.joinToString { genre -> genre }
?: "", item.season, item.episode, item.getImageUri(languages).toString(), item.getBackdropUri(languages).toString(), show, false)
val oldMediaMetadata = if (media != null) mediaMetadataRepository.getMetadata(media.id) else null
val oldImages = oldMediaMetadata?.images
mediaMetadataRepository.addMetadataImmediate(mediaMetadata)
val images = ArrayList<MediaImage>()
item.getBackdrops(languages)?.forEach {
images.add(MediaImage(item.getImageUriFromPath(it.path), mediaMetadata.moviepediaId, MediaImageType.BACKDROP, it.language))
}
item.getPosters(languages)?.forEach {
images.add(MediaImage(item.getImageUriFromPath(it.path), mediaMetadata.moviepediaId, MediaImageType.POSTER, it.language))
}
//delete old images
oldImages?.let {
mediaMetadataRepository.deleteImages(it.filter { images.any { newImage -> it.url == newImage.url }.not() })
}
mediaMetadataRepository.addImagesImmediate(images)
if (retrieveCast) {
retrieveCasting(context, mediaMetadata)
}
if (removePersonOrphans) removePersonOrphans(context)
}
suspend fun retrieveCasting(context: Context, mediaMetadata: MediaMetadata) {
val personRepo = PersonRepository.getInstance(context)
val repo = MoviepediaApiRepository.getInstance()
val personsToAdd = ArrayList<MediaPersonJoin>()
val castResult = repo.getMediaCast(mediaMetadata.moviepediaId)
castResult.actor?.forEach { actor ->
val actorEntity = Person(actor.person.personId, actor.person.name, actor.person.image())
if (BuildConfig.DEBUG) Log.d(this::class.java.simpleName, "Inserting ${actor.person.name} - ${actor.person.personId} as actor")
personRepo.addPersonImmediate(actorEntity)
personsToAdd.add(MediaPersonJoin(mediaMetadata.moviepediaId, actorEntity.moviepediaId, PersonType.ACTOR))
}
castResult.director?.forEach { actor ->
val actorEntity = Person(actor.person.personId, actor.person.name, actor.person.image())
if (BuildConfig.DEBUG) Log.d(this::class.java.simpleName, "Inserting ${actor.person.name} - ${actor.person.personId} as director")
personRepo.addPersonImmediate(actorEntity)
personsToAdd.add(MediaPersonJoin(mediaMetadata.moviepediaId, actorEntity.moviepediaId, PersonType.DIRECTOR))
}
castResult.writer?.forEach { actor ->
val actorEntity = Person(actor.person.personId, actor.person.name, actor.person.image())
if (BuildConfig.DEBUG) Log.d(this::class.java.simpleName, "Inserting ${actor.person.name} - ${actor.person.personId} as writer")
personRepo.addPersonImmediate(actorEntity)
personsToAdd.add(MediaPersonJoin(mediaMetadata.moviepediaId, actorEntity.moviepediaId, PersonType.WRITER))
}
castResult.musician?.forEach { actor ->
val actorEntity = Person(actor.person.personId, actor.person.name, actor.person.image())
if (BuildConfig.DEBUG) Log.d(this::class.java.simpleName, "Inserting ${actor.person.name} - ${actor.person.personId} as musician")
personRepo.addPersonImmediate(actorEntity)
personsToAdd.add(MediaPersonJoin(mediaMetadata.moviepediaId, actorEntity.moviepediaId, PersonType.MUSICIAN))
}
castResult.producer?.forEach { actor ->
val actorEntity = Person(actor.person.personId, actor.person.name, actor.person.image())
if (BuildConfig.DEBUG) Log.d(this::class.java.simpleName, "Inserting ${actor.person.name} - ${actor.person.personId} as producer")
personRepo.addPersonImmediate(actorEntity)
personsToAdd.add(MediaPersonJoin(mediaMetadata.moviepediaId, actorEntity.moviepediaId, PersonType.PRODUCER))
}
MediaPersonRepository.getInstance(context).removeAllFor(mediaMetadata.moviepediaId)
MediaPersonRepository.getInstance(context).addPersons(personsToAdd)
mediaMetadata.hasCast = true
MediaMetadataRepository.getInstance(context).addMetadataImmediate(mediaMetadata)
}
}
\ No newline at end of file
......@@ -37,4 +37,10 @@ data class ScrobbleBody(
val season: String? = null,
val episode: String? = null,
val duration: String? = null
)
\ No newline at end of file
)
data class ScrobbleBodyBatch(
val id: String,
val metadata: ScrobbleBody
)
/*
* ************************************************************************
* IdentifyBatch.kt
* *************************************************************************
* Copyright © 2019 VLC authors and VideoLAN
* Author: Nicolas POMEPUY
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
* **************************************************************************
*
*
*/
package org.videolan.vlc.moviepedia.models.identify
import com.squareup.moshi.Json
data class IdentifyBatchResult(
@Json(name = "id")
val id: String,
@Json(name = "lucky")
val lucky: Media?
)
\ No newline at end of file
......@@ -94,6 +94,8 @@ class MediaMetadataRepository(private val mediaMetadataFullDao: MediaMetadataDat
return mediaMetadataFullDao.getAllPaged(query)
}
fun getAllLive(): LiveData<List<MediaMetadataWithImages>> = mediaMetadataFullDao.getAllLive()
fun getTvshow(showId: String) = mediaMetadataFullDao.getMediaById(showId)
fun getTvshowLive(showId: String) = mediaMetadataFullDao.getMediaByIdLive(showId)
......
......@@ -30,6 +30,8 @@ import kotlinx.coroutines.withContext
import org.videolan.vlc.moviepedia.IMoviepediaApiService
import org.videolan.vlc.moviepedia.NextApiClient
import org.videolan.vlc.moviepedia.models.body.ScrobbleBody
import org.videolan.vlc.moviepedia.models.body.ScrobbleBodyBatch
import org.videolan.vlc.moviepedia.models.identify.IdentifyBatchResult
import org.videolan.vlc.moviepedia.models.identify.IdentifyResult
import org.videolan.vlc.util.FileUtils
import java.io.File
......@@ -44,6 +46,19 @@ class MoviepediaApiRepository(private val moviepediaApiService: IMoviepediaApiSe
return moviepediaApiService.searchMedia(scrobbleBody)
}
suspend fun searchMediaBatch(uris: HashMap<Long, Uri>): List<IdentifyBatchResult> {
val body = ArrayList<ScrobbleBodyBatch>()
uris.forEach { uri ->
val hash = withContext(Dispatchers.IO) { FileUtils.computeHash(File(uri.value.path)) }
val scrobbleBody = ScrobbleBody(filename = uri.value.lastPathSegment, osdbhash = hash)
val scrobbleBodyBatch = ScrobbleBodyBatch(id = uri.key.toString(), metadata = scrobbleBody)
body.add(scrobbleBodyBatch)
}
return moviepediaApiService.searchMediaBatch(body)
}
suspend fun searchTitle(title: String) = moviepediaApiService.searchMedia(ScrobbleBody(title = title, filename = title))
suspend fun searchMedia(query: ScrobbleBody) = moviepediaApiService.searchMedia(query)
......
......@@ -32,6 +32,7 @@ import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.actor
import org.videolan.vlc.database.models.*
import org.videolan.vlc.moviepedia.MoviepediaIndexer
import org.videolan.vlc.repository.MediaMetadataRepository
import org.videolan.vlc.repository.MediaPersonRepository
import org.videolan.vlc.util.getFromMl
......@@ -55,25 +56,30 @@ class MediaMetadataModel(private val context: Context, mlId: Long? = null, movie
mediaMetadataFull.metadata = mediaMetadataWithImages
updateActor.offer(mediaMetadataFull)
mediaMetadataWithImages?.metadata?.let {
updateLiveData.addSource(MediaPersonRepository.getInstance(context).getPersonsByType(it.moviepediaId, PersonType.ACTOR)) { persons ->
mediaMetadataFull.actors = persons
updateActor.offer(mediaMetadataFull)
}
updateLiveData.addSource(MediaPersonRepository.getInstance(context).getPersonsByType(it.moviepediaId, PersonType.WRITER)) { persons ->
mediaMetadataFull.writers = persons
updateActor.offer(mediaMetadataFull)
}
updateLiveData.addSource(MediaPersonRepository.getInstance(context).getPersonsByType(it.moviepediaId, PersonType.PRODUCER)) { persons ->
mediaMetadataFull.producers = persons
updateActor.offer(mediaMetadataFull)
}
updateLiveData.addSource(MediaPersonRepository.getInstance(context).getPersonsByType(it.moviepediaId, PersonType.MUSICIAN)) { persons ->
mediaMetadataFull.musicians = persons
updateActor.offer(mediaMetadataFull)
}
updateLiveData.addSource(MediaPersonRepository.getInstance(context).getPersonsByType(it.moviepediaId, PersonType.DIRECTOR)) { persons ->
mediaMetadataFull.directors = persons
updateActor.offer(mediaMetadataFull)
launch {
if (!it.hasCast && it.mlId != null) {
withContext(Dispatchers.IO) { MoviepediaIndexer.retrieveCasting(context, it) }
}
updateLiveData.addSource(MediaPersonRepository.getInstance(context).getPersonsByType(it.moviepediaId, PersonType.ACTOR)) { persons ->
mediaMetadataFull.actors = persons
updateActor.offer(mediaMetadataFull)
}
updateLiveData.addSource(MediaPersonRepository.getInstance(context).getPersonsByType(it.moviepediaId, PersonType.WRITER)) { persons ->
mediaMetadataFull.writers = persons
updateActor.offer(mediaMetadataFull)
}
updateLiveData.addSource(MediaPersonRepository.getInstance(context).getPersonsByType(it.moviepediaId, PersonType.PRODUCER)) { persons ->
mediaMetadataFull.producers = persons
updateActor.offer(mediaMetadataFull)
}
updateLiveData.addSource(MediaPersonRepository.getInstance(context).getPersonsByType(it.moviepediaId, PersonType.MUSICIAN)) { persons ->
mediaMetadataFull.musicians = persons
updateActor.offer(mediaMetadataFull)
}
updateLiveData.addSource(MediaPersonRepository.getInstance(context).getPersonsByType(it.moviepediaId, PersonType.DIRECTOR)) { persons ->
mediaMetadataFull.directors = persons
updateActor.offer(mediaMetadataFull)
}
}
}
......
......@@ -24,24 +24,15 @@
package org.videolan.vlc.viewmodels
import android.content.Context
import android.net.Uri
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import org.videolan.medialibrary.interfaces.media.AbstractMediaWrapper
import org.videolan.vlc.BuildConfig
import org.videolan.vlc.database.models.*
import org.videolan.vlc.moviepedia.models.identify.*
import org.videolan.vlc.moviepedia.models.media.cast.image
import org.videolan.vlc.repository.MediaMetadataRepository
import org.videolan.vlc.repository.MediaPersonRepository
import org.videolan.vlc.moviepedia.models.identify.IdentifyResult
import org.videolan.vlc.moviepedia.models.identify.Media
import org.videolan.vlc.repository.MoviepediaApiRepository
import org.videolan.vlc.repository.PersonRepository
import org.videolan.vlc.util.getLocaleLanguages
class MoviepediaModel : ViewModel() {
......@@ -77,102 +68,4 @@ class MoviepediaModel : ViewModel() {
}
}
suspend fun saveMediaMetadata(context: Context, media: AbstractMediaWrapper?, item: Media) {
val type = when (item.mediaType) {
MediaType.TV_EPISODE -> MediaMetadataType.TV_EPISODE
MediaType.MOVIE -> MediaMetadataType.MOVIE
else -> MediaMetadataType.TV_SHOW
}
val mediaMetadataRepository = MediaMetadataRepository.getInstance(context)
val personRepo = PersonRepository.getInstance(context)
val show = when (item.mediaType) {
MediaType.TV_EPISODE -> {
//check if show already exists
mediaMetadataRepository.getTvshow(item.showId)?.metadata?.moviepediaId
//show doesn't exist, let's retrieve it
val tvShowResult = repo.getMedia(item.showId)
saveMediaMetadata(context, null, tvShowResult)
item.showId
}
else -> null
}
val languages = context.getLocaleLanguages()
val mediaMetadata = MediaMetadata(
item.mediaId,
media?.id,
type,
item.title,
item.summary ?: "",
item.genre?.joinToString { genre -> genre } ?: "",
item.date,
item.country?.joinToString { genre -> genre }
?: "", item.season, item.episode, item.getImageUri(languages).toString(), item.getBackdropUri(languages).toString(), show)
val oldMediaMetadata = if (media != null) mediaMetadataRepository.getMetadata(media.id) else null
val oldImages = oldMediaMetadata?.images
mediaMetadataRepository.addMetadataImmediate(mediaMetadata)
val images = ArrayList<MediaImage>()
item.getBackdrops(languages)?.forEach {
images.add(MediaImage(item.getImageUriFromPath(it.path), mediaMetadata.moviepediaId, MediaImageType.BACKDROP, it.language))
}
item.getPosters(languages)?.forEach {
images.add(MediaImage(item.getImageUriFromPath(it.path), mediaMetadata.moviepediaId, MediaImageType.POSTER, it.language))
}
//delete old images
oldImages?.let {
mediaMetadataRepository.deleteImages(it.filter { images.any { newImage -> it.url == newImage.url }.not() })
}
mediaMetadataRepository.addImagesImmediate(images)
val personsToAdd = ArrayList<MediaPersonJoin>()
val castResult = repo.getMediaCast(item.mediaId)
castResult.actor?.forEach { actor ->
val actorEntity = Person(actor.person.personId, actor.person.name, actor.person.image())
if (BuildConfig.DEBUG) Log.d(this::class.java.simpleName, "Inserting ${actor.person.name} - ${actor.person.personId} as actor")
personRepo.addPersonImmediate(actorEntity)
personsToAdd.add(MediaPersonJoin(mediaMetadata.moviepediaId, actorEntity.moviepediaId, PersonType.ACTOR))
}
castResult.director?.forEach { actor ->
val actorEntity = Person(actor.person.personId, actor.person.name, actor.person.image())
if (BuildConfig.DEBUG) Log.d(this::class.java.simpleName, "Inserting ${actor.person.name} - ${actor.person.personId} as director")
personRepo.addPersonImmediate(actorEntity)
personsToAdd.add(MediaPersonJoin(mediaMetadata.moviepediaId, actorEntity.moviepediaId, PersonType.DIRECTOR))
}
castResult.writer?.forEach { actor ->
val actorEntity = Person(actor.person.personId, actor.person.name, actor.person.image())
if (BuildConfig.DEBUG) Log.d(this::class.java.simpleName, "Inserting ${actor.person.name} - ${actor.person.personId} as writer")
personRepo.addPersonImmediate(actorEntity)
personsToAdd.add(MediaPersonJoin(mediaMetadata.moviepediaId, actorEntity.moviepediaId, PersonType.WRITER))
}
castResult.musician?.forEach { actor ->
val actorEntity = Person(actor.person.personId, actor.person.name, actor.person.image())
if (BuildConfig.DEBUG) Log.d(this::class.java.simpleName, "Inserting ${actor.person.name} - ${actor.person.personId} as musician")
personRepo.addPersonImmediate(actorEntity)
personsToAdd.add(MediaPersonJoin(mediaMetadata.moviepediaId, actorEntity.moviepediaId, PersonType.MUSICIAN))
}
castResult.producer?.forEach { actor ->
val actorEntity = Person(actor.person.personId, actor.person.name, actor.person.image())
if (BuildConfig.DEBUG) Log.d(this::class.java.simpleName, "Inserting ${actor.person.name} - ${actor.person.personId} as producer")
personRepo.addPersonImmediate(actorEntity)
personsToAdd.add(MediaPersonJoin(mediaMetadata.moviepediaId, actorEntity.moviepediaId, PersonType.PRODUCER))
}
MediaPersonRepository.getInstance(context).removeAllFor(mediaMetadata.moviepediaId)
MediaPersonRepository.getInstance(context).addPersons(personsToAdd)
//Remove orphans
val allPersons = PersonRepository.getInstance(context).getAll()
val allPersonJoins = MediaPersonRepository.getInstance(context).getAll()
val personsToRemove = allPersons.filter { person -> allPersonJoins.any { personJoin -> person.moviepediaId == personJoin.personId }.not() }
PersonRepository.getInstance(context).deleteAll(personsToRemove)
}
}
\ No newline at end of file
......@@ -27,12 +27,9 @@ import android.content.Intent
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ObsoleteCoroutinesApi
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.actor
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.videolan.medialibrary.interfaces.AbstractMedialibrary
import org.videolan.medialibrary.interfaces.media.AbstractMediaWrapper
import org.videolan.medialibrary.media.DummyItem
......@@ -41,7 +38,6 @@ import org.videolan.vlc.ExternalMonitor
import org.videolan.vlc.PlaybackService
import org.videolan.vlc.R
import org.videolan.vlc.database.models.BrowserFav
import org.videolan.vlc.database.models.MediaMetadata
import org.videolan.vlc.database.models.MediaMetadataWithImages
import org.videolan.vlc.gui.DialogActivity
import org.videolan.vlc.gui.tv.*
......@@ -68,6 +64,7 @@ class MainTvModel(app: Application) : AndroidViewModel(app), AbstractMedialibrar
val settings = Settings.getInstance(context)
private val showInternalStorage = AndroidDevices.showInternalStorage()
private val browserFavRepository = BrowserFavRepository.getInstance(context)
private val mediaMetadataRepository = MediaMetadataRepository.getInstance(context)
private var updatedFavoritList: List<AbstractMediaWrapper> = listOf()
var showHistory = false
private set
......@@ -79,8 +76,6 @@ class MainTvModel(app: Application) : AndroidViewModel(app), AbstractMedialibrar
val browsers: LiveData<List<MediaLibraryItem>> = MutableLiveData()
val history: LiveData<List<AbstractMediaWrapper>> = MutableLiveData()
val playlist: LiveData<List<MediaLibraryItem>> = MutableLiveData()
val movies: MediatorLiveData<List<MediaMetadataWithImages>> = MediatorLiveData()
val tvshows: MediatorLiveData<List<MediaMetadata>> = MediatorLiveData()
private val nowPlayingDelegate = NowPlayingDelegate(this)
......@@ -101,6 +96,8 @@ class MainTvModel(app: Application) : AndroidViewModel(app), AbstractMedialibrar
private val playerObserver = Observer<Boolean> { updateAudioCategories() }
private val videoObserver = Observer<Any> { updateVideos() }
init {
medialibrary.addOnMedialibraryReadyListener(this)
medialibrary.addOnDeviceChangeListener(this)
......@@ -109,6 +106,8 @@ class MainTvModel(app: Application) : AndroidViewModel(app), AbstractMedialibrar
ExternalMonitor.storageUnplugged.observeForever(monitorObserver)
ExternalMonitor.storagePlugged.observeForever(monitorObserver)
PlaylistManager.showAudioPlayer.observeForever(playerObserver)
mediaMetadataRepository.getAllLive().observeForever(videoObserver)
}
fun refresh() = viewModelScope.launch {
......@@ -134,8 +133,8 @@ class MainTvModel(app: Application) : AndroidViewModel(app), AbstractMedialibrar
}
private fun updateVideos() = viewModelScope.launch {
val allMovies = withContext(Dispatchers.IO) { MediaMetadataRepository.getInstance(context).getMovieCount() }
val allTvshows = withContext(Dispatchers.IO) { MediaMetadataRepository.getInstance(context).getTvshowsCount() }
val allMovies = withContext(Dispatchers.IO) { mediaMetadataRepository.getMovieCount() }
val allTvshows = withContext(Dispatchers.IO) { mediaMetadataRepository.getTvshowsCount() }
context.getFromMl {
getPagedVideos(AbstractMedialibrary.SORT_INSERTIONDATE, true, NUM_ITEMS_PREVIEW, 0)
}.let {
......@@ -260,7 +259,7 @@ class MainTvModel(app: Application) : AndroidViewModel(app), AbstractMedialibrar
}
is MediaMetadataWithImages -> {
item.metadata.mlId?.let {
launch {
viewModelScope.launch {
context.getFromMl {
getMedia(it)
}.let {
......
......@@ -59,6 +59,8 @@ class MoviepediaBrowserViewModel(context: Context, val category: Long) : Sortabl
provider.refresh()
}
override fun isEmpty() = provider.pagedList.value?.isEmpty() != false
override var currentItem: MediaMetadataWithImages? = null
override var nbColumns = 0
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment