Compare commits

...

18 Commits

Author SHA1 Message Date
advplyr
fdece944f4 Remove leftover string in chapter editor 2022-10-08 17:37:43 -05:00
advplyr
d7952dab04 Fix:Setting book chapters from audio files #1052 2022-10-08 17:32:46 -05:00
advplyr
bec599f325 Update:30s timeout for file downloading axios request #1050 2022-10-08 17:15:37 -05:00
advplyr
affcc03c61 Update debian service with audiobookshelf group 2022-10-08 17:12:23 -05:00
advplyr
db18c71857 Version bump 2.2.1 2022-10-08 16:48:20 -05:00
advplyr
dcc223949a Update:Add note for number of backups to keep #1041 2022-10-08 16:30:21 -05:00
advplyr
6a6d384d88 Update:Scanner folder name parse sequence starting with decimal and cast to number 2022-10-08 15:42:38 -05:00
advplyr
cd57667444 Fix:Library item edit modal clear loading indicator when changing tabs 2022-10-07 17:22:23 -05:00
advplyr
3900db14d3 Add:Multi-region audible & audnexus support #731 2022-10-07 17:18:28 -05:00
advplyr
1fa94cbfad Update tone to v0.1.1 2022-10-07 16:21:47 -05:00
advplyr
793233e782 Fix:Authors page to only include library items from the current library #1049 2022-10-06 17:06:06 -05:00
advplyr
94012e5dff Add:Chapter editor shift all chapters by X seconds #927 2022-10-05 18:01:42 -05:00
advplyr
d440a9fd6a Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2022-10-04 17:43:43 -05:00
advplyr
928c6cf5b3 Fix:iTunes returning artist names with & instead of all comma separated #1022 2022-10-04 17:41:26 -05:00
advplyr
23a25d420c Fix:Escape ebook URLs #1039 2022-10-04 17:29:26 -05:00
advplyr
dc779a3fc5 Merge pull request #1031 from Undergrid/Issue_1030
Fix a crash under certain circumstances when updating tags when quick… (Issue #1030)
2022-10-03 09:50:12 -05:00
Nick Thomson
876badbeea Fix a crash under certain circumstances when updating tags when quick matching. 2022-10-03 01:38:52 +01:00
advplyr
8563bdde74 Update:Podcast episode downloads using episode title as filename without prefixing episode num 2022-10-02 17:12:44 -05:00
26 changed files with 184 additions and 91 deletions

View File

@@ -6,7 +6,7 @@ RUN npm ci && npm cache clean --force
RUN npm run generate
### STAGE 1: Build server ###
FROM sandreas/tone:v0.0.9 AS tone
FROM sandreas/tone:v0.1.1 AS tone
FROM node:16-alpine
ENV NODE_ENV=production

View File

@@ -50,7 +50,7 @@ install_ffmpeg() {
echo "Starting FFMPEG Install"
WGET="wget https://johnvansickle.com/ffmpeg/builds/ffmpeg-git-amd64-static.tar.xz"
WGET_TONE="wget https://github.com/sandreas/tone/releases/download/v0.0.9/tone-0.0.9-linux-x64.tar.gz"
WGET_TONE="wget https://github.com/sandreas/tone/releases/download/v0.1.1/tone-0.1.1-linux-x64.tar.gz"
if ! cd "$FFMPEG_INSTALL_DIR"; then
echo "Creating ffmpeg install dir at $FFMPEG_INSTALL_DIR"
@@ -66,8 +66,8 @@ install_ffmpeg() {
# Temp downloading tone library to the ffmpeg dir
echo "Getting tone.."
$WGET_TONE
tar xvf tone-0.0.9-linux-x64.tar.gz --strip-components=1
rm tone-0.0.9-linux-x64.tar.gz
tar xvf tone-0.1.1-linux-x64.tar.gz --strip-components=1
rm tone-0.1.1-linux-x64.tar.gz
echo "Good to go on Ffmpeg (& tone)... hopefully"
}

View File

@@ -10,6 +10,7 @@ ExecStart=/usr/share/audiobookshelf/audiobookshelf
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
User=audiobookshelf
Group=audiobookshelf
PermissionsStartOnly=true
[Install]

View File

@@ -231,8 +231,10 @@ export default {
}
},
selectTab(tab) {
if (this.selectedTab === tab) return
if (this.availableTabs.find((t) => t.id === tab)) {
this.selectedTab = tab
this.processing = false
}
},
libraryItemUpdated(expandedLibraryItem) {

View File

@@ -125,7 +125,7 @@ export default {
return this.$store.state.scanners.providers
},
searchTitleLabel() {
if (this.provider == 'audible') return 'Search Title or ASIN'
if (this.provider.startsWith('audible')) return 'Search Title or ASIN'
else if (this.provider == 'itunes') return 'Search Term'
return 'Search Title'
},

View File

@@ -258,7 +258,7 @@ export default {
return this.$store.state.scanners.providers
},
searchTitleLabel() {
if (this.provider == 'audible') return 'Search Title or ASIN'
if (this.provider.startsWith('audible')) return 'Search Title or ASIN'
else if (this.provider == 'itunes') return 'Search Term'
return 'Search Title'
},
@@ -312,7 +312,7 @@ export default {
this.isProcessing = true
this.lastSearch = searchQuery
var searchEntity = this.isPodcast ? 'podcast' : 'books'
var results = await this.$axios.$get(`/api/search/${searchEntity}?${searchQuery}`, { timeout: 10000 }).catch((error) => {
var results = await this.$axios.$get(`/api/search/${searchEntity}?${searchQuery}`, { timeout: 20000 }).catch((error) => {
console.error('Failed', error)
return []
})

View File

@@ -96,7 +96,9 @@ export default {
if (itemRelPath.startsWith('/')) itemRelPath = itemRelPath.slice(1)
var relPath = this.ebookFile.metadata.relPath
if (relPath.startsWith('/')) relPath = relPath.slice(1)
return `/ebook/${this.libraryId}/${this.folderId}/${itemRelPath}/${relPath}`
const relRelPath = this.$encodeUriPath(`${itemRelPath}/${relPath}`)
return `/ebook/${this.libraryId}/${this.folderId}/${relRelPath}`
},
userToken() {
return this.$store.getters['user/getToken']

View File

@@ -1,12 +1,12 @@
{
"name": "audiobookshelf-client",
"version": "2.2.0",
"version": "2.2.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "audiobookshelf-client",
"version": "2.1.5",
"version": "2.2.1",
"license": "ISC",
"dependencies": {
"@nuxtjs/axios": "^5.13.6",

View File

@@ -1,6 +1,6 @@
{
"name": "audiobookshelf-client",
"version": "2.2.0",
"version": "2.2.1",
"description": "Self-hosted audiobook and podcast client",
"main": "index.js",
"scripts": {

View File

@@ -18,11 +18,35 @@
<p class="text-lg mb-4 font-semibold">Audiobook Chapters</p>
<div class="flex-grow" />
<ui-checkbox v-model="showSecondInputs" checkbox-bg="primary" small label-class="text-sm text-gray-200 pl-1" label="Show seconds" class="mx-2" />
<div class="w-40" />
</div>
<div class="flex items-center mb-3 py-1">
<div class="flex-grow" />
<ui-btn v-if="newChapters.length > 1" :color="showShiftTimes ? 'bg' : 'primary'" small @click="showShiftTimes = !showShiftTimes">Shift Times</ui-btn>
<ui-btn color="primary" small class="mx-2" @click="showFindChaptersModal = true">Lookup</ui-btn>
<ui-btn color="success" small @click="saveChapters">Save</ui-btn>
<div class="w-40" />
</div>
<div class="overflow-hidden">
<transition name="slide">
<div v-if="showShiftTimes" class="flex mb-4">
<div class="w-12"></div>
<div class="flex-grow">
<div class="flex items-center">
<p class="text-sm mb-1 font-semibold pr-2">Time to shift in seconds</p>
<ui-text-input v-model="shiftAmount" type="number" class="max-w-20" style="height: 30px" />
<ui-btn color="primary" class="mx-1" small @click="shiftChapterTimes">Add</ui-btn>
<div class="flex-grow" />
<span class="material-icons text-gray-200 hover:text-white cursor-pointer" @click="showShiftTimes = false">close</span>
</div>
<p class="text-xs py-1.5 text-gray-300 max-w-md">Note: First chapter start time must remain at 0:00 and the last chapter start time cannot exceed this audiobooks duration.</p>
</div>
<div class="w-40"></div>
</div>
</transition>
</div>
<div class="flex text-xs uppercase text-gray-300 font-semibold mb-2">
<div class="w-12"></div>
<div class="w-32 px-2">Start</div>
@@ -186,6 +210,8 @@ export default {
return {
newChapters: [],
selectedChapter: null,
showShiftTimes: false,
shiftAmount: 0,
audioEl: null,
isPlayingChapter: false,
isLoadingChapter: false,
@@ -237,6 +263,32 @@ export default {
}
},
methods: {
shiftChapterTimes() {
if (!this.shiftAmount || isNaN(this.shiftAmount) || this.newChapters.length <= 1) {
return
}
const amount = Number(this.shiftAmount)
const lastChapter = this.newChapters[this.newChapters.length - 1]
if (lastChapter.start + amount > this.mediaDurationRounded) {
this.$toast.error('Invalid shift amount. Last chapter start time would extend beyond the duration of this audiobook.')
return
}
if (this.newChapters[0].end + amount <= 0) {
this.$toast.error('Invalid shift amount. First chapter would have zero or negative length.')
return
}
for (let i = 0; i < this.newChapters.length; i++) {
const chap = this.newChapters[i]
chap.end = Math.min(chap.end + amount, this.mediaDuration)
if (i > 0) {
chap.start = Math.max(0, chap.start + amount)
}
}
},
editItem() {
this.$store.commit('showEditModal', this.libraryItem)
},

View File

@@ -44,7 +44,7 @@
<script>
export default {
async asyncData({ store, app, params, redirect }) {
const author = await app.$axios.$get(`/api/authors/${params.id}?include=items,series`).catch((error) => {
const author = await app.$axios.$get(`/api/authors/${params.id}?library=${store.state.libraries.currentLibraryId}&include=items,series`).catch((error) => {
console.error('Failed to get author', error)
return null
})

View File

@@ -25,7 +25,9 @@
<div class="flex items-center py-2">
<ui-text-input type="number" v-model="backupsToKeep" no-spinner :disabled="updatingServerSettings" :padding-x="1" text-center class="w-10" @change="updateBackupsSettings" />
<p class="pl-4 text-lg">Number of backups to keep</p>
<ui-tooltip :text="numBackupsToKeepTooltip">
<p class="pl-4 text-lg">Number of backups to keep <span class="material-icons icon-text">info_outlined</span></p>
</ui-tooltip>
</div>
<div class="flex items-center py-2">
@@ -53,7 +55,10 @@ export default {
maxBackupSize: 1,
cronExpression: '',
newServerSettings: {},
showCronBuilder: false
showCronBuilder: false,
backupsTooltip: 'Backups saved in /metadata/backups',
numBackupsToKeepTooltip: 'Only 1 backup will be removed at a time so if you already have more backups than this you should manually remove them.',
maxBackupSizeTooltip: 'As a safeguard against misconfiguration, backups will fail if they exceed the configured size.'
}
},
watch: {
@@ -65,12 +70,6 @@ export default {
}
},
computed: {
backupsTooltip() {
return 'Backups saved in /metadata/backups'
},
maxBackupSizeTooltip() {
return 'As a safeguard against misconfiguration, backups will fail if they exceed the configured size.'
},
serverSettings() {
return this.$store.state.serverSettings
},

View File

@@ -10,12 +10,48 @@ export const state = () => ({
value: 'openlibrary'
},
{
text: 'Audible',
text: 'iTunes',
value: 'itunes'
},
{
text: 'Audible.com',
value: 'audible'
},
{
text: 'iTunes',
value: 'itunes'
text: 'Audible.ca',
value: 'audible.ca'
},
{
text: 'Audible.co.uk',
value: 'audible.uk'
},
{
text: 'Audible.co.au',
value: 'audible.au'
},
{
text: 'Audible.fr',
value: 'audible.fr'
},
{
text: 'Audible.de',
value: 'audible.de'
},
{
text: 'Audible.co.jp',
value: 'audible.jp'
},
{
text: 'Audible.it',
value: 'audible.it'
},
{
text: 'Audible.co.in',
value: 'audible.in'
},
{
text: 'Audible.es',
value: 'audible.es'
}
],
podcastProviders: [

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "audiobookshelf",
"version": "2.2.0",
"version": "2.2.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "audiobookshelf",
"version": "2.1.5",
"version": "2.2.1",
"license": "GPL-3.0",
"dependencies": {
"axios": "^0.26.1",

View File

@@ -1,6 +1,6 @@
{
"name": "audiobookshelf",
"version": "2.2.0",
"version": "2.2.1",
"description": "Self-hosted audiobook and podcast server",
"main": "index.js",
"scripts": {

View File

@@ -9,6 +9,7 @@ class AuthorController {
constructor() { }
async findOne(req, res) {
const libraryId = req.query.library
const include = (req.query.include || '').split(',')
const authorJson = req.author.toJSON()
@@ -16,6 +17,7 @@ class AuthorController {
// Used on author landing page to include library items and items grouped in series
if (include.includes('items')) {
authorJson.libraryItems = this.db.libraryItems.filter(li => {
if (libraryId && li.libraryId !== libraryId) return false
if (!req.user.checkCanAccessLibraryItem(li)) return false // filter out library items user cannot access
return li.media.metadata.hasAuthor && li.media.metadata.hasAuthor(req.author.id)
})

View File

@@ -150,8 +150,9 @@ class BookFinder {
return this.iTunesApi.searchAudiobooks(title)
}
async getAudibleResults(title, author, asin) {
var books = await this.audible.search(title, author, asin);
async getAudibleResults(title, author, asin, provider) {
const region = provider.includes('.') ? provider.split('.').pop() : ''
const books = await this.audible.search(title, author, asin, region)
if (this.verbose) Logger.debug(`Audible Book Search Results: ${books.length || 0}`)
if (!books) return []
return books
@@ -165,8 +166,8 @@ class BookFinder {
if (provider === 'google') {
books = await this.getGoogleBooksResults(title, author)
} else if (provider === 'audible') {
books = await this.getAudibleResults(title, author, asin)
} else if (provider.startsWith('audible')) {
books = await this.getAudibleResults(title, author, asin, provider)
} else if (provider === 'itunes') {
books = await this.getiTunesAudiobooksResults(title, author)
} else if (provider === 'openlibrary') {

View File

@@ -22,7 +22,7 @@ class PodcastEpisodeDownload {
toJSONForClient() {
return {
id: this.id,
episodeDisplayTitle: this.podcastEpisode ? this.podcastEpisode.bestFilename : null,
episodeDisplayTitle: this.podcastEpisode ? this.podcastEpisode.title : null,
url: this.url,
libraryItemId: this.libraryItem ? this.libraryItem.id : null,
isDownloading: this.isDownloading,
@@ -35,7 +35,7 @@ class PodcastEpisodeDownload {
}
get targetFilename() {
return sanitizeFilename(`${this.podcastEpisode.bestFilename}.mp3`)
return sanitizeFilename(`${this.podcastEpisode.title}.mp3`)
}
get targetPath() {
return Path.join(this.libraryItem.path, this.targetFilename)

View File

@@ -103,10 +103,6 @@ class PodcastEpisode {
return this.audioFile.duration
}
get size() { return this.audioFile.metadata.size }
get bestFilename() {
if (this.episode) return `${this.episode} - ${this.title}`
return this.title
}
get enclosureUrl() {
return this.enclosure ? this.enclosure.url : null
}

View File

@@ -408,54 +408,29 @@ class Book {
setChapters(preferOverdriveMediaMarker = false) {
// If 1 audio file without chapters, then no chapters will be set
var includedAudioFiles = this.audioFiles.filter(af => !af.exclude)
if (!includedAudioFiles.length) return
// If overdrive media markers are present and preferred, use those instead
if (preferOverdriveMediaMarker) {
var overdriveChapters = parseOverdriveMediaMarkersAsChapters(includedAudioFiles)
if (overdriveChapters) {
Logger.info('[Book] Overdrive Media Markers and preference found! Using these for chapter definitions')
return this.chapters = overdriveChapters
this.chapters = overdriveChapters
return
}
}
if (includedAudioFiles.length === 1) {
// 1 audio file with chapters
if (includedAudioFiles[0].chapters) {
this.chapters = includedAudioFiles[0].chapters.map(c => ({ ...c }))
}
} else {
// IF first audio file has embedded chapters then use embedded chapters
if (includedAudioFiles[0].chapters && includedAudioFiles[0].chapters.length) {
Logger.debug(`[Book] setChapters: Using embedded chapters in audio file ${includedAudioFiles[0].metadata.path}`)
this.chapters = includedAudioFiles[0].chapters.map(c => ({ ...c }))
} else if (includedAudioFiles.length > 1) {
// Build chapters from audio files
this.chapters = []
var currChapterId = 0
var currStartTime = 0
includedAudioFiles.forEach((file) => {
// If audio file has chapters use chapters
if (file.chapters && file.chapters.length) {
file.chapters.forEach((chapter) => {
if (currStartTime > this.duration) {
Logger.warn(`[Book] Invalid chapter start time > duration`)
} else {
var chapterAlreadyExists = this.chapters.find(ch => ch.start === currStartTime)
if (!chapterAlreadyExists) {
var chapterDuration = chapter.end - chapter.start
if (chapterDuration > 0) {
var title = `Chapter ${currChapterId}`
if (chapter.title) {
title += ` (${chapter.title})`
}
var endTime = Math.min(this.duration, currStartTime + chapterDuration)
this.chapters.push({
id: currChapterId++,
start: currStartTime,
end: endTime,
title
})
currStartTime += chapterDuration
}
}
}
})
} else if (file.duration) {
// Otherwise just use track has chapter
if (file.duration) {
this.chapters.push({
id: currChapterId++,
start: currStartTime,

View File

@@ -3,7 +3,20 @@ const htmlSanitizer = require('../utils/htmlSanitizer')
const Logger = require('../Logger')
class Audible {
constructor() { }
constructor() {
this.regionMap = {
'us': '.com',
'ca': '.ca',
'uk': '.co.uk',
'au': '.co.au',
'fr': '.fr',
'de': '.de',
'jp': '.co.jp',
'it': '.it',
'in': '.co.in',
'es': '.es'
}
}
cleanResult(item) {
var { title, subtitle, asin, authors, narrators, publisherName, summary, releaseDate, image, genres, seriesPrimary, seriesSecondary, language, runtimeLengthMin } = item
@@ -29,7 +42,9 @@ class Audible {
tags: tagsFiltered.length ? tagsFiltered.join(', ') : null,
series: series != [] ? series.map(({ name, position }) => ({ series: name, sequence: position })) : null,
language: language ? language.charAt(0).toUpperCase() + language.slice(1) : null,
duration: runtimeLengthMin && !isNaN(runtimeLengthMin) ? Number(runtimeLengthMin) : 0
duration: runtimeLengthMin && !isNaN(runtimeLengthMin) ? Number(runtimeLengthMin) : 0,
region: item.region || null,
rating: item.rating || null
}
}
@@ -37,9 +52,10 @@ class Audible {
return /^[0-9A-Z]{10}$/.test(title)
}
asinSearch(asin) {
asinSearch(asin, region) {
asin = encodeURIComponent(asin);
var url = `https://api.audnex.us/books/${asin}`
var regionQuery = region ? `?region=${region}` : ''
var url = `https://api.audnex.us/books/${asin}${regionQuery}`
Logger.debug(`[Audible] ASIN url: ${url}`)
return axios.get(url).then((res) => {
if (!res || !res.data || !res.data.asin) return null
@@ -50,14 +66,19 @@ class Audible {
})
}
async search(title, author, asin) {
async search(title, author, asin, region) {
if (region && !this.regionMap[region]) {
Logger.error(`[Audible] search: Invalid region ${region}`)
region = ''
}
var items
if (asin) {
items = [await this.asinSearch(asin)]
items = [await this.asinSearch(asin, region)]
}
if (!items && this.isProbablyAsin(title)) {
items = [await this.asinSearch(title)]
items = [await this.asinSearch(title, region)]
}
if (!items) {
@@ -65,14 +86,15 @@ class Audible {
num_results: '10',
products_sort_by: 'Relevance',
title: title
};
}
if (author) queryObj.author = author
var queryString = (new URLSearchParams(queryObj)).toString();
var url = `https://api.audible.com/1.0/catalog/products?${queryString}`
const queryString = (new URLSearchParams(queryObj)).toString()
const tld = region ? this.regionMap[region] : '.com'
const url = `https://api.audible${tld}/1.0/catalog/products?${queryString}`
Logger.debug(`[Audible] Search url: ${url}`)
items = await axios.get(url).then((res) => {
if (!res || !res.data || !res.data.products) return null
return Promise.all(res.data.products.map(result => this.asinSearch(result.asin)))
return Promise.all(res.data.products.map(result => this.asinSearch(result.asin, region)))
}).catch(error => {
Logger.error('[Audible] query search error', error)
return []

View File

@@ -35,7 +35,7 @@ class Audnexus {
return {
asin: author.asin,
description: author.description,
image: author.image,
image: author.image || null,
name: author.name
}
}
@@ -54,7 +54,7 @@ class Audnexus {
return {
asin: author.asin,
description: author.description,
image: author.image,
image: author.image || null,
name: author.name
}
}

View File

@@ -60,11 +60,15 @@ class iTunes {
}
cleanAudiobook(data) {
// artistName can be "Name1, Name2 & Name3" so we refactor this to "Name1, Name2, Name3"
// see: https://github.com/advplyr/audiobookshelf/issues/1022
const author = (data.artistName || '').split(' & ').join(', ')
return {
id: data.collectionId,
artistId: data.artistId,
title: data.collectionName,
author: data.artistName,
author,
description: htmlSanitizer.stripAllTags(data.description || ''),
publishedYear: data.releaseDate ? data.releaseDate.split('-')[0] : null,
genres: data.primaryGenreName ? [data.primaryGenreName] : null,

View File

@@ -819,7 +819,7 @@ class Scanner {
if ((!libraryItem.media.tags.length || options.overrideDetails)) {
var tagsArray = []
if (Array.isArray(matchData[key])) tagsArray = [...matchData[key]]
else tagsArray = tagsArray[key].split(',').map(v => v.trim()).filter(v => !!v)
else tagsArray = matchData[key].split(',').map(v => v.trim()).filter(v => !!v)
updatePayload[key] = tagsArray
}
} else if ((!libraryItem.media.metadata[key] || options.overrideDetails)) {

View File

@@ -168,7 +168,8 @@ module.exports.downloadFile = async (url, filepath) => {
const response = await axios({
url,
method: 'GET',
responseType: 'stream'
responseType: 'stream',
timeout: 30000
})
response.data.pipe(writer)
return new Promise((resolve, reject) => {

View File

@@ -254,6 +254,7 @@ function getBookDataFromDir(folderPath, relPath, parseSubtitle = false) {
path: Path.posix.join(folderPath, relPath) // i.e. /audiobook/Author Name/Book Name/..
}
}
module.exports.getBookDataFromDir = getBookDataFromDir
function getNarrator(folder) {
let pattern = /^(?<title>.*) \{(?<narrators>.*)\}$/
@@ -277,16 +278,15 @@ function getSequence(folder) {
// ]
// Matches a valid volume string. Also matches a book whose title starts with a 1 to 3 digit number. Will handle that later.
let pattern = /^(?<volumeLabel>vol\.? |volume |book )?(?<sequence>\d{1,3}(?:\.\d{1,2})?)(?<trailingDot>\.?)(?: (?<suffix>.*))?$/i
let pattern = /^(?<volumeLabel>vol\.? |volume |book )?(?<sequence>\d{0,3}(?:\.\d{1,2})?)(?<trailingDot>\.?)(?: (?<suffix>.*))?$/i
let volumeNumber = null
let parts = folder.split(' - ')
for (let i = 0; i < parts.length; i++) {
let match = parts[i].match(pattern)
// This excludes '101 Dalmations' but includes '101. Dalmations'
if (match && !(match.groups.suffix && !(match.groups.volumeLabel || match.groups.trailingDot))) {
volumeNumber = match.groups.sequence
volumeNumber = isNaN(match.groups.sequence) ? match.groups.sequence : Number(match.groups.sequence).toString()
parts[i] = match.groups.suffix
if (!parts[i]) { parts.splice(i, 1) }
break
@@ -338,7 +338,7 @@ function getDataFromMediaDir(libraryMediaType, folderPath, relPath, serverSettin
var parseSubtitle = !!serverSettings.scannerParseSubtitle
return getBookDataFromDir(folderPath, relPath, parseSubtitle)
} else {
return this.getPodcastDataFromDir(folderPath, relPath)
return getPodcastDataFromDir(folderPath, relPath)
}
}