mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-11-03 19:07:00 -05:00 
			
		
		
		
	Use local image as cover if found, adding release-it version control
This commit is contained in:
		
							parent
							
								
									7d4e2e3d97
								
							
						
					
					
						commit
						f30fa2fb0c
					
				@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					.env
 | 
				
			||||||
node_modules
 | 
					node_modules
 | 
				
			||||||
npm-debug.log
 | 
					npm-debug.log
 | 
				
			||||||
.git
 | 
					.git
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					.env
 | 
				
			||||||
dev.js
 | 
					dev.js
 | 
				
			||||||
node_modules/
 | 
					node_modules/
 | 
				
			||||||
/config/
 | 
					/config/
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										5
									
								
								.release-it.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.release-it.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "github": {
 | 
				
			||||||
 | 
					    "release": true
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,6 +1,5 @@
 | 
				
			|||||||
### STAGE 0: FFMPEG ###
 | 
					### STAGE 0: FFMPEG ###
 | 
				
			||||||
FROM jrottenberg/ffmpeg:4.1-alpine AS ffmpeg
 | 
					FROM jrottenberg/ffmpeg:4.1-alpine AS ffmpeg
 | 
				
			||||||
# FROM alfg/ffmpeg AS ffmpeg
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
### STAGE 1: Build client ###
 | 
					### STAGE 1: Build client ###
 | 
				
			||||||
FROM node:12-alpine AS build
 | 
					FROM node:12-alpine AS build
 | 
				
			||||||
@ -11,8 +10,6 @@ RUN npm run generate
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
### STAGE 2: Build server ###
 | 
					### STAGE 2: Build server ###
 | 
				
			||||||
FROM node:12-alpine
 | 
					FROM node:12-alpine
 | 
				
			||||||
# RUN apk add --no-cache ffmpeg
 | 
					 | 
				
			||||||
# RUN apt-get install -y ffmpeg
 | 
					 | 
				
			||||||
ENV NODE_ENV=production
 | 
					ENV NODE_ENV=production
 | 
				
			||||||
ENV LOG_LEVEL=INFO
 | 
					ENV LOG_LEVEL=INFO
 | 
				
			||||||
COPY --from=build /client/dist /client/dist
 | 
					COPY --from=build /client/dist /client/dist
 | 
				
			||||||
@ -22,5 +19,4 @@ COPY package.json package.json
 | 
				
			|||||||
COPY server server
 | 
					COPY server server
 | 
				
			||||||
RUN npm install --production
 | 
					RUN npm install --production
 | 
				
			||||||
EXPOSE 80
 | 
					EXPOSE 80
 | 
				
			||||||
# CMD ["node", "index.js"]
 | 
					 | 
				
			||||||
CMD ["npm", "start"]
 | 
					CMD ["npm", "start"]
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,13 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div class="relative rounded-sm overflow-hidden" :style="{ height: width * 1.6 + 'px', width: width + 'px', maxWidth: width + 'px', minWidth: width + 'px' }">
 | 
					  <div class="relative rounded-sm overflow-hidden" :style="{ height: width * 1.6 + 'px', width: width + 'px', maxWidth: width + 'px', minWidth: width + 'px' }">
 | 
				
			||||||
    <img ref="cover" :src="cover" class="w-full h-full object-cover" />
 | 
					    <img ref="cover" :src="cover" @error="imageError" class="w-full h-full object-cover" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div v-if="imageFailed" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full bg-red-100" :style="{ padding: placeholderCoverPadding + 'rem' }">
 | 
				
			||||||
 | 
					      <div class="w-full h-full border-2 border-error flex flex-col items-center justify-center">
 | 
				
			||||||
 | 
					        <img src="/LogoTransparent.png" class="mb-2" :style="{ height: 64 * sizeMultiplier + 'px' }" />
 | 
				
			||||||
 | 
					        <p class="text-center font-book text-error" :style="{ fontSize: titleFontSize + 'rem' }">Invalid Cover</p>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
    <div v-if="!hasCover" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full flex items-center justify-center" :style="{ padding: placeholderCoverPadding + 'rem' }">
 | 
					    <div v-if="!hasCover" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full flex items-center justify-center" :style="{ padding: placeholderCoverPadding + 'rem' }">
 | 
				
			||||||
      <div>
 | 
					      <div>
 | 
				
			||||||
        <p class="text-center font-book" style="color: rgb(247 223 187)" :style="{ fontSize: titleFontSize + 'rem' }">{{ titleCleaned }}</p>
 | 
					        <p class="text-center font-book" style="color: rgb(247 223 187)" :style="{ fontSize: titleFontSize + 'rem' }">{{ titleCleaned }}</p>
 | 
				
			||||||
@ -26,7 +32,9 @@ export default {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  data() {
 | 
					  data() {
 | 
				
			||||||
    return {}
 | 
					    return {
 | 
				
			||||||
 | 
					      imageFailed: false
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  computed: {
 | 
					  computed: {
 | 
				
			||||||
    book() {
 | 
					    book() {
 | 
				
			||||||
@ -56,23 +64,28 @@ export default {
 | 
				
			|||||||
    hasCover() {
 | 
					    hasCover() {
 | 
				
			||||||
      return !!this.book.cover
 | 
					      return !!this.book.cover
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    fontSizeMultiplier() {
 | 
					    sizeMultiplier() {
 | 
				
			||||||
      return this.width / 120
 | 
					      return this.width / 120
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    titleFontSize() {
 | 
					    titleFontSize() {
 | 
				
			||||||
      return 0.75 * this.fontSizeMultiplier
 | 
					      return 0.75 * this.sizeMultiplier
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    authorFontSize() {
 | 
					    authorFontSize() {
 | 
				
			||||||
      return 0.6 * this.fontSizeMultiplier
 | 
					      return 0.6 * this.sizeMultiplier
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    placeholderCoverPadding() {
 | 
					    placeholderCoverPadding() {
 | 
				
			||||||
      return 0.8 * this.fontSizeMultiplier
 | 
					      return 0.8 * this.sizeMultiplier
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    authorBottom() {
 | 
					    authorBottom() {
 | 
				
			||||||
      return 0.75 * this.fontSizeMultiplier
 | 
					      return 0.75 * this.sizeMultiplier
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  methods: {
 | 
				
			||||||
 | 
					    imageError(err) {
 | 
				
			||||||
 | 
					      console.error('ImgError', err)
 | 
				
			||||||
 | 
					      this.imageFailed = true
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  methods: {},
 | 
					 | 
				
			||||||
  mounted() {}
 | 
					  mounted() {}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div class="w-full">
 | 
					  <div class="w-full">
 | 
				
			||||||
    <p class="px-1 text-sm">{{ label }}</p>
 | 
					    <p class="px-1 text-sm font-semibold">{{ label }}</p>
 | 
				
			||||||
    <div ref="wrapper" class="relative">
 | 
					    <div ref="wrapper" class="relative">
 | 
				
			||||||
      <form @submit.prevent="submitForm">
 | 
					      <form @submit.prevent="submitForm">
 | 
				
			||||||
        <div ref="inputWrapper" style="min-height: 40px" class="flex-wrap relative w-full shadow-sm flex items-center bg-primary border border-gray-600 rounded-md px-2 py-1 cursor-text" @click.stop.prevent="clickWrapper" @mouseup.stop.prevent @mousedown.prevent>
 | 
					        <div ref="inputWrapper" style="min-height: 40px" class="flex-wrap relative w-full shadow-sm flex items-center bg-primary border border-gray-600 rounded-md px-2 py-1 cursor-text" @click.stop.prevent="clickWrapper" @mouseup.stop.prevent @mousedown.prevent>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div class="w-full">
 | 
					  <div class="w-full">
 | 
				
			||||||
    <p class="px-1 text-sm">{{ label }}</p>
 | 
					    <p class="px-1 text-sm font-semibold">{{ label }}</p>
 | 
				
			||||||
    <ui-text-input v-model="inputValue" :disabled="disabled" :type="type" class="w-full" />
 | 
					    <ui-text-input v-model="inputValue" :disabled="disabled" :type="type" class="w-full" />
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div class="w-full">
 | 
					  <div class="w-full">
 | 
				
			||||||
    <p class="px-1 text-sm">{{ label }}</p>
 | 
					    <p class="px-1 text-sm font-semibold">{{ label }}</p>
 | 
				
			||||||
    <ui-textarea-input v-model="inputValue" :rows="rows" class="w-full" />
 | 
					    <ui-textarea-input v-model="inputValue" :rows="rows" class="w-full" />
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
				
			|||||||
@ -136,6 +136,11 @@ export default {
 | 
				
			|||||||
      this.socket.on('scan_start', this.scanStart)
 | 
					      this.socket.on('scan_start', this.scanStart)
 | 
				
			||||||
      this.socket.on('scan_complete', this.scanComplete)
 | 
					      this.socket.on('scan_complete', this.scanComplete)
 | 
				
			||||||
      this.socket.on('scan_progress', this.scanProgress)
 | 
					      this.socket.on('scan_progress', this.scanProgress)
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    checkVersion() {
 | 
				
			||||||
 | 
					      this.$axios.$get('http://github.com/advplyr/audiobookshelf/raw/master/package.json').then((data) => {
 | 
				
			||||||
 | 
					        console.log('GOT DATA', data)
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  beforeMount() {
 | 
					  beforeMount() {
 | 
				
			||||||
@ -145,6 +150,7 @@ export default {
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  mounted() {
 | 
					  mounted() {
 | 
				
			||||||
    this.initializeSocket()
 | 
					    this.initializeSocket()
 | 
				
			||||||
 | 
					    this.checkVersion()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
@ -35,7 +35,7 @@ module.exports = {
 | 
				
			|||||||
    ],
 | 
					    ],
 | 
				
			||||||
    link: [
 | 
					    link: [
 | 
				
			||||||
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
 | 
					      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
 | 
				
			||||||
      { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Fira+Mono&family=Ubuntu+Mono&family=Open+Sans:wght@600&family=Gentium+Book+Basic' },
 | 
					      { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Fira+Mono&family=Ubuntu+Mono&family=Open+Sans:wght@400;600&family=Gentium+Book+Basic' },
 | 
				
			||||||
      { rel: 'stylesheet', href: 'https://fonts.googleapis.com/icon?family=Material+Icons' }
 | 
					      { rel: 'stylesheet', href: 'https://fonts.googleapis.com/icon?family=Material+Icons' }
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
@ -69,7 +69,8 @@ module.exports = {
 | 
				
			|||||||
  ],
 | 
					  ],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  proxy: {
 | 
					  proxy: {
 | 
				
			||||||
    '/dev/': { target: 'http://localhost:3333', pathRewrite: { '^/dev/': '' } }
 | 
					    '/dev/': { target: 'http://localhost:3333', pathRewrite: { '^/dev/': '' } },
 | 
				
			||||||
 | 
					    '/local/': { target: process.env.NODE_ENV !== 'production' ? 'http://localhost:3333' : '/', pathRewrite: { '^/local/': '' } }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  io: {
 | 
					  io: {
 | 
				
			||||||
 | 
				
			|||||||
@ -14,7 +14,7 @@
 | 
				
			|||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div class="h-0.5 bg-primary bg-opacity-50 w-full" />
 | 
					      <div class="h-0.5 bg-primary bg-opacity-50 w-full" />
 | 
				
			||||||
      <div class="flex items-center py-4">
 | 
					      <div class="flex items-center py-4">
 | 
				
			||||||
        <p class="font-mono">Beta v{{ $config.version }}</p>
 | 
					        <p class="font-mono">v{{ $config.version }}</p>
 | 
				
			||||||
        <div class="flex-grow" />
 | 
					        <div class="flex-grow" />
 | 
				
			||||||
        <p class="pr-2 text-sm font-book text-yellow-400">Report bugs, request features, provide feedback, and contribute on <a class="underline" href="https://github.com/advplyr/audiobookshelf" target="_blank">github</a>.</p>
 | 
					        <p class="pr-2 text-sm font-book text-yellow-400">Report bugs, request features, provide feedback, and contribute on <a class="underline" href="https://github.com/advplyr/audiobookshelf" target="_blank">github</a>.</p>
 | 
				
			||||||
        <a href="https://github.com/advplyr/audiobookshelf" target="_blank" class="text-white hover:text-gray-200 hover:scale-150 hover:rotate-6 transform duration-500">
 | 
					        <a href="https://github.com/advplyr/audiobookshelf" target="_blank" class="text-white hover:text-gray-200 hover:scale-150 hover:rotate-6 transform duration-500">
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,9 @@
 | 
				
			|||||||
export default function ({ $axios, store }) {
 | 
					export default function ({ $axios, store }) {
 | 
				
			||||||
  $axios.onRequest(config => {
 | 
					  $axios.onRequest(config => {
 | 
				
			||||||
    console.log('Making request to ' + config.url)
 | 
					    console.log('Making request to ' + config.url)
 | 
				
			||||||
 | 
					    if (config.url.startsWith('http:') || config.url.startsWith('https:')) {
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    var bearerToken = store.state.user ? store.state.user.token : null
 | 
					    var bearerToken = store.state.user ? store.state.user.token : null
 | 
				
			||||||
    // console.log('Bearer token', bearerToken)
 | 
					    // console.log('Bearer token', bearerToken)
 | 
				
			||||||
    if (bearerToken) {
 | 
					    if (bearerToken) {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2137
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2137
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -5,7 +5,9 @@
 | 
				
			|||||||
  "main": "index.js",
 | 
					  "main": "index.js",
 | 
				
			||||||
  "scripts": {
 | 
					  "scripts": {
 | 
				
			||||||
    "dev": "node index.js",
 | 
					    "dev": "node index.js",
 | 
				
			||||||
    "start": "node index.js"
 | 
					    "start": "node index.js",
 | 
				
			||||||
 | 
					    "release": "dotenv release-it --disable-metrics --no-npm --npm.skipChecks",
 | 
				
			||||||
 | 
					    "release-dry": "dotenv release-it --disable-metrics --no-npm --npm.skipChecks --dry-run"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "author": "advplyr",
 | 
					  "author": "advplyr",
 | 
				
			||||||
  "license": "ISC",
 | 
					  "license": "ISC",
 | 
				
			||||||
@ -22,5 +24,9 @@
 | 
				
			|||||||
    "njodb": "^0.4.20",
 | 
					    "njodb": "^0.4.20",
 | 
				
			||||||
    "node-dir": "^0.1.17",
 | 
					    "node-dir": "^0.1.17",
 | 
				
			||||||
    "socket.io": "^4.1.3"
 | 
					    "socket.io": "^4.1.3"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "devDependencies": {
 | 
				
			||||||
 | 
					    "dotenv-cli": "^4.0.0",
 | 
				
			||||||
 | 
					    "release-it": "^14.11.5"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					const Path = require('path')
 | 
				
			||||||
class Book {
 | 
					class Book {
 | 
				
			||||||
  constructor(book = null) {
 | 
					  constructor(book = null) {
 | 
				
			||||||
    this.olid = null
 | 
					    this.olid = null
 | 
				
			||||||
@ -42,7 +43,6 @@ class Book {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  setData(data) {
 | 
					  setData(data) {
 | 
				
			||||||
    console.log('SET DATA', data)
 | 
					 | 
				
			||||||
    this.olid = data.olid || null
 | 
					    this.olid = data.olid || null
 | 
				
			||||||
    this.title = data.title || null
 | 
					    this.title = data.title || null
 | 
				
			||||||
    this.author = data.author || null
 | 
					    this.author = data.author || null
 | 
				
			||||||
@ -51,6 +51,14 @@ class Book {
 | 
				
			|||||||
    this.description = data.description || null
 | 
					    this.description = data.description || null
 | 
				
			||||||
    this.cover = data.cover || null
 | 
					    this.cover = data.cover || null
 | 
				
			||||||
    this.genres = data.genres || []
 | 
					    this.genres = data.genres || []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Use first image file as cover
 | 
				
			||||||
 | 
					    if (data.otherFiles && data.otherFiles.length) {
 | 
				
			||||||
 | 
					      var imageFile = data.otherFiles.find(f => f.filetype === 'image')
 | 
				
			||||||
 | 
					      if (imageFile) {
 | 
				
			||||||
 | 
					        this.cover = Path.join('/local', imageFile.path)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  update(payload) {
 | 
					  update(payload) {
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,6 @@ const ApiController = require('./ApiController')
 | 
				
			|||||||
const HlsController = require('./HlsController')
 | 
					const HlsController = require('./HlsController')
 | 
				
			||||||
const StreamManager = require('./StreamManager')
 | 
					const StreamManager = require('./StreamManager')
 | 
				
			||||||
const Logger = require('./Logger')
 | 
					const Logger = require('./Logger')
 | 
				
			||||||
const streamTest = require('./streamTest')
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Server {
 | 
					class Server {
 | 
				
			||||||
  constructor(PORT, CONFIG_PATH, METADATA_PATH, AUDIOBOOK_PATH) {
 | 
					  constructor(PORT, CONFIG_PATH, METADATA_PATH, AUDIOBOOK_PATH) {
 | 
				
			||||||
@ -110,7 +109,6 @@ class Server {
 | 
				
			|||||||
      const distPath = Path.join(global.appRoot, '/client/dist')
 | 
					      const distPath = Path.join(global.appRoot, '/client/dist')
 | 
				
			||||||
      app.use(express.static(distPath))
 | 
					      app.use(express.static(distPath))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    app.use(express.static(this.AudiobookPath))
 | 
					    app.use(express.static(this.AudiobookPath))
 | 
				
			||||||
    app.use(express.static(this.MetadataPath))
 | 
					    app.use(express.static(this.MetadataPath))
 | 
				
			||||||
    app.use(express.urlencoded({ extended: true }));
 | 
					    app.use(express.urlencoded({ extended: true }));
 | 
				
			||||||
@ -122,13 +120,6 @@ class Server {
 | 
				
			|||||||
    app.get('/', (req, res) => {
 | 
					    app.get('/', (req, res) => {
 | 
				
			||||||
      res.sendFile('/index.html')
 | 
					      res.sendFile('/index.html')
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    app.get('/test/:id', (req, res) => {
 | 
					 | 
				
			||||||
      var audiobook = this.audiobooks.find(a => a.id === req.params.id)
 | 
					 | 
				
			||||||
      var startTime = !isNaN(req.query.start) ? Number(req.query.start) : 0
 | 
					 | 
				
			||||||
      Logger.info('/test with audiobook', audiobook.title)
 | 
					 | 
				
			||||||
      streamTest.start(audiobook, startTime)
 | 
					 | 
				
			||||||
      res.sendStatus(200)
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    app.post('/login', (req, res) => this.auth.login(req, res))
 | 
					    app.post('/login', (req, res) => this.auth.login(req, res))
 | 
				
			||||||
    app.post('/logout', this.logout.bind(this))
 | 
					    app.post('/logout', this.logout.bind(this))
 | 
				
			||||||
 | 
				
			|||||||
@ -1,112 +0,0 @@
 | 
				
			|||||||
const Ffmpeg = require('fluent-ffmpeg')
 | 
					 | 
				
			||||||
const Path = require('path')
 | 
					 | 
				
			||||||
const fs = require('fs-extra')
 | 
					 | 
				
			||||||
const Logger = require('./Logger')
 | 
					 | 
				
			||||||
const { secondsToTimestamp } = require('./utils/fileUtils')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function escapeSingleQuotes(path) {
 | 
					 | 
				
			||||||
  return path.replace(/\\/g, '/').replace(/ /g, '\\ ').replace(/'/g, '\\\'')
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function getNumSegments(audiobook, segmentLength) {
 | 
					 | 
				
			||||||
  var numSegments = Math.floor(audiobook.totalDuration / segmentLength)
 | 
					 | 
				
			||||||
  var remainingTime = audiobook.totalDuration - (numSegments * segmentLength)
 | 
					 | 
				
			||||||
  if (remainingTime > 0) numSegments++
 | 
					 | 
				
			||||||
  return numSegments
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async function start(audiobook, startTime = 0, segmentLength = 6) {
 | 
					 | 
				
			||||||
  var testDir = Path.join(global.appRoot, 'test', audiobook.id)
 | 
					 | 
				
			||||||
  var existsAlready = await fs.pathExists(testDir)
 | 
					 | 
				
			||||||
  if (existsAlready) {
 | 
					 | 
				
			||||||
    await fs.remove(testDir).then(() => {
 | 
					 | 
				
			||||||
      Logger.info('Deleted test dir data', testDir)
 | 
					 | 
				
			||||||
    }).catch((err) => {
 | 
					 | 
				
			||||||
      Logger.error('Failed to delete test dir', err)
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  fs.ensureDirSync(testDir)
 | 
					 | 
				
			||||||
  var concatFilePath = Path.join(testDir, 'concat.txt')
 | 
					 | 
				
			||||||
  var playlistPath = Path.join(testDir, 'output.m3u8')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const numSegments = getNumSegments(audiobook, segmentLength)
 | 
					 | 
				
			||||||
  const segmentStartNumber = Math.floor(startTime / segmentLength)
 | 
					 | 
				
			||||||
  Logger.info(`[STREAM] START STREAM - Num Segments: ${numSegments} - Segment Start: ${segmentStartNumber}`)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const tracks = audiobook.tracks
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const ffmpeg = Ffmpeg()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  var currTrackEnd = 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  var startingTrack = tracks.find(t => {
 | 
					 | 
				
			||||||
    currTrackEnd += t.duration
 | 
					 | 
				
			||||||
    return startTime < currTrackEnd
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
  var trackStartTime = currTrackEnd - startingTrack.duration
 | 
					 | 
				
			||||||
  var currInpoint = startTime - trackStartTime
 | 
					 | 
				
			||||||
  Logger.info('Starting Track Index', startingTrack.index)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  var tracksToInclude = tracks.filter(t => t.index >= startingTrack.index)
 | 
					 | 
				
			||||||
  var trackPaths = tracksToInclude.map(t => {
 | 
					 | 
				
			||||||
    var line = 'file ' + escapeSingleQuotes(t.fullPath) + '\n' + `duration ${t.duration}`
 | 
					 | 
				
			||||||
    // if (t.index === startingTrack.index) {
 | 
					 | 
				
			||||||
    // currInpoint = 60 * 5 + 4
 | 
					 | 
				
			||||||
    // line += `\ninpoint ${currInpoint}`
 | 
					 | 
				
			||||||
    // }
 | 
					 | 
				
			||||||
    return line
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  var inputstr = trackPaths.join('\n\n')
 | 
					 | 
				
			||||||
  await fs.writeFile(concatFilePath, inputstr)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  ffmpeg.addInput(concatFilePath)
 | 
					 | 
				
			||||||
  ffmpeg.inputFormat('concat')
 | 
					 | 
				
			||||||
  ffmpeg.inputOption('-safe 0')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  var shiftedStartTime = startTime - trackStartTime
 | 
					 | 
				
			||||||
  if (startTime > 0) {
 | 
					 | 
				
			||||||
    Logger.info(`[STREAM] Starting Stream at startTime ${secondsToTimestamp(startTime)} and Segment #${segmentStartNumber}`)
 | 
					 | 
				
			||||||
    ffmpeg.inputOption(`-ss ${shiftedStartTime}`)
 | 
					 | 
				
			||||||
    ffmpeg.inputOption('-noaccurate_seek')
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  ffmpeg.addOption([
 | 
					 | 
				
			||||||
    '-loglevel warning',
 | 
					 | 
				
			||||||
    '-map 0:a',
 | 
					 | 
				
			||||||
    '-c:a copy'
 | 
					 | 
				
			||||||
  ])
 | 
					 | 
				
			||||||
  ffmpeg.addOption([
 | 
					 | 
				
			||||||
    '-f hls',
 | 
					 | 
				
			||||||
    "-copyts",
 | 
					 | 
				
			||||||
    "-avoid_negative_ts disabled",
 | 
					 | 
				
			||||||
    "-max_delay 5000000",
 | 
					 | 
				
			||||||
    "-max_muxing_queue_size 2048",
 | 
					 | 
				
			||||||
    `-hls_time 6`,
 | 
					 | 
				
			||||||
    "-hls_segment_type mpegts",
 | 
					 | 
				
			||||||
    `-start_number ${segmentStartNumber}`,
 | 
					 | 
				
			||||||
    "-hls_playlist_type vod",
 | 
					 | 
				
			||||||
    "-hls_list_size 0",
 | 
					 | 
				
			||||||
    "-hls_allow_cache 0"
 | 
					 | 
				
			||||||
  ])
 | 
					 | 
				
			||||||
  var segmentFilename = Path.join(testDir, 'output-%d.ts')
 | 
					 | 
				
			||||||
  ffmpeg.addOption(`-hls_segment_filename ${segmentFilename}`)
 | 
					 | 
				
			||||||
  ffmpeg.output(playlistPath)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  ffmpeg.on('start', (command) => {
 | 
					 | 
				
			||||||
    Logger.info('[FFMPEG-START] FFMPEG transcoding started with command: ' + command)
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
  ffmpeg.on('stderr', (stdErrline) => {
 | 
					 | 
				
			||||||
    Logger.info('[FFMPEG-STDERR]', stdErrline)
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
  ffmpeg.on('error', (err, stdout, stderr) => {
 | 
					 | 
				
			||||||
    Logger.info('[FFMPEG-ERROR]', err)
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
  ffmpeg.on('end', (stdout, stderr) => {
 | 
					 | 
				
			||||||
    Logger.info('[FFMPEG] Transcode ended')
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
  ffmpeg.run()
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
module.exports.start = start
 | 
					 | 
				
			||||||
@ -40,6 +40,10 @@ async function getAllAudiobookFiles(abRootPath) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // If relative file directory has 3 folders, then the middle folder will be series
 | 
					    // If relative file directory has 3 folders, then the middle folder will be series
 | 
				
			||||||
    var splitDir = pathformat.dir.split(Path.sep)
 | 
					    var splitDir = pathformat.dir.split(Path.sep)
 | 
				
			||||||
 | 
					    if (splitDir.length === 1) {
 | 
				
			||||||
 | 
					      Logger.error('Invalid file in root dir', filepath)
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    var author = splitDir.shift()
 | 
					    var author = splitDir.shift()
 | 
				
			||||||
    var series = null
 | 
					    var series = null
 | 
				
			||||||
    if (splitDir.length > 1) series = splitDir.shift()
 | 
					    if (splitDir.length > 1) series = splitDir.shift()
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user