mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Updated matching with latest changes, Added override toggle for quickmatch, added asin and isbn to quickmatch query, updated audible provider to use audnexus
This commit is contained in:
		
							parent
							
								
									f2e16017f6
								
							
						
					
					
						commit
						d15264832d
					
				@ -87,7 +87,7 @@
 | 
				
			|||||||
        <div v-if="selectedMatch.series" class="flex items-center py-2">
 | 
					        <div v-if="selectedMatch.series" class="flex items-center py-2">
 | 
				
			||||||
          <ui-checkbox v-model="selectedMatchUsage.series" />
 | 
					          <ui-checkbox v-model="selectedMatchUsage.series" />
 | 
				
			||||||
          <div class="flex-grow ml-4">
 | 
					          <div class="flex-grow ml-4">
 | 
				
			||||||
            <ui-text-input-with-label v-model="selectedMatch.series" :disabled="!selectedMatchUsage.series" label="Series" />
 | 
					            <ui-multi-select-query-input v-if="selectedMatch.series" ref="seriesSelect" v-model="seriesItems" text-key="displayName" label="Series" readonly />
 | 
				
			||||||
            <p v-if="mediaMetadata.seriesName" class="text-xs ml-1 text-white text-opacity-60">Currently: {{ mediaMetadata.seriesName || '' }}</p>
 | 
					            <p v-if="mediaMetadata.seriesName" class="text-xs ml-1 text-white text-opacity-60">Currently: {{ mediaMetadata.seriesName || '' }}</p>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
@ -95,6 +95,27 @@
 | 
				
			|||||||
          <ui-checkbox v-model="selectedMatchUsage.volumeNumber" />
 | 
					          <ui-checkbox v-model="selectedMatchUsage.volumeNumber" />
 | 
				
			||||||
          <ui-text-input-with-label v-model="selectedMatch.volumeNumber" :disabled="!selectedMatchUsage.volumeNumber" label="Volume Number" class="flex-grow ml-4" />
 | 
					          <ui-text-input-with-label v-model="selectedMatch.volumeNumber" :disabled="!selectedMatchUsage.volumeNumber" label="Volume Number" class="flex-grow ml-4" />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div v-if="selectedMatch.genres" class="flex items-center py-2">
 | 
				
			||||||
 | 
					          <ui-checkbox v-model="selectedMatchUsage.genres" />
 | 
				
			||||||
 | 
					          <div class="flex-grow ml-4">
 | 
				
			||||||
 | 
					            <ui-text-input-with-label v-model="selectedMatch.genres" :disabled="!selectedMatchUsage.genres" label="Genres" />
 | 
				
			||||||
 | 
					            <p v-if="mediaMetadata.genresList" class="text-xs ml-1 text-white text-opacity-60">Currently: {{ mediaMetadata.genresList || '' }}</p>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div v-if="selectedMatch.tags" class="flex items-center py-2">
 | 
				
			||||||
 | 
					          <ui-checkbox v-model="selectedMatchUsage.tags" />
 | 
				
			||||||
 | 
					          <div class="flex-grow ml-4">
 | 
				
			||||||
 | 
					            <ui-text-input-with-label v-model="selectedMatch.tags" :disabled="!selectedMatchUsage.tags" label="Tags" />
 | 
				
			||||||
 | 
					            <p v-if="mediaMetadata.tagsList" class="text-xs ml-1 text-white text-opacity-60">Currently: {{ mediaMetadata.tagsList || '' }}</p>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div v-if="selectedMatch.language" class="flex items-center py-2">
 | 
				
			||||||
 | 
					          <ui-checkbox v-model="selectedMatchUsage.language" />
 | 
				
			||||||
 | 
					          <div class="flex-grow ml-4">
 | 
				
			||||||
 | 
					            <ui-text-input-with-label v-model="selectedMatch.language" :disabled="!selectedMatchUsage.language" label="Language" />
 | 
				
			||||||
 | 
					            <p v-if="mediaMetadata.language" class="text-xs ml-1 text-white text-opacity-60">Currently: {{ mediaMetadata.language || '' }}</p>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
        <div v-if="selectedMatch.isbn" class="flex items-center py-2">
 | 
					        <div v-if="selectedMatch.isbn" class="flex items-center py-2">
 | 
				
			||||||
          <ui-checkbox v-model="selectedMatchUsage.isbn" />
 | 
					          <ui-checkbox v-model="selectedMatchUsage.isbn" />
 | 
				
			||||||
          <div class="flex-grow ml-4">
 | 
					          <div class="flex-grow ml-4">
 | 
				
			||||||
