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
							
								
									f4cb5d101e
								
							
						
					
					
						commit
						73a786879e
					
				| @ -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,10 +17,12 @@ | ||||
|     "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