mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Fix scan for audiobook directories in root dir
This commit is contained in:
		
							parent
							
								
									5ecfaa88c2
								
							
						
					
					
						commit
						a93f409dcd
					
				@ -23,6 +23,8 @@
 | 
			
		||||
            </ui-btn>
 | 
			
		||||
            <ui-btn :padding-x="4" class="flex items-center ml-4" @click="editClick"><span class="material-icons text-white pr-2" style="font-size: 18px">edit</span>Edit</ui-btn>
 | 
			
		||||
 | 
			
		||||
            <ui-btn v-if="isDeveloperMode" class="mx-2" @click="openRssFeed">Open RSS Feed</ui-btn>
 | 
			
		||||
 | 
			
		||||
            <div v-if="progressPercent > 0" class="px-4 py-2 bg-primary text-sm font-semibold rounded-md text-gray-200 ml-4 relative" :class="resettingProgress ? 'opacity-25' : ''">
 | 
			
		||||
              <p class="leading-6">Your Progress: {{ Math.round(progressPercent * 100) }}%</p>
 | 
			
		||||
              <p class="text-gray-400 text-xs">{{ $elapsedPretty(userTimeRemaining) }} remaining</p>
 | 
			
		||||
@ -82,6 +84,9 @@ export default {
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    isDeveloperMode() {
 | 
			
		||||
      return this.$store.state.developerMode
 | 
			
		||||
    },
 | 
			
		||||
    missingPartChunks() {
 | 
			
		||||
      if (this.missingParts === 1) return this.missingParts[0]
 | 
			
		||||
      var chunks = []
 | 
			
		||||
@ -180,6 +185,18 @@ export default {
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    openRssFeed() {
 | 
			
		||||
      this.$axios
 | 
			
		||||
        .$post('/api/feed', { audiobookId: this.audiobook.id })
 | 
			
		||||
        .then((res) => {
 | 
			
		||||
          console.log('Feed open', res)
 | 
			
		||||
          this.$toast.success('RSS Feed Open')
 | 
			
		||||
        })
 | 
			
		||||
        .catch((error) => {
 | 
			
		||||
          console.error('Failed', error)
 | 
			
		||||
          this.$toast.error('Failed to open feed')
 | 
			
		||||
        })
 | 
			
		||||
    },
 | 
			
		||||
    startStream() {
 | 
			
		||||
      this.$store.commit('setStreamAudiobook', this.audiobook)
 | 
			
		||||
      this.$root.socket.emit('open_stream', this.audiobook.id)
 | 
			
		||||
 | 
			
		||||
@ -53,6 +53,7 @@
 | 
			
		||||
        </a>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="fixed bottom-0 left-0 w-10 h-10" @dblclick="setDeveloperMode"></div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@ -70,6 +71,11 @@ export default {
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    setDeveloperMode() {
 | 
			
		||||
      var value = !this.$store.state.developerMode
 | 
			
		||||
      this.$store.commit('setDeveloperMode', value)
 | 
			
		||||
      this.$toast.info(`Developer Mode ${value ? 'Enabled' : 'Disabled'}`)
 | 
			
		||||
    },
 | 
			
		||||
    scan() {
 | 
			
		||||
      this.$root.socket.emit('scan')
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,8 @@ export const state = () => ({
 | 
			
		||||
  selectedAudiobook: null,
 | 
			
		||||
  playOnLoad: false,
 | 
			
		||||
  isScanning: false,
 | 
			
		||||
  scanProgress: null
 | 
			
		||||
  scanProgress: null,
 | 
			
		||||
  developerMode: false
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
export const getters = {
 | 
			
		||||
@ -59,5 +60,8 @@ export const mutations = {
 | 
			
		||||
  setScanProgress(state, progress) {
 | 
			
		||||
    if (progress > 0) state.isScanning = true
 | 
			
		||||
    state.scanProgress = progress
 | 
			
		||||
  },
 | 
			
		||||
  setDeveloperMode(state, val) {
 | 
			
		||||
    state.developerMode = val
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										44
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "audiobookshelf",
 | 
			
		||||
  "version": "0.9.61-beta.0",
 | 
			
		||||
  "version": "0.9.64-beta",
 | 
			
		||||
  "lockfileVersion": 1,
 | 
			
		||||
  "requires": true,
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
@ -561,6 +561,11 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
 | 
			
		||||
      "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
 | 
			
		||||
    },
 | 
			
		||||
    "ip": {
 | 
			
		||||
      "version": "1.1.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
 | 
			
		||||
      "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo="
 | 
			
		||||
    },
 | 
			
		||||
    "ipaddr.js": {
 | 
			
		||||
      "version": "1.9.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
 | 
			
		||||
@ -833,6 +838,14 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
 | 
			
		||||
      "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw=="
 | 
			
		||||
    },
 | 
			
		||||
    "podcast": {
 | 
			
		||||
      "version": "1.3.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/podcast/-/podcast-1.3.0.tgz",
 | 
			
		||||
      "integrity": "sha512-L0UNP8SMdoihxgpdXCaXZEKZBBCGzld5PSy8QbQYsk83bdzq14cdW8flJduZjQNbB2If5frwVIC5VpMq9CHchA==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "rss": "^1.2.2"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "proper-lockfile": {
 | 
			
		||||
      "version": "4.1.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz",
 | 
			
		||||
@ -913,6 +926,30 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
 | 
			
		||||
      "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs="
 | 
			
		||||
    },
 | 
			
		||||
    "rss": {
 | 
			
		||||
      "version": "1.2.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/rss/-/rss-1.2.2.tgz",
 | 
			
		||||
      "integrity": "sha1-UKFpiHYTgTOnT5oF0r3I240nqSE=",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "mime-types": "2.1.13",
 | 
			
		||||
        "xml": "1.0.1"
 | 
			
		||||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "mime-db": {
 | 
			
		||||
          "version": "1.25.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.25.0.tgz",
 | 
			
		||||
          "integrity": "sha1-wY29fHOl2/b0SgJNwNFloeexw5I="
 | 
			
		||||
        },
 | 
			
		||||
        "mime-types": {
 | 
			
		||||
          "version": "2.1.13",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.13.tgz",
 | 
			
		||||
          "integrity": "sha1-4HqqnGxrmnyjASxpADrSWjnpKog=",
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "mime-db": "~1.25.0"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "safe-buffer": {
 | 
			
		||||
      "version": "5.1.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
 | 
			
		||||
@ -1101,6 +1138,11 @@
 | 
			
		||||
      "version": "7.4.6",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
 | 
			
		||||
      "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A=="
 | 
			
		||||
    },
 | 
			
		||||
    "xml": {
 | 
			
		||||
      "version": "1.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
 | 
			
		||||
      "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU="
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -17,11 +17,13 @@
 | 
			
		||||
    "express": "^4.17.1",
 | 
			
		||||
    "fluent-ffmpeg": "^2.1.2",
 | 
			
		||||
    "fs-extra": "^10.0.0",
 | 
			
		||||
    "ip": "^1.1.5",
 | 
			
		||||
    "jsonwebtoken": "^8.5.1",
 | 
			
		||||
    "libgen": "^2.1.0",
 | 
			
		||||
    "njodb": "^0.4.20",
 | 
			
		||||
    "node-dir": "^0.1.17",
 | 
			
		||||
    "podcast": "^1.3.0",
 | 
			
		||||
    "socket.io": "^4.1.3"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -2,11 +2,12 @@ const express = require('express')
 | 
			
		||||
const Logger = require('./Logger')
 | 
			
		||||
 | 
			
		||||
class ApiController {
 | 
			
		||||
  constructor(db, scanner, auth, streamManager, emitter) {
 | 
			
		||||
  constructor(db, scanner, auth, streamManager, rssFeeds, emitter) {
 | 
			
		||||
    this.db = db
 | 
			
		||||
    this.scanner = scanner
 | 
			
		||||
    this.auth = auth
 | 
			
		||||
    this.streamManager = streamManager
 | 
			
		||||
    this.rssFeeds = rssFeeds
 | 
			
		||||
    this.emitter = emitter
 | 
			
		||||
 | 
			
		||||
    this.router = express()
 | 
			
		||||
@ -35,6 +36,8 @@ class ApiController {
 | 
			
		||||
    this.router.post('/authorize', this.authorize.bind(this))
 | 
			
		||||
 | 
			
		||||
    this.router.get('/genres', this.getGenres.bind(this))
 | 
			
		||||
 | 
			
		||||
    this.router.post('/feed', this.openRssFeed.bind(this))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  find(req, res) {
 | 
			
		||||
@ -42,7 +45,6 @@ class ApiController {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  findCovers(req, res) {
 | 
			
		||||
    console.log('Find covers', req.query)
 | 
			
		||||
    this.scanner.findCovers(req, res)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -174,6 +176,15 @@ class ApiController {
 | 
			
		||||
    this.auth.userChangePassword(req, res)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async openRssFeed(req, res) {
 | 
			
		||||
    var audiobookId = req.body.audiobookId
 | 
			
		||||
    var audiobook = this.db.audiobooks.find(ab => ab.id === audiobookId)
 | 
			
		||||
    if (!audiobook) return res.sendStatus(404)
 | 
			
		||||
    var feed = await this.rssFeeds.openFeed(audiobook)
 | 
			
		||||
    console.log('Feed open', feed)
 | 
			
		||||
    res.json(feed)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getGenres(req, res) {
 | 
			
		||||
    res.json({
 | 
			
		||||
      genres: this.db.getGenres()
 | 
			
		||||
 | 
			
		||||
@ -75,6 +75,10 @@ class Auth {
 | 
			
		||||
  verifyToken(token) {
 | 
			
		||||
    return new Promise((resolve) => {
 | 
			
		||||
      jwt.verify(token, process.env.TOKEN_SECRET, (err, payload) => {
 | 
			
		||||
        if (!payload || err) {
 | 
			
		||||
          Logger.error('JWT Verify Token Failed', err)
 | 
			
		||||
          return resolve(null)
 | 
			
		||||
        }
 | 
			
		||||
        var user = this.users.find(u => u.id === payload.userId)
 | 
			
		||||
        resolve(user || null)
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										53
									
								
								server/RssFeeds.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								server/RssFeeds.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,53 @@
 | 
			
		||||
const Podcast = require('podcast')
 | 
			
		||||
const express = require('express')
 | 
			
		||||
const ip = require('ip')
 | 
			
		||||
const Logger = require('./Logger')
 | 
			
		||||
 | 
			
		||||
class RssFeeds {
 | 
			
		||||
  constructor(Port, db) {
 | 
			
		||||
    this.Port = Port
 | 
			
		||||
    this.db = db
 | 
			
		||||
    this.feeds = {}
 | 
			
		||||
 | 
			
		||||
    this.router = express()
 | 
			
		||||
    this.init()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  init() {
 | 
			
		||||
    this.router.get('/:id', this.getFeed.bind(this))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getFeed(req, res) {
 | 
			
		||||
    var feed = this.feeds[req.params.id]
 | 
			
		||||
    if (!feed) return null
 | 
			
		||||
    var xml = feed.buildXml()
 | 
			
		||||
    res.set('Content-Type', 'text/xml')
 | 
			
		||||
    res.send(xml)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  openFeed(audiobook) {
 | 
			
		||||
    var serverAddress = 'http://' + ip.address('public', 'ipv4') + ':' + this.Port
 | 
			
		||||
    Logger.info('Open RSS Feed', 'Server address', serverAddress)
 | 
			
		||||
 | 
			
		||||
    var feedId = (Date.now() + Math.floor(Math.random() * 1000)).toString(36)
 | 
			
		||||
    const feed = new Podcast({
 | 
			
		||||
      title: audiobook.title,
 | 
			
		||||
      description: 'AudioBookshelf RSS Feed',
 | 
			
		||||
      feedUrl: `${serverAddress}/feeds/${feedId}`,
 | 
			
		||||
      imageUrl: `${serverAddress}/Logo.png`,
 | 
			
		||||
      author: 'advplyr',
 | 
			
		||||
      language: 'en'
 | 
			
		||||
    })
 | 
			
		||||
    audiobook.tracks.forEach((track) => {
 | 
			
		||||
      feed.addItem({
 | 
			
		||||
        title: `Track ${track.index}`,
 | 
			
		||||
        description: `AudioBookshelf Audiobook Track #${track.index}`,
 | 
			
		||||
        url: `${serverAddress}/feeds/${feedId}?track=${track.index}`,
 | 
			
		||||
        author: 'advplyr'
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
    this.feeds[feedId] = feed
 | 
			
		||||
    return feed
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
module.exports = RssFeeds
 | 
			
		||||
@ -11,6 +11,7 @@ const Db = require('./Db')
 | 
			
		||||
const ApiController = require('./ApiController')
 | 
			
		||||
const HlsController = require('./HlsController')
 | 
			
		||||
const StreamManager = require('./StreamManager')
 | 
			
		||||
const RssFeeds = require('./RssFeeds')
 | 
			
		||||
const Logger = require('./Logger')
 | 
			
		||||
 | 
			
		||||
class Server {
 | 
			
		||||
@ -30,9 +31,11 @@ class Server {
 | 
			
		||||
    this.watcher = new Watcher(this.AudiobookPath)
 | 
			
		||||
    this.scanner = new Scanner(this.AudiobookPath, this.MetadataPath, this.db, this.emitter.bind(this))
 | 
			
		||||
    this.streamManager = new StreamManager(this.db, this.MetadataPath)
 | 
			
		||||
    this.apiController = new ApiController(this.db, this.scanner, this.auth, this.streamManager, this.emitter.bind(this))
 | 
			
		||||
    this.rssFeeds = new RssFeeds(this.Port, this.db)
 | 
			
		||||
    this.apiController = new ApiController(this.db, this.scanner, this.auth, this.streamManager, this.rssFeeds, this.emitter.bind(this))
 | 
			
		||||
    this.hlsController = new HlsController(this.db, this.scanner, this.auth, this.streamManager, this.emitter.bind(this), this.MetadataPath)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    this.server = null
 | 
			
		||||
    this.io = null
 | 
			
		||||
 | 
			
		||||
@ -112,11 +115,13 @@ class Server {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    app.use(express.static(this.MetadataPath))
 | 
			
		||||
    app.use(express.static(Path.join(global.appRoot, 'static')))
 | 
			
		||||
    app.use(express.urlencoded({ extended: true }));
 | 
			
		||||
    app.use(express.json())
 | 
			
		||||
 | 
			
		||||
    app.use('/api', this.authMiddleware.bind(this), this.apiController.router)
 | 
			
		||||
    app.use('/hls', this.authMiddleware.bind(this), this.hlsController.router)
 | 
			
		||||
    app.use('/feeds', this.rssFeeds.router)
 | 
			
		||||
 | 
			
		||||
    app.get('/', (req, res) => {
 | 
			
		||||
      res.sendFile('/index.html')
 | 
			
		||||
 | 
			
		||||
@ -39,13 +39,15 @@ async function getAllAudiobookFiles(abRootPath) {
 | 
			
		||||
    var pathformat = Path.parse(relpath)
 | 
			
		||||
    var path = pathformat.dir
 | 
			
		||||
 | 
			
		||||
    // If relative file directory has 3 folders, then the middle folder will be series
 | 
			
		||||
    var splitDir = pathformat.dir.split(Path.sep)
 | 
			
		||||
    if (splitDir.length === 1) {
 | 
			
		||||
      Logger.error('Invalid file in root dir', filepath)
 | 
			
		||||
    if (!path) {
 | 
			
		||||
      Logger.error('Ignoring file in root dir', filepath)
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
    var author = splitDir.shift()
 | 
			
		||||
 | 
			
		||||
    // If relative file directory has 3 folders, then the middle folder will be series
 | 
			
		||||
    var splitDir = pathformat.dir.split(Path.sep)
 | 
			
		||||
    var author = null
 | 
			
		||||
    if (splitDir.length > 1) author = splitDir.shift()
 | 
			
		||||
    var series = null
 | 
			
		||||
    if (splitDir.length > 1) series = splitDir.shift()
 | 
			
		||||
    var title = splitDir.shift()
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								static/Logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								static/Logo.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 72 KiB  | 
		Loading…
	
		Reference in New Issue
	
	Block a user