@ -177,6 +198,10 @@ export default {
 | 
				
			|||||||
        publishedYear: true,
 | 
					        publishedYear: true,
 | 
				
			||||||
        series: true,
 | 
					        series: true,
 | 
				
			||||||
        volumeNumber: true,
 | 
					        volumeNumber: true,
 | 
				
			||||||
 | 
					        genres: true, 
 | 
				
			||||||
 | 
					        tags: true, 
 | 
				
			||||||
 | 
					        language: true, 
 | 
				
			||||||
 | 
					        explicit: true,
 | 
				
			||||||
        asin: true,
 | 
					        asin: true,
 | 
				
			||||||
        isbn: true,
 | 
					        isbn: true,
 | 
				
			||||||
        // Podcast specific
 | 
					        // Podcast specific
 | 
				
			||||||
@ -204,6 +229,19 @@ export default {
 | 
				
			|||||||
        this.$emit('update:processing', val)
 | 
					        this.$emit('update:processing', val)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    seriesItems: {
 | 
				
			||||||
 | 
					      get() {
 | 
				
			||||||
 | 
					        return this.selectedMatch.series.map((se) => {
 | 
				
			||||||
 | 
					          return {
 | 
				
			||||||
 | 
					            displayName: se.volumeNumber ? `${se.series} #${se.volumeNumber}` : se.series,
 | 
				
			||||||
 | 
					            ...se
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      set(val) {
 | 
				
			||||||
 | 
					        this.selectedMatch.series = val
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    bookCoverAspectRatio() {
 | 
					    bookCoverAspectRatio() {
 | 
				
			||||||
      return this.$store.getters['getBookCoverAspectRatio']
 | 
					      return this.$store.getters['getBookCoverAspectRatio']
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@ -294,6 +332,10 @@ export default {
 | 
				
			|||||||
        publishedYear: true,
 | 
					        publishedYear: true,
 | 
				
			||||||
        series: true,
 | 
					        series: true,
 | 
				
			||||||
        volumeNumber: true,
 | 
					        volumeNumber: true,
 | 
				
			||||||
 | 
					        genres: true, 
 | 
				
			||||||
 | 
					        tags: true, 
 | 
				
			||||||
 | 
					        language: true, 
 | 
				
			||||||
 | 
					        explicit: true,
 | 
				
			||||||
        asin: true,
 | 
					        asin: true,
 | 
				
			||||||
        isbn: true,
 | 
					        isbn: true,
 | 
				
			||||||
        // Podcast specific
 | 
					        // Podcast specific
 | 
				
			||||||
@ -324,32 +366,42 @@ export default {
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    buildMatchUpdatePayload() {
 | 
					    buildMatchUpdatePayload() {
 | 
				
			||||||
      var updatePayload = {}
 | 
					      var updatePayload = {}
 | 
				
			||||||
 | 
					      updatePayload.metadata = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      var volumeNumber = this.selectedMatchUsage.volumeNumber ? this.selectedMatch.volumeNumber || null : null
 | 
					      var volumeNumber = this.selectedMatchUsage.volumeNumber ? this.selectedMatch.volumeNumber || null : null
 | 
				
			||||||
      for (const key in this.selectedMatchUsage) {
 | 
					      for (const key in this.selectedMatchUsage) {
 | 
				
			||||||
        if (this.selectedMatchUsage[key] && this.selectedMatch[key]) {
 | 
					        if (this.selectedMatchUsage[key] && this.selectedMatch[key]) {
 | 
				
			||||||
          if (key === 'series') {
 | 
					          if (key === 'series') {
 | 
				
			||||||
            var seriesItem = {
 | 
					            if(!Array.isArray(this.selectedMatch[key])) this.selectedMatch[key] = [{ series: this.selectedMatch[key], volumeNumber: volumeNumber }]
 | 
				
			||||||
 | 
					            var seriesPayload = []
 | 
				
			||||||
 | 
					            this.selectedMatch[key].forEach(seriesItem => seriesPayload.push({
 | 
				
			||||||
              id: `new-${Math.floor(Math.random() * 10000)}`,
 | 
					              id: `new-${Math.floor(Math.random() * 10000)}`,
 | 
				
			||||||
              name: this.selectedMatch[key],
 | 
					              name: seriesItem.series,
 | 
				
			||||||
              sequence: volumeNumber
 | 
					              sequence: seriesItem.volumeNumber
 | 
				
			||||||
            }
 | 
					            }))
 | 
				
			||||||
            updatePayload.series = [seriesItem]
 | 
					            updatePayload.metadata.series = seriesPayload
 | 
				
			||||||
          } else if (key === 'author' && !this.isPodcast) {
 | 
					          } else if (key === 'author' && !this.isPodcast) {
 | 
				
			||||||
            var authorItem = {
 | 
					            if(!Array.isArray(this.selectedMatch[key])) this.selectedMatch[key] = [this.selectedMatch[key]]
 | 
				
			||||||
 | 
					            var authorPayload = []
 | 
				
			||||||
 | 
					            this.selectedMatch[key].forEach(authorName => authorPayload.push({
 | 
				
			||||||
              id: `new-${Math.floor(Math.random() * 10000)}`,
 | 
					              id: `new-${Math.floor(Math.random() * 10000)}`,
 | 
				
			||||||
              name: this.selectedMatch[key]
 | 
					              name: authorName
 | 
				
			||||||
            }
 | 
					            }))
 | 
				
			||||||
            updatePayload.authors = [authorItem]
 | 
					            updatePayload.metadata.authors = authorPayload
 | 
				
			||||||
          } else if (key === 'narrator') {
 | 
					          } else if (key === 'narrator') {
 | 
				
			||||||
            updatePayload.narrators = [this.selectedMatch[key]]
 | 
					            updatePayload.metadata.narrators = [this.selectedMatch[key]]
 | 
				
			||||||
 | 
					          } else if (key === 'genres') {
 | 
				
			||||||
 | 
					            updatePayload.metadata.genres = this.selectedMatch[key].split(',')
 | 
				
			||||||
 | 
					          } else if (key === 'tags') {
 | 
				
			||||||
 | 
					            updatePayload.tags = this.selectedMatch[key].split(',')
 | 
				
			||||||
          } else if (key === 'itunesId') {
 | 
					          } else if (key === 'itunesId') {
 | 
				
			||||||
            updatePayload.itunesId = Number(this.selectedMatch[key])
 | 
					            updatePayload.metadata.itunesId = Number(this.selectedMatch[key])
 | 
				
			||||||
          } else if (key !== 'volumeNumber') {
 | 
					          } else {
 | 
				
			||||||
            updatePayload[key] = this.selectedMatch[key]
 | 
					            updatePayload.metadata[key] = this.selectedMatch[key]
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					      console.log(updatePayload)
 | 
				
			||||||
      return updatePayload
 | 
					      return updatePayload
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    async submitMatchUpdate() {
 | 
					    async submitMatchUpdate() {
 | 
				
			||||||
@ -361,7 +413,7 @@ export default {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      if (updatePayload.cover) {
 | 
					      if (updatePayload.cover) {
 | 
				
			||||||
        var coverPayload = {
 | 
					        var coverPayload = {
 | 
				
			||||||
          url: updatePayload.cover
 | 
					          url: updatePayload.metadata.cover
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        var success = await this.$axios.$post(`/api/items/${this.libraryItemId}/cover`, coverPayload).catch((error) => {
 | 
					        var success = await this.$axios.$post(`/api/items/${this.libraryItemId}/cover`, coverPayload).catch((error) => {
 | 
				
			||||||
          console.error('Failed to update', error)
 | 
					          console.error('Failed to update', error)
 | 
				
			||||||
@ -373,13 +425,11 @@ export default {
 | 
				
			|||||||
          this.$toast.error('Item Cover Failed to Update')
 | 
					          this.$toast.error('Item Cover Failed to Update')
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        console.log('Updated cover')
 | 
					        console.log('Updated cover')
 | 
				
			||||||
        delete updatePayload.cover
 | 
					        delete updatePayload.metadata.cover
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (Object.keys(updatePayload).length) {
 | 
					      if (Object.keys(updatePayload).length) {
 | 
				
			||||||
        var mediaUpdatePayload = {
 | 
					        var mediaUpdatePayload = updatePayload
 | 
				
			||||||
          metadata: updatePayload
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        var updateResult = await this.$axios.$patch(`/api/items/${this.libraryItemId}/media`, mediaUpdatePayload).catch((error) => {
 | 
					        var updateResult = await this.$axios.$patch(`/api/items/${this.libraryItemId}/media`, mediaUpdatePayload).catch((error) => {
 | 
				
			||||||
          console.error('Failed to update', error)
 | 
					          console.error('Failed to update', error)
 | 
				
			||||||
          return false
 | 
					          return false
 | 
				
			||||||
 | 
				
			|||||||
@ -113,6 +113,16 @@
 | 
				
			|||||||
        </ui-tooltip>
 | 
					        </ui-tooltip>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <div class="flex items-center py-2">
 | 
				
			||||||
 | 
					        <ui-toggle-switch v-model="newServerSettings.scannerPreferMatchedMetadata" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('scannerPreferMatchedMetadata', val)" />
 | 
				
			||||||
 | 
					        <ui-tooltip :text="tooltips.scannerPreferMatchedMetadata">
 | 
				
			||||||
 | 
					          <p class="pl-4 text-lg">
 | 
				
			||||||
 | 
					            Scanner prefer matched metadata
 | 
				
			||||||
 | 
					            <span class="material-icons icon-text">info_outlined</span>
 | 
				
			||||||
 | 
					          </p>
 | 
				
			||||||
 | 
					        </ui-tooltip>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <div class="flex items-center py-2">
 | 
					      <div class="flex items-center py-2">
 | 
				
			||||||
        <ui-toggle-switch v-model="newServerSettings.scannerDisableWatcher" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('scannerDisableWatcher', val)" />
 | 
					        <ui-toggle-switch v-model="newServerSettings.scannerDisableWatcher" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('scannerDisableWatcher', val)" />
 | 
				
			||||||
        <ui-tooltip :text="tooltips.scannerDisableWatcher">
 | 
					        <ui-tooltip :text="tooltips.scannerDisableWatcher">
 | 
				
			||||||
@ -226,6 +236,7 @@ export default {
 | 
				
			|||||||
        experimentalFeatures: 'Features in development that could use your feedback and help testing. Click to open github discussion.',
 | 
					        experimentalFeatures: 'Features in development that could use your feedback and help testing. Click to open github discussion.',
 | 
				
			||||||
        scannerDisableWatcher: 'Disables the automatic adding/updating of items when file changes are detected. *Requires server restart',
 | 
					        scannerDisableWatcher: 'Disables the automatic adding/updating of items when file changes are detected. *Requires server restart',
 | 
				
			||||||
        scannerPreferOpfMetadata: 'OPF file metadata will be used for book details over folder names',
 | 
					        scannerPreferOpfMetadata: 'OPF file metadata will be used for book details over folder names',
 | 
				
			||||||
 | 
					        scannerPreferMatchedMetadata: 'Matched data will overide book details when using Quick Match',
 | 
				
			||||||
        scannerPreferAudioMetadata: 'Audio file ID3 meta tags will be used for book details over folder names',
 | 
					        scannerPreferAudioMetadata: 'Audio file ID3 meta tags will be used for book details over folder names',
 | 
				
			||||||
        scannerParseSubtitle: 'Extract subtitles from audiobook folder names.<br>Subtitle must be seperated by " - "<br>i.e. "Book Title - A Subtitle Here" has the subtitle "A Subtitle Here"',
 | 
					        scannerParseSubtitle: 'Extract subtitles from audiobook folder names.<br>Subtitle must be seperated by " - "<br>i.e. "Book Title - A Subtitle Here" has the subtitle "A Subtitle Here"',
 | 
				
			||||||
        sortingIgnorePrefix: 'i.e. for prefix "the" book title "The Book Title" would sort as "Book Title, The"',
 | 
					        sortingIgnorePrefix: 'i.e. for prefix "the" book title "The Book Title" would sort as "Book Title, The"',
 | 
				
			||||||
 | 
				
			|||||||
@ -166,14 +166,14 @@ class BookFinder {
 | 
				
			|||||||
    return this.iTunesApi.searchAudiobooks(title)
 | 
					    return this.iTunesApi.searchAudiobooks(title)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async getAudibleResults(title, author) {
 | 
					  async getAudibleResults(title, author, asin) {
 | 
				
			||||||
    var books = await this.audible.search(title, author);
 | 
					    var books = await this.audible.search(title, author, asin);
 | 
				
			||||||
    if (this.verbose) Logger.debug(`Audible Book Search Results: ${books.length || 0}`)
 | 
					    if (this.verbose) Logger.debug(`Audible Book Search Results: ${books.length || 0}`)
 | 
				
			||||||
    if (!books) return []
 | 
					    if (!books) return []
 | 
				
			||||||
    return books
 | 
					    return books
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async search(provider, title, author, options = {}) {
 | 
					  async search(provider, title, author, isbn, asin, options = {}) {
 | 
				
			||||||
    var books = []
 | 
					    var books = []
 | 
				
			||||||
    var maxTitleDistance = !isNaN(options.titleDistance) ? Number(options.titleDistance) : 4
 | 
					    var maxTitleDistance = !isNaN(options.titleDistance) ? Number(options.titleDistance) : 4
 | 
				
			||||||
    var maxAuthorDistance = !isNaN(options.authorDistance) ? Number(options.authorDistance) : 4
 | 
					    var maxAuthorDistance = !isNaN(options.authorDistance) ? Number(options.authorDistance) : 4
 | 
				
			||||||
@ -182,7 +182,7 @@ class BookFinder {
 | 
				
			|||||||
    if (provider === 'google') {
 | 
					    if (provider === 'google') {
 | 
				
			||||||
      return this.getGoogleBooksResults(title, author)
 | 
					      return this.getGoogleBooksResults(title, author)
 | 
				
			||||||
    } else if (provider === 'audible') {
 | 
					    } else if (provider === 'audible') {
 | 
				
			||||||
      return this.getAudibleResults(title, author)
 | 
					      return this.getAudibleResults(title, author, asin)
 | 
				
			||||||
    } else if (provider === 'itunes') {
 | 
					    } else if (provider === 'itunes') {
 | 
				
			||||||
      return this.getiTunesAudiobooksResults(title, author)
 | 
					      return this.getiTunesAudiobooksResults(title, author)
 | 
				
			||||||
    } else if (provider === 'libgen') {
 | 
					    } else if (provider === 'libgen') {
 | 
				
			||||||
 | 
				
			|||||||
@ -48,7 +48,7 @@ class Series {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  checkNameEquals(name) {
 | 
					  checkNameEquals(name) {
 | 
				
			||||||
    if (!name) return false
 | 
					    if (!name || !this.name) return false
 | 
				
			||||||
    return this.name.toLowerCase() == name.toLowerCase().trim()
 | 
					    return this.name.toLowerCase() == name.toLowerCase().trim()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -11,6 +11,7 @@ class ServerSettings {
 | 
				
			|||||||
    this.scannerCoverProvider = 'google'
 | 
					    this.scannerCoverProvider = 'google'
 | 
				
			||||||
    this.scannerPreferAudioMetadata = false
 | 
					    this.scannerPreferAudioMetadata = false
 | 
				
			||||||
    this.scannerPreferOpfMetadata = false
 | 
					    this.scannerPreferOpfMetadata = false
 | 
				
			||||||
 | 
					    this.scannerPreferMatchedMetadata = false
 | 
				
			||||||
    this.scannerDisableWatcher = false 
 | 
					    this.scannerDisableWatcher = false 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Metadata - choose to store inside users library item folder
 | 
					    // Metadata - choose to store inside users library item folder
 | 
				
			||||||
@ -62,6 +63,7 @@ class ServerSettings {
 | 
				
			|||||||
    this.scannerParseSubtitle = settings.scannerParseSubtitle
 | 
					    this.scannerParseSubtitle = settings.scannerParseSubtitle
 | 
				
			||||||
    this.scannerPreferAudioMetadata = !!settings.scannerPreferAudioMetadata
 | 
					    this.scannerPreferAudioMetadata = !!settings.scannerPreferAudioMetadata
 | 
				
			||||||
    this.scannerPreferOpfMetadata = !!settings.scannerPreferOpfMetadata
 | 
					    this.scannerPreferOpfMetadata = !!settings.scannerPreferOpfMetadata
 | 
				
			||||||
 | 
					    this.scannerPreferMatchedMetadata = !!settings.scannerPreferMatchedMetadata
 | 
				
			||||||
    this.scannerDisableWatcher = !!settings.scannerDisableWatcher
 | 
					    this.scannerDisableWatcher = !!settings.scannerDisableWatcher
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.storeCoverWithItem = !!settings.storeCoverWithItem
 | 
					    this.storeCoverWithItem = !!settings.storeCoverWithItem
 | 
				
			||||||
@ -107,6 +109,7 @@ class ServerSettings {
 | 
				
			|||||||
      scannerParseSubtitle: this.scannerParseSubtitle,
 | 
					      scannerParseSubtitle: this.scannerParseSubtitle,
 | 
				
			||||||
      scannerPreferAudioMetadata: this.scannerPreferAudioMetadata,
 | 
					      scannerPreferAudioMetadata: this.scannerPreferAudioMetadata,
 | 
				
			||||||
      scannerPreferOpfMetadata: this.scannerPreferOpfMetadata,
 | 
					      scannerPreferOpfMetadata: this.scannerPreferOpfMetadata,
 | 
				
			||||||
 | 
					      scannerPreferMatchedMetadata: this.scannerPreferMatchedMetadata,
 | 
				
			||||||
      scannerDisableWatcher: this.scannerDisableWatcher,
 | 
					      scannerDisableWatcher: this.scannerDisableWatcher,
 | 
				
			||||||
      storeCoverWithItem: this.storeCoverWithItem,
 | 
					      storeCoverWithItem: this.storeCoverWithItem,
 | 
				
			||||||
      storeMetadataWithItem: this.storeMetadataWithItem,
 | 
					      storeMetadataWithItem: this.storeMetadataWithItem,
 | 
				
			||||||
 | 
				
			|||||||
@ -6,64 +6,60 @@ class Audible {
 | 
				
			|||||||
    constructor() { }
 | 
					    constructor() { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    cleanResult(item) {
 | 
					    cleanResult(item) {
 | 
				
			||||||
        var { title, subtitle, asin, authors, narrators, publisher_name, publisher_summary, release_date, series, product_images, publication_name } = item;
 | 
					        var { title, subtitle, asin, authors, narrators, publisherName, summary, releaseDate, image, genres, seriesPrimary, seriesSecondary, language } = item;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var primarySeries = this.getPrimarySeries(series, publication_name);
 | 
					        var series = []
 | 
				
			||||||
 | 
					        if(seriesPrimary) series.push(seriesPrimary)
 | 
				
			||||||
 | 
					        if(seriesSecondary) series.push(seriesSecondary)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var genresFiltered = genres ? genres.filter(g => g.type == "genre") : []
 | 
				
			||||||
 | 
					        var tagsFiltered = genres ? genres.filter(g => g.type == "tag") : []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            title,
 | 
					            title,
 | 
				
			||||||
            subtitle: subtitle || null,
 | 
					            subtitle: subtitle || null,
 | 
				
			||||||
            author: authors ? authors.map(({ name }) => name).join(', ') : null,
 | 
					            author: authors ? authors.map(({ name }) => name).join(', ') : null,
 | 
				
			||||||
            narrator: narrators ? narrators.map(({ name }) => name).join(', ') : null,
 | 
					            narrator: narrators ? narrators.map(({ name }) => name).join(', ') : null,
 | 
				
			||||||
            publisher: publisher_name,
 | 
					            publisher: publisherName,
 | 
				
			||||||
            publishedYear: release_date ? release_date.split('-')[0] : null,
 | 
					            publishedYear: releaseDate ? releaseDate.split('-')[0] : null,
 | 
				
			||||||
            description: publisher_summary ? stripHtml(publisher_summary).result : null,
 | 
					            description: summary ? stripHtml(summary).result : null,
 | 
				
			||||||
            cover: this.getBestImageLink(product_images),
 | 
					            cover: image,
 | 
				
			||||||
            asin,
 | 
					            asin,
 | 
				
			||||||
            series: primarySeries ? primarySeries.title : null,
 | 
					            genres: genresFiltered.length > 0 ? genresFiltered.map(({ name }) => name).join(', ') : null,
 | 
				
			||||||
            volumeNumber: primarySeries ? primarySeries.sequence : null
 | 
					            tags: tagsFiltered.length > 0 ? tagsFiltered.map(({ name }) => name).join(', ') : null,
 | 
				
			||||||
 | 
					            series: series != [] ? series.map(({name, position}) => ({ series: name, volumeNumber: position })) : null,
 | 
				
			||||||
 | 
					            language: language ? language.charAt(0).toUpperCase() + language.slice(1) : null
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    getBestImageLink(images) {
 | 
					 | 
				
			||||||
        if (!images) return null
 | 
					 | 
				
			||||||
        var keys = Object.keys(images)
 | 
					 | 
				
			||||||
        if (!keys.length) return null
 | 
					 | 
				
			||||||
        return images[keys[keys.length - 1]]
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    getPrimarySeries(series, publication_name) {
 | 
					 | 
				
			||||||
        return (series && series.length > 0) ? series.find((s) => s.title == publication_name) || series[0] : null
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    isProbablyAsin(title) {
 | 
					    isProbablyAsin(title) {
 | 
				
			||||||
        return /^[0-9A-Z]{10}$/.test(title)
 | 
					        return /^[0-9A-Z]{10}$/.test(title)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    asinSearch(asin) {
 | 
					    asinSearch(asin) {
 | 
				
			||||||
        var queryObj = {
 | 
					 | 
				
			||||||
            response_groups: 'rating,series,contributors,product_desc,media,product_extended_attrs',
 | 
					 | 
				
			||||||
            image_sizes: '500,1024,2000'
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        var queryString = (new URLSearchParams(queryObj)).toString();
 | 
					 | 
				
			||||||
        asin = encodeURIComponent(asin);
 | 
					        asin = encodeURIComponent(asin);
 | 
				
			||||||
        var url = `https://api.audible.com/1.0/catalog/products/${asin}?${queryString}`
 | 
					        var url = `https://api.audnex.us/books/${asin}`
 | 
				
			||||||
        Logger.debug(`[Audible] ASIN url: ${url}`)
 | 
					        Logger.debug(`[Audible] ASIN url: ${url}`)
 | 
				
			||||||
        return axios.get(url).then((res) => {
 | 
					        return axios.get(url).then((res) => {
 | 
				
			||||||
            if (!res || !res.data || !res.data.product || !res.data.product.authors) return []
 | 
					            if (!res || !res.data || !res.data.asin) return null
 | 
				
			||||||
            return [res.data.product]
 | 
					            return res.data
 | 
				
			||||||
        }).catch(error => {
 | 
					        }).catch(error => {
 | 
				
			||||||
            Logger.error('[Audible] search error', error)
 | 
					            Logger.error('[Audible] ASIN search error', error)
 | 
				
			||||||
            return []
 | 
					            return []
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async search(title, author) {
 | 
					    async search(title, author, asin) {        
 | 
				
			||||||
        if (this.isProbablyAsin(title)) {
 | 
					        var items
 | 
				
			||||||
            var items = await this.asinSearch(title)
 | 
					        if(asin) {
 | 
				
			||||||
            if (items.length > 0) return items.map(item => this.cleanResult(item))
 | 
					            items = [await this.asinSearch(asin)]
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
 | 
					        if (!items && this.isProbablyAsin(title)) {
 | 
				
			||||||
 | 
					            items = [await this.asinSearch(title)]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(!items) {
 | 
				
			||||||
            var queryObj = {
 | 
					            var queryObj = {
 | 
				
			||||||
                response_groups: 'rating,series,contributors,product_desc,media,product_extended_attrs',
 | 
					                response_groups: 'rating,series,contributors,product_desc,media,product_extended_attrs',
 | 
				
			||||||
                image_sizes: '500,1024,2000',
 | 
					                image_sizes: '500,1024,2000',
 | 
				
			||||||
@ -75,14 +71,16 @@ class Audible {
 | 
				
			|||||||
            var queryString = (new URLSearchParams(queryObj)).toString();
 | 
					            var queryString = (new URLSearchParams(queryObj)).toString();
 | 
				
			||||||
            var url = `https://api.audible.com/1.0/catalog/products?${queryString}`
 | 
					            var url = `https://api.audible.com/1.0/catalog/products?${queryString}`
 | 
				
			||||||
            Logger.debug(`[Audible] Search url: ${url}`)
 | 
					            Logger.debug(`[Audible] Search url: ${url}`)
 | 
				
			||||||
        var items = await axios.get(url).then((res) => {
 | 
					            items = await axios.get(url).then((res) => {
 | 
				
			||||||
            if (!res || !res.data || !res.data.products) return []
 | 
					                if (!res || !res.data || !res.data.products) return null
 | 
				
			||||||
            return res.data.products
 | 
					                return Promise.all(res.data.products.map(result => this.asinSearch(result.asin)))
 | 
				
			||||||
            }).catch(error => {
 | 
					            }).catch(error => {
 | 
				
			||||||
            Logger.error('[Audible] search error', error)
 | 
					                Logger.error('[Audible] query search error', error)
 | 
				
			||||||
                return []
 | 
					                return []
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
        return items.map(item => this.cleanResult(item))
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return items ? items.map(item => this.cleanResult(item)) : []
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -8,6 +8,7 @@ class ScanOptions {
 | 
				
			|||||||
    this.storeCoverWithItem = false
 | 
					    this.storeCoverWithItem = false
 | 
				
			||||||
    this.preferAudioMetadata = false
 | 
					    this.preferAudioMetadata = false
 | 
				
			||||||
    this.preferOpfMetadata = false
 | 
					    this.preferOpfMetadata = false
 | 
				
			||||||
 | 
					    this.preferMatchedMetadata = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (options) {
 | 
					    if (options) {
 | 
				
			||||||
      this.construct(options)
 | 
					      this.construct(options)
 | 
				
			||||||
@ -32,7 +33,8 @@ class ScanOptions {
 | 
				
			|||||||
      findCovers: this.findCovers,
 | 
					      findCovers: this.findCovers,
 | 
				
			||||||
      storeCoverWithItem: this.storeCoverWithItem,
 | 
					      storeCoverWithItem: this.storeCoverWithItem,
 | 
				
			||||||
      preferAudioMetadata: this.preferAudioMetadata,
 | 
					      preferAudioMetadata: this.preferAudioMetadata,
 | 
				
			||||||
      preferOpfMetadata: this.preferOpfMetadata
 | 
					      preferOpfMetadata: this.preferOpfMetadata,
 | 
				
			||||||
 | 
					      preferOpfMetadata: this.preferMatchedMetadata
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -44,6 +46,7 @@ class ScanOptions {
 | 
				
			|||||||
    this.storeCoverWithItem = serverSettings.storeCoverWithItem
 | 
					    this.storeCoverWithItem = serverSettings.storeCoverWithItem
 | 
				
			||||||
    this.preferAudioMetadata = serverSettings.scannerPreferAudioMetadata
 | 
					    this.preferAudioMetadata = serverSettings.scannerPreferAudioMetadata
 | 
				
			||||||
    this.preferOpfMetadata = serverSettings.scannerPreferOpfMetadata
 | 
					    this.preferOpfMetadata = serverSettings.scannerPreferOpfMetadata
 | 
				
			||||||
 | 
					    this.preferOpfMetadata = serverSettings.scannerPreferMatchedMetadata
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
module.exports = ScanOptions
 | 
					module.exports = ScanOptions
 | 
				
			||||||
@ -632,8 +632,10 @@ class Scanner {
 | 
				
			|||||||
    var provider = options.provider || 'google'
 | 
					    var provider = options.provider || 'google'
 | 
				
			||||||
    var searchTitle = options.title || libraryItem.media.metadata.title
 | 
					    var searchTitle = options.title || libraryItem.media.metadata.title
 | 
				
			||||||
    var searchAuthor = options.author || libraryItem.media.metadata.authorName
 | 
					    var searchAuthor = options.author || libraryItem.media.metadata.authorName
 | 
				
			||||||
 | 
					    var searchISBN = options.isbn || libraryItem.media.metadata.isbn
 | 
				
			||||||
 | 
					    var searchASIN = options.asin || libraryItem.media.metadata.asin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var results = await this.bookFinder.search(provider, searchTitle, searchAuthor)
 | 
					    var results = await this.bookFinder.search(provider, searchTitle, searchAuthor, searchISBN, searchASIN)
 | 
				
			||||||
    if (!results.length) {
 | 
					    if (!results.length) {
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        warning: `No ${provider} match found`
 | 
					        warning: `No ${provider} match found`
 | 
				
			||||||
@ -641,6 +643,12 @@ class Scanner {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    var matchData = results[0]
 | 
					    var matchData = results[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Set to override existing metadata if scannerPreferMatchedMetadata setting is true
 | 
				
			||||||
 | 
					    if(this.db.serverSettings.scannerPreferMatchedMetadata) {
 | 
				
			||||||
 | 
					      options.overrideCover = true
 | 
				
			||||||
 | 
					      options.overrideDetails = true
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Update cover if not set OR overrideCover flag
 | 
					    // Update cover if not set OR overrideCover flag
 | 
				
			||||||
    var hasUpdated = false
 | 
					    var hasUpdated = false
 | 
				
			||||||
    if (matchData.cover && (!libraryItem.media.coverPath || options.overrideCover)) {
 | 
					    if (matchData.cover && (!libraryItem.media.coverPath || options.overrideCover)) {
 | 
				
			||||||
@ -654,47 +662,68 @@ class Scanner {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Update media metadata if not set OR overrideDetails flag
 | 
					    // Update media metadata if not set OR overrideDetails flag
 | 
				
			||||||
    const detailKeysToUpdate = ['title', 'subtitle', 'description', 'narrator', 'publisher', 'publishedYear', 'asin', 'isbn']
 | 
					    const detailKeysToUpdate = ['title', 'subtitle', 'description', 'narrator', 'publisher', 'publishedYear', 'genres', 'tags', 'language', 'explicit', 'asin', 'isbn']
 | 
				
			||||||
    const updatePayload = {}
 | 
					    const updatePayload = {}
 | 
				
			||||||
 | 
					    updatePayload.metadata = {}
 | 
				
			||||||
    for (const key in matchData) {
 | 
					    for (const key in matchData) {
 | 
				
			||||||
      if (matchData[key] && detailKeysToUpdate.includes(key)) {
 | 
					      if (matchData[key] && detailKeysToUpdate.includes(key)) {
 | 
				
			||||||
        if (key === 'narrator') {
 | 
					        if (key === 'narrator') {
 | 
				
			||||||
          if ((!libraryItem.media.metadata.narratorName || options.overrideDetails)) {
 | 
					          if ((!libraryItem.media.metadata.narratorName || options.overrideDetails)) {
 | 
				
			||||||
            updatePayload.narrators = [matchData[key]]
 | 
					            updatePayload.metadata.narrators = matchData[key].split(',')
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        } else if (key === 'genres') {
 | 
				
			||||||
 | 
					          if ((!libraryItem.media.metadata.genres || options.overrideDetails)) {
 | 
				
			||||||
 | 
					            updatePayload.metadata[key] = matchData[key].split(',')
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        } else if (key === 'tags') {
 | 
				
			||||||
 | 
					          if ((!libraryItem.media.tags || options.overrideDetails)) {
 | 
				
			||||||
 | 
					            updatePayload[key] = matchData[key].split(',')
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        } else if ((!libraryItem.media.metadata[key] || options.overrideDetails)) {
 | 
					        } else if ((!libraryItem.media.metadata[key] || options.overrideDetails)) {
 | 
				
			||||||
          updatePayload[key] = matchData[key]
 | 
					          updatePayload.metadata[key] = matchData[key]
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Add or set author if not set
 | 
					    // Add or set author if not set
 | 
				
			||||||
    if (matchData.author && !libraryItem.media.metadata.authorName) {
 | 
					    if (matchData.author && !libraryItem.media.metadata.authorName || options.overrideDetails) {
 | 
				
			||||||
      var author = this.db.authors.find(au => au.checkNameEquals(matchData.author))
 | 
					      if(!Array.isArray(matchData.author)) matchData.author = [matchData.author]
 | 
				
			||||||
 | 
					      const authorPayload = []
 | 
				
			||||||
 | 
					      for (let index = 0; index < matchData.author.length; index++) {
 | 
				
			||||||
 | 
					        const authorName = matchData.author[index]
 | 
				
			||||||
 | 
					        var author = this.db.authors.find(au => au.checkNameEquals(authorName))
 | 
				
			||||||
        if (!author) {
 | 
					        if (!author) {
 | 
				
			||||||
          author = new Author()
 | 
					          author = new Author()
 | 
				
			||||||
        author.setData({ name: matchData.author })
 | 
					          author.setData({ name: authorName })
 | 
				
			||||||
          await this.db.insertEntity('author', author)
 | 
					          await this.db.insertEntity('author', author)
 | 
				
			||||||
          this.emitter('author_added', author)
 | 
					          this.emitter('author_added', author)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      updatePayload.authors = [author.toJSONMinimal()]
 | 
					        authorPayload.push(author.toJSONMinimal())
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      updatePayload.metadata.authors = authorPayload
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Add or set series if not set
 | 
					    // Add or set series if not set
 | 
				
			||||||
    if (matchData.series && !libraryItem.media.metadata.seriesName) {
 | 
					    if (matchData.series && !libraryItem.media.metadata.seriesName || options.overrideDetails) {
 | 
				
			||||||
      var seriesItem = this.db.series.find(au => au.checkNameEquals(matchData.series))
 | 
					      if(!Array.isArray(matchData.series)) matchData.series = [{ series: matchData.series, volumeNumber: matchData.volumeNumber }]
 | 
				
			||||||
 | 
					      const seriesPayload = []
 | 
				
			||||||
 | 
					      for (let index = 0; index < matchData.series.length; index++) {
 | 
				
			||||||
 | 
					        const seriesMatchItem = matchData.series[index]
 | 
				
			||||||
 | 
					        var seriesItem = this.db.series.find(au => au.checkNameEquals(seriesMatchItem.series))
 | 
				
			||||||
        if (!seriesItem) {
 | 
					        if (!seriesItem) {
 | 
				
			||||||
          seriesItem = new Series()
 | 
					          seriesItem = new Series()
 | 
				
			||||||
        seriesItem.setData({ name: matchData.series })
 | 
					          seriesItem.setData({ name: seriesMatchItem.series })
 | 
				
			||||||
          await this.db.insertEntity('series', seriesItem)
 | 
					          await this.db.insertEntity('series', seriesItem)
 | 
				
			||||||
          this.emitter('series_added', seriesItem)
 | 
					          this.emitter('series_added', seriesItem)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      updatePayload.series = [seriesItem.toJSONMinimal(matchData.volumeNumber)]
 | 
					        seriesPayload.push(seriesItem.toJSONMinimal(seriesMatchItem.volumeNumber))
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      updatePayload.metadata.series = seriesPayload
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (Object.keys(updatePayload).length) {
 | 
					    if (Object.keys(updatePayload).length) {
 | 
				
			||||||
      Logger.debug('[Scanner] Updating details', updatePayload)
 | 
					      Logger.debug('[Scanner] Updating details', updatePayload)
 | 
				
			||||||
      if (libraryItem.media.update({ metadata: updatePayload })) {
 | 
					      if (libraryItem.media.update(updatePayload)) {
 | 
				
			||||||
        hasUpdated = true
 | 
					        hasUpdated = true
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user