mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-04-20 01:17:45 +02:00
Clean out old unused objects
This commit is contained in:
parent
24923c0009
commit
0344a63b48
@ -264,7 +264,6 @@ export default {
|
|||||||
libraryItems.forEach((item) => {
|
libraryItems.forEach((item) => {
|
||||||
let subtitle = ''
|
let subtitle = ''
|
||||||
if (item.mediaType === 'book') subtitle = item.media.metadata.authors.map((au) => au.name).join(', ')
|
if (item.mediaType === 'book') subtitle = item.media.metadata.authors.map((au) => au.name).join(', ')
|
||||||
else if (item.mediaType === 'music') subtitle = item.media.metadata.artists.join(', ')
|
|
||||||
queueItems.push({
|
queueItems.push({
|
||||||
libraryItemId: item.id,
|
libraryItemId: item.id,
|
||||||
libraryId: item.libraryId,
|
libraryId: item.libraryId,
|
||||||
|
@ -246,9 +246,6 @@ export default {
|
|||||||
isPodcastLibrary() {
|
isPodcastLibrary() {
|
||||||
return this.currentLibraryMediaType === 'podcast'
|
return this.currentLibraryMediaType === 'podcast'
|
||||||
},
|
},
|
||||||
isMusicLibrary() {
|
|
||||||
return this.currentLibraryMediaType === 'music'
|
|
||||||
},
|
|
||||||
isLibraryPage() {
|
isLibraryPage() {
|
||||||
return this.page === ''
|
return this.page === ''
|
||||||
},
|
},
|
||||||
@ -281,7 +278,6 @@ export default {
|
|||||||
},
|
},
|
||||||
entityName() {
|
entityName() {
|
||||||
if (this.isAlbumsPage) return 'Albums'
|
if (this.isAlbumsPage) return 'Albums'
|
||||||
if (this.isMusicLibrary) return 'Tracks'
|
|
||||||
|
|
||||||
if (this.isPodcastLibrary) return this.$strings.LabelPodcasts
|
if (this.isPodcastLibrary) return this.$strings.LabelPodcasts
|
||||||
if (!this.page) return this.$strings.LabelBooks
|
if (!this.page) return this.$strings.LabelBooks
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="streamLibraryItem" id="mediaPlayerContainer" class="w-full fixed bottom-0 left-0 right-0 h-48 lg:h-40 z-50 bg-primary px-2 lg:px-4 pb-1 lg:pb-4 pt-2">
|
<div v-if="streamLibraryItem" id="mediaPlayerContainer" class="w-full fixed bottom-0 left-0 right-0 h-48 lg:h-40 z-50 bg-primary px-2 lg:px-4 pb-1 lg:pb-4 pt-2">
|
||||||
<div id="videoDock" />
|
|
||||||
<div class="absolute left-2 top-2 lg:left-4 cursor-pointer">
|
<div class="absolute left-2 top-2 lg:left-4 cursor-pointer">
|
||||||
<covers-book-cover expand-on-click :library-item="streamLibraryItem" :width="bookCoverWidth" :book-cover-aspect-ratio="coverAspectRatio" />
|
<covers-book-cover expand-on-click :library-item="streamLibraryItem" :width="bookCoverWidth" :book-cover-aspect-ratio="coverAspectRatio" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-start mb-6 lg:mb-0" :class="playerHandler.isVideo ? 'ml-4 pl-96' : isSquareCover ? 'pl-18 sm:pl-24' : 'pl-12 sm:pl-16'">
|
<div class="flex items-start mb-6 lg:mb-0" :class="isSquareCover ? 'pl-18 sm:pl-24' : 'pl-12 sm:pl-16'">
|
||||||
<div class="min-w-0 w-full">
|
<div class="min-w-0 w-full">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<nuxt-link :to="`/item/${streamLibraryItem.id}`" class="hover:underline cursor-pointer text-sm sm:text-lg block truncate">
|
<nuxt-link :to="`/item/${streamLibraryItem.id}`" class="hover:underline cursor-pointer text-sm sm:text-lg block truncate">
|
||||||
@ -12,10 +11,9 @@
|
|||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
<widgets-explicit-indicator v-if="isExplicit" />
|
<widgets-explicit-indicator v-if="isExplicit" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!playerHandler.isVideo" class="text-gray-400 flex items-center w-1/2 sm:w-4/5 lg:w-2/5">
|
<div class="text-gray-400 flex items-center w-1/2 sm:w-4/5 lg:w-2/5">
|
||||||
<span class="material-symbols text-sm">person</span>
|
<span class="material-symbols text-sm">person</span>
|
||||||
<div v-if="podcastAuthor" class="pl-1 sm:pl-1.5 text-xs sm:text-base">{{ podcastAuthor }}</div>
|
<div v-if="podcastAuthor" class="pl-1 sm:pl-1.5 text-xs sm:text-base">{{ podcastAuthor }}</div>
|
||||||
<div v-else-if="musicArtists" class="pl-1 sm:pl-1.5 text-xs sm:text-base">{{ musicArtists }}</div>
|
|
||||||
<div v-else-if="authors.length" class="pl-1 sm:pl-1.5 text-xs sm:text-base truncate">
|
<div v-else-if="authors.length" class="pl-1 sm:pl-1.5 text-xs sm:text-base truncate">
|
||||||
<nuxt-link v-for="(author, index) in authors" :key="index" :to="`/author/${author.id}`" class="hover:underline">{{ author.name }}<span v-if="index < authors.length - 1">, </span></nuxt-link>
|
<nuxt-link v-for="(author, index) in authors" :key="index" :to="`/author/${author.id}`" class="hover:underline">{{ author.name }}<span v-if="index < authors.length - 1">, </span></nuxt-link>
|
||||||
</div>
|
</div>
|
||||||
@ -140,9 +138,6 @@ export default {
|
|||||||
isPodcast() {
|
isPodcast() {
|
||||||
return this.streamLibraryItem?.mediaType === 'podcast'
|
return this.streamLibraryItem?.mediaType === 'podcast'
|
||||||
},
|
},
|
||||||
isMusic() {
|
|
||||||
return this.streamLibraryItem?.mediaType === 'music'
|
|
||||||
},
|
|
||||||
isExplicit() {
|
isExplicit() {
|
||||||
return !!this.mediaMetadata.explicit
|
return !!this.mediaMetadata.explicit
|
||||||
},
|
},
|
||||||
@ -174,10 +169,6 @@ export default {
|
|||||||
if (!this.isPodcast) return null
|
if (!this.isPodcast) return null
|
||||||
return this.mediaMetadata.author || 'Unknown'
|
return this.mediaMetadata.author || 'Unknown'
|
||||||
},
|
},
|
||||||
musicArtists() {
|
|
||||||
if (!this.isMusic) return null
|
|
||||||
return this.mediaMetadata.artists.join(', ')
|
|
||||||
},
|
|
||||||
hasNextItemInQueue() {
|
hasNextItemInQueue() {
|
||||||
return this.currentPlayerQueueIndex < this.playerQueueItems.length - 1
|
return this.currentPlayerQueueIndex < this.playerQueueItems.length - 1
|
||||||
},
|
},
|
||||||
|
@ -95,14 +95,6 @@
|
|||||||
<div v-show="isPodcastSearchPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
<div v-show="isPodcastSearchPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
|
|
||||||
<nuxt-link v-if="isMusicLibrary" :to="`/library/${currentLibraryId}/bookshelf/albums`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isMusicAlbumsPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
|
|
||||||
<span class="material-symbols text-xl">album</span>
|
|
||||||
|
|
||||||
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">Albums</p>
|
|
||||||
|
|
||||||
<div v-show="isMusicAlbumsPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
|
||||||
</nuxt-link>
|
|
||||||
|
|
||||||
<nuxt-link v-if="isPodcastLibrary && userIsAdminOrUp" :to="`/library/${currentLibraryId}/podcast/download-queue`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isPodcastDownloadQueuePage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
|
<nuxt-link v-if="isPodcastLibrary && userIsAdminOrUp" :to="`/library/${currentLibraryId}/podcast/download-queue`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isPodcastDownloadQueuePage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
|
||||||
<span class="material-symbols text-2xl"></span>
|
<span class="material-symbols text-2xl"></span>
|
||||||
|
|
||||||
@ -172,9 +164,6 @@ export default {
|
|||||||
isPodcastLibrary() {
|
isPodcastLibrary() {
|
||||||
return this.currentLibraryMediaType === 'podcast'
|
return this.currentLibraryMediaType === 'podcast'
|
||||||
},
|
},
|
||||||
isMusicLibrary() {
|
|
||||||
return this.currentLibraryMediaType === 'music'
|
|
||||||
},
|
|
||||||
isPodcastDownloadQueuePage() {
|
isPodcastDownloadQueuePage() {
|
||||||
return this.$route.name === 'library-library-podcast-download-queue'
|
return this.$route.name === 'library-library-podcast-download-queue'
|
||||||
},
|
},
|
||||||
@ -184,9 +173,6 @@ export default {
|
|||||||
isPodcastLatestPage() {
|
isPodcastLatestPage() {
|
||||||
return this.$route.name === 'library-library-podcast-latest'
|
return this.$route.name === 'library-library-podcast-latest'
|
||||||
},
|
},
|
||||||
isMusicAlbumsPage() {
|
|
||||||
return this.paramId === 'albums'
|
|
||||||
},
|
|
||||||
homePage() {
|
homePage() {
|
||||||
return this.$route.name === 'library-library'
|
return this.$route.name === 'library-library'
|
||||||
},
|
},
|
||||||
|
@ -226,9 +226,6 @@ export default {
|
|||||||
isPodcast() {
|
isPodcast() {
|
||||||
return this.mediaType === 'podcast' || this.store.getters['libraries/getCurrentLibraryMediaType'] === 'podcast'
|
return this.mediaType === 'podcast' || this.store.getters['libraries/getCurrentLibraryMediaType'] === 'podcast'
|
||||||
},
|
},
|
||||||
isMusic() {
|
|
||||||
return this.mediaType === 'music'
|
|
||||||
},
|
|
||||||
isExplicit() {
|
isExplicit() {
|
||||||
return this.mediaMetadata.explicit || false
|
return this.mediaMetadata.explicit || false
|
||||||
},
|
},
|
||||||
@ -336,7 +333,6 @@ export default {
|
|||||||
displayLineTwo() {
|
displayLineTwo() {
|
||||||
if (this.recentEpisode) return this.title
|
if (this.recentEpisode) return this.title
|
||||||
if (this.isPodcast) return this.author
|
if (this.isPodcast) return this.author
|
||||||
if (this.isMusic) return this.artist
|
|
||||||
if (this.collapsedSeries) return ''
|
if (this.collapsedSeries) return ''
|
||||||
if (this.isAuthorBookshelfView) {
|
if (this.isAuthorBookshelfView) {
|
||||||
return this.mediaMetadata.publishedYear || ''
|
return this.mediaMetadata.publishedYear || ''
|
||||||
@ -364,7 +360,6 @@ export default {
|
|||||||
return this.store.getters['user/getUserMediaProgress'](this.libraryItemId, this.recentEpisode.id)
|
return this.store.getters['user/getUserMediaProgress'](this.libraryItemId, this.recentEpisode.id)
|
||||||
},
|
},
|
||||||
userProgress() {
|
userProgress() {
|
||||||
if (this.isMusic) return null
|
|
||||||
if (this.episodeProgress) return this.episodeProgress
|
if (this.episodeProgress) return this.episodeProgress
|
||||||
return this.store.getters['user/getUserMediaProgress'](this.libraryItemId)
|
return this.store.getters['user/getUserMediaProgress'](this.libraryItemId)
|
||||||
},
|
},
|
||||||
@ -420,7 +415,7 @@ export default {
|
|||||||
return !this.isSelectionMode && !this.showPlayButton && this.ebookFormat
|
return !this.isSelectionMode && !this.showPlayButton && this.ebookFormat
|
||||||
},
|
},
|
||||||
showPlayButton() {
|
showPlayButton() {
|
||||||
return !this.isSelectionMode && !this.isMissing && !this.isInvalid && !this.isStreaming && (this.numTracks || this.recentEpisode || this.isMusic)
|
return !this.isSelectionMode && !this.isMissing && !this.isInvalid && !this.isStreaming && (this.numTracks || this.recentEpisode)
|
||||||
},
|
},
|
||||||
showSmallEBookIcon() {
|
showSmallEBookIcon() {
|
||||||
return !this.isSelectionMode && this.ebookFormat
|
return !this.isSelectionMode && this.ebookFormat
|
||||||
@ -464,8 +459,6 @@ export default {
|
|||||||
return this.store.getters['user/getIsAdminOrUp']
|
return this.store.getters['user/getIsAdminOrUp']
|
||||||
},
|
},
|
||||||
moreMenuItems() {
|
moreMenuItems() {
|
||||||
if (this.isMusic) return []
|
|
||||||
|
|
||||||
if (this.recentEpisode) {
|
if (this.recentEpisode) {
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
|
@ -27,38 +27,6 @@
|
|||||||
<nuxt-link :to="`/library/${libraryId}/bookshelf?filter=publishers.${$encode(publisher)}`" class="hover:underline">{{ publisher }}</nuxt-link>
|
<nuxt-link :to="`/library/${libraryId}/bookshelf?filter=publishers.${$encode(publisher)}`" class="hover:underline">{{ publisher }}</nuxt-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="musicAlbum" class="flex py-0.5">
|
|
||||||
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
|
|
||||||
<span class="text-white text-opacity-60 uppercase text-sm">Album</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{{ musicAlbum }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="musicAlbumArtist" class="flex py-0.5">
|
|
||||||
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
|
|
||||||
<span class="text-white text-opacity-60 uppercase text-sm">Album Artist</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{{ musicAlbumArtist }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="musicTrackPretty" class="flex py-0.5">
|
|
||||||
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
|
|
||||||
<span class="text-white text-opacity-60 uppercase text-sm">Track</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{{ musicTrackPretty }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="musicDiscPretty" class="flex py-0.5">
|
|
||||||
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
|
|
||||||
<span class="text-white text-opacity-60 uppercase text-sm">Disc</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{{ musicDiscPretty }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="podcastType" class="flex py-0.5">
|
<div v-if="podcastType" class="flex py-0.5">
|
||||||
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
|
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
|
||||||
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelPodcastType }}</span>
|
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelPodcastType }}</span>
|
||||||
@ -97,7 +65,7 @@
|
|||||||
<nuxt-link :to="`/library/${libraryId}/bookshelf?filter=languages.${$encode(language)}`" class="hover:underline">{{ language }}</nuxt-link>
|
<nuxt-link :to="`/library/${libraryId}/bookshelf?filter=languages.${$encode(language)}`" class="hover:underline">{{ language }}</nuxt-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="tracks.length || audioFile || (isPodcast && totalPodcastDuration)" class="flex py-0.5">
|
<div v-if="tracks.length || (isPodcast && totalPodcastDuration)" class="flex py-0.5">
|
||||||
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
|
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
|
||||||
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelDuration }}</span>
|
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelDuration }}</span>
|
||||||
</div>
|
</div>
|
||||||
@ -134,10 +102,6 @@ export default {
|
|||||||
isPodcast() {
|
isPodcast() {
|
||||||
return this.libraryItem.mediaType === 'podcast'
|
return this.libraryItem.mediaType === 'podcast'
|
||||||
},
|
},
|
||||||
audioFile() {
|
|
||||||
// Music track
|
|
||||||
return this.media.audioFile
|
|
||||||
},
|
|
||||||
media() {
|
media() {
|
||||||
return this.libraryItem.media || {}
|
return this.libraryItem.media || {}
|
||||||
},
|
},
|
||||||
@ -168,25 +132,6 @@ export default {
|
|||||||
publisher() {
|
publisher() {
|
||||||
return this.mediaMetadata.publisher || ''
|
return this.mediaMetadata.publisher || ''
|
||||||
},
|
},
|
||||||
musicArtists() {
|
|
||||||
return this.mediaMetadata.artists || []
|
|
||||||
},
|
|
||||||
musicAlbum() {
|
|
||||||
return this.mediaMetadata.album || ''
|
|
||||||
},
|
|
||||||
musicAlbumArtist() {
|
|
||||||
return this.mediaMetadata.albumArtist || ''
|
|
||||||
},
|
|
||||||
musicTrackPretty() {
|
|
||||||
if (!this.mediaMetadata.trackNumber) return null
|
|
||||||
if (!this.mediaMetadata.trackTotal) return this.mediaMetadata.trackNumber
|
|
||||||
return `${this.mediaMetadata.trackNumber} / ${this.mediaMetadata.trackTotal}`
|
|
||||||
},
|
|
||||||
musicDiscPretty() {
|
|
||||||
if (!this.mediaMetadata.discNumber) return null
|
|
||||||
if (!this.mediaMetadata.discTotal) return this.mediaMetadata.discNumber
|
|
||||||
return `${this.mediaMetadata.discNumber} / ${this.mediaMetadata.discTotal}`
|
|
||||||
},
|
|
||||||
narrators() {
|
narrators() {
|
||||||
return this.mediaMetadata.narrators || []
|
return this.mediaMetadata.narrators || []
|
||||||
},
|
},
|
||||||
|
@ -98,9 +98,6 @@ export default {
|
|||||||
isPodcast() {
|
isPodcast() {
|
||||||
return this.libraryMediaType === 'podcast'
|
return this.libraryMediaType === 'podcast'
|
||||||
},
|
},
|
||||||
isMusic() {
|
|
||||||
return this.libraryMediaType === 'music'
|
|
||||||
},
|
|
||||||
seriesItems() {
|
seriesItems() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -274,35 +271,9 @@ export default {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
musicItems() {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
text: this.$strings.LabelAll,
|
|
||||||
value: 'all'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: this.$strings.LabelGenre,
|
|
||||||
textPlural: this.$strings.LabelGenres,
|
|
||||||
value: 'genres',
|
|
||||||
sublist: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: this.$strings.LabelTag,
|
|
||||||
textPlural: this.$strings.LabelTags,
|
|
||||||
value: 'tags',
|
|
||||||
sublist: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: this.$strings.ButtonIssues,
|
|
||||||
value: 'issues',
|
|
||||||
sublist: false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
selectItems() {
|
selectItems() {
|
||||||
if (this.isSeries) return this.seriesItems
|
if (this.isSeries) return this.seriesItems
|
||||||
if (this.isPodcast) return this.podcastItems
|
if (this.isPodcast) return this.podcastItems
|
||||||
if (this.isMusic) return this.musicItems
|
|
||||||
return this.bookItems
|
return this.bookItems
|
||||||
},
|
},
|
||||||
selectedItemSublist() {
|
selectedItemSublist() {
|
||||||
|
@ -56,9 +56,6 @@ export default {
|
|||||||
isPodcast() {
|
isPodcast() {
|
||||||
return this.libraryMediaType === 'podcast'
|
return this.libraryMediaType === 'podcast'
|
||||||
},
|
},
|
||||||
isMusic() {
|
|
||||||
return this.libraryMediaType === 'music'
|
|
||||||
},
|
|
||||||
podcastItems() {
|
podcastItems() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -148,40 +145,10 @@ export default {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
musicItems() {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
text: this.$strings.LabelTitle,
|
|
||||||
value: 'media.metadata.title'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: this.$strings.LabelAddedAt,
|
|
||||||
value: 'addedAt'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: this.$strings.LabelSize,
|
|
||||||
value: 'size'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: this.$strings.LabelDuration,
|
|
||||||
value: 'media.duration'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: this.$strings.LabelFileBirthtime,
|
|
||||||
value: 'birthtimeMs'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: this.$strings.LabelFileModified,
|
|
||||||
value: 'mtimeMs'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
selectItems() {
|
selectItems() {
|
||||||
let items = null
|
let items = null
|
||||||
if (this.isPodcast) {
|
if (this.isPodcast) {
|
||||||
items = this.podcastItems
|
items = this.podcastItems
|
||||||
} else if (this.isMusic) {
|
|
||||||
items = this.musicItems
|
|
||||||
} else if (this.$store.getters['user/getUserSetting']('filterBy').startsWith('series.')) {
|
} else if (this.$store.getters['user/getUserSetting']('filterBy').startsWith('series.')) {
|
||||||
items = this.seriesItems
|
items = this.seriesItems
|
||||||
} else {
|
} else {
|
||||||
|
@ -178,22 +178,6 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
toggleFullscreen(isFullscreen) {
|
toggleFullscreen(isFullscreen) {
|
||||||
this.$store.commit('setPlayerIsFullscreen', isFullscreen)
|
this.$store.commit('setPlayerIsFullscreen', isFullscreen)
|
||||||
|
|
||||||
var videoPlayerEl = document.getElementById('video-player')
|
|
||||||
if (videoPlayerEl) {
|
|
||||||
if (isFullscreen) {
|
|
||||||
videoPlayerEl.style.width = '100vw'
|
|
||||||
videoPlayerEl.style.height = '100vh'
|
|
||||||
videoPlayerEl.style.top = '0px'
|
|
||||||
videoPlayerEl.style.left = '0px'
|
|
||||||
} else {
|
|
||||||
videoPlayerEl.style.width = '384px'
|
|
||||||
videoPlayerEl.style.height = '216px'
|
|
||||||
videoPlayerEl.style.top = 'unset'
|
|
||||||
videoPlayerEl.style.bottom = '80px'
|
|
||||||
videoPlayerEl.style.left = '16px'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
setDuration(duration) {
|
setDuration(duration) {
|
||||||
this.duration = duration
|
this.duration = duration
|
||||||
|
@ -39,16 +39,11 @@
|
|||||||
><span :key="index" v-if="index < seriesList.length - 1">, </span>
|
><span :key="index" v-if="index < seriesList.length - 1">, </span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="!isVideo">
|
|
||||||
<p v-if="isPodcast" class="mb-2 mt-0.5 text-gray-200 text-lg md:text-xl">{{ $getString('LabelByAuthor', [podcastAuthor]) }}</p>
|
<p v-if="isPodcast" class="mb-2 mt-0.5 text-gray-200 text-lg md:text-xl">{{ $getString('LabelByAuthor', [podcastAuthor]) }}</p>
|
||||||
<p v-else-if="musicArtists.length" class="mb-2 mt-0.5 text-gray-200 text-lg md:text-xl max-w-[calc(100vw-2rem)] overflow-hidden overflow-ellipsis">
|
|
||||||
<nuxt-link v-for="(artist, index) in musicArtists" :key="index" :to="`/artist/${$encode(artist)}`" class="hover:underline">{{ artist }}<span v-if="index < musicArtists.length - 1">, </span></nuxt-link>
|
|
||||||
</p>
|
|
||||||
<p v-else-if="authors.length" class="mb-2 mt-0.5 text-gray-200 text-lg md:text-xl max-w-[calc(100vw-2rem)] overflow-hidden overflow-ellipsis">
|
<p v-else-if="authors.length" class="mb-2 mt-0.5 text-gray-200 text-lg md:text-xl max-w-[calc(100vw-2rem)] overflow-hidden overflow-ellipsis">
|
||||||
by <nuxt-link v-for="(author, index) in authors" :key="index" :to="`/author/${author.id}`" class="hover:underline">{{ author.name }}<span v-if="index < authors.length - 1">, </span></nuxt-link>
|
by <nuxt-link v-for="(author, index) in authors" :key="index" :to="`/author/${author.id}`" class="hover:underline">{{ author.name }}<span v-if="index < authors.length - 1">, </span></nuxt-link>
|
||||||
</p>
|
</p>
|
||||||
<p v-else class="mb-2 mt-0.5 text-gray-200 text-xl">by Unknown</p>
|
<p v-else class="mb-2 mt-0.5 text-gray-200 text-xl">by Unknown</p>
|
||||||
</template>
|
|
||||||
|
|
||||||
<content-library-item-details :library-item="libraryItem" />
|
<content-library-item-details :library-item="libraryItem" />
|
||||||
</div>
|
</div>
|
||||||
@ -109,7 +104,7 @@
|
|||||||
<ui-icon-btn icon="" outlined class="mx-0.5" @click="editClick" />
|
<ui-icon-btn icon="" outlined class="mx-0.5" @click="editClick" />
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
|
|
||||||
<ui-tooltip v-if="!isPodcast && !isMusic" :text="userIsFinished ? $strings.MessageMarkAsNotFinished : $strings.MessageMarkAsFinished" direction="top">
|
<ui-tooltip v-if="!isPodcast" :text="userIsFinished ? $strings.MessageMarkAsNotFinished : $strings.MessageMarkAsFinished" direction="top">
|
||||||
<ui-read-icon-btn :disabled="isProcessingReadUpdate" :is-read="userIsFinished" class="mx-0.5" @click="toggleFinished" />
|
<ui-read-icon-btn :disabled="isProcessingReadUpdate" :is-read="userIsFinished" class="mx-0.5" @click="toggleFinished" />
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
|
|
||||||
@ -220,12 +215,6 @@ export default {
|
|||||||
isPodcast() {
|
isPodcast() {
|
||||||
return this.libraryItem.mediaType === 'podcast'
|
return this.libraryItem.mediaType === 'podcast'
|
||||||
},
|
},
|
||||||
isVideo() {
|
|
||||||
return this.libraryItem.mediaType === 'video'
|
|
||||||
},
|
|
||||||
isMusic() {
|
|
||||||
return this.libraryItem.mediaType === 'music'
|
|
||||||
},
|
|
||||||
isMissing() {
|
isMissing() {
|
||||||
return this.libraryItem.isMissing
|
return this.libraryItem.isMissing
|
||||||
},
|
},
|
||||||
@ -240,8 +229,6 @@ export default {
|
|||||||
},
|
},
|
||||||
showPlayButton() {
|
showPlayButton() {
|
||||||
if (this.isMissing || this.isInvalid) return false
|
if (this.isMissing || this.isInvalid) return false
|
||||||
if (this.isMusic) return !!this.audioFile
|
|
||||||
if (this.isVideo) return !!this.videoFile
|
|
||||||
if (this.isPodcast) return this.podcastEpisodes.length
|
if (this.isPodcast) return this.podcastEpisodes.length
|
||||||
return this.tracks.length
|
return this.tracks.length
|
||||||
},
|
},
|
||||||
@ -292,9 +279,6 @@ export default {
|
|||||||
authors() {
|
authors() {
|
||||||
return this.mediaMetadata.authors || []
|
return this.mediaMetadata.authors || []
|
||||||
},
|
},
|
||||||
musicArtists() {
|
|
||||||
return this.mediaMetadata.artists || []
|
|
||||||
},
|
|
||||||
series() {
|
series() {
|
||||||
return this.mediaMetadata.series || []
|
return this.mediaMetadata.series || []
|
||||||
},
|
},
|
||||||
@ -309,7 +293,7 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
duration() {
|
duration() {
|
||||||
if (!this.tracks.length && !this.audioFile) return 0
|
if (!this.tracks.length) return 0
|
||||||
return this.media.duration
|
return this.media.duration
|
||||||
},
|
},
|
||||||
libraryFiles() {
|
libraryFiles() {
|
||||||
@ -321,18 +305,10 @@ export default {
|
|||||||
ebookFile() {
|
ebookFile() {
|
||||||
return this.media.ebookFile
|
return this.media.ebookFile
|
||||||
},
|
},
|
||||||
videoFile() {
|
|
||||||
return this.media.videoFile
|
|
||||||
},
|
|
||||||
audioFile() {
|
|
||||||
// Music track
|
|
||||||
return this.media.audioFile
|
|
||||||
},
|
|
||||||
description() {
|
description() {
|
||||||
return this.mediaMetadata.description || ''
|
return this.mediaMetadata.description || ''
|
||||||
},
|
},
|
||||||
userMediaProgress() {
|
userMediaProgress() {
|
||||||
if (this.isMusic) return null
|
|
||||||
return this.$store.getters['user/getUserMediaProgress'](this.libraryItemId)
|
return this.$store.getters['user/getUserMediaProgress'](this.libraryItemId)
|
||||||
},
|
},
|
||||||
userIsFinished() {
|
userIsFinished() {
|
||||||
|
@ -1,260 +0,0 @@
|
|||||||
import Hls from 'hls.js'
|
|
||||||
import EventEmitter from 'events'
|
|
||||||
|
|
||||||
export default class LocalVideoPlayer extends EventEmitter {
|
|
||||||
constructor(ctx) {
|
|
||||||
super()
|
|
||||||
|
|
||||||
this.ctx = ctx
|
|
||||||
this.player = null
|
|
||||||
|
|
||||||
this.libraryItem = null
|
|
||||||
this.videoTrack = null
|
|
||||||
this.isHlsTranscode = null
|
|
||||||
this.hlsInstance = null
|
|
||||||
this.usingNativeplayer = false
|
|
||||||
this.startTime = 0
|
|
||||||
this.playWhenReady = false
|
|
||||||
this.defaultPlaybackRate = 1
|
|
||||||
|
|
||||||
this.playableMimeTypes = []
|
|
||||||
|
|
||||||
this.initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize() {
|
|
||||||
if (document.getElementById('video-player')) {
|
|
||||||
document.getElementById('video-player').remove()
|
|
||||||
}
|
|
||||||
var videoEl = document.createElement('video')
|
|
||||||
videoEl.id = 'video-player'
|
|
||||||
// videoEl.style.display = 'none'
|
|
||||||
videoEl.className = 'absolute bg-black z-50'
|
|
||||||
videoEl.style.height = '216px'
|
|
||||||
videoEl.style.width = '384px'
|
|
||||||
videoEl.style.bottom = '80px'
|
|
||||||
videoEl.style.left = '16px'
|
|
||||||
document.body.appendChild(videoEl)
|
|
||||||
this.player = videoEl
|
|
||||||
|
|
||||||
this.player.addEventListener('play', this.evtPlay.bind(this))
|
|
||||||
this.player.addEventListener('pause', this.evtPause.bind(this))
|
|
||||||
this.player.addEventListener('progress', this.evtProgress.bind(this))
|
|
||||||
this.player.addEventListener('ended', this.evtEnded.bind(this))
|
|
||||||
this.player.addEventListener('error', this.evtError.bind(this))
|
|
||||||
this.player.addEventListener('loadedmetadata', this.evtLoadedMetadata.bind(this))
|
|
||||||
this.player.addEventListener('timeupdate', this.evtTimeupdate.bind(this))
|
|
||||||
|
|
||||||
var mimeTypes = ['video/mp4']
|
|
||||||
var mimeTypeCanPlayMap = {}
|
|
||||||
mimeTypes.forEach((mt) => {
|
|
||||||
var canPlay = this.player.canPlayType(mt)
|
|
||||||
mimeTypeCanPlayMap[mt] = canPlay
|
|
||||||
if (canPlay) this.playableMimeTypes.push(mt)
|
|
||||||
})
|
|
||||||
console.log(`[LocalVideoPlayer] Supported mime types`, mimeTypeCanPlayMap, this.playableMimeTypes)
|
|
||||||
}
|
|
||||||
|
|
||||||
evtPlay() {
|
|
||||||
this.emit('stateChange', 'PLAYING')
|
|
||||||
}
|
|
||||||
evtPause() {
|
|
||||||
this.emit('stateChange', 'PAUSED')
|
|
||||||
}
|
|
||||||
evtProgress() {
|
|
||||||
var lastBufferTime = this.getLastBufferedTime()
|
|
||||||
this.emit('buffertimeUpdate', lastBufferTime)
|
|
||||||
}
|
|
||||||
evtEnded() {
|
|
||||||
console.log(`[LocalVideoPlayer] Ended`)
|
|
||||||
this.emit('finished')
|
|
||||||
}
|
|
||||||
evtError(error) {
|
|
||||||
console.error('Player error', error)
|
|
||||||
this.emit('error', error)
|
|
||||||
}
|
|
||||||
evtLoadedMetadata(data) {
|
|
||||||
if (!this.isHlsTranscode) {
|
|
||||||
this.player.currentTime = this.startTime
|
|
||||||
}
|
|
||||||
|
|
||||||
this.emit('stateChange', 'LOADED')
|
|
||||||
if (this.playWhenReady) {
|
|
||||||
this.playWhenReady = false
|
|
||||||
this.play()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
evtTimeupdate() {
|
|
||||||
if (this.player.paused) {
|
|
||||||
this.emit('timeupdate', this.getCurrentTime())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
this.destroyHlsInstance()
|
|
||||||
if (this.player) {
|
|
||||||
this.player.remove()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
set(libraryItem, videoTrack, isHlsTranscode, startTime, playWhenReady = false) {
|
|
||||||
this.libraryItem = libraryItem
|
|
||||||
this.videoTrack = videoTrack
|
|
||||||
this.isHlsTranscode = isHlsTranscode
|
|
||||||
this.playWhenReady = playWhenReady
|
|
||||||
this.startTime = startTime
|
|
||||||
|
|
||||||
if (this.hlsInstance) {
|
|
||||||
this.destroyHlsInstance()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isHlsTranscode) {
|
|
||||||
this.setHlsStream()
|
|
||||||
} else {
|
|
||||||
this.setDirectPlay()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setHlsStream() {
|
|
||||||
// iOS does not support Media Elements but allows for HLS in the native video player
|
|
||||||
if (!Hls.isSupported()) {
|
|
||||||
console.warn('HLS is not supported - fallback to using video element')
|
|
||||||
this.usingNativeplayer = true
|
|
||||||
this.player.src = this.videoTrack.relativeContentUrl
|
|
||||||
this.player.currentTime = this.startTime
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var hlsOptions = {
|
|
||||||
startPosition: this.startTime || -1
|
|
||||||
// No longer needed because token is put in a query string
|
|
||||||
// xhrSetup: (xhr) => {
|
|
||||||
// xhr.setRequestHeader('Authorization', `Bearer ${this.token}`)
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
this.hlsInstance = new Hls(hlsOptions)
|
|
||||||
|
|
||||||
this.hlsInstance.attachMedia(this.player)
|
|
||||||
this.hlsInstance.on(Hls.Events.MEDIA_ATTACHED, () => {
|
|
||||||
this.hlsInstance.loadSource(this.videoTrack.relativeContentUrl)
|
|
||||||
|
|
||||||
this.hlsInstance.on(Hls.Events.MANIFEST_PARSED, () => {
|
|
||||||
console.log('[HLS] Manifest Parsed')
|
|
||||||
})
|
|
||||||
|
|
||||||
this.hlsInstance.on(Hls.Events.ERROR, (e, data) => {
|
|
||||||
console.error('[HLS] Error', data.type, data.details, data)
|
|
||||||
if (data.details === Hls.ErrorDetails.BUFFER_STALLED_ERROR) {
|
|
||||||
console.error('[HLS] BUFFER STALLED ERROR')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
this.hlsInstance.on(Hls.Events.DESTROYING, () => {
|
|
||||||
console.log('[HLS] Destroying HLS Instance')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
setDirectPlay() {
|
|
||||||
this.player.src = this.videoTrack.relativeContentUrl
|
|
||||||
console.log(`[LocalVideoPlayer] Loading track src ${this.videoTrack.relativeContentUrl}`)
|
|
||||||
this.player.load()
|
|
||||||
}
|
|
||||||
|
|
||||||
destroyHlsInstance() {
|
|
||||||
if (!this.hlsInstance) return
|
|
||||||
if (this.hlsInstance.destroy) {
|
|
||||||
var temp = this.hlsInstance
|
|
||||||
temp.destroy()
|
|
||||||
}
|
|
||||||
this.hlsInstance = null
|
|
||||||
}
|
|
||||||
|
|
||||||
async resetStream(startTime) {
|
|
||||||
this.destroyHlsInstance()
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
||||||
this.set(this.libraryItem, this.videoTrack, this.isHlsTranscode, startTime, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
playPause() {
|
|
||||||
if (!this.player) return
|
|
||||||
if (this.player.paused) this.play()
|
|
||||||
else this.pause()
|
|
||||||
}
|
|
||||||
|
|
||||||
play() {
|
|
||||||
if (this.player) this.player.play()
|
|
||||||
}
|
|
||||||
|
|
||||||
pause() {
|
|
||||||
if (this.player) this.player.pause()
|
|
||||||
}
|
|
||||||
|
|
||||||
getCurrentTime() {
|
|
||||||
return this.player ? this.player.currentTime : 0
|
|
||||||
}
|
|
||||||
|
|
||||||
getDuration() {
|
|
||||||
return this.videoTrack.duration
|
|
||||||
}
|
|
||||||
|
|
||||||
setPlaybackRate(playbackRate) {
|
|
||||||
if (!this.player) return
|
|
||||||
this.defaultPlaybackRate = playbackRate
|
|
||||||
this.player.playbackRate = playbackRate
|
|
||||||
}
|
|
||||||
|
|
||||||
seek(time) {
|
|
||||||
if (!this.player) return
|
|
||||||
this.player.currentTime = Math.max(0, time)
|
|
||||||
}
|
|
||||||
|
|
||||||
setVolume(volume) {
|
|
||||||
if (!this.player) return
|
|
||||||
this.player.volume = volume
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utils
|
|
||||||
isValidDuration(duration) {
|
|
||||||
if (duration && !isNaN(duration) && duration !== Number.POSITIVE_INFINITY && duration !== Number.NEGATIVE_INFINITY) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
getBufferedRanges() {
|
|
||||||
if (!this.player) return []
|
|
||||||
const ranges = []
|
|
||||||
const seekable = this.player.buffered || []
|
|
||||||
|
|
||||||
let offset = 0
|
|
||||||
|
|
||||||
for (let i = 0, length = seekable.length; i < length; i++) {
|
|
||||||
let start = seekable.start(i)
|
|
||||||
let end = seekable.end(i)
|
|
||||||
if (!this.isValidDuration(start)) {
|
|
||||||
start = 0
|
|
||||||
}
|
|
||||||
if (!this.isValidDuration(end)) {
|
|
||||||
end = 0
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ranges.push({
|
|
||||||
start: start + offset,
|
|
||||||
end: end + offset
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return ranges
|
|
||||||
}
|
|
||||||
|
|
||||||
getLastBufferedTime() {
|
|
||||||
var bufferedRanges = this.getBufferedRanges()
|
|
||||||
if (!bufferedRanges.length) return 0
|
|
||||||
|
|
||||||
var buff = bufferedRanges.find((buff) => buff.start < this.player.currentTime && buff.end > this.player.currentTime)
|
|
||||||
if (buff) return buff.end
|
|
||||||
|
|
||||||
var last = bufferedRanges[bufferedRanges.length - 1]
|
|
||||||
return last.end
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +1,6 @@
|
|||||||
import LocalAudioPlayer from './LocalAudioPlayer'
|
import LocalAudioPlayer from './LocalAudioPlayer'
|
||||||
import LocalVideoPlayer from './LocalVideoPlayer'
|
|
||||||
import CastPlayer from './CastPlayer'
|
import CastPlayer from './CastPlayer'
|
||||||
import AudioTrack from './AudioTrack'
|
import AudioTrack from './AudioTrack'
|
||||||
import VideoTrack from './VideoTrack'
|
|
||||||
|
|
||||||
export default class PlayerHandler {
|
export default class PlayerHandler {
|
||||||
constructor(ctx) {
|
constructor(ctx) {
|
||||||
@ -16,8 +14,6 @@ export default class PlayerHandler {
|
|||||||
this.player = null
|
this.player = null
|
||||||
this.playerState = 'IDLE'
|
this.playerState = 'IDLE'
|
||||||
this.isHlsTranscode = false
|
this.isHlsTranscode = false
|
||||||
this.isVideo = false
|
|
||||||
this.isMusic = false
|
|
||||||
this.currentSessionId = null
|
this.currentSessionId = null
|
||||||
this.startTimeOverride = undefined // Used for starting playback at a specific time (i.e. clicking bookmark from library item page)
|
this.startTimeOverride = undefined // Used for starting playback at a specific time (i.e. clicking bookmark from library item page)
|
||||||
this.startTime = 0
|
this.startTime = 0
|
||||||
@ -65,12 +61,10 @@ export default class PlayerHandler {
|
|||||||
|
|
||||||
load(libraryItem, episodeId, playWhenReady, playbackRate, startTimeOverride = undefined) {
|
load(libraryItem, episodeId, playWhenReady, playbackRate, startTimeOverride = undefined) {
|
||||||
this.libraryItem = libraryItem
|
this.libraryItem = libraryItem
|
||||||
this.isVideo = libraryItem.mediaType === 'video'
|
|
||||||
this.isMusic = libraryItem.mediaType === 'music'
|
|
||||||
|
|
||||||
this.episodeId = episodeId
|
this.episodeId = episodeId
|
||||||
this.playWhenReady = playWhenReady
|
this.playWhenReady = playWhenReady
|
||||||
this.initialPlaybackRate = this.isMusic ? 1 : playbackRate
|
this.initialPlaybackRate = playbackRate
|
||||||
|
|
||||||
this.startTimeOverride = startTimeOverride == null || isNaN(startTimeOverride) ? undefined : Number(startTimeOverride)
|
this.startTimeOverride = startTimeOverride == null || isNaN(startTimeOverride) ? undefined : Number(startTimeOverride)
|
||||||
|
|
||||||
@ -97,7 +91,7 @@ export default class PlayerHandler {
|
|||||||
this.playWhenReady = playWhenReady
|
this.playWhenReady = playWhenReady
|
||||||
this.prepare()
|
this.prepare()
|
||||||
}
|
}
|
||||||
} else if (!this.isCasting && !(this.player instanceof LocalAudioPlayer) && !(this.player instanceof LocalVideoPlayer)) {
|
} else if (!this.isCasting && !(this.player instanceof LocalAudioPlayer)) {
|
||||||
console.log('[PlayerHandler] Switching to local player')
|
console.log('[PlayerHandler] Switching to local player')
|
||||||
|
|
||||||
this.stopPlayInterval()
|
this.stopPlayInterval()
|
||||||
@ -107,11 +101,7 @@ export default class PlayerHandler {
|
|||||||
this.player.destroy()
|
this.player.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isVideo) {
|
|
||||||
this.player = new LocalVideoPlayer(this.ctx)
|
|
||||||
} else {
|
|
||||||
this.player = new LocalAudioPlayer(this.ctx)
|
this.player = new LocalAudioPlayer(this.ctx)
|
||||||
}
|
|
||||||
|
|
||||||
this.setPlayerListeners()
|
this.setPlayerListeners()
|
||||||
|
|
||||||
@ -203,7 +193,7 @@ export default class PlayerHandler {
|
|||||||
supportedMimeTypes: this.player.playableMimeTypes,
|
supportedMimeTypes: this.player.playableMimeTypes,
|
||||||
mediaPlayer: this.isCasting ? 'chromecast' : 'html5',
|
mediaPlayer: this.isCasting ? 'chromecast' : 'html5',
|
||||||
forceTranscode,
|
forceTranscode,
|
||||||
forceDirectPlay: this.isCasting || this.isVideo // TODO: add transcode support for chromecast
|
forceDirectPlay: this.isCasting // TODO: add transcode support for chromecast
|
||||||
}
|
}
|
||||||
|
|
||||||
const path = this.episodeId ? `/api/items/${this.libraryItem.id}/play/${this.episodeId}` : `/api/items/${this.libraryItem.id}/play`
|
const path = this.episodeId ? `/api/items/${this.libraryItem.id}/play/${this.episodeId}` : `/api/items/${this.libraryItem.id}/play`
|
||||||
@ -218,7 +208,6 @@ export default class PlayerHandler {
|
|||||||
if (!this.player) this.switchPlayer() // Must set player first for open sessions
|
if (!this.player) this.switchPlayer() // Must set player first for open sessions
|
||||||
|
|
||||||
this.libraryItem = session.libraryItem
|
this.libraryItem = session.libraryItem
|
||||||
this.isVideo = session.libraryItem.mediaType === 'video'
|
|
||||||
this.playWhenReady = false
|
this.playWhenReady = false
|
||||||
this.initialPlaybackRate = playbackRate
|
this.initialPlaybackRate = playbackRate
|
||||||
this.startTimeOverride = undefined
|
this.startTimeOverride = undefined
|
||||||
@ -237,17 +226,6 @@ export default class PlayerHandler {
|
|||||||
|
|
||||||
console.log('[PlayerHandler] Preparing Session', session)
|
console.log('[PlayerHandler] Preparing Session', session)
|
||||||
|
|
||||||
if (session.videoTrack) {
|
|
||||||
var videoTrack = new VideoTrack(session.videoTrack, this.userToken)
|
|
||||||
|
|
||||||
this.ctx.playerLoading = true
|
|
||||||
this.isHlsTranscode = true
|
|
||||||
if (session.playMethod === this.ctx.$constants.PlayMethod.DIRECTPLAY) {
|
|
||||||
this.isHlsTranscode = false
|
|
||||||
}
|
|
||||||
|
|
||||||
this.player.set(this.libraryItem, videoTrack, this.isHlsTranscode, this.startTime, this.playWhenReady)
|
|
||||||
} else {
|
|
||||||
var audioTracks = session.audioTracks.map((at) => new AudioTrack(at, this.userToken))
|
var audioTracks = session.audioTracks.map((at) => new AudioTrack(at, this.userToken))
|
||||||
|
|
||||||
this.ctx.playerLoading = true
|
this.ctx.playerLoading = true
|
||||||
@ -257,7 +235,6 @@ export default class PlayerHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.player.set(this.libraryItem, audioTracks, this.isHlsTranscode, this.startTime, this.playWhenReady)
|
this.player.set(this.libraryItem, audioTracks, this.isHlsTranscode, this.startTime, this.playWhenReady)
|
||||||
}
|
|
||||||
|
|
||||||
// browser media session api
|
// browser media session api
|
||||||
this.ctx.setMediaSession()
|
this.ctx.setMediaSession()
|
||||||
@ -333,8 +310,6 @@ export default class PlayerHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sendProgressSync(currentTime) {
|
sendProgressSync(currentTime) {
|
||||||
if (this.isMusic) return
|
|
||||||
|
|
||||||
const diffSinceLastSync = Math.abs(this.lastSyncTime - currentTime)
|
const diffSinceLastSync = Math.abs(this.lastSyncTime - currentTime)
|
||||||
if (diffSinceLastSync < 1) return
|
if (diffSinceLastSync < 1) return
|
||||||
|
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
export default class VideoTrack {
|
|
||||||
constructor(track, userToken) {
|
|
||||||
this.index = track.index || 0
|
|
||||||
this.startOffset = track.startOffset || 0 // Total time of all previous tracks
|
|
||||||
this.duration = track.duration || 0
|
|
||||||
this.title = track.title || ''
|
|
||||||
this.contentUrl = track.contentUrl || null
|
|
||||||
this.mimeType = track.mimeType
|
|
||||||
this.metadata = track.metadata || {}
|
|
||||||
|
|
||||||
this.userToken = userToken
|
|
||||||
}
|
|
||||||
|
|
||||||
get fullContentUrl() {
|
|
||||||
if (!this.contentUrl || this.contentUrl.startsWith('http')) return this.contentUrl
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'development') {
|
|
||||||
return `${process.env.serverUrl}${this.contentUrl}?token=${this.userToken}`
|
|
||||||
}
|
|
||||||
return `${window.location.origin}${this.contentUrl}?token=${this.userToken}`
|
|
||||||
}
|
|
||||||
|
|
||||||
get relativeContentUrl() {
|
|
||||||
if (!this.contentUrl || this.contentUrl.startsWith('http')) return this.contentUrl
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'development') {
|
|
||||||
return `${process.env.serverUrl}${this.contentUrl}?token=${this.userToken}`
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.contentUrl + `?token=${this.userToken}`
|
|
||||||
}
|
|
||||||
}
|
|
@ -384,7 +384,7 @@ class LibraryItemController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
startPlaybackSession(req, res) {
|
startPlaybackSession(req, res) {
|
||||||
if (!req.libraryItem.media.numTracks && req.libraryItem.mediaType !== 'video') {
|
if (!req.libraryItem.media.numTracks) {
|
||||||
Logger.error(`[LibraryItemController] startPlaybackSession cannot playback ${req.libraryItem.id}`)
|
Logger.error(`[LibraryItemController] startPlaybackSession cannot playback ${req.libraryItem.id}`)
|
||||||
return res.sendStatus(404)
|
return res.sendStatus(404)
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ const Logger = require('../Logger')
|
|||||||
const BookFinder = require('../finders/BookFinder')
|
const BookFinder = require('../finders/BookFinder')
|
||||||
const PodcastFinder = require('../finders/PodcastFinder')
|
const PodcastFinder = require('../finders/PodcastFinder')
|
||||||
const AuthorFinder = require('../finders/AuthorFinder')
|
const AuthorFinder = require('../finders/AuthorFinder')
|
||||||
const MusicFinder = require('../finders/MusicFinder')
|
|
||||||
const Database = require('../Database')
|
const Database = require('../Database')
|
||||||
const { isValidASIN } = require('../utils')
|
const { isValidASIN } = require('../utils')
|
||||||
|
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
const MusicBrainz = require('../providers/MusicBrainz')
|
|
||||||
|
|
||||||
class MusicFinder {
|
|
||||||
constructor() {
|
|
||||||
this.musicBrainz = new MusicBrainz()
|
|
||||||
}
|
|
||||||
|
|
||||||
searchTrack(options) {
|
|
||||||
return this.musicBrainz.searchTrack(options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
module.exports = new MusicFinder()
|
|
@ -293,15 +293,6 @@ class PlaybackSessionManager {
|
|||||||
const newPlaybackSession = new PlaybackSession()
|
const newPlaybackSession = new PlaybackSession()
|
||||||
newPlaybackSession.setData(libraryItem, user.id, mediaPlayer, deviceInfo, userStartTime, episodeId)
|
newPlaybackSession.setData(libraryItem, user.id, mediaPlayer, deviceInfo, userStartTime, episodeId)
|
||||||
|
|
||||||
if (libraryItem.mediaType === 'video') {
|
|
||||||
if (shouldDirectPlay) {
|
|
||||||
Logger.debug(`[PlaybackSessionManager] "${user.username}" starting direct play session for item "${libraryItem.id}" with id ${newPlaybackSession.id}`)
|
|
||||||
newPlaybackSession.videoTrack = libraryItem.media.getVideoTrack()
|
|
||||||
newPlaybackSession.playMethod = PlayMethod.DIRECTPLAY
|
|
||||||
} else {
|
|
||||||
// HLS not supported for video yet
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let audioTracks = []
|
let audioTracks = []
|
||||||
if (shouldDirectPlay) {
|
if (shouldDirectPlay) {
|
||||||
Logger.debug(`[PlaybackSessionManager] "${user.username}" starting direct play session for item "${libraryItem.id}" with id ${newPlaybackSession.id} (Device: ${newPlaybackSession.deviceDescription})`)
|
Logger.debug(`[PlaybackSessionManager] "${user.username}" starting direct play session for item "${libraryItem.id}" with id ${newPlaybackSession.id} (Device: ${newPlaybackSession.deviceDescription})`)
|
||||||
@ -323,7 +314,6 @@ class PlaybackSessionManager {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
newPlaybackSession.audioTracks = audioTracks
|
newPlaybackSession.audioTracks = audioTracks
|
||||||
}
|
|
||||||
|
|
||||||
this.sessions.push(newPlaybackSession)
|
this.sessions.push(newPlaybackSession)
|
||||||
SocketAuthority.adminEmitter('user_stream_update', user.toJSONForPublic(this.sessions))
|
SocketAuthority.adminEmitter('user_stream_update', user.toJSONForPublic(this.sessions))
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
const uuidv4 = require("uuid").v4
|
const uuidv4 = require('uuid').v4
|
||||||
const fs = require('../libs/fsExtra')
|
const fs = require('../libs/fsExtra')
|
||||||
const Path = require('path')
|
const Path = require('path')
|
||||||
const Logger = require('../Logger')
|
const Logger = require('../Logger')
|
||||||
const LibraryFile = require('./files/LibraryFile')
|
const LibraryFile = require('./files/LibraryFile')
|
||||||
const Book = require('./mediaTypes/Book')
|
const Book = require('./mediaTypes/Book')
|
||||||
const Podcast = require('./mediaTypes/Podcast')
|
const Podcast = require('./mediaTypes/Podcast')
|
||||||
const Video = require('./mediaTypes/Video')
|
|
||||||
const Music = require('./mediaTypes/Music')
|
|
||||||
const { areEquivalent, copyValue } = require('../utils/index')
|
const { areEquivalent, copyValue } = require('../utils/index')
|
||||||
const { filePathToPOSIX, getFileTimestampsWithIno } = require('../utils/fileUtils')
|
const { filePathToPOSIX, getFileTimestampsWithIno } = require('../utils/fileUtils')
|
||||||
|
|
||||||
@ -74,14 +72,10 @@ class LibraryItem {
|
|||||||
this.media = new Book(libraryItem.media)
|
this.media = new Book(libraryItem.media)
|
||||||
} else if (this.mediaType === 'podcast') {
|
} else if (this.mediaType === 'podcast') {
|
||||||
this.media = new Podcast(libraryItem.media)
|
this.media = new Podcast(libraryItem.media)
|
||||||
} else if (this.mediaType === 'video') {
|
|
||||||
this.media = new Video(libraryItem.media)
|
|
||||||
} else if (this.mediaType === 'music') {
|
|
||||||
this.media = new Music(libraryItem.media)
|
|
||||||
}
|
}
|
||||||
this.media.libraryItemId = this.id
|
this.media.libraryItemId = this.id
|
||||||
|
|
||||||
this.libraryFiles = libraryItem.libraryFiles.map(f => new LibraryFile(f))
|
this.libraryFiles = libraryItem.libraryFiles.map((f) => new LibraryFile(f))
|
||||||
|
|
||||||
// Migration for v2.2.23 to set ebook library files as supplementary
|
// Migration for v2.2.23 to set ebook library files as supplementary
|
||||||
if (this.isBook && this.media.ebookFile) {
|
if (this.isBook && this.media.ebookFile) {
|
||||||
@ -91,7 +85,6 @@ class LibraryItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
toJSON() {
|
||||||
@ -115,7 +108,7 @@ class LibraryItem {
|
|||||||
isInvalid: !!this.isInvalid,
|
isInvalid: !!this.isInvalid,
|
||||||
mediaType: this.mediaType,
|
mediaType: this.mediaType,
|
||||||
media: this.media.toJSON(),
|
media: this.media.toJSON(),
|
||||||
libraryFiles: this.libraryFiles.map(f => f.toJSON())
|
libraryFiles: this.libraryFiles.map((f) => f.toJSON())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,21 +158,24 @@ class LibraryItem {
|
|||||||
isInvalid: !!this.isInvalid,
|
isInvalid: !!this.isInvalid,
|
||||||
mediaType: this.mediaType,
|
mediaType: this.mediaType,
|
||||||
media: this.media.toJSONExpanded(),
|
media: this.media.toJSONExpanded(),
|
||||||
libraryFiles: this.libraryFiles.map(f => f.toJSON()),
|
libraryFiles: this.libraryFiles.map((f) => f.toJSON()),
|
||||||
size: this.size
|
size: this.size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get isPodcast() { return this.mediaType === 'podcast' }
|
get isPodcast() {
|
||||||
get isBook() { return this.mediaType === 'book' }
|
return this.mediaType === 'podcast'
|
||||||
get isMusic() { return this.mediaType === 'music' }
|
}
|
||||||
|
get isBook() {
|
||||||
|
return this.mediaType === 'book'
|
||||||
|
}
|
||||||
get size() {
|
get size() {
|
||||||
let total = 0
|
let total = 0
|
||||||
this.libraryFiles.forEach((lf) => total += lf.metadata.size)
|
this.libraryFiles.forEach((lf) => (total += lf.metadata.size))
|
||||||
return total
|
return total
|
||||||
}
|
}
|
||||||
get hasAudioFiles() {
|
get hasAudioFiles() {
|
||||||
return this.libraryFiles.some(lf => lf.fileType === 'audio')
|
return this.libraryFiles.some((lf) => lf.fileType === 'audio')
|
||||||
}
|
}
|
||||||
get hasMediaEntities() {
|
get hasMediaEntities() {
|
||||||
return this.media.hasMediaEntities
|
return this.media.hasMediaEntities
|
||||||
@ -201,17 +197,16 @@ class LibraryItem {
|
|||||||
|
|
||||||
for (const key in payload) {
|
for (const key in payload) {
|
||||||
if (key === 'libraryFiles') {
|
if (key === 'libraryFiles') {
|
||||||
this.libraryFiles = payload.libraryFiles.map(lf => lf.clone())
|
this.libraryFiles = payload.libraryFiles.map((lf) => lf.clone())
|
||||||
|
|
||||||
// Set cover image
|
// Set cover image
|
||||||
const imageFiles = this.libraryFiles.filter(lf => lf.fileType === 'image')
|
const imageFiles = this.libraryFiles.filter((lf) => lf.fileType === 'image')
|
||||||
const coverMatch = imageFiles.find(iFile => /\/cover\.[^.\/]*$/.test(iFile.metadata.path))
|
const coverMatch = imageFiles.find((iFile) => /\/cover\.[^.\/]*$/.test(iFile.metadata.path))
|
||||||
if (coverMatch) {
|
if (coverMatch) {
|
||||||
this.media.coverPath = coverMatch.metadata.path
|
this.media.coverPath = coverMatch.metadata.path
|
||||||
} else if (imageFiles.length) {
|
} else if (imageFiles.length) {
|
||||||
this.media.coverPath = imageFiles[0].metadata.path
|
this.media.coverPath = imageFiles[0].metadata.path
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (this[key] !== undefined && key !== 'media') {
|
} else if (this[key] !== undefined && key !== 'media') {
|
||||||
this[key] = payload[key]
|
this[key] = payload[key]
|
||||||
}
|
}
|
||||||
@ -283,9 +278,11 @@ class LibraryItem {
|
|||||||
|
|
||||||
const metadataFilePath = Path.join(metadataPath, `metadata.${global.ServerSettings.metadataFileFormat}`)
|
const metadataFilePath = Path.join(metadataPath, `metadata.${global.ServerSettings.metadataFileFormat}`)
|
||||||
|
|
||||||
return fs.writeFile(metadataFilePath, JSON.stringify(this.media.toJSONForMetadataFile(), null, 2)).then(async () => {
|
return fs
|
||||||
|
.writeFile(metadataFilePath, JSON.stringify(this.media.toJSONForMetadataFile(), null, 2))
|
||||||
|
.then(async () => {
|
||||||
// Add metadata.json to libraryFiles array if it is new
|
// Add metadata.json to libraryFiles array if it is new
|
||||||
let metadataLibraryFile = this.libraryFiles.find(lf => lf.metadata.path === filePathToPOSIX(metadataFilePath))
|
let metadataLibraryFile = this.libraryFiles.find((lf) => lf.metadata.path === filePathToPOSIX(metadataFilePath))
|
||||||
if (storeMetadataWithItem) {
|
if (storeMetadataWithItem) {
|
||||||
if (!metadataLibraryFile) {
|
if (!metadataLibraryFile) {
|
||||||
metadataLibraryFile = new LibraryFile()
|
metadataLibraryFile = new LibraryFile()
|
||||||
@ -310,19 +307,21 @@ class LibraryItem {
|
|||||||
Logger.debug(`[LibraryItem] Success saving abmetadata to "${metadataFilePath}"`)
|
Logger.debug(`[LibraryItem] Success saving abmetadata to "${metadataFilePath}"`)
|
||||||
|
|
||||||
return metadataLibraryFile
|
return metadataLibraryFile
|
||||||
}).catch((error) => {
|
})
|
||||||
|
.catch((error) => {
|
||||||
Logger.error(`[LibraryItem] Failed to save json file at "${metadataFilePath}"`, error)
|
Logger.error(`[LibraryItem] Failed to save json file at "${metadataFilePath}"`, error)
|
||||||
return null
|
return null
|
||||||
}).finally(() => {
|
})
|
||||||
|
.finally(() => {
|
||||||
this.isSavingMetadata = false
|
this.isSavingMetadata = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
removeLibraryFile(ino) {
|
removeLibraryFile(ino) {
|
||||||
if (!ino) return false
|
if (!ino) return false
|
||||||
const libraryFile = this.libraryFiles.find(lf => lf.ino === ino)
|
const libraryFile = this.libraryFiles.find((lf) => lf.ino === ino)
|
||||||
if (libraryFile) {
|
if (libraryFile) {
|
||||||
this.libraryFiles = this.libraryFiles.filter(lf => lf.ino !== ino)
|
this.libraryFiles = this.libraryFiles.filter((lf) => lf.ino !== ino)
|
||||||
this.updatedAt = Date.now()
|
this.updatedAt = Date.now()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -337,7 +336,7 @@ class LibraryItem {
|
|||||||
* @param {LibraryFile} [libraryFile]
|
* @param {LibraryFile} [libraryFile]
|
||||||
*/
|
*/
|
||||||
setPrimaryEbook(ebookLibraryFile = null) {
|
setPrimaryEbook(ebookLibraryFile = null) {
|
||||||
const ebookLibraryFiles = this.libraryFiles.filter(lf => lf.isEBookFile)
|
const ebookLibraryFiles = this.libraryFiles.filter((lf) => lf.isEBookFile)
|
||||||
for (const libraryFile of ebookLibraryFiles) {
|
for (const libraryFile of ebookLibraryFiles) {
|
||||||
libraryFile.isSupplementary = ebookLibraryFile?.ino !== libraryFile.ino
|
libraryFile.isSupplementary = ebookLibraryFile?.ino !== libraryFile.ino
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ const serverVersion = require('../../package.json').version
|
|||||||
const BookMetadata = require('./metadata/BookMetadata')
|
const BookMetadata = require('./metadata/BookMetadata')
|
||||||
const PodcastMetadata = require('./metadata/PodcastMetadata')
|
const PodcastMetadata = require('./metadata/PodcastMetadata')
|
||||||
const DeviceInfo = require('./DeviceInfo')
|
const DeviceInfo = require('./DeviceInfo')
|
||||||
const VideoMetadata = require('./metadata/VideoMetadata')
|
|
||||||
|
|
||||||
class PlaybackSession {
|
class PlaybackSession {
|
||||||
constructor(session) {
|
constructor(session) {
|
||||||
@ -41,7 +40,6 @@ class PlaybackSession {
|
|||||||
// Not saved in DB
|
// Not saved in DB
|
||||||
this.lastSave = 0
|
this.lastSave = 0
|
||||||
this.audioTracks = []
|
this.audioTracks = []
|
||||||
this.videoTrack = null
|
|
||||||
this.stream = null
|
this.stream = null
|
||||||
// Used for share sessions
|
// Used for share sessions
|
||||||
this.shareSessionId = null
|
this.shareSessionId = null
|
||||||
@ -114,7 +112,6 @@ class PlaybackSession {
|
|||||||
startedAt: this.startedAt,
|
startedAt: this.startedAt,
|
||||||
updatedAt: this.updatedAt,
|
updatedAt: this.updatedAt,
|
||||||
audioTracks: this.audioTracks.map((at) => at.toJSON?.() || { ...at }),
|
audioTracks: this.audioTracks.map((at) => at.toJSON?.() || { ...at }),
|
||||||
videoTrack: this.videoTrack?.toJSON() || null,
|
|
||||||
libraryItem: libraryItem?.toJSONExpanded() || null
|
libraryItem: libraryItem?.toJSONExpanded() || null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -157,8 +154,6 @@ class PlaybackSession {
|
|||||||
this.mediaMetadata = new BookMetadata(session.mediaMetadata)
|
this.mediaMetadata = new BookMetadata(session.mediaMetadata)
|
||||||
} else if (this.mediaType === 'podcast') {
|
} else if (this.mediaType === 'podcast') {
|
||||||
this.mediaMetadata = new PodcastMetadata(session.mediaMetadata)
|
this.mediaMetadata = new PodcastMetadata(session.mediaMetadata)
|
||||||
} else if (this.mediaType === 'video') {
|
|
||||||
this.mediaMetadata = new VideoMetadata(session.mediaMetadata)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.displayTitle = session.displayTitle || ''
|
this.displayTitle = session.displayTitle || ''
|
||||||
|
@ -43,14 +43,13 @@ class LibraryFile {
|
|||||||
if (globals.SupportedImageTypes.includes(this.metadata.format)) return 'image'
|
if (globals.SupportedImageTypes.includes(this.metadata.format)) return 'image'
|
||||||
if (globals.SupportedAudioTypes.includes(this.metadata.format)) return 'audio'
|
if (globals.SupportedAudioTypes.includes(this.metadata.format)) return 'audio'
|
||||||
if (globals.SupportedEbookTypes.includes(this.metadata.format)) return 'ebook'
|
if (globals.SupportedEbookTypes.includes(this.metadata.format)) return 'ebook'
|
||||||
if (globals.SupportedVideoTypes.includes(this.metadata.format)) return 'video'
|
|
||||||
if (globals.TextFileTypes.includes(this.metadata.format)) return 'text'
|
if (globals.TextFileTypes.includes(this.metadata.format)) return 'text'
|
||||||
if (globals.MetadataFileTypes.includes(this.metadata.format)) return 'metadata'
|
if (globals.MetadataFileTypes.includes(this.metadata.format)) return 'metadata'
|
||||||
return 'unknown'
|
return 'unknown'
|
||||||
}
|
}
|
||||||
|
|
||||||
get isMediaFile() {
|
get isMediaFile() {
|
||||||
return this.fileType === 'audio' || this.fileType === 'ebook' || this.fileType === 'video'
|
return this.fileType === 'audio' || this.fileType === 'ebook'
|
||||||
}
|
}
|
||||||
|
|
||||||
get isEBookFile() {
|
get isEBookFile() {
|
||||||
|
@ -1,109 +0,0 @@
|
|||||||
const { VideoMimeType } = require('../../utils/constants')
|
|
||||||
const FileMetadata = require('../metadata/FileMetadata')
|
|
||||||
|
|
||||||
class VideoFile {
|
|
||||||
constructor(data) {
|
|
||||||
this.index = null
|
|
||||||
this.ino = null
|
|
||||||
this.metadata = null
|
|
||||||
this.addedAt = null
|
|
||||||
this.updatedAt = null
|
|
||||||
|
|
||||||
this.format = null
|
|
||||||
this.duration = null
|
|
||||||
this.bitRate = null
|
|
||||||
this.language = null
|
|
||||||
this.codec = null
|
|
||||||
this.timeBase = null
|
|
||||||
this.frameRate = null
|
|
||||||
this.width = null
|
|
||||||
this.height = null
|
|
||||||
this.embeddedCoverArt = null
|
|
||||||
|
|
||||||
this.invalid = false
|
|
||||||
this.error = null
|
|
||||||
|
|
||||||
if (data) {
|
|
||||||
this.construct(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON() {
|
|
||||||
return {
|
|
||||||
index: this.index,
|
|
||||||
ino: this.ino,
|
|
||||||
metadata: this.metadata.toJSON(),
|
|
||||||
addedAt: this.addedAt,
|
|
||||||
updatedAt: this.updatedAt,
|
|
||||||
invalid: !!this.invalid,
|
|
||||||
error: this.error || null,
|
|
||||||
format: this.format,
|
|
||||||
duration: this.duration,
|
|
||||||
bitRate: this.bitRate,
|
|
||||||
language: this.language,
|
|
||||||
codec: this.codec,
|
|
||||||
timeBase: this.timeBase,
|
|
||||||
frameRate: this.frameRate,
|
|
||||||
width: this.width,
|
|
||||||
height: this.height,
|
|
||||||
embeddedCoverArt: this.embeddedCoverArt,
|
|
||||||
mimeType: this.mimeType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
construct(data) {
|
|
||||||
this.index = data.index
|
|
||||||
this.ino = data.ino
|
|
||||||
this.metadata = new FileMetadata(data.metadata || {})
|
|
||||||
this.addedAt = data.addedAt
|
|
||||||
this.updatedAt = data.updatedAt
|
|
||||||
this.invalid = !!data.invalid
|
|
||||||
this.error = data.error || null
|
|
||||||
|
|
||||||
this.format = data.format
|
|
||||||
this.duration = data.duration
|
|
||||||
this.bitRate = data.bitRate
|
|
||||||
this.language = data.language
|
|
||||||
this.codec = data.codec || null
|
|
||||||
this.timeBase = data.timeBase
|
|
||||||
this.frameRate = data.frameRate
|
|
||||||
this.width = data.width
|
|
||||||
this.height = data.height
|
|
||||||
this.embeddedCoverArt = data.embeddedCoverArt || null
|
|
||||||
}
|
|
||||||
|
|
||||||
get mimeType() {
|
|
||||||
var format = this.metadata.format.toUpperCase()
|
|
||||||
if (VideoMimeType[format]) {
|
|
||||||
return VideoMimeType[format]
|
|
||||||
} else {
|
|
||||||
return VideoMimeType.MP4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
clone() {
|
|
||||||
return new VideoFile(this.toJSON())
|
|
||||||
}
|
|
||||||
|
|
||||||
setDataFromProbe(libraryFile, probeData) {
|
|
||||||
this.ino = libraryFile.ino || null
|
|
||||||
|
|
||||||
this.metadata = libraryFile.metadata.clone()
|
|
||||||
this.addedAt = Date.now()
|
|
||||||
this.updatedAt = Date.now()
|
|
||||||
|
|
||||||
const videoStream = probeData.videoStream
|
|
||||||
|
|
||||||
this.format = probeData.format
|
|
||||||
this.duration = probeData.duration
|
|
||||||
this.bitRate = videoStream.bit_rate || probeData.bitRate || null
|
|
||||||
this.language = probeData.language
|
|
||||||
this.codec = videoStream.codec || null
|
|
||||||
this.timeBase = videoStream.time_base
|
|
||||||
this.frameRate = videoStream.frame_rate || null
|
|
||||||
this.width = videoStream.width || null
|
|
||||||
this.height = videoStream.height || null
|
|
||||||
this.embeddedCoverArt = probeData.embeddedCoverArt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
module.exports = VideoFile
|
|
@ -1,45 +0,0 @@
|
|||||||
const Path = require('path')
|
|
||||||
const { encodeUriPath } = require('../../utils/fileUtils')
|
|
||||||
|
|
||||||
class VideoTrack {
|
|
||||||
constructor() {
|
|
||||||
this.index = null
|
|
||||||
this.duration = null
|
|
||||||
this.title = null
|
|
||||||
this.contentUrl = null
|
|
||||||
this.mimeType = null
|
|
||||||
this.codec = null
|
|
||||||
this.metadata = null
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON() {
|
|
||||||
return {
|
|
||||||
index: this.index,
|
|
||||||
duration: this.duration,
|
|
||||||
title: this.title,
|
|
||||||
contentUrl: this.contentUrl,
|
|
||||||
mimeType: this.mimeType,
|
|
||||||
codec: this.codec,
|
|
||||||
metadata: this.metadata ? this.metadata.toJSON() : null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setData(itemId, videoFile) {
|
|
||||||
this.index = videoFile.index
|
|
||||||
this.duration = videoFile.duration
|
|
||||||
this.title = videoFile.metadata.filename || ''
|
|
||||||
this.contentUrl = Path.join(`${global.RouterBasePath}/api/items/${itemId}/file/${videoFile.ino}`, encodeUriPath(videoFile.metadata.relPath))
|
|
||||||
this.mimeType = videoFile.mimeType
|
|
||||||
this.codec = videoFile.codec
|
|
||||||
this.metadata = videoFile.metadata.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
setFromStream(title, duration, contentUrl) {
|
|
||||||
this.index = 1
|
|
||||||
this.duration = duration
|
|
||||||
this.title = title
|
|
||||||
this.contentUrl = contentUrl
|
|
||||||
this.mimeType = 'application/vnd.apple.mpegurl'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
module.exports = VideoTrack
|
|
@ -1,145 +0,0 @@
|
|||||||
const Logger = require('../../Logger')
|
|
||||||
const AudioFile = require('../files/AudioFile')
|
|
||||||
const AudioTrack = require('../files/AudioTrack')
|
|
||||||
const MusicMetadata = require('../metadata/MusicMetadata')
|
|
||||||
const { areEquivalent, copyValue } = require('../../utils/index')
|
|
||||||
const { filePathToPOSIX } = require('../../utils/fileUtils')
|
|
||||||
|
|
||||||
class Music {
|
|
||||||
constructor(music) {
|
|
||||||
this.libraryItemId = null
|
|
||||||
this.metadata = null
|
|
||||||
this.coverPath = null
|
|
||||||
this.tags = []
|
|
||||||
this.audioFile = null
|
|
||||||
|
|
||||||
if (music) {
|
|
||||||
this.construct(music)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
construct(music) {
|
|
||||||
this.libraryItemId = music.libraryItemId
|
|
||||||
this.metadata = new MusicMetadata(music.metadata)
|
|
||||||
this.coverPath = music.coverPath
|
|
||||||
this.tags = [...music.tags]
|
|
||||||
this.audioFile = new AudioFile(music.audioFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON() {
|
|
||||||
return {
|
|
||||||
libraryItemId: this.libraryItemId,
|
|
||||||
metadata: this.metadata.toJSON(),
|
|
||||||
coverPath: this.coverPath,
|
|
||||||
tags: [...this.tags],
|
|
||||||
audioFile: this.audioFile.toJSON(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSONMinified() {
|
|
||||||
return {
|
|
||||||
metadata: this.metadata.toJSONMinified(),
|
|
||||||
coverPath: this.coverPath,
|
|
||||||
tags: [...this.tags],
|
|
||||||
audioFile: this.audioFile.toJSON(),
|
|
||||||
duration: this.duration,
|
|
||||||
size: this.size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSONExpanded() {
|
|
||||||
return {
|
|
||||||
libraryItemId: this.libraryItemId,
|
|
||||||
metadata: this.metadata.toJSONExpanded(),
|
|
||||||
coverPath: this.coverPath,
|
|
||||||
tags: [...this.tags],
|
|
||||||
audioFile: this.audioFile.toJSON(),
|
|
||||||
duration: this.duration,
|
|
||||||
size: this.size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get size() {
|
|
||||||
return this.audioFile.metadata.size
|
|
||||||
}
|
|
||||||
get hasMediaEntities() {
|
|
||||||
return !!this.audioFile
|
|
||||||
}
|
|
||||||
get duration() {
|
|
||||||
return this.audioFile.duration || 0
|
|
||||||
}
|
|
||||||
get audioTrack() {
|
|
||||||
const audioTrack = new AudioTrack()
|
|
||||||
audioTrack.setData(this.libraryItemId, this.audioFile, 0)
|
|
||||||
return audioTrack
|
|
||||||
}
|
|
||||||
get numTracks() {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
update(payload) {
|
|
||||||
const json = this.toJSON()
|
|
||||||
delete json.episodes // do not update media entities here
|
|
||||||
let hasUpdates = false
|
|
||||||
for (const key in json) {
|
|
||||||
if (payload[key] !== undefined) {
|
|
||||||
if (key === 'metadata') {
|
|
||||||
if (this.metadata.update(payload.metadata)) {
|
|
||||||
hasUpdates = true
|
|
||||||
}
|
|
||||||
} else if (!areEquivalent(payload[key], json[key])) {
|
|
||||||
this[key] = copyValue(payload[key])
|
|
||||||
Logger.debug('[Podcast] Key updated', key, this[key])
|
|
||||||
hasUpdates = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hasUpdates
|
|
||||||
}
|
|
||||||
|
|
||||||
updateCover(coverPath) {
|
|
||||||
coverPath = filePathToPOSIX(coverPath)
|
|
||||||
if (this.coverPath === coverPath) return false
|
|
||||||
this.coverPath = coverPath
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
removeFileWithInode(inode) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
findFileWithInode(inode) {
|
|
||||||
return (this.audioFile && this.audioFile.ino === inode) ? this.audioFile : null
|
|
||||||
}
|
|
||||||
|
|
||||||
setData(mediaData) {
|
|
||||||
this.metadata = new MusicMetadata()
|
|
||||||
if (mediaData.metadata) {
|
|
||||||
this.metadata.setData(mediaData.metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.coverPath = mediaData.coverPath || null
|
|
||||||
}
|
|
||||||
|
|
||||||
setAudioFile(audioFile) {
|
|
||||||
this.audioFile = audioFile
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only checks container format
|
|
||||||
checkCanDirectPlay(payload) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
getDirectPlayTracklist() {
|
|
||||||
return [this.audioTrack]
|
|
||||||
}
|
|
||||||
|
|
||||||
getPlaybackTitle() {
|
|
||||||
return this.metadata.title
|
|
||||||
}
|
|
||||||
|
|
||||||
getPlaybackAuthor() {
|
|
||||||
return this.metadata.artist
|
|
||||||
}
|
|
||||||
}
|
|
||||||
module.exports = Music
|
|
@ -1,137 +0,0 @@
|
|||||||
const Logger = require('../../Logger')
|
|
||||||
const VideoFile = require('../files/VideoFile')
|
|
||||||
const VideoTrack = require('../files/VideoTrack')
|
|
||||||
const VideoMetadata = require('../metadata/VideoMetadata')
|
|
||||||
const { areEquivalent, copyValue } = require('../../utils/index')
|
|
||||||
const { filePathToPOSIX } = require('../../utils/fileUtils')
|
|
||||||
|
|
||||||
class Video {
|
|
||||||
constructor(video) {
|
|
||||||
this.libraryItemId = null
|
|
||||||
this.metadata = null
|
|
||||||
this.coverPath = null
|
|
||||||
this.tags = []
|
|
||||||
this.episodes = []
|
|
||||||
|
|
||||||
this.autoDownloadEpisodes = false
|
|
||||||
this.lastEpisodeCheck = 0
|
|
||||||
|
|
||||||
this.lastCoverSearch = null
|
|
||||||
this.lastCoverSearchQuery = null
|
|
||||||
|
|
||||||
if (video) {
|
|
||||||
this.construct(video)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
construct(video) {
|
|
||||||
this.libraryItemId = video.libraryItemId
|
|
||||||
this.metadata = new VideoMetadata(video.metadata)
|
|
||||||
this.coverPath = video.coverPath
|
|
||||||
this.tags = [...video.tags]
|
|
||||||
this.videoFile = new VideoFile(video.videoFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON() {
|
|
||||||
return {
|
|
||||||
libraryItemId: this.libraryItemId,
|
|
||||||
metadata: this.metadata.toJSONExpanded(),
|
|
||||||
coverPath: this.coverPath,
|
|
||||||
tags: [...this.tags],
|
|
||||||
videoFile: this.videoFile.toJSON()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSONMinified() {
|
|
||||||
return {
|
|
||||||
metadata: this.metadata.toJSONMinified(),
|
|
||||||
coverPath: this.coverPath,
|
|
||||||
tags: [...this.tags],
|
|
||||||
videoFile: this.videoFile.toJSON(),
|
|
||||||
size: this.size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSONExpanded() {
|
|
||||||
return {
|
|
||||||
libraryItemId: this.libraryItemId,
|
|
||||||
metadata: this.metadata.toJSONExpanded(),
|
|
||||||
coverPath: this.coverPath,
|
|
||||||
tags: [...this.tags],
|
|
||||||
videoFile: this.videoFile.toJSON(),
|
|
||||||
size: this.size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get size() {
|
|
||||||
return this.videoFile.metadata.size
|
|
||||||
}
|
|
||||||
get hasMediaEntities() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
get duration() {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
update(payload) {
|
|
||||||
var json = this.toJSON()
|
|
||||||
var hasUpdates = false
|
|
||||||
for (const key in json) {
|
|
||||||
if (payload[key] !== undefined) {
|
|
||||||
if (key === 'metadata') {
|
|
||||||
if (this.metadata.update(payload.metadata)) {
|
|
||||||
hasUpdates = true
|
|
||||||
}
|
|
||||||
} else if (!areEquivalent(payload[key], json[key])) {
|
|
||||||
this[key] = copyValue(payload[key])
|
|
||||||
Logger.debug('[Video] Key updated', key, this[key])
|
|
||||||
hasUpdates = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hasUpdates
|
|
||||||
}
|
|
||||||
|
|
||||||
updateCover(coverPath) {
|
|
||||||
coverPath = filePathToPOSIX(coverPath)
|
|
||||||
if (this.coverPath === coverPath) return false
|
|
||||||
this.coverPath = coverPath
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
removeFileWithInode(inode) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
findFileWithInode(inode) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
setVideoFile(videoFile) {
|
|
||||||
this.videoFile = videoFile
|
|
||||||
}
|
|
||||||
|
|
||||||
setData(mediaMetadata) {
|
|
||||||
this.metadata = new VideoMetadata()
|
|
||||||
if (mediaMetadata.metadata) {
|
|
||||||
this.metadata.setData(mediaMetadata.metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.coverPath = mediaMetadata.coverPath || null
|
|
||||||
}
|
|
||||||
|
|
||||||
getPlaybackTitle() {
|
|
||||||
return this.metadata.title
|
|
||||||
}
|
|
||||||
|
|
||||||
getPlaybackAuthor() {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
getVideoTrack() {
|
|
||||||
var track = new VideoTrack()
|
|
||||||
track.setData(this.libraryItemId, this.videoFile)
|
|
||||||
return track
|
|
||||||
}
|
|
||||||
}
|
|
||||||
module.exports = Video
|
|
@ -1,307 +0,0 @@
|
|||||||
const Logger = require('../../Logger')
|
|
||||||
const { areEquivalent, copyValue, getTitleIgnorePrefix, getTitlePrefixAtEnd } = require('../../utils/index')
|
|
||||||
|
|
||||||
class MusicMetadata {
|
|
||||||
constructor(metadata) {
|
|
||||||
this.title = null
|
|
||||||
this.artists = [] // Array of strings
|
|
||||||
this.album = null
|
|
||||||
this.albumArtist = null
|
|
||||||
this.genres = [] // Array of strings
|
|
||||||
this.composer = null
|
|
||||||
this.originalYear = null
|
|
||||||
this.releaseDate = null
|
|
||||||
this.releaseCountry = null
|
|
||||||
this.releaseType = null
|
|
||||||
this.releaseStatus = null
|
|
||||||
this.recordLabel = null
|
|
||||||
this.language = null
|
|
||||||
this.explicit = false
|
|
||||||
|
|
||||||
this.discNumber = null
|
|
||||||
this.discTotal = null
|
|
||||||
this.trackNumber = null
|
|
||||||
this.trackTotal = null
|
|
||||||
|
|
||||||
this.isrc = null
|
|
||||||
this.musicBrainzTrackId = null
|
|
||||||
this.musicBrainzAlbumId = null
|
|
||||||
this.musicBrainzAlbumArtistId = null
|
|
||||||
this.musicBrainzArtistId = null
|
|
||||||
|
|
||||||
if (metadata) {
|
|
||||||
this.construct(metadata)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
construct(metadata) {
|
|
||||||
this.title = metadata.title
|
|
||||||
this.artists = metadata.artists ? [...metadata.artists] : []
|
|
||||||
this.album = metadata.album
|
|
||||||
this.albumArtist = metadata.albumArtist
|
|
||||||
this.genres = metadata.genres ? [...metadata.genres] : []
|
|
||||||
this.composer = metadata.composer || null
|
|
||||||
this.originalYear = metadata.originalYear || null
|
|
||||||
this.releaseDate = metadata.releaseDate || null
|
|
||||||
this.releaseCountry = metadata.releaseCountry || null
|
|
||||||
this.releaseType = metadata.releaseType || null
|
|
||||||
this.releaseStatus = metadata.releaseStatus || null
|
|
||||||
this.recordLabel = metadata.recordLabel || null
|
|
||||||
this.language = metadata.language || null
|
|
||||||
this.explicit = !!metadata.explicit
|
|
||||||
this.discNumber = metadata.discNumber || null
|
|
||||||
this.discTotal = metadata.discTotal || null
|
|
||||||
this.trackNumber = metadata.trackNumber || null
|
|
||||||
this.trackTotal = metadata.trackTotal || null
|
|
||||||
this.isrc = metadata.isrc || null
|
|
||||||
this.musicBrainzTrackId = metadata.musicBrainzTrackId || null
|
|
||||||
this.musicBrainzAlbumId = metadata.musicBrainzAlbumId || null
|
|
||||||
this.musicBrainzAlbumArtistId = metadata.musicBrainzAlbumArtistId || null
|
|
||||||
this.musicBrainzArtistId = metadata.musicBrainzArtistId || null
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON() {
|
|
||||||
return {
|
|
||||||
title: this.title,
|
|
||||||
artists: [...this.artists],
|
|
||||||
album: this.album,
|
|
||||||
albumArtist: this.albumArtist,
|
|
||||||
genres: [...this.genres],
|
|
||||||
composer: this.composer,
|
|
||||||
originalYear: this.originalYear,
|
|
||||||
releaseDate: this.releaseDate,
|
|
||||||
releaseCountry: this.releaseCountry,
|
|
||||||
releaseType: this.releaseType,
|
|
||||||
releaseStatus: this.releaseStatus,
|
|
||||||
recordLabel: this.recordLabel,
|
|
||||||
language: this.language,
|
|
||||||
explicit: this.explicit,
|
|
||||||
discNumber: this.discNumber,
|
|
||||||
discTotal: this.discTotal,
|
|
||||||
trackNumber: this.trackNumber,
|
|
||||||
trackTotal: this.trackTotal,
|
|
||||||
isrc: this.isrc,
|
|
||||||
musicBrainzTrackId: this.musicBrainzTrackId,
|
|
||||||
musicBrainzAlbumId: this.musicBrainzAlbumId,
|
|
||||||
musicBrainzAlbumArtistId: this.musicBrainzAlbumArtistId,
|
|
||||||
musicBrainzArtistId: this.musicBrainzArtistId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSONMinified() {
|
|
||||||
return {
|
|
||||||
title: this.title,
|
|
||||||
titleIgnorePrefix: this.titlePrefixAtEnd,
|
|
||||||
artists: [...this.artists],
|
|
||||||
album: this.album,
|
|
||||||
albumArtist: this.albumArtist,
|
|
||||||
genres: [...this.genres],
|
|
||||||
composer: this.composer,
|
|
||||||
originalYear: this.originalYear,
|
|
||||||
releaseDate: this.releaseDate,
|
|
||||||
releaseCountry: this.releaseCountry,
|
|
||||||
releaseType: this.releaseType,
|
|
||||||
releaseStatus: this.releaseStatus,
|
|
||||||
recordLabel: this.recordLabel,
|
|
||||||
language: this.language,
|
|
||||||
explicit: this.explicit,
|
|
||||||
discNumber: this.discNumber,
|
|
||||||
discTotal: this.discTotal,
|
|
||||||
trackNumber: this.trackNumber,
|
|
||||||
trackTotal: this.trackTotal,
|
|
||||||
isrc: this.isrc,
|
|
||||||
musicBrainzTrackId: this.musicBrainzTrackId,
|
|
||||||
musicBrainzAlbumId: this.musicBrainzAlbumId,
|
|
||||||
musicBrainzAlbumArtistId: this.musicBrainzAlbumArtistId,
|
|
||||||
musicBrainzArtistId: this.musicBrainzArtistId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSONExpanded() {
|
|
||||||
return this.toJSONMinified()
|
|
||||||
}
|
|
||||||
|
|
||||||
clone() {
|
|
||||||
return new MusicMetadata(this.toJSON())
|
|
||||||
}
|
|
||||||
|
|
||||||
get titleIgnorePrefix() {
|
|
||||||
return getTitleIgnorePrefix(this.title)
|
|
||||||
}
|
|
||||||
|
|
||||||
get titlePrefixAtEnd() {
|
|
||||||
return getTitlePrefixAtEnd(this.title)
|
|
||||||
}
|
|
||||||
|
|
||||||
setData(mediaMetadata = {}) {
|
|
||||||
this.title = mediaMetadata.title || null
|
|
||||||
this.artist = mediaMetadata.artist || null
|
|
||||||
this.album = mediaMetadata.album || null
|
|
||||||
}
|
|
||||||
|
|
||||||
update(payload) {
|
|
||||||
const json = this.toJSON()
|
|
||||||
let hasUpdates = false
|
|
||||||
for (const key in json) {
|
|
||||||
if (payload[key] !== undefined) {
|
|
||||||
if (!areEquivalent(payload[key], json[key])) {
|
|
||||||
this[key] = copyValue(payload[key])
|
|
||||||
Logger.debug('[MusicMetadata] Key updated', key, this[key])
|
|
||||||
hasUpdates = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hasUpdates
|
|
||||||
}
|
|
||||||
|
|
||||||
parseArtistsTag(artistsTag) {
|
|
||||||
if (!artistsTag || !artistsTag.length) return []
|
|
||||||
const separators = ['/', '//', ';']
|
|
||||||
for (let i = 0; i < separators.length; i++) {
|
|
||||||
if (artistsTag.includes(separators[i])) {
|
|
||||||
return artistsTag.split(separators[i]).map(artist => artist.trim()).filter(a => !!a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [artistsTag]
|
|
||||||
}
|
|
||||||
|
|
||||||
parseGenresTag(genreTag) {
|
|
||||||
if (!genreTag || !genreTag.length) return []
|
|
||||||
const separators = ['/', '//', ';']
|
|
||||||
for (let i = 0; i < separators.length; i++) {
|
|
||||||
if (genreTag.includes(separators[i])) {
|
|
||||||
return genreTag.split(separators[i]).map(genre => genre.trim()).filter(g => !!g)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [genreTag]
|
|
||||||
}
|
|
||||||
|
|
||||||
setDataFromAudioMetaTags(audioFileMetaTags, overrideExistingDetails = false) {
|
|
||||||
const MetadataMapArray = [
|
|
||||||
{
|
|
||||||
tag: 'tagTitle',
|
|
||||||
key: 'title',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagArtist',
|
|
||||||
key: 'artists'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagAlbumArtist',
|
|
||||||
key: 'albumArtist'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagAlbum',
|
|
||||||
key: 'album',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagPublisher',
|
|
||||||
key: 'recordLabel'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagComposer',
|
|
||||||
key: 'composer'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagDate',
|
|
||||||
key: 'releaseDate'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagReleaseCountry',
|
|
||||||
key: 'releaseCountry'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagReleaseType',
|
|
||||||
key: 'releaseType'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagReleaseStatus',
|
|
||||||
key: 'releaseStatus'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagOriginalYear',
|
|
||||||
key: 'originalYear'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagGenre',
|
|
||||||
key: 'genres'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagLanguage',
|
|
||||||
key: 'language'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagLanguage',
|
|
||||||
key: 'language'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagISRC',
|
|
||||||
key: 'isrc'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagMusicBrainzTrackId',
|
|
||||||
key: 'musicBrainzTrackId'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagMusicBrainzAlbumId',
|
|
||||||
key: 'musicBrainzAlbumId'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagMusicBrainzAlbumArtistId',
|
|
||||||
key: 'musicBrainzAlbumArtistId'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagMusicBrainzArtistId',
|
|
||||||
key: 'musicBrainzArtistId'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'trackNumber',
|
|
||||||
key: 'trackNumber'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'trackTotal',
|
|
||||||
key: 'trackTotal'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'discNumber',
|
|
||||||
key: 'discNumber'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'discTotal',
|
|
||||||
key: 'discTotal'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const updatePayload = {}
|
|
||||||
|
|
||||||
// Metadata is only mapped to the music track if it is empty
|
|
||||||
MetadataMapArray.forEach((mapping) => {
|
|
||||||
let value = audioFileMetaTags[mapping.tag]
|
|
||||||
|
|
||||||
// let tagToUse = mapping.tag
|
|
||||||
if (!value && mapping.altTag) {
|
|
||||||
value = audioFileMetaTags[mapping.altTag]
|
|
||||||
// tagToUse = mapping.altTag
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value && (typeof value === 'string' || typeof value === 'number')) {
|
|
||||||
value = value.toString().trim() // Trim whitespace
|
|
||||||
|
|
||||||
if (mapping.key === 'artists' && (!this.artists.length || overrideExistingDetails)) {
|
|
||||||
updatePayload.artists = this.parseArtistsTag(value)
|
|
||||||
} else if (mapping.key === 'genres' && (!this.genres.length || overrideExistingDetails)) {
|
|
||||||
updatePayload.genres = this.parseGenresTag(value)
|
|
||||||
} else if (!this[mapping.key] || overrideExistingDetails) {
|
|
||||||
updatePayload[mapping.key] = value
|
|
||||||
// Logger.debug(`[Book] Mapping metadata to key ${tagToUse} => ${mapping.key}: ${updatePayload[mapping.key]}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (Object.keys(updatePayload).length) {
|
|
||||||
return this.update(updatePayload)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
module.exports = MusicMetadata
|
|
@ -1,80 +0,0 @@
|
|||||||
const Logger = require('../../Logger')
|
|
||||||
const { areEquivalent, copyValue, getTitleIgnorePrefix, getTitlePrefixAtEnd } = require('../../utils/index')
|
|
||||||
|
|
||||||
class VideoMetadata {
|
|
||||||
constructor(metadata) {
|
|
||||||
this.title = null
|
|
||||||
this.description = null
|
|
||||||
this.explicit = false
|
|
||||||
this.language = null
|
|
||||||
|
|
||||||
if (metadata) {
|
|
||||||
this.construct(metadata)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
construct(metadata) {
|
|
||||||
this.title = metadata.title
|
|
||||||
this.description = metadata.description
|
|
||||||
this.explicit = metadata.explicit
|
|
||||||
this.language = metadata.language || null
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON() {
|
|
||||||
return {
|
|
||||||
title: this.title,
|
|
||||||
description: this.description,
|
|
||||||
explicit: this.explicit,
|
|
||||||
language: this.language
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSONMinified() {
|
|
||||||
return {
|
|
||||||
title: this.title,
|
|
||||||
titleIgnorePrefix: this.titlePrefixAtEnd,
|
|
||||||
description: this.description,
|
|
||||||
explicit: this.explicit,
|
|
||||||
language: this.language
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSONExpanded() {
|
|
||||||
return this.toJSONMinified()
|
|
||||||
}
|
|
||||||
|
|
||||||
clone() {
|
|
||||||
return new VideoMetadata(this.toJSON())
|
|
||||||
}
|
|
||||||
|
|
||||||
get titleIgnorePrefix() {
|
|
||||||
return getTitleIgnorePrefix(this.title)
|
|
||||||
}
|
|
||||||
|
|
||||||
get titlePrefixAtEnd() {
|
|
||||||
return getTitlePrefixAtEnd(this.title)
|
|
||||||
}
|
|
||||||
|
|
||||||
setData(mediaMetadata = {}) {
|
|
||||||
this.title = mediaMetadata.title || null
|
|
||||||
this.description = mediaMetadata.description || null
|
|
||||||
this.explicit = !!mediaMetadata.explicit
|
|
||||||
this.language = mediaMetadata.language || null
|
|
||||||
}
|
|
||||||
|
|
||||||
update(payload) {
|
|
||||||
var json = this.toJSON()
|
|
||||||
var hasUpdates = false
|
|
||||||
for (const key in json) {
|
|
||||||
if (payload[key] !== undefined) {
|
|
||||||
if (!areEquivalent(payload[key], json[key])) {
|
|
||||||
this[key] = copyValue(payload[key])
|
|
||||||
Logger.debug('[VideoMetadata] Key updated', key, this[key])
|
|
||||||
hasUpdates = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hasUpdates
|
|
||||||
}
|
|
||||||
}
|
|
||||||
module.exports = VideoMetadata
|
|
@ -51,7 +51,3 @@ module.exports.AudioMimeType = {
|
|||||||
AWB: 'audio/amr-wb',
|
AWB: 'audio/amr-wb',
|
||||||
CAF: 'audio/x-caf'
|
CAF: 'audio/x-caf'
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.VideoMimeType = {
|
|
||||||
MP4: 'video/mp4'
|
|
||||||
}
|
|
@ -2,7 +2,6 @@ const globals = {
|
|||||||
SupportedImageTypes: ['png', 'jpg', 'jpeg', 'webp'],
|
SupportedImageTypes: ['png', 'jpg', 'jpeg', 'webp'],
|
||||||
SupportedAudioTypes: ['m4b', 'mp3', 'm4a', 'flac', 'opus', 'ogg', 'oga', 'mp4', 'aac', 'wma', 'aiff', 'wav', 'webm', 'webma', 'mka', 'awb', 'caf'],
|
SupportedAudioTypes: ['m4b', 'mp3', 'm4a', 'flac', 'opus', 'ogg', 'oga', 'mp4', 'aac', 'wma', 'aiff', 'wav', 'webm', 'webma', 'mka', 'awb', 'caf'],
|
||||||
SupportedEbookTypes: ['epub', 'pdf', 'mobi', 'azw3', 'cbr', 'cbz'],
|
SupportedEbookTypes: ['epub', 'pdf', 'mobi', 'azw3', 'cbr', 'cbz'],
|
||||||
SupportedVideoTypes: ['mp4'],
|
|
||||||
TextFileTypes: ['txt', 'nfo'],
|
TextFileTypes: ['txt', 'nfo'],
|
||||||
MetadataFileTypes: ['opf', 'abs', 'xml', 'json']
|
MetadataFileTypes: ['opf', 'abs', 'xml', 'json']
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,7 @@ const parseNameString = require('./parsers/parseNameString')
|
|||||||
function isMediaFile(mediaType, ext, audiobooksOnly = false) {
|
function isMediaFile(mediaType, ext, audiobooksOnly = false) {
|
||||||
if (!ext) return false
|
if (!ext) return false
|
||||||
const extclean = ext.slice(1).toLowerCase()
|
const extclean = ext.slice(1).toLowerCase()
|
||||||
if (mediaType === 'podcast' || mediaType === 'music') return globals.SupportedAudioTypes.includes(extclean)
|
if (mediaType === 'podcast') return globals.SupportedAudioTypes.includes(extclean)
|
||||||
else if (mediaType === 'video') return globals.SupportedVideoTypes.includes(extclean)
|
|
||||||
else if (audiobooksOnly) return globals.SupportedAudioTypes.includes(extclean)
|
else if (audiobooksOnly) return globals.SupportedAudioTypes.includes(extclean)
|
||||||
return globals.SupportedAudioTypes.includes(extclean) || globals.SupportedEbookTypes.includes(extclean)
|
return globals.SupportedAudioTypes.includes(extclean) || globals.SupportedEbookTypes.includes(extclean)
|
||||||
}
|
}
|
||||||
@ -42,18 +41,22 @@ module.exports.checkFilepathIsAudioFile = checkFilepathIsAudioFile
|
|||||||
function groupFilesIntoLibraryItemPaths(mediaType, paths) {
|
function groupFilesIntoLibraryItemPaths(mediaType, paths) {
|
||||||
// Step 1: Clean path, Remove leading "/", Filter out non-media files in root dir
|
// Step 1: Clean path, Remove leading "/", Filter out non-media files in root dir
|
||||||
var nonMediaFilePaths = []
|
var nonMediaFilePaths = []
|
||||||
var pathsFiltered = paths.map(path => {
|
var pathsFiltered = paths
|
||||||
|
.map((path) => {
|
||||||
return path.startsWith('/') ? path.slice(1) : path
|
return path.startsWith('/') ? path.slice(1) : path
|
||||||
}).filter(path => {
|
})
|
||||||
|
.filter((path) => {
|
||||||
let parsedPath = Path.parse(path)
|
let parsedPath = Path.parse(path)
|
||||||
// Is not in root dir OR is a book media file
|
// Is not in root dir OR is a book media file
|
||||||
if (parsedPath.dir) {
|
if (parsedPath.dir) {
|
||||||
if (!isMediaFile(mediaType, parsedPath.ext, false)) { // Seperate out non-media files
|
if (!isMediaFile(mediaType, parsedPath.ext, false)) {
|
||||||
|
// Seperate out non-media files
|
||||||
nonMediaFilePaths.push(path)
|
nonMediaFilePaths.push(path)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
} else if (mediaType === 'book' && isMediaFile(mediaType, parsedPath.ext, false)) { // (book media type supports single file audiobooks/ebooks in root dir)
|
} else if (mediaType === 'book' && isMediaFile(mediaType, parsedPath.ext, false)) {
|
||||||
|
// (book media type supports single file audiobooks/ebooks in root dir)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@ -69,7 +72,9 @@ function groupFilesIntoLibraryItemPaths(mediaType, paths) {
|
|||||||
// Step 3: Group files in dirs
|
// Step 3: Group files in dirs
|
||||||
var itemGroup = {}
|
var itemGroup = {}
|
||||||
pathsFiltered.forEach((path) => {
|
pathsFiltered.forEach((path) => {
|
||||||
var dirparts = Path.dirname(path).split('/').filter(p => !!p && p !== '.') // dirname returns . if no directory
|
var dirparts = Path.dirname(path)
|
||||||
|
.split('/')
|
||||||
|
.filter((p) => !!p && p !== '.') // dirname returns . if no directory
|
||||||
var numparts = dirparts.length
|
var numparts = dirparts.length
|
||||||
var _path = ''
|
var _path = ''
|
||||||
|
|
||||||
@ -82,14 +87,17 @@ function groupFilesIntoLibraryItemPaths(mediaType, paths) {
|
|||||||
var dirpart = dirparts.shift()
|
var dirpart = dirparts.shift()
|
||||||
_path = Path.posix.join(_path, dirpart)
|
_path = Path.posix.join(_path, dirpart)
|
||||||
|
|
||||||
if (itemGroup[_path]) { // Directory already has files, add file
|
if (itemGroup[_path]) {
|
||||||
|
// Directory already has files, add file
|
||||||
var relpath = Path.posix.join(dirparts.join('/'), Path.basename(path))
|
var relpath = Path.posix.join(dirparts.join('/'), Path.basename(path))
|
||||||
itemGroup[_path].push(relpath)
|
itemGroup[_path].push(relpath)
|
||||||
return
|
return
|
||||||
} else if (!dirparts.length) { // This is the last directory, create group
|
} else if (!dirparts.length) {
|
||||||
|
// This is the last directory, create group
|
||||||
itemGroup[_path] = [Path.basename(path)]
|
itemGroup[_path] = [Path.basename(path)]
|
||||||
return
|
return
|
||||||
} else if (dirparts.length === 1 && /^cd\d{1,3}$/i.test(dirparts[0])) { // Next directory is the last and is a CD dir, create group
|
} else if (dirparts.length === 1 && /^cd\d{1,3}$/i.test(dirparts[0])) {
|
||||||
|
// Next directory is the last and is a CD dir, create group
|
||||||
itemGroup[_path] = [Path.posix.join(dirparts[0], Path.basename(path))]
|
itemGroup[_path] = [Path.posix.join(dirparts[0], Path.basename(path))]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -99,7 +107,6 @@ function groupFilesIntoLibraryItemPaths(mediaType, paths) {
|
|||||||
|
|
||||||
// Step 4: Add in non-media files if they fit into item group
|
// Step 4: Add in non-media files if they fit into item group
|
||||||
if (nonMediaFilePaths.length) {
|
if (nonMediaFilePaths.length) {
|
||||||
|
|
||||||
for (const nonMediaFilePath of nonMediaFilePaths) {
|
for (const nonMediaFilePath of nonMediaFilePaths) {
|
||||||
const pathDir = Path.dirname(nonMediaFilePath)
|
const pathDir = Path.dirname(nonMediaFilePath)
|
||||||
const filename = Path.basename(nonMediaFilePath)
|
const filename = Path.basename(nonMediaFilePath)
|
||||||
@ -111,7 +118,8 @@ function groupFilesIntoLibraryItemPaths(mediaType, paths) {
|
|||||||
for (let i = 0; i < numparts; i++) {
|
for (let i = 0; i < numparts; i++) {
|
||||||
const dirpart = dirparts.shift()
|
const dirpart = dirparts.shift()
|
||||||
_path = Path.posix.join(_path, dirpart)
|
_path = Path.posix.join(_path, dirpart)
|
||||||
if (itemGroup[_path]) { // Directory is a group
|
if (itemGroup[_path]) {
|
||||||
|
// Directory is a group
|
||||||
const relpath = Path.posix.join(dirparts.join('/'), filename)
|
const relpath = Path.posix.join(dirparts.join('/'), filename)
|
||||||
itemGroup[_path].push(relpath)
|
itemGroup[_path].push(relpath)
|
||||||
} else if (!dirparts.length) {
|
} else if (!dirparts.length) {
|
||||||
@ -132,25 +140,16 @@ module.exports.groupFilesIntoLibraryItemPaths = groupFilesIntoLibraryItemPaths
|
|||||||
* @returns {Record<string,string[]>} map of files grouped into potential libarary item dirs
|
* @returns {Record<string,string[]>} map of files grouped into potential libarary item dirs
|
||||||
*/
|
*/
|
||||||
function groupFileItemsIntoLibraryItemDirs(mediaType, fileItems, audiobooksOnly = false) {
|
function groupFileItemsIntoLibraryItemDirs(mediaType, fileItems, audiobooksOnly = false) {
|
||||||
// Handle music where every audio file is a library item
|
|
||||||
if (mediaType === 'music') {
|
|
||||||
const audioFileGroup = {}
|
|
||||||
fileItems.filter(i => isMediaFile(mediaType, i.extension, audiobooksOnly)).forEach((item) => {
|
|
||||||
audioFileGroup[item.path] = item.path
|
|
||||||
})
|
|
||||||
return audioFileGroup
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 1: Filter out non-book-media files in root dir (with depth of 0)
|
// Step 1: Filter out non-book-media files in root dir (with depth of 0)
|
||||||
const itemsFiltered = fileItems.filter(i => {
|
const itemsFiltered = fileItems.filter((i) => {
|
||||||
return i.deep > 0 || ((mediaType === 'book' || mediaType === 'video' || mediaType === 'music') && isMediaFile(mediaType, i.extension, audiobooksOnly))
|
return i.deep > 0 || (mediaType === 'book' && isMediaFile(mediaType, i.extension, audiobooksOnly))
|
||||||
})
|
})
|
||||||
|
|
||||||
// Step 2: Seperate media files and other files
|
// Step 2: Seperate media files and other files
|
||||||
// - Directories without a media file will not be included
|
// - Directories without a media file will not be included
|
||||||
const mediaFileItems = []
|
const mediaFileItems = []
|
||||||
const otherFileItems = []
|
const otherFileItems = []
|
||||||
itemsFiltered.forEach(item => {
|
itemsFiltered.forEach((item) => {
|
||||||
if (isMediaFile(mediaType, item.extension, audiobooksOnly)) mediaFileItems.push(item)
|
if (isMediaFile(mediaType, item.extension, audiobooksOnly)) mediaFileItems.push(item)
|
||||||
else otherFileItems.push(item)
|
else otherFileItems.push(item)
|
||||||
})
|
})
|
||||||
@ -158,7 +157,7 @@ function groupFileItemsIntoLibraryItemDirs(mediaType, fileItems, audiobooksOnly
|
|||||||
// Step 3: Group audio files in library items
|
// Step 3: Group audio files in library items
|
||||||
const libraryItemGroup = {}
|
const libraryItemGroup = {}
|
||||||
mediaFileItems.forEach((item) => {
|
mediaFileItems.forEach((item) => {
|
||||||
const dirparts = item.reldirpath.split('/').filter(p => !!p)
|
const dirparts = item.reldirpath.split('/').filter((p) => !!p)
|
||||||
const numparts = dirparts.length
|
const numparts = dirparts.length
|
||||||
let _path = ''
|
let _path = ''
|
||||||
|
|
||||||
@ -171,14 +170,17 @@ function groupFileItemsIntoLibraryItemDirs(mediaType, fileItems, audiobooksOnly
|
|||||||
const dirpart = dirparts.shift()
|
const dirpart = dirparts.shift()
|
||||||
_path = Path.posix.join(_path, dirpart)
|
_path = Path.posix.join(_path, dirpart)
|
||||||
|
|
||||||
if (libraryItemGroup[_path]) { // Directory already has files, add file
|
if (libraryItemGroup[_path]) {
|
||||||
|
// Directory already has files, add file
|
||||||
const relpath = Path.posix.join(dirparts.join('/'), item.name)
|
const relpath = Path.posix.join(dirparts.join('/'), item.name)
|
||||||
libraryItemGroup[_path].push(relpath)
|
libraryItemGroup[_path].push(relpath)
|
||||||
return
|
return
|
||||||
} else if (!dirparts.length) { // This is the last directory, create group
|
} else if (!dirparts.length) {
|
||||||
|
// This is the last directory, create group
|
||||||
libraryItemGroup[_path] = [item.name]
|
libraryItemGroup[_path] = [item.name]
|
||||||
return
|
return
|
||||||
} else if (dirparts.length === 1 && /^cd\d{1,3}$/i.test(dirparts[0])) { // Next directory is the last and is a CD dir, create group
|
} else if (dirparts.length === 1 && /^cd\d{1,3}$/i.test(dirparts[0])) {
|
||||||
|
// Next directory is the last and is a CD dir, create group
|
||||||
libraryItemGroup[_path] = [Path.posix.join(dirparts[0], item.name)]
|
libraryItemGroup[_path] = [Path.posix.join(dirparts[0], item.name)]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -196,7 +198,8 @@ function groupFileItemsIntoLibraryItemDirs(mediaType, fileItems, audiobooksOnly
|
|||||||
for (let i = 0; i < numparts; i++) {
|
for (let i = 0; i < numparts; i++) {
|
||||||
const dirpart = dirparts.shift()
|
const dirpart = dirparts.shift()
|
||||||
_path = Path.posix.join(_path, dirpart)
|
_path = Path.posix.join(_path, dirpart)
|
||||||
if (libraryItemGroup[_path]) { // Directory is audiobook group
|
if (libraryItemGroup[_path]) {
|
||||||
|
// Directory is audiobook group
|
||||||
const relpath = Path.posix.join(dirparts.join('/'), item.name)
|
const relpath = Path.posix.join(dirparts.join('/'), item.name)
|
||||||
libraryItemGroup[_path].push(relpath)
|
libraryItemGroup[_path].push(relpath)
|
||||||
return
|
return
|
||||||
@ -214,12 +217,14 @@ module.exports.groupFileItemsIntoLibraryItemDirs = groupFileItemsIntoLibraryItem
|
|||||||
* @returns {import('../objects/files/LibraryFile')}
|
* @returns {import('../objects/files/LibraryFile')}
|
||||||
*/
|
*/
|
||||||
function buildLibraryFile(libraryItemPath, files) {
|
function buildLibraryFile(libraryItemPath, files) {
|
||||||
return Promise.all(files.map(async (file) => {
|
return Promise.all(
|
||||||
|
files.map(async (file) => {
|
||||||
const filePath = Path.posix.join(libraryItemPath, file)
|
const filePath = Path.posix.join(libraryItemPath, file)
|
||||||
const newLibraryFile = new LibraryFile()
|
const newLibraryFile = new LibraryFile()
|
||||||
await newLibraryFile.setDataFromPath(filePath, file)
|
await newLibraryFile.setDataFromPath(filePath, file)
|
||||||
return newLibraryFile
|
return newLibraryFile
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
module.exports.buildLibraryFile = buildLibraryFile
|
module.exports.buildLibraryFile = buildLibraryFile
|
||||||
|
|
||||||
@ -234,8 +239,8 @@ function getBookDataFromDir(relPath, parseSubtitle = false) {
|
|||||||
const splitDir = relPath.split('/')
|
const splitDir = relPath.split('/')
|
||||||
|
|
||||||
var folder = splitDir.pop() // Audio files will always be in the directory named for the title
|
var folder = splitDir.pop() // Audio files will always be in the directory named for the title
|
||||||
series = (splitDir.length > 1) ? splitDir.pop() : null // If there are at least 2 more directories, next furthest will be the series
|
series = splitDir.length > 1 ? splitDir.pop() : null // If there are at least 2 more directories, next furthest will be the series
|
||||||
author = (splitDir.length > 0) ? splitDir.pop() : null // There could be many more directories, but only the top 3 are used for naming /author/series/title/
|
author = splitDir.length > 0 ? splitDir.pop() : null // There could be many more directories, but only the top 3 are used for naming /author/series/title/
|
||||||
|
|
||||||
// The may contain various other pieces of metadata, these functions extract it.
|
// The may contain various other pieces of metadata, these functions extract it.
|
||||||
var [folder, asin] = getASIN(folder)
|
var [folder, asin] = getASIN(folder)
|
||||||
@ -244,7 +249,6 @@ function getBookDataFromDir(relPath, parseSubtitle = false) {
|
|||||||
var [folder, publishedYear] = getPublishedYear(folder)
|
var [folder, publishedYear] = getPublishedYear(folder)
|
||||||
var [title, subtitle] = parseSubtitle ? getSubtitle(folder) : [folder, null]
|
var [title, subtitle] = parseSubtitle ? getSubtitle(folder) : [folder, null]
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title,
|
title,
|
||||||
subtitle,
|
subtitle,
|
||||||
@ -299,7 +303,9 @@ function getSequence(folder) {
|
|||||||
if (match && !(match.groups.suffix && !(match.groups.volumeLabel || match.groups.trailingDot))) {
|
if (match && !(match.groups.suffix && !(match.groups.volumeLabel || match.groups.trailingDot))) {
|
||||||
volumeNumber = isNaN(match.groups.sequence) ? match.groups.sequence : Number(match.groups.sequence).toString()
|
volumeNumber = isNaN(match.groups.sequence) ? match.groups.sequence : Number(match.groups.sequence).toString()
|
||||||
parts[i] = match.groups.suffix
|
parts[i] = match.groups.suffix
|
||||||
if (!parts[i]) { parts.splice(i, 1) }
|
if (!parts[i]) {
|
||||||
|
parts.splice(i, 1)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -386,7 +392,8 @@ function getDataFromMediaDir(libraryMediaType, folderPath, relPath) {
|
|||||||
|
|
||||||
if (libraryMediaType === 'podcast') {
|
if (libraryMediaType === 'podcast') {
|
||||||
mediaMetadata = getPodcastDataFromDir(relPath)
|
mediaMetadata = getPodcastDataFromDir(relPath)
|
||||||
} else { // book
|
} else {
|
||||||
|
// book
|
||||||
mediaMetadata = getBookDataFromDir(relPath, !!global.ServerSettings.scannerParseSubtitle)
|
mediaMetadata = getBookDataFromDir(relPath, !!global.ServerSettings.scannerParseSubtitle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user