diff --git a/aircox/models/program.py b/aircox/models/program.py index 892c0ab..4854609 100644 --- a/aircox/models/program.py +++ b/aircox/models/program.py @@ -78,13 +78,13 @@ class Program(Page): """ Return absolute path to program's dir """ return os.path.join(conf.MEDIA_ROOT, self.path) - def archives_path(self, abs=False): - return os.path.join(abs and self.abspath or self.path, - settings.AIRCOX_SOUND_ARCHIVES_SUBDIR) + @property + def archives_path(self): + return os.path.join(self.path, settings.AIRCOX_SOUND_ARCHIVES_SUBDIR) - def excerpts_path(self, abs=False): - return os.path.join(abs and self.abspath or self.path, - settings.AIRCOX_SOUND_ARCHIVES_SUBDIR) + @property + def excerpts_path(self): + return os.path.join(self.path, settings.AIRCOX_SOUND_ARCHIVES_SUBDIR) def __init__(self, *kargs, **kwargs): super().__init__(*kargs, **kwargs) diff --git a/aircox/models/sound.py b/aircox/models/sound.py index e805d11..c1c1904 100644 --- a/aircox/models/sound.py +++ b/aircox/models/sound.py @@ -2,7 +2,7 @@ from enum import IntEnum import logging import os -from django.conf import settings as main_settings +from django.conf import settings as conf from django.db import models from django.db.models import Q, Value as V from django.db.models.functions import Concat @@ -48,20 +48,19 @@ class SoundQuerySet(models.QuerySet): def paths(self, archive=True, order_by=True): """ - Return paths as a flat list (exclude sound without path). + Return files absolute paths as a flat list (exclude sound without path). If `order_by` is True, order by path. """ if archive: self = self.archive() if order_by: - self = self.order_by('path') - return self.filter(file__isnull=False) \ - .annotate(file_path=Concat(V(conf.MEDIA_ROOT), 'file')) \ - .values_list('file_path', flat=True) + self = self.order_by('file') + return [os.path.join(conf.MEDIA_ROOT, file) for file in self.filter(file__isnull=False) \ + .values_list('file', flat=True)] def search(self, query): return self.filter( - Q(name__icontains=query) | Q(path__icontains=query) | + Q(name__icontains=query) | Q(file__icontains=query) | Q(program__title__icontains=query) | Q(episode__title__icontains=query) ) @@ -179,7 +178,7 @@ class Sound(models.Model): if self.type == self.TYPE_REMOVED and self.program: changed = True self.type = self.TYPE_ARCHIVE \ - if self.file.path.startswith(self.program.archives_path) else \ + if self.file.name.startswith(self.program.archives_path) else \ self.TYPE_EXCERPT # check mtime -> reset quality if changed (assume file changed) @@ -195,7 +194,7 @@ class Sound(models.Model): return changed def __check_name(self): - if not self.name and self.file and self.file.path: + if not self.name and self.file and self.file.name: # FIXME: later, remove date? self.name = os.path.basename(self.file.name) self.name = os.path.splitext(self.name)[0] diff --git a/aircox/static/aircox/js/chunk-common.js b/aircox/static/aircox/js/chunk-common.js index fe48887..8e8abde 100644 --- a/aircox/static/aircox/js/chunk-common.js +++ b/aircox/static/aircox/js/chunk-common.js @@ -55,7 +55,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony default export */ \*****************************************************************************************************************************************************************************************/ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"State\": function() { return /* binding */ State; }\n/* harmony export */ });\n/* harmony import */ var _live__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../live */ \"./src/live.js\");\n/* harmony import */ var _sound__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../sound */ \"./src/sound.js\");\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../model */ \"./src/model.js\");\n/* harmony import */ var _APlaylist__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./APlaylist */ \"./src/components/APlaylist.vue\");\n/* harmony import */ var _AProgress__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./AProgress */ \"./src/components/AProgress.vue\");\n\n\n\n\n\nconst State = {\n paused: 0,\n playing: 1,\n loading: 2\n};\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n components: {\n APlaylist: _APlaylist__WEBPACK_IMPORTED_MODULE_3__[\"default\"],\n AProgress: _AProgress__WEBPACK_IMPORTED_MODULE_4__[\"default\"]\n },\n\n data() {\n let audio = new Audio();\n audio.addEventListener('ended', e => this.onState(e));\n audio.addEventListener('pause', e => this.onState(e));\n audio.addEventListener('playing', e => this.onState(e));\n audio.addEventListener('timeupdate', () => {\n this.currentTime = this.audio.currentTime;\n });\n audio.addEventListener('durationchange', () => {\n this.duration = Number.isFinite(this.audio.duration) ? this.audio.duration : null;\n });\n let live = this.liveArgs ? new _live__WEBPACK_IMPORTED_MODULE_0__[\"default\"](this.liveArgs) : null;\n live && live.refresh();\n return {\n audio,\n duration: 0,\n currentTime: 0,\n state: State.paused,\n live,\n /// Loaded item\n loaded: null,\n //! Active panel name\n panel: null,\n //! current playing playlist name\n playlistName: null,\n //! players' playlists' sets\n sets: {\n queue: _model__WEBPACK_IMPORTED_MODULE_2__.Set.storeLoad(_sound__WEBPACK_IMPORTED_MODULE_1__[\"default\"], \"playlist.queue\", {\n max: 30,\n unique: true\n }),\n pin: _model__WEBPACK_IMPORTED_MODULE_2__.Set.storeLoad(_sound__WEBPACK_IMPORTED_MODULE_1__[\"default\"], \"player.pin\", {\n max: 30,\n unique: true\n })\n }\n };\n },\n\n props: {\n buttonTitle: String,\n liveArgs: Object\n },\n computed: {\n self() {\n return this;\n },\n\n paused() {\n return this.state == State.paused;\n },\n\n playing() {\n return this.state == State.playing;\n },\n\n loading() {\n return this.state == State.loading;\n },\n\n playlist() {\n return this.playlistName ? this.$refs[this.playlistName] : null;\n },\n\n current() {\n return this.loaded ? this.loaded : this.live && this.live.current;\n }\n\n },\n methods: {\n displayTime(seconds) {\n seconds = parseInt(seconds);\n let s = seconds % 60;\n seconds = (seconds - s) / 60;\n let m = seconds % 60;\n let h = (seconds - m) / 60;\n let [ss, mm, hh] = [s.toString().padStart(2, '0'), m.toString().padStart(2, '0'), h.toString().padStart(2, '0')];\n return h ? `${hh}:${mm}:${ss}` : `${mm}:${ss}`;\n },\n\n playlistButtonClass(name) {\n let set = this.sets[name];\n return (set ? (set.length ? \"\" : \"has-text-grey-light \") + (this.panel == name ? \"is-info \" : this.playlistName == name ? 'is-primary ' : '') : '') + \"button has-text-weight-bold\";\n },\n\n /// Show/hide panel\n togglePanel(panel) {\n this.panel = this.panel == panel ? null : panel;\n },\n\n /// Return True if item is loaded\n isLoaded(item) {\n return this.loaded && this.loaded.id == item.id;\n },\n\n /// Return True if item is loaded\n isPlaying(item) {\n return this.isLoaded(item) && !this.paused;\n },\n\n _setPlaylist(playlist) {\n this.playlistName = playlist;\n\n for (var p in this.sets) if (p != playlist) this.$refs[p].unselect();\n },\n\n /// Load a sound from playlist or live\n load(playlist = null, index = 0) {\n let src = null; // from playlist\n\n if (playlist !== null) {\n let item = this.$refs[playlist].get(index);\n if (!item) throw `No sound at index ${index} for playlist ${playlist}`;\n this.loaded = item;\n src = item.src;\n } // from live\n else {\n this.loaded = null;\n src = this.live.src;\n }\n\n this._setPlaylist(playlist); // load sources\n\n\n const audio = this.audio;\n\n if (src instanceof Array) {\n audio.innerHTML = '';\n audio.removeAttribute('src');\n\n for (var s of src) {\n let source = document.createElement('source');\n source.setAttribute('src', s);\n audio.appendChild(source);\n }\n } else {\n audio.src = src;\n }\n\n audio.load();\n },\n\n play(playlist = null, index = 0) {\n this.load(playlist, index);\n this.audio.play().catch(e => console.error(e));\n },\n\n /// Push items to playlist (by name)\n push(playlist, ...items) {\n return this.sets[playlist].push(...items);\n },\n\n /// Push and play items\n playItems(playlist, ...items) {\n let index = this.push(playlist, ...items);\n this.$refs[playlist].selectedIndex = index;\n this.play(playlist, index);\n },\n\n /// Handle click event that plays multiple items (from `data-sounds` attribute)\n playButtonClick(event) {\n var items = JSON.parse(event.currentTarget.dataset.sounds);\n this.playItems('queue', ...items);\n },\n\n /// Pause\n pause() {\n this.audio.pause();\n },\n\n //! Play/pause\n togglePlay(playlist = null, index = 0) {\n if (playlist !== null) {\n let item = this.sets[playlist].get(index);\n\n if (!this.playlist || this.playlistName !== playlist || this.loaded != item) {\n this.play(playlist, index);\n return;\n }\n }\n\n if (this.paused) this.audio.play().catch(e => console.error(e));else this.audio.pause();\n },\n\n //! Pin/Unpin an item\n togglePin(item) {\n let index = this.sets.pin.findIndex(item);\n if (index > -1) this.sets.pin.remove(index);else {\n this.sets.pin.push(item);\n this.$refs.pinPlaylistButton.focus();\n }\n },\n\n /// Audio player state change event\n onState(event) {\n const audio = this.audio;\n this.state = audio.paused ? State.paused : State.playing;\n if (event.type == 'ended' && (!this.playlist || this.playlist.selectNext() == -1)) this.play();\n }\n\n },\n\n mounted() {\n this.load();\n }\n\n});\n\n//# sourceURL=webpack://aircox-assets/./src/components/APlayer.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use%5B0%5D!./node_modules/vue-loader/dist/index.js??ruleSet%5B0%5D.use%5B0%5D"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"State\": function() { return /* binding */ State; }\n/* harmony export */ });\n/* harmony import */ var _live__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../live */ \"./src/live.js\");\n/* harmony import */ var _sound__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../sound */ \"./src/sound.js\");\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../model */ \"./src/model.js\");\n/* harmony import */ var _APlaylist__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./APlaylist */ \"./src/components/APlaylist.vue\");\n/* harmony import */ var _AProgress__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./AProgress */ \"./src/components/AProgress.vue\");\n\n\n\n\n\nconst State = {\n paused: 0,\n playing: 1,\n loading: 2\n};\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n components: {\n APlaylist: _APlaylist__WEBPACK_IMPORTED_MODULE_3__[\"default\"],\n AProgress: _AProgress__WEBPACK_IMPORTED_MODULE_4__[\"default\"]\n },\n\n data() {\n let audio = new Audio();\n audio.addEventListener('ended', e => this.onState(e));\n audio.addEventListener('pause', e => this.onState(e));\n audio.addEventListener('playing', e => this.onState(e));\n audio.addEventListener('timeupdate', () => {\n this.currentTime = this.audio.currentTime;\n });\n audio.addEventListener('durationchange', () => {\n this.duration = Number.isFinite(this.audio.duration) ? this.audio.duration : null;\n });\n let live = this.liveArgs ? new _live__WEBPACK_IMPORTED_MODULE_0__[\"default\"](this.liveArgs) : null;\n live && live.refresh();\n return {\n audio,\n duration: 0,\n currentTime: 0,\n state: State.paused,\n live,\n /// Loaded item\n loaded: null,\n //! Active panel name\n panel: null,\n //! current playing playlist name\n playlistName: null,\n //! players' playlists' sets\n sets: {\n queue: _model__WEBPACK_IMPORTED_MODULE_2__.Set.storeLoad(_sound__WEBPACK_IMPORTED_MODULE_1__[\"default\"], \"playlist.queue\", {\n max: 30,\n unique: true\n }),\n pin: _model__WEBPACK_IMPORTED_MODULE_2__.Set.storeLoad(_sound__WEBPACK_IMPORTED_MODULE_1__[\"default\"], \"player.pin\", {\n max: 30,\n unique: true\n })\n }\n };\n },\n\n props: {\n buttonTitle: String,\n liveArgs: Object\n },\n computed: {\n self() {\n return this;\n },\n\n paused() {\n return this.state == State.paused;\n },\n\n playing() {\n return this.state == State.playing;\n },\n\n loading() {\n return this.state == State.loading;\n },\n\n playlist() {\n return this.playlistName ? this.$refs[this.playlistName] : null;\n },\n\n current() {\n return this.loaded ? this.loaded : this.live && this.live.current;\n }\n\n },\n methods: {\n displayTime(seconds) {\n seconds = parseInt(seconds);\n let s = seconds % 60;\n seconds = (seconds - s) / 60;\n let m = seconds % 60;\n let h = (seconds - m) / 60;\n let [ss, mm, hh] = [s.toString().padStart(2, '0'), m.toString().padStart(2, '0'), h.toString().padStart(2, '0')];\n return h ? `${hh}:${mm}:${ss}` : `${mm}:${ss}`;\n },\n\n playlistButtonClass(name) {\n let set = this.sets[name];\n return (set ? (set.length ? \"\" : \"has-text-grey-light \") + (this.panel == name ? \"is-info \" : this.playlistName == name ? 'is-primary ' : '') : '') + \"button has-text-weight-bold\";\n },\n\n /// Show/hide panel\n togglePanel(panel) {\n this.panel = this.panel == panel ? null : panel;\n },\n\n /// Return True if item is loaded\n isLoaded(item) {\n return this.loaded && this.loaded.id == item.id;\n },\n\n /// Return True if item is loaded\n isPlaying(item) {\n return this.isLoaded(item) && !this.paused;\n },\n\n _setPlaylist(playlist) {\n this.playlistName = playlist;\n\n for (var p in this.sets) if (p != playlist) this.$refs[p].unselect();\n },\n\n /// Load a sound from playlist or live\n load(playlist = null, index = 0) {\n let src = null; // from playlist\n\n if (playlist !== null && index != -1) {\n let item = this.$refs[playlist].get(index);\n if (!item) throw `No sound at index ${index} for playlist ${playlist}`;\n this.loaded = item;\n src = item.src;\n } // from live\n else {\n this.loaded = null;\n src = this.live.src;\n }\n\n this._setPlaylist(playlist); // load sources\n\n\n const audio = this.audio;\n\n if (src instanceof Array) {\n audio.innerHTML = '';\n audio.removeAttribute('src');\n\n for (var s of src) {\n let source = document.createElement('source');\n source.setAttribute('src', s);\n audio.appendChild(source);\n }\n } else {\n audio.src = src;\n }\n\n audio.load();\n },\n\n play(playlist = null, index = 0) {\n this.load(playlist, index);\n this.audio.play().catch(e => console.error(e));\n },\n\n /// Push items to playlist (by name)\n push(playlist, ...items) {\n return this.sets[playlist].push(...items);\n },\n\n /// Push and play items\n playItems(playlist, ...items) {\n let index = this.push(playlist, ...items);\n this.$refs[playlist].selectedIndex = index;\n this.play(playlist, index);\n },\n\n /// Handle click event that plays multiple items (from `data-sounds` attribute)\n playButtonClick(event) {\n var items = JSON.parse(event.currentTarget.dataset.sounds);\n this.playItems('queue', ...items);\n },\n\n /// Pause\n pause() {\n this.audio.pause();\n },\n\n //! Play/pause\n togglePlay(playlist = null, index = 0) {\n if (playlist !== null) {\n let item = this.sets[playlist].get(index);\n\n if (!this.playlist || this.playlistName !== playlist || this.loaded != item) {\n this.play(playlist, index);\n return;\n }\n }\n\n if (this.paused) this.audio.play().catch(e => console.error(e));else this.audio.pause();\n },\n\n //! Pin/Unpin an item\n togglePin(item) {\n let index = this.sets.pin.findIndex(item);\n if (index > -1) this.sets.pin.remove(index);else {\n this.sets.pin.push(item);\n this.$refs.pinPlaylistButton.focus();\n }\n },\n\n /// Audio player state change event\n onState(event) {\n const audio = this.audio;\n this.state = audio.paused ? State.paused : State.playing;\n if (event.type == 'ended' && (!this.playlist || this.playlist.selectNext() == -1)) this.play();\n }\n\n },\n\n mounted() {\n this.load();\n }\n\n});\n\n//# sourceURL=webpack://aircox-assets/./src/components/APlayer.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use%5B0%5D!./node_modules/vue-loader/dist/index.js??ruleSet%5B0%5D.use%5B0%5D"); /***/ }), diff --git a/assets/src/components/APlayer.vue b/assets/src/components/APlayer.vue index f97156e..7a8a22f 100644 --- a/assets/src/components/APlayer.vue +++ b/assets/src/components/APlayer.vue @@ -180,7 +180,7 @@ export default { let src = null; // from playlist - if(playlist !== null) { + if(playlist !== null && index != -1) { let item = this.$refs[playlist].get(index); if(!item) throw `No sound at index ${index} for playlist ${playlist}`;