mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-01-01 12:09:43 -05:00
Compare commits
114 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e8547f0c8 | ||
|
|
96930d7ecc | ||
|
|
23f2c8a251 | ||
|
|
c5372d1405 | ||
|
|
dcfbed5f30 | ||
|
|
895ab8d18a | ||
|
|
8b5d05739f | ||
|
|
a8f6202302 | ||
|
|
5d40fdf277 | ||
|
|
f35c96e118 | ||
|
|
8f8d6f81ab | ||
|
|
e195eec1c5 | ||
|
|
33846e46fa | ||
|
|
2ad03bcb9a | ||
|
|
371cd3b2e5 | ||
|
|
b9307143bd | ||
|
|
36e44e902a | ||
|
|
4529fc0124 | ||
|
|
ba07761de3 | ||
|
|
3b7ce69327 | ||
|
|
cf927f61a0 | ||
|
|
61c32d99e7 | ||
|
|
19e39f6321 | ||
|
|
f9e6655359 | ||
|
|
debf0f495d | ||
|
|
3383ec2046 | ||
|
|
b957e1a36b | ||
|
|
c93f17051a | ||
|
|
5983f0262f | ||
|
|
96a8e74d38 | ||
|
|
6f3d488c3d | ||
|
|
74dcc4f9e4 | ||
|
|
8c4d3b93c8 | ||
|
|
c411cf04cc | ||
|
|
d1b25da408 | ||
|
|
08f765fa51 | ||
|
|
337cf90c4b | ||
|
|
17b930e13d | ||
|
|
639b600570 | ||
|
|
7a751b8f91 | ||
|
|
68621e0c07 | ||
|
|
5abb02e93a | ||
|
|
573079c5a1 | ||
|
|
5bde320ac7 | ||
|
|
5f63d97e59 | ||
|
|
bf2fe3faea | ||
|
|
ea60f19e7a | ||
|
|
cefc75a4ed | ||
|
|
d8753aafb9 | ||
|
|
ba5ad228cc | ||
|
|
0203f9cc1b | ||
|
|
4770be5a39 | ||
|
|
1bac395bed | ||
|
|
e818f270cd | ||
|
|
c4e2726622 | ||
|
|
74d8a09f31 | ||
|
|
618338165e | ||
|
|
db494001a2 | ||
|
|
a67213fb7b | ||
|
|
5d96b2cc6e | ||
|
|
72d0b097ab | ||
|
|
36d2957fb4 | ||
|
|
b5de517aad | ||
|
|
41db0e3bb1 | ||
|
|
e8d582269b | ||
|
|
80ef8ee890 | ||
|
|
a65859f575 | ||
|
|
5724887785 | ||
|
|
8908aa7a82 | ||
|
|
f83dd29213 | ||
|
|
99d90778f4 | ||
|
|
49279430fc | ||
|
|
030c20b12e | ||
|
|
5e943ef152 | ||
|
|
4ae057f626 | ||
|
|
9ebe4b55dd | ||
|
|
2f7403adec | ||
|
|
2777b496ad | ||
|
|
f7a3dbf209 | ||
|
|
d900093976 | ||
|
|
08250e266e | ||
|
|
da2d1455d7 | ||
|
|
b6c6c4c939 | ||
|
|
22179d82b8 | ||
|
|
343ce312f1 | ||
|
|
10677d6fb0 | ||
|
|
49a8aead9b | ||
|
|
274b0e48be | ||
|
|
4d8ffc5d99 | ||
|
|
4f3029e5b2 | ||
|
|
a1b49f5fcf | ||
|
|
89d497a305 | ||
|
|
9e095a4bc1 | ||
|
|
024d052a7b | ||
|
|
c312979aec | ||
|
|
773e621944 | ||
|
|
ed4f33b565 | ||
|
|
f8a0852dfc | ||
|
|
6dec750d3e | ||
|
|
3c98a5fb24 | ||
|
|
702ee3d350 | ||
|
|
fcc2f3650b | ||
|
|
e4ad622c01 | ||
|
|
458403eec9 | ||
|
|
aaede2752c | ||
|
|
39d8c2cf04 | ||
|
|
dd5c940d36 | ||
|
|
277f024bbc | ||
|
|
59ad1e5e36 | ||
|
|
02c4b21d3f | ||
|
|
33ae5445be | ||
|
|
5ed06871b6 | ||
|
|
e98eb8f1eb | ||
|
|
f095d89980 |
@@ -48,14 +48,6 @@
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Gentium Book Basic';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(~static/fonts/GentiumBookBasic.woff2) format('woff2');
|
||||
}
|
||||
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
<div id="appbar" class="absolute top-0 bottom-0 left-0 w-full h-full px-2 md:px-6 py-1 z-60">
|
||||
<div class="flex h-full items-center">
|
||||
<nuxt-link to="/">
|
||||
<img src="~static/icon.svg" :alt="$strings.ButtonHome" class="w-8 min-w-8 h-8 mr-2 sm:w-12 sm:min-w-12 sm:h-12 sm:mr-4" />
|
||||
<img src="~static/icon.svg" :alt="$strings.ButtonHome" class="w-8 min-w-8 h-8 mr-2 sm:w-10 sm:min-w-10 sm:h-10 sm:mr-4" />
|
||||
</nuxt-link>
|
||||
|
||||
<nuxt-link to="/">
|
||||
<h1 class="text-2xl font-book mr-6 hidden lg:block hover:underline">audiobookshelf <span v-if="showExperimentalFeatures" class="material-icons text-lg text-warning pr-1">logo_dev</span></h1>
|
||||
<h1 class="text-xl mr-6 hidden lg:block hover:underline">audiobookshelf <span v-if="showExperimentalFeatures" class="material-icons text-lg text-warning pr-1">logo_dev</span></h1>
|
||||
</nuxt-link>
|
||||
|
||||
<ui-libraries-dropdown class="mr-2" />
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
<widgets-cover-size-widget class="fixed bottom-4 right-4 z-50" />
|
||||
|
||||
<div v-if="loaded && !shelves.length && !search" class="w-full flex flex-col items-center justify-center py-12">
|
||||
<p class="text-center text-2xl font-book mb-4 py-4">{{ libraryName }} Library is empty!</p>
|
||||
<p class="text-center text-2xl mb-4 py-4">{{ libraryName }} Library is empty!</p>
|
||||
<div v-if="userIsAdminOrUp" class="flex">
|
||||
<ui-btn to="/config" color="primary" class="w-52 mr-2">Configure Scanner</ui-btn>
|
||||
<ui-btn color="success" class="w-52" @click="scan">Scan Library</ui-btn>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="loaded && !shelves.length && search" class="w-full h-40 flex items-center justify-center">
|
||||
<p class="text-center text-xl font-book py-4">No results for query</p>
|
||||
<p class="text-center text-xl py-4">No results for query</p>
|
||||
</div>
|
||||
<!-- Alternate plain view -->
|
||||
<div v-else-if="isAlternativeBookshelfView" class="w-full mb-24">
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="absolute text-center categoryPlacard font-book transform z-30 bottom-px left-4 md:left-8 w-44 rounded-md" style="height: 22px">
|
||||
<div class="absolute text-center categoryPlacard transform z-30 bottom-px left-4 md:left-8 w-44 rounded-md" style="height: 22px">
|
||||
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border">
|
||||
<p class="transform text-sm">{{ $strings[shelf.labelStringKey] }}</p>
|
||||
</div>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<div id="toolbar" class="absolute top-10 md:top-0 left-0 w-full h-10 md:h-full z-40 flex items-center justify-end md:justify-start px-2 md:px-8">
|
||||
<!-- Series books page -->
|
||||
<template v-if="selectedSeries">
|
||||
<p class="pl-2 font-book text-base md:text-lg">
|
||||
<p class="pl-2 text-base md:text-lg">
|
||||
{{ seriesName }}
|
||||
</p>
|
||||
<div class="w-6 h-6 rounded-full bg-black bg-opacity-30 flex items-center justify-center ml-3">
|
||||
@@ -60,7 +60,7 @@
|
||||
</template>
|
||||
<!-- library & collections page -->
|
||||
<template v-else-if="page !== 'search' && page !== 'podcast-search' && page !== 'recent-episodes' && !isHome">
|
||||
<p class="font-book hidden md:block">{{ numShowing }} {{ entityName }}</p>
|
||||
<p class="hidden md:block">{{ numShowing }} {{ entityName }}</p>
|
||||
|
||||
<div class="flex-grow hidden sm:inline-block" />
|
||||
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
<template>
|
||||
<div class="w-44 fixed left-0 top-16 h-full bg-bg bg-opacity-100 md:bg-opacity-70 shadow-lg border-r border-white border-opacity-5 py-3 transform transition-transform" :class="wrapperClass" v-click-outside="clickOutside">
|
||||
<div v-show="isMobilePortrait" class="flex items-center justify-end pb-2 px-4 mb-1" @click="closeDrawer">
|
||||
<span class="material-icons text-2xl">arrow_back</span>
|
||||
<div>
|
||||
<div class="w-44 fixed left-0 top-16 bg-bg bg-opacity-100 md:bg-opacity-70 shadow-lg border-r border-white border-opacity-5 py-3 transform transition-transform mb-12 overflow-y-auto" :class="wrapperClass + ' ' + (streamLibraryItem ? 'h-[calc(100%-270px)]' : 'h-[calc(100%-110px)]')" v-click-outside="clickOutside">
|
||||
<div v-show="isMobilePortrait" class="flex items-center justify-end pb-2 px-4 mb-1" @click="closeDrawer">
|
||||
<span class="material-icons text-2xl">arrow_back</span>
|
||||
</div>
|
||||
|
||||
<nuxt-link v-for="route in configRoutes" :key="route.id" :to="route.path" class="w-full px-4 h-12 border-b border-primary border-opacity-30 flex items-center cursor-pointer relative" :class="routeName === route.id ? 'bg-primary bg-opacity-70' : 'hover:bg-primary hover:bg-opacity-30'">
|
||||
<p>{{ route.title }}</p>
|
||||
<div v-show="routeName === route.iod" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
||||
</nuxt-link>
|
||||
|
||||
<modals-changelog-view-modal v-model="showChangelogModal" :changelog="currentVersionChangelog" :currentVersion="$config.version" />
|
||||
</div>
|
||||
|
||||
<nuxt-link v-for="route in configRoutes" :key="route.id" :to="route.path" class="w-full px-4 h-12 border-b border-primary border-opacity-30 flex items-center cursor-pointer relative" :class="routeName === route.id ? 'bg-primary bg-opacity-70' : 'hover:bg-primary hover:bg-opacity-30'">
|
||||
<p>{{ route.title }}</p>
|
||||
<div v-show="routeName === route.iod" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
||||
</nuxt-link>
|
||||
|
||||
<div class="w-full h-12 px-4 border-t border-black border-opacity-20 absolute left-0 flex flex-col justify-center" :style="{ bottom: streamLibraryItem && isMobileLandscape ? '300px' : '65px' }">
|
||||
<div class="w-44 h-12 px-4 border-t bg-bg border-black border-opacity-20 fixed left-0 flex flex-col justify-center" :class="wrapperClass" :style="{ bottom: streamLibraryItem ? '160px' : '0px' }">
|
||||
<div class="flex justify-between">
|
||||
<p class="underline font-mono text-sm" @click="clickChangelog">v{{ $config.version }}</p>
|
||||
|
||||
@@ -17,8 +21,6 @@
|
||||
</div>
|
||||
<a v-if="hasUpdate" :href="githubTagUrl" target="_blank" class="text-warning text-xs">Latest: {{ latestVersion }}</a>
|
||||
</div>
|
||||
|
||||
<modals-changelog-view-modal v-model="showChangelogModal" :changelog="currentVersionChangelog" :currentVersion="$config.version" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</template>
|
||||
|
||||
<div v-if="initialized && !totalShelves && !hasFilter && entityName === 'items'" class="w-full flex flex-col items-center justify-center py-12">
|
||||
<p class="text-center text-2xl font-book mb-4 py-4">{{ $getString('MessageXLibraryIsEmpty', [libraryName]) }}</p>
|
||||
<p class="text-center text-2xl mb-4 py-4">{{ $getString('MessageXLibraryIsEmpty', [libraryName]) }}</p>
|
||||
<div v-if="userIsAdminOrUp" class="flex">
|
||||
<ui-btn to="/config" color="primary" class="w-52 mr-2">{{ $strings.ButtonConfigureScanner }}</ui-btn>
|
||||
<ui-btn color="success" class="w-52" @click="scan">{{ $strings.ButtonScanLibrary }}</ui-btn>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<h1 class="text-xl">{{ headerText }}</h1>
|
||||
|
||||
<div v-if="showAddButton" class="mx-2 w-7 h-7 flex items-center justify-center rounded-full cursor-pointer hover:bg-white hover:bg-opacity-10 text-center" @click="clicked">
|
||||
<button class="material-icons" :aria-label="$strings.ButtonAdd + ': ' + headerText" style="font-size: 1.4rem">add</button>
|
||||
<button type="button" class="material-icons" :aria-label="$strings.ButtonAdd + ': ' + headerText" style="font-size: 1.4rem">add</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
||||
</svg>
|
||||
|
||||
<p class="font-book pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonHome }}</p>
|
||||
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonHome }}</p>
|
||||
|
||||
<div v-show="homePage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
||||
</nuxt-link>
|
||||
@@ -16,7 +16,7 @@
|
||||
<nuxt-link v-if="isPodcastLibrary" :to="`/library/${currentLibraryId}/podcast/latest`" class="w-full h-20 flex flex-col items-center justify-center text-white border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isPodcastLatestPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
|
||||
<span class="material-icons text-2xl">format_list_bulleted</span>
|
||||
|
||||
<p class="font-book pt-1 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonLatest }}</p>
|
||||
<p class="pt-1 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonLatest }}</p>
|
||||
|
||||
<div v-show="isPodcastLatestPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
||||
</nuxt-link>
|
||||
@@ -26,7 +26,7 @@
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
|
||||
</svg>
|
||||
|
||||
<p class="font-book pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonLibrary }}</p>
|
||||
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonLibrary }}</p>
|
||||
|
||||
<div v-show="showLibrary" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
||||
</nuxt-link>
|
||||
@@ -36,7 +36,7 @@
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2" />
|
||||
</svg>
|
||||
|
||||
<p class="font-book pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonSeries }}</p>
|
||||
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonSeries }}</p>
|
||||
|
||||
<div v-show="isSeriesPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
||||
</nuxt-link>
|
||||
@@ -44,7 +44,7 @@
|
||||
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/bookshelf/collections`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="paramId === 'collections' ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
|
||||
<span class="material-icons-outlined text-2xl">collections_bookmark</span>
|
||||
|
||||
<p class="font-book pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonCollections }}</p>
|
||||
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonCollections }}</p>
|
||||
|
||||
<div v-show="paramId === 'collections'" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
||||
</nuxt-link>
|
||||
@@ -57,7 +57,7 @@
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<p class="font-book pt-1 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonAuthors }}</p>
|
||||
<p class="pt-1 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonAuthors }}</p>
|
||||
|
||||
<div v-show="isAuthorsPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
||||
</nuxt-link>
|
||||
@@ -65,7 +65,7 @@
|
||||
<nuxt-link v-if="isPodcastLibrary && userIsAdminOrUp" :to="`/library/${currentLibraryId}/podcast/search`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isPodcastSearchPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
|
||||
<span class="abs-icons icon-podcast text-xl"></span>
|
||||
|
||||
<p class="font-book pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonSearch }}</p>
|
||||
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonSearch }}</p>
|
||||
|
||||
<div v-show="isPodcastSearchPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
||||
</nuxt-link>
|
||||
@@ -73,7 +73,7 @@
|
||||
<nuxt-link v-if="isMusicLibrary" :to="`/library/${currentLibraryId}/bookshelf/albums`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isMusicAlbumsPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
|
||||
<span class="material-icons-outlined text-xl">album</span>
|
||||
|
||||
<p class="font-book pt-1.5 text-center leading-4" style="font-size: 0.9rem">Albums</p>
|
||||
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">Albums</p>
|
||||
|
||||
<div v-show="isMusicAlbumsPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
||||
</nuxt-link>
|
||||
@@ -81,7 +81,7 @@
|
||||
<nuxt-link v-if="showPlaylists" :to="`/library/${currentLibraryId}/bookshelf/playlists`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isPlaylistsPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
|
||||
<span class="material-icons text-2.5xl">queue_music</span>
|
||||
|
||||
<p class="font-book pt-0.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonPlaylists }}</p>
|
||||
<p class="pt-0.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonPlaylists }}</p>
|
||||
|
||||
<div v-show="isPlaylistsPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
||||
</nuxt-link>
|
||||
@@ -89,7 +89,7 @@
|
||||
<nuxt-link v-if="numIssues" :to="`/library/${currentLibraryId}/bookshelf?filter=issues`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-opacity-40 cursor-pointer relative" :class="showingIssues ? 'bg-error bg-opacity-40' : ' bg-error bg-opacity-20'">
|
||||
<span class="material-icons text-2xl">warning</span>
|
||||
|
||||
<p class="font-book pt-1.5 text-center leading-4" style="font-size: 1rem">{{ $strings.ButtonIssues }}</p>
|
||||
<p class="pt-1.5 text-center leading-4" style="font-size: 1rem">{{ $strings.ButtonIssues }}</p>
|
||||
|
||||
<div v-show="showingIssues" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
||||
<div class="absolute top-1 right-1 w-4 h-4 rounded-full bg-white bg-opacity-30 flex items-center justify-center">
|
||||
|
||||
@@ -361,13 +361,15 @@ export default {
|
||||
}
|
||||
},
|
||||
streamProgress(data) {
|
||||
if (!data.numSegments) return
|
||||
var chunks = data.chunks
|
||||
console.log(`[StreamContainer] Stream Progress ${data.percent}`)
|
||||
if (this.$refs.audioPlayer) {
|
||||
this.$refs.audioPlayer.setChunksReady(chunks, data.numSegments)
|
||||
} else {
|
||||
console.error('No Audio Ref')
|
||||
if (this.playerHandler.isPlayingLocalItem && this.playerHandler.currentStreamId === data.stream) {
|
||||
if (!data.numSegments) return
|
||||
var chunks = data.chunks
|
||||
console.log(`[StreamContainer] Stream Progress ${data.percent}`)
|
||||
if (this.$refs.audioPlayer) {
|
||||
this.$refs.audioPlayer.setChunksReady(chunks, data.numSegments)
|
||||
} else {
|
||||
console.error('No Audio Ref')
|
||||
}
|
||||
}
|
||||
},
|
||||
sessionOpen(session) {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<covers-group-cover ref="groupcover" :id="groupEncode" :name="groupName" :type="groupType" :book-items="bookItems" :width="width" :height="height" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||
|
||||
<div v-if="hasValidCovers" class="bg-black bg-opacity-60 absolute top-0 left-0 w-full h-full flex items-center justify-center text-center transition-opacity z-30" :class="isHovering ? '' : 'opacity-0'" :style="{ padding: `${sizeMultiplier}rem` }">
|
||||
<p class="font-book" :style="{ fontSize: 1.2 * sizeMultiplier + 'rem' }">{{ groupName }}</p>
|
||||
<p :style="{ fontSize: 1.2 * sizeMultiplier + 'rem' }">{{ groupName }}</p>
|
||||
</div>
|
||||
|
||||
<div class="absolute z-10 top-1.5 right-1.5 rounded-md leading-3 text-sm p-1 font-semibold text-white flex items-center justify-center" style="background-color: #cd9d49dd">{{ bookItems.length }}</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<covers-preview-cover ref="cover" :src="coverSrc" :width="width" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||
</div>
|
||||
|
||||
<div v-if="!isAlternativeBookshelfView" class="categoryPlacard absolute z-30 left-0 right-0 mx-auto -bottom-6 h-6 rounded-md font-book text-center" :style="{ width: Math.min(200, width) + 'px' }">
|
||||
<div v-if="!isAlternativeBookshelfView" class="categoryPlacard absolute z-30 left-0 right-0 mx-auto -bottom-6 h-6 rounded-md text-center" :style="{ width: Math.min(200, width) + 'px' }">
|
||||
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border" :style="{ padding: `0rem ${0.5 * sizeMultiplier}rem` }">
|
||||
<p class="truncate" :style="{ fontSize: labelFontSize + 'rem' }">{{ title }}</p>
|
||||
</div>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
<div class="w-full h-full absolute top-0 left-0 rounded overflow-hidden z-10">
|
||||
<div v-show="libraryItem && !imageReady" class="absolute top-0 left-0 w-full h-full flex items-center justify-center" :style="{ padding: sizeMultiplier * 0.5 + 'rem' }">
|
||||
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }" class="font-book text-gray-300 text-center">{{ title }}</p>
|
||||
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }" class="text-gray-300 text-center">{{ title }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Cover Image -->
|
||||
@@ -32,13 +32,13 @@
|
||||
<!-- Placeholder Cover Title & Author -->
|
||||
<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>
|
||||
<p class="text-center font-book" style="color: rgb(247 223 187)" :style="{ fontSize: titleFontSize + 'rem' }">
|
||||
<p class="text-center" style="color: rgb(247 223 187)" :style="{ fontSize: titleFontSize + 'rem' }">
|
||||
{{ titleCleaned }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!hasCover" class="absolute left-0 right-0 w-full flex items-center justify-center" :style="{ padding: placeholderCoverPadding + 'rem', bottom: authorBottom + 'rem' }">
|
||||
<p class="text-center font-book" style="color: rgb(247 223 187); opacity: 0.75" :style="{ fontSize: authorFontSize + 'rem' }">{{ authorCleaned }}</p>
|
||||
<p class="text-center" style="color: rgb(247 223 187); opacity: 0.75" :style="{ fontSize: authorFontSize + 'rem' }">{{ authorCleaned }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -662,7 +662,7 @@ export default {
|
||||
const axios = this.$axios || this.$nuxt.$axios
|
||||
this.processing = true
|
||||
axios
|
||||
.$get(`/api/items/${this.libraryItemId}/scan`)
|
||||
.$post(`/api/items/${this.libraryItemId}/scan`)
|
||||
.then((data) => {
|
||||
var result = data.result
|
||||
if (!result) {
|
||||
@@ -820,7 +820,6 @@ export default {
|
||||
return null
|
||||
})
|
||||
if (!libraryItem) return
|
||||
console.log('Got library itemn', libraryItem)
|
||||
this.store.commit('showEReader', libraryItem)
|
||||
},
|
||||
selectBtnClick(evt) {
|
||||
|
||||
@@ -12,13 +12,13 @@
|
||||
|
||||
<span v-if="!isHovering && rssFeed" class="absolute z-10 material-icons text-success" :style="{ top: 0.5 * sizeMultiplier + 'rem', left: 0.5 * sizeMultiplier + 'rem', fontSize: 1.5 * sizeMultiplier + 'rem' }">rss_feed</span>
|
||||
|
||||
<div v-if="!isAlternativeBookshelfView" class="categoryPlacard absolute z-30 left-0 right-0 mx-auto -bottom-6 h-6 rounded-md font-book text-center" :style="{ width: Math.min(200, width) + 'px' }">
|
||||
<div v-if="!isAlternativeBookshelfView" class="categoryPlacard absolute z-30 left-0 right-0 mx-auto -bottom-6 h-6 rounded-md text-center" :style="{ width: Math.min(200, width) + 'px' }">
|
||||
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border" :style="{ padding: `0rem ${0.5 * sizeMultiplier}rem` }">
|
||||
<p class="truncate" :style="{ fontSize: labelFontSize + 'rem' }">{{ title }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="absolute z-30 left-0 right-0 mx-auto -bottom-8 h-8 py-1 rounded-md text-center">
|
||||
<p class="truncate" :style="{ fontSize: labelFontSize + 'rem' }">{{ title }}</p>
|
||||
<p class="truncate" :style="{ fontSize: labelFontSize * sizeMultiplier + 'rem' }">{{ title }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
<span class="material-icons text-xl text-white text-opacity-75 hover:text-opacity-100">edit</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!isAlternativeBookshelfView" class="categoryPlacard absolute z-30 left-0 right-0 mx-auto -bottom-6 h-6 rounded-md font-book text-center" :style="{ width: Math.min(200, width) + 'px' }">
|
||||
<div v-if="!isAlternativeBookshelfView" class="categoryPlacard absolute z-30 left-0 right-0 mx-auto -bottom-6 h-6 rounded-md text-center" :style="{ width: Math.min(200, width) + 'px' }">
|
||||
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border" :style="{ padding: `0rem ${0.5 * sizeMultiplier}rem` }">
|
||||
<p class="truncate" :style="{ fontSize: labelFontSize + 'rem' }">{{ title }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="absolute z-30 left-0 right-0 mx-auto -bottom-8 h-8 py-1 rounded-md text-center">
|
||||
<p class="truncate" :style="{ fontSize: labelFontSize + 'rem' }">{{ title }}</p>
|
||||
<p class="truncate" :style="{ fontSize: labelFontSize * sizeMultiplier + 'rem' }">{{ title }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -50,8 +50,8 @@ export default {
|
||||
return 0.875
|
||||
},
|
||||
sizeMultiplier() {
|
||||
if (this.bookCoverAspectRatio === 1) return this.width / (120 * 1.6 * 2)
|
||||
return this.width / 240
|
||||
if (this.bookCoverAspectRatio === 1) return this.width / (120 * 1.6)
|
||||
return this.width / 120
|
||||
},
|
||||
title() {
|
||||
return this.playlist ? this.playlist.name : ''
|
||||
|
||||
@@ -10,18 +10,18 @@
|
||||
<div v-if="isSeriesFinished" class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b bg-success w-full" />
|
||||
|
||||
<div v-if="hasValidCovers" class="bg-black bg-opacity-60 absolute top-0 left-0 w-full h-full flex items-center justify-center text-center transition-opacity" :class="isHovering ? '' : 'opacity-0'" :style="{ padding: `${sizeMultiplier}rem` }">
|
||||
<p class="font-book" :style="{ fontSize: 1.2 * sizeMultiplier + 'rem' }">{{ displayTitle }}</p>
|
||||
<p :style="{ fontSize: 1.2 * sizeMultiplier + 'rem' }">{{ displayTitle }}</p>
|
||||
</div>
|
||||
|
||||
<span v-if="!isHovering && rssFeed" class="absolute z-10 material-icons text-success" :style="{ top: 0.5 * sizeMultiplier + 'rem', left: 0.5 * sizeMultiplier + 'rem', fontSize: 1.5 * sizeMultiplier + 'rem' }">rss_feed</span>
|
||||
|
||||
<div v-if="!isAlternativeBookshelfView" class="categoryPlacard absolute z-10 left-0 right-0 mx-auto -bottom-6 h-6 rounded-md font-book text-center" :style="{ width: Math.min(200, width) + 'px' }">
|
||||
<div v-if="!isAlternativeBookshelfView" class="categoryPlacard absolute z-10 left-0 right-0 mx-auto -bottom-6 h-6 rounded-md text-center" :style="{ width: Math.min(200, width) + 'px' }">
|
||||
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border" :style="{ padding: `0rem ${0.5 * sizeMultiplier}rem` }">
|
||||
<p class="truncate" :style="{ fontSize: labelFontSize + 'rem' }">{{ displayTitle }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="absolute z-30 left-0 right-0 mx-auto -bottom-8 h-8 py-1 rounded-md text-center">
|
||||
<p class="truncate" :style="{ fontSize: labelFontSize + 'rem' }">{{ displayTitle }}</p>
|
||||
<p class="truncate" :style="{ fontSize: labelFontSize * sizeMultiplier + 'rem' }">{{ displayTitle }}</p>
|
||||
<p v-if="displaySortLine" class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displaySortLine }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -54,7 +54,7 @@ export default {
|
||||
},
|
||||
folderPath() {
|
||||
if (!this.libraryFolderPath) return ''
|
||||
return `${this.libraryFolderPath}\\${this.$sanitizeFilename(this.title)}`
|
||||
return `${this.libraryFolderPath}/${this.$sanitizeFilename(this.title)}`
|
||||
},
|
||||
detailsWidth() {
|
||||
return this.width - 85
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
<img v-if="libraryItem" ref="cover" :src="fullCoverUrl" loading="lazy" @error="imageError" @load="imageLoaded" class="w-full h-full absolute top-0 left-0 z-10 duration-300 transition-opacity" :style="{ opacity: imageReady ? '1' : '0' }" :class="showCoverBg ? 'object-contain' : 'object-fill'" />
|
||||
<div v-show="loading && libraryItem" class="absolute top-0 left-0 h-full w-full flex items-center justify-center">
|
||||
<p class="font-book text-center" :style="{ fontSize: 0.75 * sizeMultiplier + 'rem' }">{{ title }}</p>
|
||||
<p class="text-center" :style="{ fontSize: 0.75 * sizeMultiplier + 'rem' }">{{ title }}</p>
|
||||
<div class="absolute top-2 right-2">
|
||||
<widgets-loading-spinner />
|
||||
</div>
|
||||
@@ -17,17 +17,17 @@
|
||||
<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="/Logo.png" loading="lazy" class="mb-2" :style="{ height: 64 * sizeMultiplier + 'px' }" />
|
||||
<p class="text-center font-book text-error" :style="{ fontSize: titleFontSize + 'rem' }">Invalid Cover</p>
|
||||
<p class="text-center 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 z-10" :style="{ padding: placeholderCoverPadding + 'rem' }">
|
||||
<div>
|
||||
<p class="text-center font-book" style="color: rgb(247 223 187)" :style="{ fontSize: titleFontSize + 'rem' }">{{ titleCleaned }}</p>
|
||||
<p class="text-center" style="color: rgb(247 223 187)" :style="{ fontSize: titleFontSize + 'rem' }">{{ titleCleaned }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!hasCover" class="absolute left-0 right-0 w-full flex items-center justify-center z-10" :style="{ padding: placeholderCoverPadding + 'rem', bottom: authorBottom + 'rem' }">
|
||||
<p class="text-center font-book" style="color: rgb(247 223 187); opacity: 0.75" :style="{ fontSize: authorFontSize + 'rem' }">{{ authorCleaned }}</p>
|
||||
<p class="text-center" style="color: rgb(247 223 187); opacity: 0.75" :style="{ fontSize: authorFontSize + 'rem' }">{{ authorCleaned }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<div v-else class="relative w-full h-full flex items-center justify-center p-2 bg-primary rounded-sm">
|
||||
<div class="absolute top-0 left-0 w-full h-full bg-gray-400 bg-opacity-5" />
|
||||
|
||||
<p class="font-book text-white text-opacity-60 text-center" :style="{ fontSize: Math.min(1, sizeMultiplier) + 'rem' }">Empty Collection</p>
|
||||
<p class="text-white text-opacity-60 text-center" :style="{ fontSize: Math.min(1, sizeMultiplier) + 'rem' }">Empty Collection</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -138,7 +138,7 @@ export default {
|
||||
|
||||
var innerP = document.createElement('p')
|
||||
innerP.textContent = this.name
|
||||
innerP.className = 'text-sm font-book text-white'
|
||||
innerP.className = 'text-sm text-white'
|
||||
imgdiv.appendChild(innerP)
|
||||
|
||||
return imgdiv
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<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="/Logo.png" class="mb-2" :style="{ height: 64 * sizeMultiplier + 'px' }" />
|
||||
<p class="text-center font-book text-error" :style="{ fontSize: sizeMultiplier + 'rem' }">Invalid Cover</p>
|
||||
<p class="text-center text-error" :style="{ fontSize: sizeMultiplier + 'rem' }">Invalid Cover</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<modals-modal ref="modal" v-model="show" name="account" :width="800" :height="'unset'" :processing="processing">
|
||||
<template #outer>
|
||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
||||
<p class="font-book text-3xl text-white truncate">{{ title }}</p>
|
||||
<p class="text-3xl text-white truncate">{{ title }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<form @submit.prevent="submitForm">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<modals-modal v-model="show" name="backup-scheduler" :width="700" :height="'unset'" :processing="processing">
|
||||
<template #outer>
|
||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
||||
<p class="font-book text-3xl text-white truncate">{{ $strings.HeaderSetBackupSchedule }}</p>
|
||||
<p class="text-3xl text-white truncate">{{ $strings.HeaderSetBackupSchedule }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="show && newCronExpression" class="p-4 w-full text-sm py-6 rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-hidden" style="min-height: 400px; max-height: 80vh">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<modals-modal v-model="show" name="batchQuickMatch" :processing="processing" :width="500" :height="'unset'">
|
||||
<template #outer>
|
||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
||||
<p class="font-book text-3xl text-white truncate">{{ title }}</p>
|
||||
<p class="text-3xl text-white truncate">{{ title }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<modals-modal v-model="show" name="bookmarks" :width="500" :height="'unset'">
|
||||
<template #outer>
|
||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
||||
<p class="font-book text-3xl text-white truncate">{{ $strings.LabelYourBookmarks }}</p>
|
||||
<p class="text-3xl text-white truncate">{{ $strings.LabelYourBookmarks }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<div ref="container" class="w-full rounded-lg bg-bg box-shadow-md overflow-y-auto overflow-x-hidden" style="max-height: 80vh">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<modals-modal v-model="show" name="listening-session-modal" :processing="processing" :width="700" :height="'unset'">
|
||||
<template #outer>
|
||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
||||
<p class="font-book text-3xl text-white truncate">{{ $strings.HeaderSession }} {{ _session.id }}</p>
|
||||
<p class="text-3xl text-white truncate">{{ $strings.HeaderSession }} {{ _session.id }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<div ref="container" class="w-full rounded-lg bg-bg box-shadow-md overflow-y-auto overflow-x-hidden p-6" style="max-height: 80vh">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<modals-modal v-model="show" name="sleep-timer" :width="350" :height="'unset'">
|
||||
<template #outer>
|
||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden pointer-events-none">
|
||||
<p class="font-book text-3xl text-white truncate pointer-events-none">{{ $strings.HeaderSleepTimer }}</p>
|
||||
<p class="text-3xl text-white truncate pointer-events-none">{{ $strings.HeaderSleepTimer }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<modals-modal v-model="show" name="edit-author" :width="800" :height="'unset'" :processing="processing">
|
||||
<template #outer>
|
||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
||||
<p class="font-book text-3xl text-white truncate">{{ title }}</p>
|
||||
<p class="text-3xl text-white truncate">{{ title }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<div class="p-4 w-full text-sm py-6 rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-hidden" style="min-height: 400px; max-height: 80vh">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<modals-modal v-model="show" name="changelog" :width="800" :height="'unset'">
|
||||
<template #outer>
|
||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
||||
<p class="font-book text-3xl text-white truncate">Changelog</p>
|
||||
<p class="text-3xl text-white truncate">Changelog</p>
|
||||
</div>
|
||||
</template>
|
||||
<div class="px-8 py-6 w-full rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-y-scroll" style="max-height: 80vh">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<modals-modal v-model="show" name="collections" :processing="processing" :width="500" :height="'unset'">
|
||||
<template #outer>
|
||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden pointer-events-none">
|
||||
<p class="font-book text-3xl text-white truncate">{{ title }}</p>
|
||||
<p class="text-3xl text-white truncate">{{ title }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<modals-modal v-model="show" name="edit-collection" :width="700" :height="'unset'" :processing="processing">
|
||||
<template #outer>
|
||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
||||
<p class="font-book text-3xl text-white truncate">{{ $strings.HeaderCollection }}</p>
|
||||
<p class="text-3xl text-white truncate">{{ $strings.HeaderCollection }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<div class="p-4 w-full text-sm py-6 rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-hidden" style="min-height: 400px; max-height: 80vh">
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
<modals-modal v-model="show" name="edit-book" :width="800" :height="height" :processing="processing" :content-margin-top="marginTop">
|
||||
<template #outer>
|
||||
<div class="absolute top-0 left-0 p-4 landscape:px-4 landscape:py-2 md:portrait:p-5 lg:p-5 w-2/3 overflow-hidden pointer-events-none">
|
||||
<p class="font-book text-xl md:portrait:text-3xl md:landscape:text-lg lg:text-3xl text-white truncate pointer-events-none">{{ title }}</p>
|
||||
<p class="text-xl md:portrait:text-3xl md:landscape:text-lg lg:text-3xl text-white truncate pointer-events-none">{{ title }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<div class="absolute -top-10 left-0 z-10 w-full flex">
|
||||
<template v-for="tab in availableTabs">
|
||||
<div :key="tab.id" class="w-28 rounded-t-lg flex items-center justify-center mr-0.5 sm:mr-1 cursor-pointer hover:bg-bg font-book border-t border-l border-r border-black-300 tab text-xs sm:text-base" :class="selectedTab === tab.id ? 'tab-selected bg-bg pb-px' : 'bg-primary text-gray-400'" @click="selectTab(tab.id)">{{ tab.title }}</div>
|
||||
<div :key="tab.id" class="w-28 rounded-t-lg flex items-center justify-center mr-0.5 sm:mr-1 cursor-pointer hover:bg-bg border-t border-l border-r border-black-300 tab text-xs sm:text-base" :class="selectedTab === tab.id ? 'tab-selected bg-bg pb-px' : 'bg-primary text-gray-400'" @click="selectTab(tab.id)">{{ tab.title }}</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ export default {
|
||||
rescan() {
|
||||
this.rescanning = true
|
||||
this.$axios
|
||||
.$get(`/api/items/${this.libraryItemId}/scan`)
|
||||
.$post(`/api/items/${this.libraryItemId}/scan`)
|
||||
.then((data) => {
|
||||
this.rescanning = false
|
||||
var result = data.result
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
</div>
|
||||
<div v-if="!episodes.length" class="flex my-4 text-center justify-center text-xl">{{ $strings.MessageNoEpisodes }}</div>
|
||||
<table v-else class="text-sm tracksTable">
|
||||
<tr class="font-book">
|
||||
<tr>
|
||||
<th class="text-left">Sort #</th>
|
||||
<th class="text-left whitespace-nowrap">{{ $strings.LabelEpisode }}</th>
|
||||
<th class="text-left">{{ $strings.EpisodeTitle }}</th>
|
||||
@@ -33,7 +33,7 @@
|
||||
<td class="text-left">
|
||||
<p class="px-4">{{ episode.episode }}</p>
|
||||
</td>
|
||||
<td class="font-book">
|
||||
<td>
|
||||
{{ episode.title }}
|
||||
</td>
|
||||
<td class="font-mono text-center">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<ui-dropdown v-model="mediaType" :items="mediaTypes" :label="$strings.LabelMediaType" :disabled="!isNew" small @input="changedMediaType" />
|
||||
</div>
|
||||
<div class="w-full md:flex-grow px-1 py-1 md:py-0">
|
||||
<ui-text-input-with-label v-model="name" :label="$strings.LabelLibraryName" @blur="nameBlurred" />
|
||||
<ui-text-input-with-label ref="nameInput" v-model="name" :label="$strings.LabelLibraryName" @blur="nameBlurred" />
|
||||
</div>
|
||||
<div class="w-1/5 md:w-18 px-1 py-1 md:py-0">
|
||||
<ui-media-icon-picker v-model="icon" :label="$strings.LabelIcon" @input="iconChanged" />
|
||||
@@ -20,12 +20,12 @@
|
||||
<p class="px-1 text-sm font-semibold">{{ $strings.LabelFolders }}</p>
|
||||
<div v-for="(folder, index) in folders" :key="index" class="w-full flex items-center py-1 px-2">
|
||||
<span class="material-icons bg-opacity-50 mr-2 text-yellow-200" style="font-size: 1.2rem">folder</span>
|
||||
<ui-editable-text v-model="folder.fullPath" readonly type="text" class="w-full" />
|
||||
<ui-editable-text ref="folderInput" v-model="folder.fullPath" readonly type="text" class="w-full" />
|
||||
<span v-show="folders.length > 1" class="material-icons text-2xl ml-2 cursor-pointer hover:text-error" @click="removeFolder(folder)">close</span>
|
||||
</div>
|
||||
<div class="flex py-1 px-2 items-center w-full">
|
||||
<span class="material-icons bg-opacity-50 mr-2 text-yellow-200" style="font-size: 1.2rem">folder</span>
|
||||
<ui-editable-text v-model="newFolderPath" :placeholder="$strings.PlaceholderNewFolderPath" type="text" class="w-full" @blur="newFolderInputBlurred" />
|
||||
<ui-editable-text ref="newFolderInput" v-model="newFolderPath" :placeholder="$strings.PlaceholderNewFolderPath" type="text" class="w-full" @blur="newFolderInputBlurred" />
|
||||
</div>
|
||||
|
||||
<ui-btn class="w-full mt-2" color="primary" @click="browseForFolder">{{ $strings.ButtonBrowseForFolder }}</ui-btn>
|
||||
@@ -82,6 +82,19 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
checkBlurExpressionInput() {
|
||||
if (this.$refs.nameInput) {
|
||||
this.$refs.nameInput.blur()
|
||||
}
|
||||
if (this.$refs.folderInput && this.$refs.folderInput.length) {
|
||||
this.$refs.folderInput.forEach((input) => {
|
||||
if (input.blur) input.blur()
|
||||
})
|
||||
}
|
||||
if (this.$refs.newFolderInput) {
|
||||
this.$refs.newFolderInput.blur()
|
||||
}
|
||||
},
|
||||
browseForFolder() {
|
||||
this.showDirectoryPicker = true
|
||||
},
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
<modals-modal v-model="show" name="edit-library" :width="700" :height="'unset'" :processing="processing">
|
||||
<template #outer>
|
||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
||||
<p class="font-book text-xl md:text-3xl text-white truncate">{{ title }}</p>
|
||||
<p class="text-xl md:text-3xl text-white truncate">{{ title }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<div class="absolute -top-10 left-0 z-10 w-full flex">
|
||||
<template v-for="tab in tabs">
|
||||
<div :key="tab.id" class="w-28 rounded-t-lg flex items-center justify-center mr-1 cursor-pointer hover:bg-bg font-book border-t border-l border-r border-black-300 tab text-xs sm:text-base" :class="selectedTab === tab.id ? 'tab-selected bg-bg pb-px' : 'bg-primary text-gray-400'" @click="selectTab(tab.id)">{{ tab.title }}</div>
|
||||
<div :key="tab.id" class="w-28 rounded-t-lg flex items-center justify-center mr-1 cursor-pointer hover:bg-bg border-t border-l border-r border-black-300 tab text-xs sm:text-base" :class="selectedTab === tab.id ? 'tab-selected bg-bg pb-px' : 'bg-primary text-gray-400'" @click="selectTab(tab.id)">{{ tab.title }}</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
@@ -144,8 +144,6 @@ export default {
|
||||
return true
|
||||
},
|
||||
submit() {
|
||||
if (!this.validate()) return
|
||||
|
||||
// If custom expression input is focused then unfocus it instead of submitting
|
||||
if (this.$refs.tabComponent && this.$refs.tabComponent.checkBlurExpressionInput) {
|
||||
if (this.$refs.tabComponent.checkBlurExpressionInput()) {
|
||||
@@ -153,6 +151,8 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.validate()) return
|
||||
|
||||
if (this.library) {
|
||||
this.submitUpdateLibrary()
|
||||
} else {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<modals-modal ref="modal" v-model="show" name="notification-edit" :width="800" :height="'unset'" :processing="processing">
|
||||
<template #outer>
|
||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
||||
<p class="font-book text-3xl text-white truncate">{{ title }}</p>
|
||||
<p class="text-3xl text-white truncate">{{ title }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<form @submit.prevent="submitForm">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<modals-modal v-model="show" name="queue-items" :width="800" :height="'unset'">
|
||||
<template #outer>
|
||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
||||
<p class="font-book text-3xl text-white truncate">{{ $strings.HeaderPlayerQueue }}</p>
|
||||
<p class="text-3xl text-white truncate">{{ $strings.HeaderPlayerQueue }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<div ref="container" class="w-full rounded-lg bg-bg box-shadow-md overflow-y-auto overflow-x-hidden py-4" style="max-height: 80vh">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<modals-modal v-model="show" name="playlists" :processing="processing" :width="500" :height="'unset'">
|
||||
<template #outer>
|
||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden pointer-events-none">
|
||||
<p class="font-book text-3xl text-white truncate">{{ title }}</p>
|
||||
<p class="text-3xl text-white truncate">{{ title }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<modals-modal v-model="show" name="edit-playlist" :width="700" :height="'unset'" :processing="processing">
|
||||
<template #outer>
|
||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
||||
<p class="font-book text-3xl text-white truncate">{{ $strings.HeaderPlaylist }}</p>
|
||||
<p class="text-3xl text-white truncate">{{ $strings.HeaderPlaylist }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<div class="p-4 w-full text-sm py-6 rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-hidden" style="min-height: 400px; max-height: 80vh">
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
<modals-modal v-model="show" name="podcast-episode-edit-modal" :width="800" :height="'unset'" :processing="processing">
|
||||
<template #outer>
|
||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
||||
<p class="font-book text-3xl text-white truncate">{{ title }}</p>
|
||||
<p class="text-3xl text-white truncate">{{ title }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<div class="absolute -top-10 left-0 z-10 w-full flex">
|
||||
<template v-for="tab in tabs">
|
||||
<div :key="tab.id" class="w-28 rounded-t-lg flex items-center justify-center mr-0.5 sm:mr-1 cursor-pointer hover:bg-bg font-book border-t border-l border-r border-black-300 tab text-xs sm:text-base" :class="selectedTab === tab.id ? 'tab-selected bg-bg pb-px' : 'bg-primary text-gray-400'" @click="selectTab(tab.id)">{{ tab.title }}</div>
|
||||
<div :key="tab.id" class="w-28 rounded-t-lg flex items-center justify-center mr-0.5 sm:mr-1 cursor-pointer hover:bg-bg border-t border-l border-r border-black-300 tab text-xs sm:text-base" :class="selectedTab === tab.id ? 'tab-selected bg-bg pb-px' : 'bg-primary text-gray-400'" @click="selectTab(tab.id)">{{ tab.title }}</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<modals-modal v-model="show" name="podcast-episodes-modal" :width="1200" :height="'unset'" :processing="processing">
|
||||
<template #outer>
|
||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
||||
<p class="font-book text-3xl text-white truncate">{{ title }}</p>
|
||||
<p class="text-3xl text-white truncate">{{ title }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<div ref="wrapper" id="podcast-wrapper" class="p-4 w-full text-sm py-2 rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-hidden">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<modals-modal v-model="show" name="new-podcast-modal" :width="1000" :height="'unset'" :processing="processing">
|
||||
<template #outer>
|
||||
<div class="absolute top-0 left-0 p-5 w-3/4 overflow-hidden">
|
||||
<p class="font-book text-xl md:text-3xl text-white truncate">{{ title }}</p>
|
||||
<p class="text-xl md:text-3xl text-white truncate">{{ title }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<div ref="wrapper" id="podcast-wrapper" class="p-2 md:p-8 w-full text-sm py-2 rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-x-hidden overflow-y-auto" style="max-height: 80vh">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<modals-modal v-model="show" name="opml-feeds-modal" :width="1000" :height="'unset'" :processing="processing">
|
||||
<template #outer>
|
||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
||||
<p class="font-book text-3xl text-white truncate">{{ title }}</p>
|
||||
<p class="text-3xl text-white truncate">{{ title }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<div ref="wrapper" class="p-4 w-full text-sm py-2 rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-hidden">
|
||||
@@ -97,7 +97,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
toFeedMetadata(feed) {
|
||||
var metadata = feed.metadata
|
||||
const metadata = feed.metadata
|
||||
return {
|
||||
title: metadata.title,
|
||||
author: metadata.author,
|
||||
@@ -122,9 +122,9 @@ export default {
|
||||
},
|
||||
async submit() {
|
||||
this.processing = true
|
||||
var newFeedPayloads = this.feedMetadata.map((metadata) => {
|
||||
const newFeedPayloads = this.feedMetadata.map((metadata) => {
|
||||
return {
|
||||
path: `${this.selectedFolderPath}\\${this.$sanitizeFilename(metadata.title)}`,
|
||||
path: `${this.selectedFolderPath}/${this.$sanitizeFilename(metadata.title)}`,
|
||||
folderId: this.selectedFolderId,
|
||||
libraryId: this.currentLibrary.id,
|
||||
media: {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<modals-modal v-model="show" name="podcast-episode-remove-modal" :width="500" :height="'unset'" :processing="processing">
|
||||
<template #outer>
|
||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
||||
<p class="font-book text-3xl text-white truncate">{{ title }}</p>
|
||||
<p class="text-3xl text-white truncate">{{ title }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<div ref="wrapper" class="px-8 py-6 w-full text-sm rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-hidden">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<modals-modal v-model="show" name="podcast-episode-view-modal" :width="800" :height="'unset'" :processing="processing">
|
||||
<template #outer>
|
||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
||||
<p class="font-book text-3xl text-white truncate">{{ $strings.LabelEpisode }}</p>
|
||||
<p class="text-3xl text-white truncate">{{ $strings.LabelEpisode }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<div ref="wrapper" class="p-4 w-full text-sm rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-y-auto" style="max-height: 80vh">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<modals-modal v-model="show" name="rss-feed-modal" :width="600" :height="'unset'" :processing="processing">
|
||||
<template #outer>
|
||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
||||
<p class="font-book text-3xl text-white truncate">{{ title }}</p>
|
||||
<p class="text-3xl text-white truncate">{{ title }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<div ref="wrapper" class="px-8 py-6 w-full text-sm rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-hidden">
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Hover timestamp -->
|
||||
<div ref="hoverTimestamp" class="absolute -top-8 left-0 bg-white text-black rounded-full opacity-0 pointer-events-none">
|
||||
<div ref="hoverTimestamp" class="absolute -top-8 left-0 bg-white text-black rounded-full opacity-0 pointer-events-none z-10">
|
||||
<p ref="hoverTimestampText" class="text-xs font-mono text-center px-2 py-0.5 truncate whitespace-nowrap">00:00</p>
|
||||
</div>
|
||||
<div ref="hoverTimestampArrow" class="absolute -top-3 left-0 bg-white text-black rounded-full opacity-0 pointer-events-none">
|
||||
@@ -83,9 +83,9 @@ export default {
|
||||
|
||||
var offsetX = e.offsetX
|
||||
var perc = offsetX / this.trackWidth
|
||||
const baseTime = this.useChapterTrack ? this.currentChapterStart : 0;
|
||||
const duration = this.useChapterTrack ? this.currentChapterDuration : this.duration;
|
||||
const time = baseTime + (perc * duration);
|
||||
const baseTime = this.useChapterTrack ? this.currentChapterStart : 0
|
||||
const duration = this.useChapterTrack ? this.currentChapterDuration : this.duration
|
||||
const time = baseTime + perc * duration
|
||||
if (isNaN(time) || time === null) {
|
||||
console.error('Invalid time', perc, time)
|
||||
return
|
||||
@@ -143,10 +143,10 @@ export default {
|
||||
mousemoveTrack(e) {
|
||||
var offsetX = e.offsetX
|
||||
|
||||
const baseTime = this.useChapterTrack ? this.currentChapterStart : 0;
|
||||
const duration = this.useChapterTrack ? this.currentChapterDuration : this.duration;
|
||||
const progressTime = (offsetX / this.trackWidth) * duration;
|
||||
const totalTime = baseTime + progressTime;
|
||||
const baseTime = this.useChapterTrack ? this.currentChapterStart : 0
|
||||
const duration = this.useChapterTrack ? this.currentChapterDuration : this.duration
|
||||
const progressTime = (offsetX / this.trackWidth) * duration
|
||||
const totalTime = baseTime + progressTime
|
||||
|
||||
if (this.$refs.hoverTimestamp) {
|
||||
var width = this.$refs.hoverTimestamp.clientWidth
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<span class="material-icons cursor-pointer text-4xl" @click="close">close</span>
|
||||
</div>
|
||||
|
||||
<div class="absolute top-4 left-4 font-book">
|
||||
<div class="absolute top-4 left-4">
|
||||
<h1 class="text-2xl mb-1">{{ abTitle }}</h1>
|
||||
<p v-if="abAuthor">by {{ abAuthor }}</p>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="w-96 my-6 mx-auto">
|
||||
<h1 class="text-2xl mb-4 font-book">{{ $strings.HeaderStatsMinutesListeningChart }}</h1>
|
||||
<h1 class="text-2xl mb-4">{{ $strings.HeaderStatsMinutesListeningChart }}</h1>
|
||||
<div class="relative w-96 h-72">
|
||||
<div class="absolute top-0 left-0">
|
||||
<template v-for="lbl in yAxisLabels">
|
||||
@@ -27,7 +27,7 @@
|
||||
<div class="absolute -bottom-2 left-0 flex ml-6">
|
||||
<template v-for="dayObj in last7Days">
|
||||
<div :key="dayObj.date" :style="{ width: daySpacing + daySpacing / 14 + 'px' }">
|
||||
<p class="text-sm font-book">{{ dayObj.dayOfWeek.slice(0, 3) }}</p>
|
||||
<p class="text-sm">{{ dayObj.dayOfWeekAbbr }}</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@@ -108,6 +108,7 @@ export default {
|
||||
var _date = this.$addDaysToToday(i * -1)
|
||||
days.push({
|
||||
dayOfWeek: this.$formatJsDate(_date, 'EEEE'),
|
||||
dayOfWeekAbbr: this.$formatJsDate(_date, 'EEE'),
|
||||
date: this.$formatJsDate(_date, 'yyyy-MM-dd')
|
||||
})
|
||||
}
|
||||
@@ -218,4 +219,4 @@ export default {
|
||||
},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -68,7 +68,7 @@ export default {
|
||||
dayLabels() {
|
||||
return [
|
||||
{
|
||||
label: 'Mon',
|
||||
label: this.$formatJsDate(new Date(2023, 0, 2), 'EEE'),
|
||||
style: {
|
||||
transform: `translate(${-25}px, ${13}px)`,
|
||||
lineHeight: '10px',
|
||||
@@ -76,7 +76,7 @@ export default {
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Wed',
|
||||
label: this.$formatJsDate(new Date(2023, 0, 4), 'EEE'),
|
||||
style: {
|
||||
transform: `translate(${-25}px, ${13 * 3}px)`,
|
||||
lineHeight: '10px',
|
||||
@@ -84,7 +84,7 @@ export default {
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Fri',
|
||||
label: this.$formatJsDate(new Date(2023, 0, 6), 'EEE'),
|
||||
style: {
|
||||
transform: `translate(${-25}px, ${13 * 5}px)`,
|
||||
lineHeight: '10px',
|
||||
@@ -270,4 +270,4 @@ export default {
|
||||
},
|
||||
beforeDestroy() {}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</svg>
|
||||
<div class="px-2">
|
||||
<p class="text-4xl md:text-5xl font-bold">{{ totalItems }}</p>
|
||||
<p class="font-book text-xs md:text-sm text-white text-opacity-80">{{ $strings.LabelStatsItemsInLibrary }}</p>
|
||||
<p class="text-xs md:text-sm text-white text-opacity-80">{{ $strings.LabelStatsItemsInLibrary }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<span class="material-icons text-7xl">show_chart</span>
|
||||
<div class="px-1">
|
||||
<p class="text-4xl md:text-5xl font-bold">{{ totalTime }}</p>
|
||||
<p class="font-book text-xs md:text-sm text-white text-opacity-80">{{ useOverallHours ? $strings.LabelStatsOverallHours : $strings.LabelStatsOverallDays }}</p>
|
||||
<p class="text-xs md:text-sm text-white text-opacity-80">{{ useOverallHours ? $strings.LabelStatsOverallHours : $strings.LabelStatsOverallDays }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
</svg>
|
||||
<div class="px-1">
|
||||
<p class="text-4xl md:text-5xl font-bold">{{ totalAuthors }}</p>
|
||||
<p class="font-book text-xs md:text-sm text-white text-opacity-80">{{ $strings.LabelStatsAuthors }}</p>
|
||||
<p class="text-xs md:text-sm text-white text-opacity-80">{{ $strings.LabelStatsAuthors }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<span class="material-icons-outlined text-6xl pt-1">insert_drive_file</span>
|
||||
<div class="px-1">
|
||||
<p class="text-4xl md:text-5xl font-bold">{{ totalSizeNum }}</p>
|
||||
<p class="font-book text-xs md:text-sm text-white text-opacity-80">{{ $strings.LabelSize }} ({{ totalSizeMod }})</p>
|
||||
<p class="text-xs md:text-sm text-white text-opacity-80">{{ $strings.LabelSize }} ({{ totalSizeMod }})</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<span class="material-icons-outlined text-6xl pt-1">audio_file</span>
|
||||
<div class="px-1">
|
||||
<p class="text-4xl md:text-5xl font-bold">{{ numAudioTracks }}</p>
|
||||
<p class="font-book text-xs md:text-sm text-white text-opacity-80">{{ $strings.LabelStatsAudioTracks }}</p>
|
||||
<p class="text-xs md:text-sm text-white text-opacity-80">{{ $strings.LabelStatsAudioTracks }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
</div>
|
||||
<transition name="slide">
|
||||
<table class="text-sm tracksTable" v-show="expanded || keepOpen">
|
||||
<tr class="font-book">
|
||||
<tr>
|
||||
<th class="text-left w-16"><span class="px-4">Id</span></th>
|
||||
<th class="text-left">{{ $strings.LabelTitle }}</th>
|
||||
<th class="text-center">{{ $strings.LabelStart }}</th>
|
||||
@@ -21,7 +21,7 @@
|
||||
<td class="text-left">
|
||||
<p class="px-4">{{ chapter.id }}</p>
|
||||
</td>
|
||||
<td class="font-book">
|
||||
<td>
|
||||
{{ chapter.title }}
|
||||
</td>
|
||||
<td class="font-mono text-center hover:underline cursor-pointer" @click.stop="goToTimestamp(chapter.start)">
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<transition name="slide">
|
||||
<div class="w-full" v-show="showFiles">
|
||||
<table class="text-sm tracksTable">
|
||||
<tr class="font-book">
|
||||
<tr>
|
||||
<th class="text-left px-4">{{ $strings.LabelPath }}</th>
|
||||
<th class="text-left w-24 min-w-24">{{ $strings.LabelSize }}</th>
|
||||
<th class="text-left px-4 w-24">{{ $strings.LabelType }}</th>
|
||||
@@ -22,7 +22,7 @@
|
||||
</tr>
|
||||
<template v-for="file in files">
|
||||
<tr :key="file.path">
|
||||
<td class="font-book px-4">
|
||||
<td class="px-4">
|
||||
{{ showFullPath ? file.metadata.path : file.metadata.relPath }}
|
||||
</td>
|
||||
<td class="font-mono">
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<transition name="slide">
|
||||
<div class="w-full" v-show="showTracks">
|
||||
<table class="text-sm tracksTable">
|
||||
<tr class="font-book">
|
||||
<tr>
|
||||
<th class="w-10">#</th>
|
||||
<th class="text-left">{{ $strings.LabelFilename }}</th>
|
||||
<th class="text-left w-20">{{ $strings.LabelSize }}</th>
|
||||
|
||||
@@ -11,20 +11,20 @@
|
||||
<transition name="slide">
|
||||
<div class="w-full" v-show="expand">
|
||||
<table class="text-sm tracksTable">
|
||||
<tr class="font-book">
|
||||
<tr>
|
||||
<th class="text-left">{{ $strings.LabelFilename }}</th>
|
||||
<th class="text-left">{{ $strings.LabelSize }}</th>
|
||||
<th class="text-left">{{ $strings.LabelType }}</th>
|
||||
</tr>
|
||||
<template v-for="file in files">
|
||||
<tr :key="file.path">
|
||||
<td class="font-book pl-2">
|
||||
<td class="pl-2">
|
||||
{{ file.name }}
|
||||
</td>
|
||||
<td class="font-mono">
|
||||
{{ $bytesPretty(file.size) }}
|
||||
</td>
|
||||
<td class="font-book">
|
||||
<td>
|
||||
{{ file.filetype }}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -38,10 +38,10 @@
|
||||
<div class="w-full flex justify-left">
|
||||
<!-- Dont show edit for non-root users -->
|
||||
<div v-if="user.type !== 'root' || userIsRoot" class="h-8 w-8 flex items-center justify-center text-white text-opacity-50 hover:text-opacity-100 cursor-pointer" @click.stop="editUser(user)">
|
||||
<button :aria-label="$getString('ButtonUserEdit', [user.username])" class="material-icons text-base">edit</button>
|
||||
<button type="button" :aria-label="$getString('ButtonUserEdit', [user.username])" class="material-icons text-base">edit</button>
|
||||
</div>
|
||||
<div v-show="user.type !== 'root'" class="h-8 w-8 flex items-center justify-center text-white text-opacity-50 hover:text-error cursor-pointer" @click.stop="deleteUserClick(user)">
|
||||
<button :aria-label="$getString('ButtonUserDelete', [user.username])" class="material-icons text-base">delete</button>
|
||||
<button type="button" :aria-label="$getString('ButtonUserDelete', [user.username])" class="material-icons text-base">delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<svg v-else viewBox="0 0 24 24" class="h-6 w-6 text-white text-opacity-50 animate-spin">
|
||||
<path fill="currentColor" d="M12,4V2A10,10 0 0,0 2,12H4A8,8 0 0,1 12,4Z" />
|
||||
</svg>
|
||||
<p class="text-base md:text-xl font-book pl-2 md:pl-4 hover:underline cursor-pointer" @click.stop="$emit('click', library)">{{ library.name }}</p>
|
||||
<p class="text-base md:text-xl pl-2 md:pl-4 hover:underline cursor-pointer" @click.stop="$emit('click', library)">{{ library.name }}</p>
|
||||
|
||||
<div class="flex-grow" />
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<p class="px-1 text-sm font-semibold" :class="disabled ? 'text-gray-400' : ''">{{ label }}</p>
|
||||
<label class="px-1 text-sm font-semibold" :class="disabled ? 'text-gray-400' : ''">{{ label }}</label>
|
||||
<div ref="wrapper" class="relative">
|
||||
<form @submit.prevent="submitForm">
|
||||
<div ref="inputWrapper" class="input-wrapper flex-wrap relative w-full shadow-sm flex items-center border border-gray-600 rounded px-2 py-2" :class="disabled ? 'pointer-events-none bg-black-300 text-gray-400' : 'bg-primary'">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<button :aria-labelledby="labeledBy" role="checkbox" class="border rounded-full border-black-100 flex items-center cursor-pointer w-10 justify-start" :aria-checked="toggleValue" :class="className" @click="clickToggle">
|
||||
<button :aria-labelledby="labeledBy" role="checkbox" type="button" class="border rounded-full border-black-100 flex items-center cursor-pointer w-10 justify-start" :aria-checked="toggleValue" :class="className" @click="clickToggle">
|
||||
<span class="rounded-full border w-5 h-5 border-black-50 shadow transform transition-transform duration-100" :class="switchClassName"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -137,31 +137,31 @@ export default {
|
||||
weekdays() {
|
||||
return [
|
||||
{
|
||||
text: this.$strings.WeekdaySunday,
|
||||
text: this.$formatJsDate(new Date(2023, 0, 1), 'EEEE'),
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
text: this.$strings.WeekdayMonday,
|
||||
text: this.$formatJsDate(new Date(2023, 0, 2), 'EEEE'),
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
text: this.$strings.WeekdayTuesday,
|
||||
text: this.$formatJsDate(new Date(2023, 0, 3), 'EEEE'),
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
text: this.$strings.WeekdayWednesday,
|
||||
text: this.$formatJsDate(new Date(2023, 0, 4), 'EEEE'),
|
||||
value: 3
|
||||
},
|
||||
{
|
||||
text: this.$strings.WeekdayThursday,
|
||||
text: this.$formatJsDate(new Date(2023, 0, 5), 'EEEE'),
|
||||
value: 4
|
||||
},
|
||||
{
|
||||
text: this.$strings.WeekdayFriday,
|
||||
text: this.$formatJsDate(new Date(2023, 0, 6), 'EEEE'),
|
||||
value: 5
|
||||
},
|
||||
{
|
||||
text: this.$strings.WeekdaySaturday,
|
||||
text: this.$formatJsDate(new Date(2023, 0, 7), 'EEEE'),
|
||||
value: 6
|
||||
}
|
||||
]
|
||||
|
||||
4
client/package-lock.json
generated
4
client/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "audiobookshelf-client",
|
||||
"version": "2.2.12",
|
||||
"version": "2.2.15",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "audiobookshelf-client",
|
||||
"version": "2.2.12",
|
||||
"version": "2.2.15",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@nuxtjs/axios": "^5.13.6",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "audiobookshelf-client",
|
||||
"version": "2.2.12",
|
||||
"version": "2.2.15",
|
||||
"description": "Self-hosted audiobook and podcast client",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -136,7 +136,7 @@
|
||||
<modals-modal v-model="showFindChaptersModal" name="edit-book" :width="500" :processing="findingChapters">
|
||||
<template #outer>
|
||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden pointer-events-none">
|
||||
<p class="font-book text-3xl text-white truncate pointer-events-none">{{ $strings.HeaderFindChapters }}</p>
|
||||
<p class="text-3xl text-white truncate pointer-events-none">{{ $strings.HeaderFindChapters }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<div class="w-full h-full max-h-full text-sm rounded-lg bg-bg shadow-lg border border-black-300 relative">
|
||||
|
||||
@@ -37,23 +37,23 @@
|
||||
<draggable v-model="files" v-bind="dragOptions" class="list-group border border-gray-600" draggable=".item" tag="ul" @start="drag = true" @end="drag = false" @update="draggableUpdate">
|
||||
<transition-group type="transition" :name="!drag ? 'flip-list' : null">
|
||||
<li v-for="(audio, index) in files" :key="audio.ino" :class="audio.include ? 'item' : 'exclude'" class="w-full list-group-item flex items-center relative">
|
||||
<div class="font-book text-center px-4 py-1 w-12 min-w-12">
|
||||
<div class="text-center px-4 py-1 w-12 min-w-12">
|
||||
{{ audio.include ? index - numExcluded + 1 : -1 }}
|
||||
</div>
|
||||
<div class="font-book text-center px-4 w-24 min-w-24">{{ audio.index }}</div>
|
||||
<div class="font-book text-center px-2 w-32 min-w-32">
|
||||
<div class="text-center px-4 w-24 min-w-24">{{ audio.index }}</div>
|
||||
<div class="text-center px-2 w-32 min-w-32">
|
||||
{{ audio.trackNumFromFilename }}
|
||||
</div>
|
||||
<div class="font-book text-center w-32 min-w-32">
|
||||
<div class="text-center w-32 min-w-32">
|
||||
{{ audio.trackNumFromMeta }}
|
||||
</div>
|
||||
<div class="font-book truncate px-4 w-20 min-w-20">
|
||||
<div class="truncate px-4 w-20 min-w-20">
|
||||
{{ audio.discNumFromFilename }}
|
||||
</div>
|
||||
<div class="font-book truncate px-4 w-20 min-w-20">
|
||||
<div class="truncate px-4 w-20 min-w-20">
|
||||
{{ audio.discNumFromMeta }}
|
||||
</div>
|
||||
<div class="font-book truncate px-4 flex-grow">
|
||||
<div class="truncate px-4 flex-grow">
|
||||
{{ audio.metadata.filename }}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<ui-toggle-switch labeledBy="settings-store-cover-with-items" v-model="newServerSettings.storeCoverWithItem" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('storeCoverWithItem', val)" />
|
||||
<ui-tooltip :text="$strings.LabelSettingsStoreCoversWithItemHelp">
|
||||
<p class="pl-4">
|
||||
<span id="settings-store-cover-with-items">{{ $strings.LabelSettingsStoreCoversWithItem }}</span>
|
||||
<span id="settings-store-cover-with-items">{{ $strings.LabelSettingsStoreCoversWithItem }}</span>
|
||||
<span class="material-icons icon-text">info_outlined</span>
|
||||
</p>
|
||||
</ui-tooltip>
|
||||
@@ -206,7 +206,7 @@
|
||||
|
||||
<div class="flex items-center py-4">
|
||||
<div class="flex-grow" />
|
||||
<p class="pr-2 text-sm font-book text-yellow-400">
|
||||
<p class="pr-2 text-sm text-yellow-400">
|
||||
{{ $strings.MessageReportBugsAndContribute }}
|
||||
<a class="underline" href="https://github.com/advplyr/audiobookshelf" target="_blank">github</a>
|
||||
</p>
|
||||
@@ -217,7 +217,7 @@
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<p class="pl-4 pr-2 text-sm font-book text-yellow-400">
|
||||
<p class="pl-4 pr-2 text-sm text-yellow-400">
|
||||
{{ $strings.MessageJoinUsOn }}
|
||||
<a class="underline" href="https://discord.gg/pJsjuNCKRq" target="_blank">discord</a>
|
||||
</p>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<app-settings-content :header-text="'Item Metadata Utils'">
|
||||
<app-settings-content :header-text="$strings.HeaderItemMetadataUtils">
|
||||
<nuxt-link to="/config/item-metadata-utils/tags" class="block w-full rounded bg-primary/40 hover:bg-primary/60 text-gray-300 hover:text-white p-4 mt-6 mb-2">
|
||||
<div class="flex justify-between">
|
||||
<p>{{ $strings.HeaderManageTags }}</p>
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
|
||||
<div class="flex lg:flex-row flex-wrap justify-between flex-col mt-8">
|
||||
<div class="w-80 my-6 mx-auto">
|
||||
<h1 class="text-2xl mb-4 font-book">{{ $strings.HeaderStatsTop5Genres }}</h1>
|
||||
<h1 class="text-2xl mb-4">{{ $strings.HeaderStatsTop5Genres }}</h1>
|
||||
<p v-if="!top5Genres.length">{{ $strings.MessageNoGenres }}</p>
|
||||
<template v-for="genre in top5Genres">
|
||||
<div :key="genre.genre" class="w-full py-2">
|
||||
<div class="flex items-end mb-1">
|
||||
<p class="text-2xl font-bold">{{ Math.round((100 * genre.count) / totalItems) }} %</p>
|
||||
<div class="flex-grow" />
|
||||
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=genres.${$encode(genre.genre)}`" class="text-base font-book text-white text-opacity-70 hover:underline">
|
||||
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=genres.${$encode(genre.genre)}`" class="text-base text-white text-opacity-70 hover:underline">
|
||||
{{ genre.genre }}
|
||||
</nuxt-link>
|
||||
</div>
|
||||
@@ -23,12 +23,12 @@
|
||||
</template>
|
||||
</div>
|
||||
<div class="w-80 my-6 mx-auto">
|
||||
<h1 class="text-2xl mb-4 font-book">{{ $strings.HeaderStatsTop10Authors }}</h1>
|
||||
<h1 class="text-2xl mb-4">{{ $strings.HeaderStatsTop10Authors }}</h1>
|
||||
<p v-if="!top10Authors.length">{{ $strings.MessageNoAuthors }}</p>
|
||||
<template v-for="(author, index) in top10Authors">
|
||||
<div :key="author.id" class="w-full py-2">
|
||||
<div class="flex items-center mb-1">
|
||||
<p class="text-sm font-book text-white text-opacity-70 w-36 pr-2 truncate">
|
||||
<p class="text-sm text-white text-opacity-70 w-36 pr-2 truncate">
|
||||
{{ index + 1 }}. <nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=authors.${$encode(author.id)}`" class="hover:underline">{{ author.name }}</nuxt-link>
|
||||
</p>
|
||||
<div class="flex-grow rounded-full h-2.5 bg-primary bg-opacity-0 overflow-hidden">
|
||||
@@ -42,12 +42,12 @@
|
||||
</template>
|
||||
</div>
|
||||
<div class="w-80 my-6 mx-auto">
|
||||
<h1 class="text-2xl mb-4 font-book">{{ $strings.HeaderStatsLongestItems }}</h1>
|
||||
<h1 class="text-2xl mb-4">{{ $strings.HeaderStatsLongestItems }}</h1>
|
||||
<p v-if="!top10LongestItems.length">{{ $strings.MessageNoItems }}</p>
|
||||
<template v-for="(ab, index) in top10LongestItems">
|
||||
<div :key="index" class="w-full py-2">
|
||||
<div class="flex items-center mb-1">
|
||||
<p class="text-sm font-book text-white text-opacity-70 w-44 pr-2 truncate">
|
||||
<p class="text-sm text-white text-opacity-70 w-44 pr-2 truncate">
|
||||
{{ index + 1 }}. <nuxt-link :to="`/item/${ab.id}`" class="hover:underline">{{ ab.title }}</nuxt-link>
|
||||
</p>
|
||||
<div class="flex-grow rounded-full h-2.5 bg-primary bg-opacity-0 overflow-hidden">
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
</svg>
|
||||
<div class="px-3">
|
||||
<p class="text-4xl md:text-5xl font-bold">{{ userItemsFinished.length }}</p>
|
||||
<p class="font-book text-xs md:text-sm text-white text-opacity-80">{{ $strings.LabelStatsItemsFinished }}</p>
|
||||
<p class="text-xs md:text-sm text-white text-opacity-80">{{ $strings.LabelStatsItemsFinished }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
</div>
|
||||
<div class="px-1">
|
||||
<p class="text-4xl md:text-5xl font-bold">{{ totalDaysListened }}</p>
|
||||
<p class="font-book text-xs md:text-sm text-white text-opacity-80">{{ $strings.LabelStatsDaysListened }}</p>
|
||||
<p class="text-xs md:text-sm text-white text-opacity-80">{{ $strings.LabelStatsDaysListened }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
</div>
|
||||
<div class="px-1">
|
||||
<p class="text-4xl md:text-5xl font-bold">{{ totalMinutesListening }}</p>
|
||||
<p class="font-book text-xs md:text-sm text-white text-opacity-80">{{ $strings.LabelStatsMinutesListening }}</p>
|
||||
<p class="text-xs md:text-sm text-white text-opacity-80">{{ $strings.LabelStatsMinutesListening }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -39,7 +39,7 @@
|
||||
<stats-daily-listening-chart :listening-stats="listeningStats" class="origin-top-left transform scale-75 lg:scale-100" />
|
||||
<div class="w-80 my-6 mx-auto">
|
||||
<div class="flex mb-4 items-center">
|
||||
<h1 class="text-2xl font-book">{{ $strings.HeaderStatsRecentSessions }}</h1>
|
||||
<h1 class="text-2xl">{{ $strings.HeaderStatsRecentSessions }}</h1>
|
||||
<div class="flex-grow" />
|
||||
<ui-btn :to="`/config/users/${user.id}/sessions`" class="text-xs" :padding-x="1.5" :padding-y="1">{{ $strings.ButtonViewAll }}</ui-btn>
|
||||
</div>
|
||||
@@ -47,9 +47,9 @@
|
||||
<template v-for="(item, index) in mostRecentListeningSessions">
|
||||
<div :key="item.id" class="w-full py-0.5">
|
||||
<div class="flex items-center mb-1">
|
||||
<p class="text-sm font-book text-white text-opacity-70 w-8">{{ index + 1 }}. </p>
|
||||
<p class="text-sm text-white text-opacity-70 w-8">{{ index + 1 }}. </p>
|
||||
<div class="w-56">
|
||||
<p class="text-sm font-book text-white text-opacity-80 truncate">{{ item.mediaMetadata ? item.mediaMetadata.title : '' }}</p>
|
||||
<p class="text-sm text-white text-opacity-80 truncate">{{ item.mediaMetadata ? item.mediaMetadata.title : '' }}</p>
|
||||
<p class="text-xs text-white text-opacity-50">{{ $dateDistanceFromNow(item.updatedAt) }}</p>
|
||||
</div>
|
||||
<div class="flex-grow" />
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<td>
|
||||
<covers-book-cover :width="50" :library-item="item" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||
</td>
|
||||
<td class="font-book">
|
||||
<td>
|
||||
<template v-if="item.media && item.media.metadata && item.episode">
|
||||
<p>{{ item.episode.title || 'Unknown' }}</p>
|
||||
<p class="text-white text-opacity-50 text-sm font-sans">{{ item.media.metadata.title }}</p>
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="tracks.length || audioFile" class="flex py-0.5">
|
||||
<div v-if="tracks.length || audioFile || (isPodcast && totalPodcastDuration)" class="flex py-0.5">
|
||||
<div class="w-32">
|
||||
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelDuration }}</span>
|
||||
</div>
|
||||
@@ -415,6 +415,8 @@ export default {
|
||||
})
|
||||
},
|
||||
durationPretty() {
|
||||
if (this.isPodcast) return this.$elapsedPrettyExtended(this.totalPodcastDuration)
|
||||
|
||||
if (!this.tracks.length && !this.audioFile) return 'N/A'
|
||||
if (this.audioFile) return this.$elapsedPrettyExtended(this.duration)
|
||||
return this.$elapsedPretty(this.duration)
|
||||
@@ -423,6 +425,12 @@ export default {
|
||||
if (!this.tracks.length && !this.audioFile) return 0
|
||||
return this.media.duration
|
||||
},
|
||||
totalPodcastDuration() {
|
||||
if (!this.podcastEpisodes.length) return 0
|
||||
let totalDuration = 0
|
||||
this.podcastEpisodes.forEach((ep) => (totalDuration += ep.duration || 0))
|
||||
return totalDuration
|
||||
},
|
||||
sizePretty() {
|
||||
return this.$bytesPretty(this.media.size)
|
||||
},
|
||||
|
||||
@@ -5,18 +5,19 @@ import { supplant } from './utils'
|
||||
const defaultCode = 'en-us'
|
||||
|
||||
const languageCodeMap = {
|
||||
'de': 'Deutsch',
|
||||
'en-us': 'English',
|
||||
// 'es': 'Español',
|
||||
'fr': 'Français',
|
||||
'hr': 'Hrvatski',
|
||||
'it': 'Italiano',
|
||||
'pl': 'Polski',
|
||||
'zh-cn': '简体中文 (Simplified Chinese)'
|
||||
'de': { label: 'Deutsch', dateFnsLocale: 'de' },
|
||||
'en-us': { label: 'English', dateFnsLocale: 'enUS' },
|
||||
// 'es': { label: 'Español', dateFnsLocale: 'es' },
|
||||
'fr': { label: 'Français', dateFnsLocale: 'fr' },
|
||||
'hr': { label: 'Hrvatski', dateFnsLocale: 'hr' },
|
||||
'it': { label: 'Italiano', dateFnsLocale: 'it' },
|
||||
'pl': { label: 'Polski', dateFnsLocale: 'pl' },
|
||||
'ru': { label: 'Русский', dateFnsLocale: 'ru' },
|
||||
'zh-cn': { label: '简体中文 (Simplified Chinese)', dateFnsLocale: 'zhCN' },
|
||||
}
|
||||
Vue.prototype.$languageCodeOptions = Object.keys(languageCodeMap).map(code => {
|
||||
return {
|
||||
text: languageCodeMap[code],
|
||||
text: languageCodeMap[code].label,
|
||||
value: code
|
||||
}
|
||||
})
|
||||
@@ -73,9 +74,11 @@ async function loadi18n(code) {
|
||||
for (const key in Vue.prototype.$strings) {
|
||||
Vue.prototype.$strings[key] = strings[key] || translations[defaultCode][key]
|
||||
}
|
||||
console.log(`dateFnsLocale = ${languageCodeMap[code].dateFnsLocale}`)
|
||||
Vue.prototype.$setDateFnsLocale(languageCodeMap[code].dateFnsLocale)
|
||||
|
||||
console.log('i18n strings=', Vue.prototype.$strings)
|
||||
Vue.prototype.$eventBus.$emit('change-lang', code)
|
||||
this.$eventBus.$emit('change-lang', code)
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import Vue from 'vue'
|
||||
import Path from 'path'
|
||||
import vClickOutside from 'v-click-outside'
|
||||
import { formatDistance, format, addDays, isDate } from 'date-fns'
|
||||
import { formatDistance, format, addDays, isDate, setDefaultOptions } from 'date-fns'
|
||||
import * as locale from 'date-fns/locale'
|
||||
|
||||
Vue.directive('click-outside', vClickOutside.directive)
|
||||
|
||||
|
||||
Vue.prototype.$setDateFnsLocale = (localeString) => {
|
||||
if (!locale[localeString]) return 0
|
||||
return setDefaultOptions({ locale: locale[localeString] })
|
||||
}
|
||||
Vue.prototype.$dateDistanceFromNow = (unixms) => {
|
||||
if (!unixms) return ''
|
||||
return formatDistance(unixms, Date.now(), { addSuffix: true })
|
||||
|
||||
Binary file not shown.
@@ -66,7 +66,7 @@ export const getters = {
|
||||
|
||||
export const actions = {
|
||||
requestLibraryScan({ state, commit }, { libraryId, force }) {
|
||||
return this.$axios.$get(`/api/libraries/${libraryId}/scan`, { params: { force } })
|
||||
return this.$axios.$post(`/api/libraries/${libraryId}/scan?force=${force ? 1 : 0}`)
|
||||
},
|
||||
loadFolders({ state, commit }) {
|
||||
if (state.folders.length) {
|
||||
|
||||
@@ -52,6 +52,10 @@ export const state = () => ({
|
||||
{
|
||||
text: 'Audible.es',
|
||||
value: 'audible.es'
|
||||
},
|
||||
{
|
||||
text: 'FantLab.ru',
|
||||
value: 'fantlab'
|
||||
}
|
||||
],
|
||||
podcastProviders: [
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
"ButtonCreate": "Ertsellen",
|
||||
"ButtonCreateBackup": "Sicherung erstellen",
|
||||
"ButtonDelete": "Löschen",
|
||||
"ButtonEdit": "Edit",
|
||||
"ButtonEdit": "Bearbeiten",
|
||||
"ButtonEditChapters": "Kapitel bearbeiten",
|
||||
"ButtonEditPodcast": "Podcast bearbeiten",
|
||||
"ButtonForceReScan": "Erzwinge kompletten Neu-Scan",
|
||||
"ButtonForceReScan": "Komplett-Scan (alle Medien)",
|
||||
"ButtonFullPath": "Vollständiger Pfad",
|
||||
"ButtonHide": "Ausblenden",
|
||||
"ButtonHome": "Startseite",
|
||||
@@ -34,8 +34,8 @@
|
||||
"ButtonLookup": "Online-Suche",
|
||||
"ButtonManageTracks": "Tracks verwalten",
|
||||
"ButtonMapChapterTitles": "Kapitelüberschriften zuordnen",
|
||||
"ButtonMatchAllAuthors": "Online-Suche für alle Autoren",
|
||||
"ButtonMatchBooks": "Online-Suche für alle Hörbücher",
|
||||
"ButtonMatchAllAuthors": "Online Metadaten-Abgleich (alle Autoren)",
|
||||
"ButtonMatchBooks": "Online Metadaten-Abgleich (alle Medien)",
|
||||
"ButtonNevermind": "Vergiss es",
|
||||
"ButtonOk": "Ok",
|
||||
"ButtonOpenFeed": "Feed öffnen",
|
||||
@@ -61,7 +61,7 @@
|
||||
"ButtonSave": "Speichern",
|
||||
"ButtonSaveAndClose": "Speichern & Schließen",
|
||||
"ButtonSaveTracklist": "Speichere die Titelliste",
|
||||
"ButtonScan": "Scan",
|
||||
"ButtonScan": "Partial-Scan (nur geänderte/neue Medien)",
|
||||
"ButtonScanLibrary": "Bibliothek scannen",
|
||||
"ButtonSearch": "Suchen",
|
||||
"ButtonSelectFolderPath": "Auswahl Ordnerpfad",
|
||||
@@ -77,7 +77,7 @@
|
||||
"ButtonUploadCover": "Titelbild hochladen",
|
||||
"ButtonUploadOPMLFile": "OPML-Datei hochladen",
|
||||
"ButtonUserDelete": "Benutzer {0} löschen",
|
||||
"ButtonUserEdit": "Benutzer {0} editieren",
|
||||
"ButtonUserEdit": "Benutzer {0} bearbeiten",
|
||||
"ButtonViewAll": "Alles anzeigen",
|
||||
"ButtonYes": "Ja",
|
||||
"HeaderAccount": "Konto",
|
||||
@@ -111,7 +111,7 @@
|
||||
"HeaderManageGenres": "Kategorien verwalten",
|
||||
"HeaderManageTags": "Tags verwalten",
|
||||
"HeaderMapDetails": "Stapelverarbeitung",
|
||||
"HeaderMatch": "Online-Suche",
|
||||
"HeaderMatch": "Metadaten",
|
||||
"HeaderMetadataToEmbed": "Einzubettende Metadaten",
|
||||
"HeaderNewAccount": "Neues Konto",
|
||||
"HeaderNewLibrary": "Neue Bibliothek",
|
||||
@@ -308,6 +308,7 @@
|
||||
"LabelPublishYear": "Jahr",
|
||||
"LabelRecentlyAdded": "Kürzlich hinzugefügt",
|
||||
"LabelRecentSeries": "Aktuelle Serien",
|
||||
"LabelRecommended": "Recommended",
|
||||
"LabelRegion": "Region",
|
||||
"LabelReleaseDate": "Veröffentlichungsdatum",
|
||||
"LabelRemoveCover": "Lösche Titelbild",
|
||||
@@ -333,23 +334,23 @@
|
||||
"LabelSettingsExperimentalFeatures": "Experimentelle Funktionen",
|
||||
"LabelSettingsExperimentalFeaturesHelp": "Funktionen welche sich in der Entwicklung befinden, benötigen Ihr Feedback und Ihre Hilfe beim Testen. Klicken Sie hier, um die Github-Diskussion zu öffnen.",
|
||||
"LabelSettingsFindCovers": "Suche Titelbilder",
|
||||
"LabelSettingsFindCoversHelp": "Wenn Ihr Hörbuch kein eingebettetes Titelbild oder kein Titelbild im Ordner hat, versucht der Scanner, ein Titelbild online zu finden.<br>Hinweis: Dies verlängert die Scandauer",
|
||||
"LabelSettingsFindCoversHelp": "Wenn Ihr Medium kein eingebettetes Titelbild oder kein Titelbild im Ordner hat, versucht der Scanner, ein Titelbild online zu finden.<br>Hinweis: Dies verlängert die Scandauer",
|
||||
"LabelSettingsHomePageBookshelfView": "Starseite verwendet die Bücherregalansicht",
|
||||
"LabelSettingsLibraryBookshelfView": "Bibliothek verwendet die Bücherregalansicht",
|
||||
"LabelSettingsOverdriveMediaMarkers": "Verwende Overdrive Media Marker für Kapitel",
|
||||
"LabelSettingsOverdriveMediaMarkersHelp": "MP3-Dateien von Overdrive werden mit eingebetteten Kapitel-Timings als benutzerdefinierte Metadaten geliefert. Wenn Sie dies aktivieren, werden diese Markierungen automatisch für die Kapiteltaktung verwendet",
|
||||
"LabelSettingsParseSubtitles": "Analysiere Untertitel",
|
||||
"LabelSettingsParseSubtitlesHelp": "Extrahiere den Untertitel von Hörbuchordnernamen.<br>Untertitel müssen vom eigentlichem Titel durch ein \" - \" getrennt sein. <br>Beispiel: \"Titel - Untertitel\"",
|
||||
"LabelSettingsParseSubtitlesHelp": "Extrahiere den Untertitel von Medium-Ordnernamen.<br>Untertitel müssen vom eigentlichem Titel durch ein \" - \" getrennt sein. <br>Beispiel: \"Titel - Untertitel\"",
|
||||
"LabelSettingsPreferAudioMetadata": "Bevorzuge lokale ID3-Audiometadaten",
|
||||
"LabelSettingsPreferAudioMetadataHelp": "In den Audiodateien eingebettete ID3 Metadaten werden für die Metadaten eines Hörbuchs anstelle der Ordnernamen verwendet. Wenn keine ID3 Metadaten zur Verfügung stehen, werden die Ordnernamen verwendet.",
|
||||
"LabelSettingsPreferAudioMetadataHelp": "In den Audiodateien eingebettete ID3 Tags werden anstelle der Ordnernamen für die Bereitstellung der Metadaten verwendet. Wenn keine ID3 Tags zur Verfügung stehen, werden die Ordnernamen verwendet.",
|
||||
"LabelSettingsPreferMatchedMetadata": "Bevorzuge online abgestimmte Metadaten",
|
||||
"LabelSettingsPreferMatchedMetadataHelp": "Bei einem Schnellabgleich überschreiben neu abgestimmte online Metadaten alle schon vorhandenen Metadaten eines Hörbuchs. Standardmäßig werden bei einem Schnellabgleich nur fehlende Metadaten ersetzt.",
|
||||
"LabelSettingsPreferOPFMetadata": "Bevorzuge OPF-Metadaten aus dem Hörbuchordner",
|
||||
"LabelSettingsPreferOPFMetadataHelp": "In OPF-Dateien gespeicherte Metadaten werden anstelle der Ordnernamen für die Bereitstellung der Metadaten eines Hörbuchs verwendet. OPF-Datein sind seperate \"Textdateien\" mit der Endung \".abs\" welche in dem gleichen Ordner liegen wie das Hörbuch selber. In dieser sind verschiedene Matadaten (z.B. Titel, Autor, Jahr, Erzähler, Handlung, ISBN, ...) gespeichert. Wenn keine OPF Datei zur Verfügung steht, wird standardmäßig der Ordnername verwendet.",
|
||||
"LabelSettingsPreferMatchedMetadataHelp": "Bei einem Schnellabgleich überschreiben online neu abgestimmte Metadaten alle schon vorhandenen Metadaten eines Mediums. Standardmäßig werden bei einem Schnellabgleich nur fehlende Metadaten ersetzt.",
|
||||
"LabelSettingsPreferOPFMetadata": "Bevorzuge OPF-Metadaten",
|
||||
"LabelSettingsPreferOPFMetadataHelp": "In OPF-Dateien gespeicherte Metadaten werden anstelle der Ordnernamen für die Bereitstellung der Metadaten verwendet. OPF-Datein sind seperate \"Textdateien\" mit der Endung \".abs\" welche in dem gleichen Ordner liegen wie das Medium selber. In dieser sind verschiedene Metadaten (z.B. Titel, Autor, Jahr, Erzähler, Handlung, ISBN, ...) gespeichert. Wenn keine OPF Datei zur Verfügung steht, wird der Ordnername verwendet.",
|
||||
"LabelSettingsSkipMatchingBooksWithASIN": "Überspringe beim Online-Abgleich alle Bücher die bereits eine ASIN haben",
|
||||
"LabelSettingsSkipMatchingBooksWithISBN": "Überspringe beim Online-Abgleich alle Bücher die bereits eine ISBN haben",
|
||||
"LabelSettingsSortingIgnorePrefixes": "Vorwort/Artikel beim Sortieren ignorieren",
|
||||
"LabelSettingsSortingIgnorePrefixesHelp": "Beispiel: für den Artikel \"der\" würde der Hörbuchtitel \"Der Buchtitel\" als \"Buchtitel, Der\" sortiert werden.",
|
||||
"LabelSettingsSortingIgnorePrefixesHelp": "Beispiel: für den Artikel \"der\" würde der Mediumtitel \"Der Buchtitel\" als \"Buchtitel, Der\" sortiert werden.",
|
||||
"LabelSettingsSquareBookCovers": "Benutze quadratische Titelbilder",
|
||||
"LabelSettingsSquareBookCoversHelp": "Bevorzugen quadratische Titelbilder gegenüber den Standardtielbildern im Verhältnis 1,6:1",
|
||||
"LabelSettingsStoreCoversWithItem": "Titelbilder im Medienordner speichern",
|
||||
@@ -390,10 +391,10 @@
|
||||
"LabelTitle": "Titel",
|
||||
"LabelToolsEmbedMetadata": "Metadaten einbetten",
|
||||
"LabelToolsEmbedMetadataDescription": "Bettet die Metadaten einschließlich des Titelbildes und der Kapitel in die Audiodatein ein.",
|
||||
"LabelToolsMakeM4b": "M4B-Hörbuchdatei erstellen",
|
||||
"LabelToolsMakeM4bDescription": "Erstellt eine M4B-Hörbuchdatei mit eingebetteten Metadaten, Titelbild und Kapiteln.",
|
||||
"LabelToolsMakeM4b": "M4B-Datei erstellen",
|
||||
"LabelToolsMakeM4bDescription": "Erstellt eine M4B-Datei (Endung \".m4b\") welche mehrere mp3-Dateien in einer einzigen Datei inkl. derer Metadaten (Beschreibung, Titelbild, Kapitel, ....) zusammenfasst. M4B-Datei können darüber hinaus Lesezeichen speichern und mit einem Abspielschutz (Passwort) versehen werden.",
|
||||
"LabelToolsSplitM4b": "M4B in MP3's aufteilen",
|
||||
"LabelToolsSplitM4bDescription": "Erstellt aus einer mit Metadaten und nach Kapiteln aufgeteilten M4B-Hörbuchdastei seperate MP3's mit eingebetteten Metadaten, Coverbild und Kapiteln.",
|
||||
"LabelToolsSplitM4bDescription": "Erstellt aus einer mit Metadaten und nach Kapiteln aufgeteilten M4B-Datei seperate MP3's mit eingebetteten Metadaten, Coverbild und Kapiteln.",
|
||||
"LabelTotalDuration": "Gesamtdauer",
|
||||
"LabelTotalTimeListened": "Gehörte Gesamtzeit",
|
||||
"LabelTrackFromFilename": "Titel von Dateiname",
|
||||
@@ -421,30 +422,30 @@
|
||||
"LabelViewQueue": "Spieler-Warteschlange anzeigen",
|
||||
"LabelVolume": "Volume",
|
||||
"LabelWeekdaysToRun": "Wochentage für die Ausführung",
|
||||
"LabelYourAudiobookDuration": "Laufzeit Ihres Hörbuchs",
|
||||
"LabelYourAudiobookDuration": "Laufzeit Ihres Mediums",
|
||||
"LabelYourBookmarks": "Lesezeichen",
|
||||
"LabelYourPlaylists": "Eigene Wiedergabelisten",
|
||||
"LabelYourProgress": "Fortschritt",
|
||||
"MessageAddToPlayerQueue": "Zur Abspielwarteliste hinzufügen",
|
||||
"MessageAppriseDescription": "Um diese Funktion nutzen zu können, müssen Sie eine Instanz von <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> laufen haben oder eine API verwenden welche dieselbe Anfragen bearbeiten kann. <br />Die Apprise API Url muss der vollständige URL-Pfad sein, an den die Benachrichtigung gesendet werden soll, z.B. wenn Ihre API-Instanz unter <code>http://192.168.1.1:8337</code> läuft, würden Sie <code>http://192.168.1.1:8337/notify</code> eingeben.",
|
||||
"MessageBackupsDescription": "In einer Sicherung werden Benutzer, Benutzerfortschritte, Details zu den Bibliotheksobjekten, Servereinstellungen und Bilder welche in <code>/metadata/items</code> & <code>/metadata/authors</code> gespeichert sind gespeichert. Sicherungen enthalten keine Dateien welche in den einzelnen Bibliotheksordnern (Hörbuch-/Podcastordnern) gespeichert sind.",
|
||||
"MessageBackupsDescription": "In einer Sicherung werden Benutzer, Benutzerfortschritte, Details zu den Bibliotheksobjekten, Servereinstellungen und Bilder welche in <code>/metadata/items</code> & <code>/metadata/authors</code> gespeichert sind gespeichert. Sicherungen enthalten keine Dateien welche in den einzelnen Bibliotheksordnern (Medien-Ordnern) gespeichert sind.",
|
||||
"MessageBatchQuickMatchDescription": "Der Schnellabgleich versucht, fehlende Titelbilder und Metadaten für die ausgewählten Artikel hinzuzufügen. Aktivieren Sie die nachstehenden Optionen, damit der Schnellabgleich vorhandene Titelbilder und/oder Metadaten überschreiben kann.",
|
||||
"MessageBookshelfNoCollections": "Es wurden noch keine Sammlungen erstellt",
|
||||
"MessageBookshelfNoResultsForFilter": "Keine Ergebnisse für filter \"{0}: {1}\"",
|
||||
"MessageBookshelfNoRSSFeeds": "Keine RSS-Feeds geöffnet",
|
||||
"MessageBookshelfNoSeries": "Keine Serien vorhanden",
|
||||
"MessageChapterEndIsAfter": "Das Kapitelende liegt nach dem Ende Ihres Hörbuchs",
|
||||
"MessageChapterErrorFirstNotZero": "Das erste Kapitel muss bei 0 beginnen",
|
||||
"MessageChapterErrorStartGteDuration": "Die ungültige Startzeit darf nicht größer als die gesamte Hörbuchdauer sein",
|
||||
"MessageChapterErrorStartLtPrev": "Die ungültige Startzeit darf nicht größer oder gleich der Startzeit des vorherigen Kapitels sein",
|
||||
"MessageChapterStartIsAfter": "Der Kapitelanfang liegt nach dem Ende Ihres Hörbuchs",
|
||||
"MessageChapterEndIsAfter": "Ungültige Kapitelendzeit: Kapitelende > Mediumende (Kapitelende liegt nach dem Ende des Mediums)",
|
||||
"MessageChapterErrorFirstNotZero": "Ungültige Kapitelstartzeit: Das erste Kapitel muss bei 0 beginnen",
|
||||
"MessageChapterErrorStartGteDuration": "Ungültige Kapitelstartzeit: Kapitelanfang > Mediumlänge (Kapitelanfang liegt zeitlich nach dem Ende des Mediums -> Lösung: Kapitelanfang < Mediumlänge)",
|
||||
"MessageChapterErrorStartLtPrev": "Ungültige Kapitelstartzeit: Kapitelanfang < Kapitelanfang vorheriges Kapitel (Kapitelanfang liegt zeitlich vor dem Beginn des vorherigen Kapitels -> Lösung: Kapitelanfang >= Startzeit des vorherigen Kapitels)",
|
||||
"MessageChapterStartIsAfter": "Ungültige Kapitelstartzeit: Kapitelanfang > Mediumende (Kapitelanfang liegt nach dem Ende des Mediums)",
|
||||
"MessageCheckingCron": "Überprüfe cron...",
|
||||
"MessageConfirmDeleteBackup": "Sind Sie sicher, dass Sie die Sicherung für {0} löschen wollen?",
|
||||
"MessageConfirmDeleteLibrary": "Sind Sie sicher, dass Sie die Bibliothek \"{0}\" dauerhaft löschen wollen?",
|
||||
"MessageConfirmDeleteSession": "Sind Sie sicher, dass Sie diese Sitzung löschen möchten?",
|
||||
"MessageConfirmForceReScan": "Sind Sie sicher, dass Sie einen erneuten Scanvorgang erzwingen wollen?",
|
||||
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
|
||||
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
|
||||
"MessageConfirmMarkSeriesFinished": "Sind Sie sicher, dass Sie alle Medien dieser Reihe als abgeschlossen markieren wollen?",
|
||||
"MessageConfirmMarkSeriesNotFinished": "Sind Sie sicher, dass Sie alle Medien dieser Reihe als nicht abgeschlossen markieren wollen?",
|
||||
"MessageConfirmRemoveCollection": "Sind Sie sicher, dass Sie die Sammlung \"{0}\" löschen wollen?",
|
||||
"MessageConfirmRemoveEpisode": "Sind Sie sicher, dass Sie die Episode \"{0}\" löschen möchten?",
|
||||
"MessageConfirmRemoveEpisodes": "Sind Sie sicher, dass Sie {0} Episoden löschen wollen?",
|
||||
@@ -472,7 +473,7 @@
|
||||
"MessageLoadingFolders": "Lade Ordner...",
|
||||
"MessageM4BFailed": "M4B fehlgeschlagen!",
|
||||
"MessageM4BFinished": "M4B beendet!",
|
||||
"MessageMapChapterTitles": "Zuordnen von Kapiteltiteln zu Ihren vorhandenen Hörbuchkapiteln ohne Anpassung der Zeitangaben",
|
||||
"MessageMapChapterTitles": "Zuordnen von Kapiteltiteln zu Ihren vorhandenen Medienkapiteln ohne Anpassung der Zeitangaben",
|
||||
"MessageMarkAsFinished": "Als beendet markieren",
|
||||
"MessageMarkAsNotFinished": "Als nicht abgeschlossen markieren",
|
||||
"MessageMatchBooksDescription": "versucht, Bücher in der Bibliothek mit einem Buch des ausgewählten Suchanbieters abzugleichen und leere Details und das Titelbild auszufüllen. Details werden nicht überschrieben.",
|
||||
@@ -530,16 +531,16 @@
|
||||
"MessageValidCronExpression": "Gültiger cron-ausdruck",
|
||||
"MessageWatcherIsDisabledGlobally": "Überwachung ist in den Servereinstellungen global deaktiviert",
|
||||
"MessageXLibraryIsEmpty": "{0} Bibliothek ist leer!",
|
||||
"MessageYourAudiobookDurationIsLonger": "Die Dauer Ihres Hörbuchs ist länger als die gefundene Dauer",
|
||||
"MessageYourAudiobookDurationIsShorter": "Die Dauer Ihres Hörbuchs ist kürzer als die gefundene Dauer",
|
||||
"MessageYourAudiobookDurationIsLonger": "Die Dauer Ihres Mediums ist länger als die gefundene Dauer",
|
||||
"MessageYourAudiobookDurationIsShorter": "Die Dauer Ihres Mediums ist kürzer als die gefundene Dauer",
|
||||
"NoteChangeRootPassword": "Der Root-Benutzer (Hauptbenutzer) ist der einzige Benutzer, der ein leeres Passwort haben kann",
|
||||
"NoteChapterEditorTimes": "Hinweis: Die Anfangszeit des ersten Kapitels muss bei 0:00 beginnen und die Anfangszeit des letzten Kapitels darf die Dauer des Hörbuchs nicht überschreiten.",
|
||||
"NoteChapterEditorTimes": "Hinweis: Die Anfangszeit des ersten Kapitels muss bei 0:00 beginnen und die Anfangszeit des letzten Kapitels darf die Dauer des Mediums nicht überschreiten.",
|
||||
"NoteFolderPicker": "Hinweis: Bereits zugeordnete Ordner werden nicht angezeigt.",
|
||||
"NoteFolderPickerDebian": "Hinweis: Der Ordnerauswahldialog für die Debian-Installation ist nicht vollständig implementiert. Sie sollten den Pfad zu Ihrer Bibliothek direkt eingeben.",
|
||||
"NoteRSSFeedPodcastAppsHttps": "Warnung: Die meisten Podcast-Apps verlangen, dass die URL des RSS-Feeds HTTPS verwendet.",
|
||||
"NoteRSSFeedPodcastAppsPubDate": "Warnung: 1 oder mehrere Ihrer Episoden haben kein Veröffentlichungsdatum. Einige Podcast-Apps verlangen dies.",
|
||||
"NoteUploaderFoldersWithMediaFiles": "Ordner mit Mediendateien werden als separate Bibliothekselemente behandelt.",
|
||||
"NoteUploaderOnlyAudioFiles": "Wenn Sie nur Audiodateien hochladen, wird jede Audiodatei als ein separates Hörbuch behandelt.",
|
||||
"NoteUploaderOnlyAudioFiles": "Wenn Sie nur Audiodateien hochladen, wird jede Audiodatei als ein separates Medium behandelt.",
|
||||
"NoteUploaderUnsupportedFiles": "Nicht unterstützte Dateien werden ignoriert. Bei der Auswahl oder dem Löschen eines Ordners werden andere Dateien, die sich nicht in einem Elementordner befinden, ignoriert.",
|
||||
"PlaceholderNewCollection": "Neuer Sammlungsname",
|
||||
"PlaceholderNewFolderPath": "Neuer Ordnerpfad",
|
||||
@@ -605,20 +606,13 @@
|
||||
"ToastRemoveItemFromCollectionSuccess": "Medium aus der Sammlung gelöscht",
|
||||
"ToastRSSFeedCloseFailed": "RSS-Feed konnte nicht geschlossen werden",
|
||||
"ToastRSSFeedCloseSuccess": "RSS-Feed geschlossen",
|
||||
"ToastSeriesUpdateFailed": "Series update failed",
|
||||
"ToastSeriesUpdateSuccess": "Series update success",
|
||||
"ToastSeriesUpdateFailed": "Aktualisierung der Serien fehlgeschlagen",
|
||||
"ToastSeriesUpdateSuccess": "Serien aktualisiert",
|
||||
"ToastSessionDeleteFailed": "Sitzung konnte nicht gelöscht werden",
|
||||
"ToastSessionDeleteSuccess": "Sitzung gelöscht",
|
||||
"ToastSocketConnected": "Verbindung zum WebSocket hergestellt",
|
||||
"ToastSocketDisconnected": "Verbindung zum WebSocket verloren",
|
||||
"ToastSocketFailedToConnect": "Verbindung zum WebSocket fehlgeschlagen",
|
||||
"ToastUserDeleteFailed": "Benutzer konnte nicht gelöscht werden",
|
||||
"ToastUserDeleteSuccess": "Benutzer gelöscht",
|
||||
"WeekdayFriday": "Freitag",
|
||||
"WeekdayMonday": "Montag",
|
||||
"WeekdaySaturday": "Samstag",
|
||||
"WeekdaySunday": "Sonntag",
|
||||
"WeekdayThursday": "Donnerstag",
|
||||
"WeekdayTuesday": "Dienstag",
|
||||
"WeekdayWednesday": "Mittwoch"
|
||||
"ToastUserDeleteSuccess": "Benutzer gelöscht"
|
||||
}
|
||||
@@ -308,6 +308,7 @@
|
||||
"LabelPublishYear": "Publish Year",
|
||||
"LabelRecentlyAdded": "Recently Added",
|
||||
"LabelRecentSeries": "Recent Series",
|
||||
"LabelRecommended": "Recommended",
|
||||
"LabelRegion": "Region",
|
||||
"LabelReleaseDate": "Release Date",
|
||||
"LabelRemoveCover": "Remove cover",
|
||||
@@ -613,12 +614,5 @@
|
||||
"ToastSocketDisconnected": "Socket disconnected",
|
||||
"ToastSocketFailedToConnect": "Socket failed to connect",
|
||||
"ToastUserDeleteFailed": "Failed to delete user",
|
||||
"ToastUserDeleteSuccess": "User deleted",
|
||||
"WeekdayFriday": "Friday",
|
||||
"WeekdayMonday": "Monday",
|
||||
"WeekdaySaturday": "Saturday",
|
||||
"WeekdaySunday": "Sunday",
|
||||
"WeekdayThursday": "Thursday",
|
||||
"WeekdayTuesday": "Tuesday",
|
||||
"WeekdayWednesday": "Wednesday"
|
||||
"ToastUserDeleteSuccess": "User deleted"
|
||||
}
|
||||
@@ -308,6 +308,7 @@
|
||||
"LabelPublishYear": "Publish Year",
|
||||
"LabelRecentlyAdded": "Recently Added",
|
||||
"LabelRecentSeries": "Recent Series",
|
||||
"LabelRecommended": "Recommended",
|
||||
"LabelRegion": "Region",
|
||||
"LabelReleaseDate": "Release Date",
|
||||
"LabelRemoveCover": "Remove cover",
|
||||
@@ -613,12 +614,5 @@
|
||||
"ToastSocketDisconnected": "Socket disconnected",
|
||||
"ToastSocketFailedToConnect": "Socket failed to connect",
|
||||
"ToastUserDeleteFailed": "Failed to delete user",
|
||||
"ToastUserDeleteSuccess": "User deleted",
|
||||
"WeekdayFriday": "Friday",
|
||||
"WeekdayMonday": "Monday",
|
||||
"WeekdaySaturday": "Saturday",
|
||||
"WeekdaySunday": "Sunday",
|
||||
"WeekdayThursday": "Thursday",
|
||||
"WeekdayTuesday": "Tuesday",
|
||||
"WeekdayWednesday": "Wednesday"
|
||||
"ToastUserDeleteSuccess": "User deleted"
|
||||
}
|
||||
@@ -1,70 +1,70 @@
|
||||
{
|
||||
"ButtonAdd": "Ajouter",
|
||||
"ButtonAddChapters": "Ajouter Chapitre",
|
||||
"ButtonAddPodcasts": "Ajouter Podcasts",
|
||||
"ButtonAddYourFirstLibrary": "Ajouter votre Première Bibliothèque",
|
||||
"ButtonAddChapters": "Ajouter le chapitre",
|
||||
"ButtonAddPodcasts": "Ajouter des podcasts",
|
||||
"ButtonAddYourFirstLibrary": "Ajouter votre première bibliothèque",
|
||||
"ButtonApply": "Appliquer",
|
||||
"ButtonApplyChapters": "Appliquer les Chapitres",
|
||||
"ButtonApplyChapters": "Appliquer les chapitres",
|
||||
"ButtonAuthors": "Auteurs",
|
||||
"ButtonBrowseForFolder": "Naviguer vers le Répertoire",
|
||||
"ButtonBrowseForFolder": "Naviguer vers le répertoire",
|
||||
"ButtonCancel": "Annuler",
|
||||
"ButtonCancelEncode": "Annuler l'encodage",
|
||||
"ButtonChangeRootPassword": "Changer le mot de passe Administrateur",
|
||||
"ButtonCheckAndDownloadNewEpisodes": "Vérifier & Télécharger de Nouveaux Episodes",
|
||||
"ButtonChooseAFolder": "Choisir un Dossier",
|
||||
"ButtonChooseFiles": "Choisir les Fichiers",
|
||||
"ButtonClearFilter": "Effacer le Filtre",
|
||||
"ButtonCloseFeed": "Fermer le Flux",
|
||||
"ButtonCheckAndDownloadNewEpisodes": "Vérifier & télécharger de nouveaux épisodes",
|
||||
"ButtonChooseAFolder": "Choisir un dossier",
|
||||
"ButtonChooseFiles": "Choisir les fichiers",
|
||||
"ButtonClearFilter": "Effacer le filtre",
|
||||
"ButtonCloseFeed": "Fermer le flux",
|
||||
"ButtonCollections": "Collections",
|
||||
"ButtonConfigureScanner": "Configurer le Scan",
|
||||
"ButtonConfigureScanner": "Configurer l'analyse",
|
||||
"ButtonCreate": "Créer",
|
||||
"ButtonCreateBackup": "Créer une Sauvegarde",
|
||||
"ButtonCreateBackup": "Créer une sauvegarde",
|
||||
"ButtonDelete": "Effacer",
|
||||
"ButtonEdit": "Edit",
|
||||
"ButtonEditChapters": "Editer Chapitre",
|
||||
"ButtonEditPodcast": "Editer Podcast",
|
||||
"ButtonForceReScan": "Forcer un Re-Scan",
|
||||
"ButtonFullPath": "Chemin Complet",
|
||||
"ButtonEdit": "Modifier",
|
||||
"ButtonEditChapters": "Modifier les chapitres",
|
||||
"ButtonEditPodcast": "Modifier les podcasts",
|
||||
"ButtonForceReScan": "Forcer une nouvelle analyse",
|
||||
"ButtonFullPath": "Chemin complet",
|
||||
"ButtonHide": "Cacher",
|
||||
"ButtonHome": "Accueil",
|
||||
"ButtonIssues": "Parutions",
|
||||
"ButtonLatest": "Dernière Version",
|
||||
"ButtonLatest": "Dernière version",
|
||||
"ButtonLibrary": "Bibliothèque",
|
||||
"ButtonLogout": "Se Déconnecter",
|
||||
"ButtonLookup": "Rechercher",
|
||||
"ButtonManageTracks": "Gérer les pistes",
|
||||
"ButtonMapChapterTitles": "Correspondance des titres de chapitres",
|
||||
"ButtonMatchAllAuthors": "Rechercher tous les Auteurs",
|
||||
"ButtonMatchAllAuthors": "Rechercher tous les auteurs",
|
||||
"ButtonMatchBooks": "Rechercher les Livres",
|
||||
"ButtonNevermind": "Oubliez cela",
|
||||
"ButtonOk": "Ok",
|
||||
"ButtonOpenFeed": "Ouvrir le Flux",
|
||||
"ButtonOpenManager": "Ouvrir le Gestionnaire",
|
||||
"ButtonPlay": "Ecouter",
|
||||
"ButtonPlaying": "En Lecture",
|
||||
"ButtonPlaylists": "Listes de Lecture",
|
||||
"ButtonPurgeAllCache": "Purger Tout le Cache",
|
||||
"ButtonPurgeItemsCache": "Purger le Cache des Articles",
|
||||
"ButtonPurgeMediaProgress": "Purger la Progression des Médias",
|
||||
"ButtonQueueAddItem": "Ajouter à la Liste de Lecture",
|
||||
"ButtonQueueRemoveItem": "Supprimer de la Liste de Lecture",
|
||||
"ButtonQuickMatch": "Recherche Rapide",
|
||||
"ButtonPlay": "Écouter",
|
||||
"ButtonPlaying": "En lecture",
|
||||
"ButtonPlaylists": "Listes de lecture",
|
||||
"ButtonPurgeAllCache": "Purger le cache",
|
||||
"ButtonPurgeItemsCache": "Purger le cache des articles",
|
||||
"ButtonPurgeMediaProgress": "Purger la progression des médias",
|
||||
"ButtonQueueAddItem": "Ajouter à la liste de lecture",
|
||||
"ButtonQueueRemoveItem": "Supprimer de la liste de lecture",
|
||||
"ButtonQuickMatch": "Recherche rapide",
|
||||
"ButtonRead": "Lire",
|
||||
"ButtonRemove": "Supprimer",
|
||||
"ButtonRemoveAll": "Supprimer tout",
|
||||
"ButtonRemoveAllLibraryItems": "Supprimer tous les Articles de la Bibliothèque",
|
||||
"ButtonRemoveAllLibraryItems": "Supprimer tous les articles de la bibliothèque",
|
||||
"ButtonRemoveFromContinueListening": "Ne plus continuer à écouter",
|
||||
"ButtonRemoveSeriesFromContinueSeries": "Ne plus continuer à écouter la Série",
|
||||
"ButtonReScan": "Re-Scan",
|
||||
"ButtonRemoveSeriesFromContinueSeries": "Ne plus continuer à écouter la série",
|
||||
"ButtonReScan": "Nouvelle analyse",
|
||||
"ButtonReset": "Réinitialiser",
|
||||
"ButtonRestore": "Rétablir",
|
||||
"ButtonSave": "Sauvegarder",
|
||||
"ButtonSaveAndClose": "Sauvegarder & Fermer",
|
||||
"ButtonSaveTracklist": "Sauvegarder la liste de lecture",
|
||||
"ButtonScan": "Scanner",
|
||||
"ButtonScanLibrary": "Scanner la Bibliothèque",
|
||||
"ButtonScan": "Analyser",
|
||||
"ButtonScanLibrary": "Analyser la bibliothèque",
|
||||
"ButtonSearch": "Rechercher",
|
||||
"ButtonSelectFolderPath": "Sélectionner le Chemin du Dossier",
|
||||
"ButtonSelectFolderPath": "Sélectionner le Chemin du dossier",
|
||||
"ButtonSeries": "Séries",
|
||||
"ButtonSetChaptersFromTracks": "Positionner les Chapitre par rapports aux Pistes",
|
||||
"ButtonShiftTimes": "Décaler le Temps",
|
||||
@@ -78,88 +78,88 @@
|
||||
"ButtonUploadOPMLFile": "Téléverser un Fichier OPML",
|
||||
"ButtonUserDelete": "Effacer l'utilisateur {0}",
|
||||
"ButtonUserEdit": "Modifier l'utilisateur {0}",
|
||||
"ButtonViewAll": "Afficher Tout",
|
||||
"ButtonViewAll": "Afficher tout",
|
||||
"ButtonYes": "Oui",
|
||||
"HeaderAccount": "Compte",
|
||||
"HeaderAdvanced": "Avancé",
|
||||
"HeaderAppriseNotificationSettings": "Configuration des Notifications Apprise",
|
||||
"HeaderAudiobookTools": "Outils de Gestion de Fichier Audiobook",
|
||||
"HeaderAudioTracks": "Pistes Audio",
|
||||
"HeaderAudioTracks": "Pistes zudio",
|
||||
"HeaderBackups": "Sauvegardes",
|
||||
"HeaderChangePassword": "Chager le mot de passe",
|
||||
"HeaderChapters": "Chapitres",
|
||||
"HeaderChooseAFolder": "Choisir un Dossier",
|
||||
"HeaderChooseAFolder": "Choisir un dossier",
|
||||
"HeaderCollection": "Collection",
|
||||
"HeaderCollectionItems": "Entrées de la Collection",
|
||||
"HeaderCover": "Couverture",
|
||||
"HeaderDetails": "Détails",
|
||||
"HeaderEpisodes": "Episodes",
|
||||
"HeaderEpisodes": "Épisodes",
|
||||
"HeaderFiles": "Fichiers",
|
||||
"HeaderFindChapters": "Trouver les Chapitres",
|
||||
"HeaderFindChapters": "Trouver les chapitres",
|
||||
"HeaderIgnoredFiles": "Fichiers Ignorés",
|
||||
"HeaderItemFiles": "Fichiers des Articles",
|
||||
"HeaderItemMetadataUtils": "Outils de Gestion des Métadonnées",
|
||||
"HeaderLastListeningSession": "Dernière Session d'Ecoute",
|
||||
"HeaderLatestEpisodes": "Dernier Episodes",
|
||||
"HeaderItemMetadataUtils": "Outils de gestion des métadonnées",
|
||||
"HeaderLastListeningSession": "Dernière Session d'écoute",
|
||||
"HeaderLatestEpisodes": "Dernier épisodes",
|
||||
"HeaderLibraries": "Bibliothèque",
|
||||
"HeaderLibraryFiles": "Fichier de Bibliothèque",
|
||||
"HeaderLibraryStats": "Statistiques de Bibliothèque",
|
||||
"HeaderListeningSessions": "Sessions d'Ecoute",
|
||||
"HeaderListeningStats": "Statistiques d'Ecoute",
|
||||
"HeaderLibraryFiles": "Fichier de bibliothèque",
|
||||
"HeaderLibraryStats": "Statistiques de bibliothèque",
|
||||
"HeaderListeningSessions": "Sessions d'écoute",
|
||||
"HeaderListeningStats": "Statistiques d'écoute",
|
||||
"HeaderLogin": "Connexion",
|
||||
"HeaderLogs": "Fichiers Journaux",
|
||||
"HeaderManageGenres": "Gérer les Genres",
|
||||
"HeaderManageTags": "Gérer les Etiquettes",
|
||||
"HeaderMapDetails": "Edition en Masse",
|
||||
"HeaderManageGenres": "Gérer les genres",
|
||||
"HeaderManageTags": "Gérer les étiquettes",
|
||||
"HeaderMapDetails": "Édition en Masse",
|
||||
"HeaderMatch": "Rechercher",
|
||||
"HeaderMetadataToEmbed": "Métadonnée à Intégrer",
|
||||
"HeaderNewAccount": "Nouveau Compte",
|
||||
"HeaderNewLibrary": "Nouvelle Bibliothèque",
|
||||
"HeaderNotifications": "Notifications",
|
||||
"HeaderOpenRSSFeed": "Ouvrir Flux RSS",
|
||||
"HeaderOtherFiles": "Autres Fichiers",
|
||||
"HeaderOtherFiles": "Autres fichiers",
|
||||
"HeaderPermissions": "Permissions",
|
||||
"HeaderPlayerQueue": "Liste d'Ecoute",
|
||||
"HeaderPlaylist": "Liste de Lecture",
|
||||
"HeaderPlaylistItems": "Elements de la Liste de Lecture",
|
||||
"HeaderPodcastsToAdd": "Podcasts à Ajouter",
|
||||
"HeaderPreviewCover": "Prévisualiser la Couverture",
|
||||
"HeaderRemoveEpisode": "Supprimer l'Episode",
|
||||
"HeaderRemoveEpisodes": "Suppression de {0} Episodes",
|
||||
"HeaderPlayerQueue": "Liste d'écoute",
|
||||
"HeaderPlaylist": "Liste de lecture",
|
||||
"HeaderPlaylistItems": "Éléments de la liste de lecture",
|
||||
"HeaderPodcastsToAdd": "Podcasts à ajouter",
|
||||
"HeaderPreviewCover": "Prévisualiser la couverture",
|
||||
"HeaderRemoveEpisode": "Supprimer l'épisode",
|
||||
"HeaderRemoveEpisodes": "Suppression de {0} épisodes",
|
||||
"HeaderRSSFeedIsOpen": "Le Flux RSS et Ouvert",
|
||||
"HeaderSavedMediaProgress": "Progression de la Sauvegarde des Médias",
|
||||
"HeaderSavedMediaProgress": "Progression de la sauvegarde des médias",
|
||||
"HeaderSchedule": "Programmation",
|
||||
"HeaderScheduleLibraryScans": "Scan Automatique de la Bibliothèque",
|
||||
"HeaderScheduleLibraryScans": "Analyse automatique de la bibliothèque",
|
||||
"HeaderSession": "Session",
|
||||
"HeaderSetBackupSchedule": "Activer la Sauvegarde Automatique",
|
||||
"HeaderSettings": "Paramètres",
|
||||
"HeaderSettingsDisplay": "Affichage",
|
||||
"HeaderSettingsExperimental": "Fonctionnalités Expérimentales",
|
||||
"HeaderSettingsExperimental": "Fonctionnalités expérimentales",
|
||||
"HeaderSettingsGeneral": "Général",
|
||||
"HeaderSettingsScanner": "Scanneur",
|
||||
"HeaderSleepTimer": "Minuterie",
|
||||
"HeaderStatsLongestItems": "Articles les Plus Long (heures)",
|
||||
"HeaderStatsMinutesListeningChart": "Minutes d'Ecoute (7 derniers jours)",
|
||||
"HeaderStatsRecentSessions": "Sessions Récentes",
|
||||
"HeaderStatsLongestItems": "Articles les plus long (heures)",
|
||||
"HeaderStatsMinutesListeningChart": "Minutes d'écoute (7 derniers jours)",
|
||||
"HeaderStatsRecentSessions": "Sessions récentes",
|
||||
"HeaderStatsTop10Authors": "Top 10 Auteurs",
|
||||
"HeaderStatsTop5Genres": "Top 5 Genres",
|
||||
"HeaderTools": "Outils",
|
||||
"HeaderUpdateAccount": "Mettre à jour le Compte",
|
||||
"HeaderUpdateAuthor": "Mettre à jour l'Auteur",
|
||||
"HeaderUpdateDetails": "Mettre à jour les Détails",
|
||||
"HeaderUpdateLibrary": "Mettre à jour la Bibliothèque",
|
||||
"HeaderUpdateAccount": "Mettre à jour le compte",
|
||||
"HeaderUpdateAuthor": "Mettre à jour l'auteur",
|
||||
"HeaderUpdateDetails": "Mettre à jour les détails",
|
||||
"HeaderUpdateLibrary": "Mettre à jour la bibliothèque",
|
||||
"HeaderUsers": "Utilisateurs",
|
||||
"HeaderYourStats": "Vos Statistiques",
|
||||
"LabelAccountType": "Type de Compte",
|
||||
"HeaderYourStats": "Vos statistiques",
|
||||
"LabelAccountType": "Type de compte",
|
||||
"LabelAccountTypeAdmin": "Admin",
|
||||
"LabelAccountTypeGuest": "Invité",
|
||||
"LabelAccountTypeUser": "Utilisateur",
|
||||
"LabelActivity": "Activité",
|
||||
"LabelAddedAt": "Date d'Ajout",
|
||||
"LabelAddToCollection": "Ajouter à la Collection",
|
||||
"LabelAddToCollectionBatch": "Ajout de {0} Livres à la Collection",
|
||||
"LabelAddToPlaylist": "Ajouter à la Liste de Lecture",
|
||||
"LabelAddToPlaylistBatch": "{0} Elements Ajoutés à la Liste de Lecture",
|
||||
"LabelAddedAt": "Date d'ajout",
|
||||
"LabelAddToCollection": "Ajouter à la collection",
|
||||
"LabelAddToCollectionBatch": "Ajout de {0} livres à la lollection",
|
||||
"LabelAddToPlaylist": "Ajouter à la liste de lecture",
|
||||
"LabelAddToPlaylistBatch": "{0} éléments ajoutés à la liste de lecture",
|
||||
"LabelAll": "Tout",
|
||||
"LabelAllUsers": "Tous les Utilisateurs",
|
||||
"LabelAppend": "Ajouter",
|
||||
@@ -167,7 +167,7 @@
|
||||
"LabelAuthorFirstLast": "Auteur (Prénom Nom)",
|
||||
"LabelAuthorLastFirst": "Auteur (Nom, Prénom)",
|
||||
"LabelAuthors": "Auteurs",
|
||||
"LabelAutoDownloadEpisodes": "Téléchargement Automatique d'Episode",
|
||||
"LabelAutoDownloadEpisodes": "Téléchargement automatique d'épisode",
|
||||
"LabelBackToUser": "Revenir à l'Utilisateur",
|
||||
"LabelBackupsEnableAutomaticBackups": "Activer les Sauvegardes Automatiques",
|
||||
"LabelBackupsEnableAutomaticBackupsHelp": "Sauvegardes Enregistrées dans /metadata/backups",
|
||||
@@ -176,60 +176,60 @@
|
||||
"LabelBackupsNumberToKeep": "Nombre de Sauvegardes à maintenir",
|
||||
"LabelBackupsNumberToKeepHelp": "Une seule sauvegarde sera effacée à la fois. Si vous avez plus de sauvegardes à effacer, vous devrez le faire manuellement.",
|
||||
"LabelBooks": "Livres",
|
||||
"LabelChangePassword": "Changer le Mot de Passe",
|
||||
"LabelChaptersFound": "Chapitres Trouvés",
|
||||
"LabelChapterTitle": "Titres du Chapitre",
|
||||
"LabelClosePlayer": "Fermer le Lecteur",
|
||||
"LabelCollapseSeries": "Réduire les Séries",
|
||||
"LabelChangePassword": "Changer le mot de passe",
|
||||
"LabelChaptersFound": "Chapitres trouvés",
|
||||
"LabelChapterTitle": "Titres du chapitre",
|
||||
"LabelClosePlayer": "Fermer le lecteur",
|
||||
"LabelCollapseSeries": "Réduire les séries",
|
||||
"LabelCollections": "Collections",
|
||||
"LabelComplete": "Complet",
|
||||
"LabelConfirmPassword": "Confirmer le Mot de Passe",
|
||||
"LabelContinueListening": "Continuer la Lecture",
|
||||
"LabelContinueSeries": "Continuer la Série",
|
||||
"LabelConfirmPassword": "Confirmer le mot de passe",
|
||||
"LabelContinueListening": "Continuer la lecture",
|
||||
"LabelContinueSeries": "Continuer la série",
|
||||
"LabelCover": "Couverture",
|
||||
"LabelCoverImageURL": "URL vers l'image de Couverture",
|
||||
"LabelCoverImageURL": "URL vers l'image de couverture",
|
||||
"LabelCreatedAt": "Créé le",
|
||||
"LabelCronExpression": "Expression Cron",
|
||||
"LabelCurrent": "Courrant",
|
||||
"LabelCurrently": "En ce Moment:",
|
||||
"LabelCurrently": "En ce moment :",
|
||||
"LabelDatetime": "Datetime",
|
||||
"LabelDescription": "Description",
|
||||
"LabelDeselectAll": "Tout Déselectionner",
|
||||
"LabelDevice": "Appareil",
|
||||
"LabelDeviceInfo": "Détail de l'Appareil",
|
||||
"LabelDeviceInfo": "Détail de l'appareil",
|
||||
"LabelDirectory": "Répertoire",
|
||||
"LabelDiscFromFilename": "Disque depuis le Fichier",
|
||||
"LabelDiscFromMetadata": "Disque depuis les Métadonnées",
|
||||
"LabelDiscFromFilename": "Disque depuis le fichier",
|
||||
"LabelDiscFromMetadata": "Disque depuis les métadonnées",
|
||||
"LabelDownload": "Téléchargement",
|
||||
"LabelDuration": "Durée",
|
||||
"LabelDurationFound": "Durée Trouvée:",
|
||||
"LabelEdit": "Editer",
|
||||
"LabelDurationFound": "Durée trouvée :",
|
||||
"LabelEdit": "Modifier",
|
||||
"LabelEnable": "Activer",
|
||||
"LabelEnd": "Fin",
|
||||
"LabelEpisode": "Episode",
|
||||
"LabelEpisodeTitle": "Titre de l'Episode",
|
||||
"LabelEpisodeType": "Type de l'Episode",
|
||||
"LabelEpisode": "Épisode",
|
||||
"LabelEpisodeTitle": "Titre de l'épisode",
|
||||
"LabelEpisodeType": "Type de l'épisode",
|
||||
"LabelExplicit": "Restriction",
|
||||
"LabelFeedURL": "URL de Flux",
|
||||
"LabelFeedURL": "URL deu flux",
|
||||
"LabelFile": "Fichier",
|
||||
"LabelFileBirthtime": "Creation du Fichier",
|
||||
"LabelFileModified": "Modification du Fichier",
|
||||
"LabelFileBirthtime": "Creation du fichier",
|
||||
"LabelFileModified": "Modification du fichier",
|
||||
"LabelFilename": "Nom de Fichier",
|
||||
"LabelFilterByUser": "Filtrer par l'Utilisateur",
|
||||
"LabelFindEpisodes": "Trouver des Episodes",
|
||||
"LabelFilterByUser": "Filtrer par l'utilisateur",
|
||||
"LabelFindEpisodes": "Trouver des épisodes",
|
||||
"LabelFinished": "Fini(e)",
|
||||
"LabelFolder": "Dossier",
|
||||
"LabelFolders": "Dossiers",
|
||||
"LabelGenre": "Genre",
|
||||
"LabelGenres": "Genres",
|
||||
"LabelHardDeleteFile": "Effacement du Fichier",
|
||||
"LabelHardDeleteFile": "Suppression du fichier",
|
||||
"LabelHour": "Heure",
|
||||
"LabelIcon": "Icone",
|
||||
"LabelIncludeInTracklist": "Inclure dans la Liste des Pistes",
|
||||
"LabelIncludeInTracklist": "Inclure dans la liste des pistes",
|
||||
"LabelIncomplete": "Incomplet",
|
||||
"LabelInProgress": "En Cours",
|
||||
"LabelInProgress": "En cours",
|
||||
"LabelInterval": "Interval",
|
||||
"LabelIntervalCustomDailyWeekly": "Journalier/Hebdomadaire Personnalisé",
|
||||
"LabelIntervalCustomDailyWeekly": "Journalier / Hebdomadaire personnalisé",
|
||||
"LabelIntervalEvery12Hours": "Toutes les 12 heures",
|
||||
"LabelIntervalEvery15Minutes": "Toutes les 15 minutes",
|
||||
"LabelIntervalEvery2Hours": "Toutes les 2 heures",
|
||||
@@ -237,46 +237,46 @@
|
||||
"LabelIntervalEvery6Hours": "Toutes les 6 heures",
|
||||
"LabelIntervalEveryDay": "Tous les jours",
|
||||
"LabelIntervalEveryHour": "Toutes les heures",
|
||||
"LabelInvalidParts": "Parties Invalides",
|
||||
"LabelInvalidParts": "Parties invalides",
|
||||
"LabelItem": "Article",
|
||||
"LabelLanguage": "Langue",
|
||||
"LabelLanguageDefaultServer": "Langue par Défaut",
|
||||
"LabelLastSeen": "Vu Dernièrement",
|
||||
"LabelLanguageDefaultServer": "Langue par défaut",
|
||||
"LabelLastSeen": "Vu dernièrement",
|
||||
"LabelLastTime": "Progression",
|
||||
"LabelLastUpdate": "Dernière Mise à Jour",
|
||||
"LabelLastUpdate": "Dernière mise à jour",
|
||||
"LabelLess": "Moins",
|
||||
"LabelLibrariesAccessibleToUser": "Bibliothèque Accessible à l'Utilisateur",
|
||||
"LabelLibrariesAccessibleToUser": "Bibliothèque accessible à l'utilisateur",
|
||||
"LabelLibrary": "Bibliothèque",
|
||||
"LabelLibraryItem": "Article de Bibliothèque",
|
||||
"LabelLibraryName": "Nom de Bibliothèque",
|
||||
"LabelLibraryItem": "Article de bibliothèque",
|
||||
"LabelLibraryName": "Nom de bibliothèque",
|
||||
"LabelLimit": "Limite",
|
||||
"LabelListenAgain": "Ecouter à Nouveau",
|
||||
"LabelListenAgain": "Écouter à nouveau",
|
||||
"LabelLogLevelDebug": "Debug",
|
||||
"LabelLogLevelInfo": "Info",
|
||||
"LabelLogLevelWarn": "Warn",
|
||||
"LabelLookForNewEpisodesAfterDate": "Rechercher de Nouveaux Episode après cette Date",
|
||||
"LabelMediaPlayer": "Lecteur Multimédia",
|
||||
"LabelMediaType": "Type de Média",
|
||||
"LabelMetadataProvider": "Fournisseur de Métadonnées",
|
||||
"LabelMetaTag": "Etiquette de Métadonnée",
|
||||
"LabelLookForNewEpisodesAfterDate": "Rechercher de nouveaux épisode après cette date",
|
||||
"LabelMediaPlayer": "Lecteur multimédia",
|
||||
"LabelMediaType": "Type de média",
|
||||
"LabelMetadataProvider": "Fournisseur de métadonnées",
|
||||
"LabelMetaTag": "Etiquette de métadonnée",
|
||||
"LabelMinute": "Minute",
|
||||
"LabelMissing": "Manquant",
|
||||
"LabelMissingParts": "Parties Manquantes",
|
||||
"LabelMissingParts": "Parties manquantes",
|
||||
"LabelMore": "Plus",
|
||||
"LabelName": "Nom",
|
||||
"LabelNarrator": "Narrateur",
|
||||
"LabelNarrators": "Narrateurs",
|
||||
"LabelNew": "Nouveau",
|
||||
"LabelNewestAuthors": "Nouveaux Auteurs",
|
||||
"LabelNewestEpisodes": "Derniers Episodes",
|
||||
"LabelNewPassword": "Nouveau Mot de Passe",
|
||||
"LabelNewestAuthors": "Nouveaux auteurs",
|
||||
"LabelNewestEpisodes": "Derniers épisodes",
|
||||
"LabelNewPassword": "Nouveau mot de passe",
|
||||
"LabelNotes": "Notes",
|
||||
"LabelNotFinished": "Non Terminé(e)",
|
||||
"LabelNotificationAppriseURL": "URL(s) d'Apprise",
|
||||
"LabelNotificationAvailableVariables": "Variables Disponibles",
|
||||
"LabelNotFinished": "Non terminé(e)",
|
||||
"LabelNotificationAppriseURL": "URL(s) d'apprise",
|
||||
"LabelNotificationAvailableVariables": "Variables disponibles",
|
||||
"LabelNotificationBodyTemplate": "Modèle de Message",
|
||||
"LabelNotificationEvent": "Evènement de Notification",
|
||||
"LabelNotificationsMaxFailedAttempts": "Nombres de Tentatives d'Envoi",
|
||||
"LabelNotificationsMaxFailedAttempts": "Nombres de tentatives d'envoi",
|
||||
"LabelNotificationsMaxFailedAttemptsHelp": "La notification est abandonnée une fois ce seuil atteint",
|
||||
"LabelNotificationsMaxQueueSize": "Nombres de notifications maximum à mettre en attente",
|
||||
"LabelNotificationsMaxQueueSizeHelp": "La limite de notification est de un évènement par seconde. Le notification seront ignorées si la file d'attente est à son maximum. Cela empêche un flot trop important.",
|
||||
@@ -288,57 +288,58 @@
|
||||
"LabelOverwrite": "Ecraser",
|
||||
"LabelPassword": "Mot de Passe",
|
||||
"LabelPath": "Chemin",
|
||||
"LabelPermissionsAccessAllLibraries": "Peut Acceder à Toutes les Bibliothèque",
|
||||
"LabelPermissionsAccessAllTags": "Peut Acceder à Toutes les Etiquettes",
|
||||
"LabelPermissionsAccessExplicitContent": "Peut Acceter au Contenu Restreint",
|
||||
"LabelPermissionsDelete": "Peut Supprimer",
|
||||
"LabelPermissionsDownload": "Peut Télécharger",
|
||||
"LabelPermissionsUpdate": "Peut Mettre à Jour",
|
||||
"LabelPermissionsUpload": "Peut Téléverser",
|
||||
"LabelPhotoPathURL": "Chemin/URL des photos",
|
||||
"LabelPlaylists": "Listes de Lecture",
|
||||
"LabelPlayMethod": "Méthode d'Ecoute",
|
||||
"LabelPermissionsAccessAllLibraries": "Peut accéder à toutes les bibliothèque",
|
||||
"LabelPermissionsAccessAllTags": "Peut accéder à toutes les étiquettes",
|
||||
"LabelPermissionsAccessExplicitContent": "Peut acceter au contenu restreint",
|
||||
"LabelPermissionsDelete": "Peut supprimer",
|
||||
"LabelPermissionsDownload": "Peut télécharger",
|
||||
"LabelPermissionsUpdate": "Peut mettre à Jour",
|
||||
"LabelPermissionsUpload": "Peut téléverser",
|
||||
"LabelPhotoPathURL": "Chemin / URL des photos",
|
||||
"LabelPlaylists": "Listes de lecture",
|
||||
"LabelPlayMethod": "Méthode d'écoute",
|
||||
"LabelPodcast": "Podcast",
|
||||
"LabelPodcasts": "Podcasts",
|
||||
"LabelPrefixesToIgnore": "Préfixes à Ignorer (Insensible à la Casse)",
|
||||
"LabelProgress": "Progression",
|
||||
"LabelProvider": "Fournisseur",
|
||||
"LabelPubDate": "Date de Publication",
|
||||
"LabelPublisher": "Editeur",
|
||||
"LabelPublishYear": "Année d'Edition",
|
||||
"LabelRecentlyAdded": "Derniers Ajouts",
|
||||
"LabelRecentSeries": "Séries Récentes",
|
||||
"LabelPubDate": "Date de publication",
|
||||
"LabelPublisher": "Éditeur",
|
||||
"LabelPublishYear": "Année d'édition",
|
||||
"LabelRecentlyAdded": "Derniers ajouts",
|
||||
"LabelRecentSeries": "Séries récentes",
|
||||
"LabelRecommended": "Recommandé",
|
||||
"LabelRegion": "Région",
|
||||
"LabelReleaseDate": "Date de Parution",
|
||||
"LabelRemoveCover": "Supprimer la Couverture",
|
||||
"LabelRSSFeedOpen": "Flux RSS Ouvert",
|
||||
"LabelRSSFeedSlug": "Flux RSS Slug",
|
||||
"LabelRSSFeedURL": "URL du Flux RSS",
|
||||
"LabelSearchTerm": "Terme de Recherche",
|
||||
"LabelSearchTitle": "Titre de Recherche",
|
||||
"LabelSearchTitleOrASIN": "Recherche du Titre ou ASIN",
|
||||
"LabelReleaseDate": "Date de parution",
|
||||
"LabelRemoveCover": "Supprimer la couverture",
|
||||
"LabelRSSFeedOpen": "Flux RSS ouvert",
|
||||
"LabelRSSFeedSlug": "Identificateur d'adresse du Flux RSS ",
|
||||
"LabelRSSFeedURL": "Adresse du flux RSS",
|
||||
"LabelSearchTerm": "Terme de recherche",
|
||||
"LabelSearchTitle": "Titre de recherche",
|
||||
"LabelSearchTitleOrASIN": "Recherche du titre ou ASIN",
|
||||
"LabelSeason": "Saison",
|
||||
"LabelSequence": "Séquence",
|
||||
"LabelSeries": "Séries",
|
||||
"LabelSeriesName": "Nom de la Série",
|
||||
"LabelSeriesProgress": "Progression de Séries",
|
||||
"LabelSettingsBookshelfViewHelp": "Design Skeumorphic avec une Etagère en Bois",
|
||||
"LabelSeriesName": "Nom de la série",
|
||||
"LabelSeriesProgress": "Progression de séries",
|
||||
"LabelSettingsBookshelfViewHelp": "Design Skeuomorphic avec une étagère en bois",
|
||||
"LabelSettingsChromecastSupport": "Support Chromecast",
|
||||
"LabelSettingsDateFormat": "Format de Date",
|
||||
"LabelSettingsDisableWatcher": "Désactiver la Surveillance",
|
||||
"LabelSettingsDisableWatcherForLibrary": "Désactiver la surveillance du dossier pour la Bibliothèque",
|
||||
"LabelSettingsDateFormat": "Format de date",
|
||||
"LabelSettingsDisableWatcher": "Désactiver la surveillance",
|
||||
"LabelSettingsDisableWatcherForLibrary": "Désactiver la surveillance du dossier pour la bibliothèque",
|
||||
"LabelSettingsDisableWatcherHelp": "Désactive la mise à jour automatique lorsque les fichiers changent. *Nécessite un redémarrage*",
|
||||
"LabelSettingsEnableEReader": "Active E-reader pour tous les utilisateurs",
|
||||
"LabelSettingsEnableEReaderHelp": "E-reader est toujours en cours de développement, mais ce paramètre l'active pour tous les utilisateurs (ou utiliser l'interrupteur \"Fonctionnalités Expérimentales\" pour l'activer seulement pour vous)",
|
||||
"LabelSettingsExperimentalFeatures": "Fonctionnalités Expérimentales",
|
||||
"LabelSettingsExperimentalFeaturesHelp": "Fonctionnalités en cours de développement sur lesquels nous attendons votre retour et expérience. Cliquer pour ouvrir la discussion Github.",
|
||||
"LabelSettingsFindCovers": "Rechercher des Couvertures",
|
||||
"LabelSettingsFindCoversHelp": "Si votre Livre Audio ne possède pas de couverture intégrée ou une image de couverture dans le dossier, le scanner tentera de récupérer une couverture.<br>Attention, cela peut augmenter le temps de scan.",
|
||||
"LabelSettingsHomePageBookshelfView": "La page d'Accueil utilise la vue étagère",
|
||||
"LabelSettingsFindCoversHelp": "Si votre livre audio ne possède pas de couverture intégrée ou une image de couverture dans le dossier, l'analyser tentera de récupérer une couverture.<br>Attention, cela peut augmenter le temps d'analyse.",
|
||||
"LabelSettingsHomePageBookshelfView": "La page d'accueil utilise la vue étagère",
|
||||
"LabelSettingsLibraryBookshelfView": "La bibliothèque utilise la vue étagère",
|
||||
"LabelSettingsOverdriveMediaMarkers": "Utiliser Overdrive Media Marker pour les chapitres",
|
||||
"LabelSettingsOverdriveMediaMarkersHelp": "Les fichiers MP3 d'Overdrive viennent avec les minutages des chapitres intégrés en métadonnées. Activer ce paramètre utilisera ces minutages pour les chapitres automatiquement.",
|
||||
"LabelSettingsParseSubtitles": "Analyse des Sous-titres",
|
||||
"LabelSettingsParseSubtitles": "Analyse des sous-titres",
|
||||
"LabelSettingsParseSubtitlesHelp": "Extrait les sous-titres depuis le dossier du Livre Audio.<br>Les sous-titres doivent être séparés par \" - \"<br>i.e. \"Titre du Livre - Ceci est un sous-titre\" aura le sous-titre \"Ceci est un sous-titre\"",
|
||||
"LabelSettingsPreferAudioMetadata": "Préférer les Métadonnées Audio",
|
||||
"LabelSettingsPreferAudioMetadataHelp": "Les méta étiquettes ID3 des fichiers audios seront utilisés à la place des noms de dossier pour les détails du livre audio",
|
||||
@@ -377,12 +378,12 @@
|
||||
"LabelStatsMinutesListening": "Minutes d'écoute",
|
||||
"LabelStatsOverallDays": "Jours au total",
|
||||
"LabelStatsOverallHours": "Heures au total",
|
||||
"LabelStatsWeekListening": "Ecoute de la Semaine",
|
||||
"LabelStatsWeekListening": "Écoute de la semaine",
|
||||
"LabelSubtitle": "Sous-Titre",
|
||||
"LabelSupportedFileTypes": "Types de Fichiers Supportés",
|
||||
"LabelTag": "Etiquette",
|
||||
"LabelTags": "Etiquettes",
|
||||
"LabelTagsAccessibleToUser": "Etiquettes Accessibles à l'Utilisateur",
|
||||
"LabelSupportedFileTypes": "Types de fichiers Supportés",
|
||||
"LabelTag": "Étiquette",
|
||||
"LabelTags": "Étiquettes",
|
||||
"LabelTagsAccessibleToUser": "Étiquettes accessibles à l'utilisateur",
|
||||
"LabelTimeListened": "Temps d'écoute",
|
||||
"LabelTimeListenedToday": "Nombres d'écoutes Aujourd'hui",
|
||||
"LabelTimeRemaining": "{0} restantes",
|
||||
@@ -395,9 +396,9 @@
|
||||
"LabelToolsSplitM4b": "Scinde le fichier M4B en fichiers MP3",
|
||||
"LabelToolsSplitM4bDescription": "Créer plusieurs fichier MP3 à partir du découpage par chapitre, en incluant les métadonnées, l'image de couverture et les chapitres.",
|
||||
"LabelTotalDuration": "Durée Totale",
|
||||
"LabelTotalTimeListened": "Temps d'Ecoute Total",
|
||||
"LabelTrackFromFilename": "Piste depuis le Fichier",
|
||||
"LabelTrackFromMetadata": "Piste depuis les Métadonnées",
|
||||
"LabelTotalTimeListened": "Temps d'écoute total",
|
||||
"LabelTrackFromFilename": "Piste depuis le fichier",
|
||||
"LabelTrackFromMetadata": "Piste depuis les métadonnées",
|
||||
"LabelTracks": "Pistes",
|
||||
"LabelTracksMultiTrack": "Piste Multiple",
|
||||
"LabelTracksSingleTrack": "Piste Simple",
|
||||
@@ -408,8 +409,8 @@
|
||||
"LabelUpdatedAt": "Mis à jour à",
|
||||
"LabelUpdateDetails": "Mettre à jours les Détails",
|
||||
"LabelUpdateDetailsHelp": "Autoriser la mise à jour des détails existants lorsqu'une correspondance est trouvée",
|
||||
"LabelUploaderDragAndDrop": "Glisser & Déposer des Fichiers ou Dossiers",
|
||||
"LabelUploaderDropFiles": "Déposer des Fichiers",
|
||||
"LabelUploaderDragAndDrop": "Glisser & Déposer des fichiers ou dossiers",
|
||||
"LabelUploaderDropFiles": "Déposer des fichiers",
|
||||
"LabelUseChapterTrack": "Utiliser la Piste du Chapitre",
|
||||
"LabelUseFullTrack": "Utiliser la Piste Complète",
|
||||
"LabelUser": "Utilisateur",
|
||||
@@ -418,14 +419,14 @@
|
||||
"LabelVersion": "Version",
|
||||
"LabelViewBookmarks": "Afficher les Signets",
|
||||
"LabelViewChapters": "Afficher les Chapitres",
|
||||
"LabelViewQueue": "Afficher la Liste de Lecture",
|
||||
"LabelViewQueue": "Afficher la liste de lecture",
|
||||
"LabelVolume": "Volume",
|
||||
"LabelWeekdaysToRun": "Jours de la semaine à exécuter",
|
||||
"LabelYourAudiobookDuration": "Durée de vos Livres Audios",
|
||||
"LabelYourBookmarks": "Vos Signets",
|
||||
"LabelYourPlaylists": "Vos Listes de Lecture",
|
||||
"LabelYourProgress": "Votre Progression",
|
||||
"MessageAddToPlayerQueue": "Ajouter en Queue d'Ecoute",
|
||||
"LabelYourPlaylists": "Vos listes de lecture",
|
||||
"LabelYourProgress": "Votre progression",
|
||||
"MessageAddToPlayerQueue": "Ajouter en file d'attente",
|
||||
"MessageAppriseDescription": "Nécessite une instance d'<a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">API Apprise</a> pour utiliser cette fonctionnalité ou une api qui prend en charge les mêmes requêtes. <br />L'URL de l'API Apprise doit comprendre le chemin complet pour envoyer la notification. Par exemple, si votre instance écoute sur <code>http://192.168.1.1:8337</code> alors vous devez mettre <code>http://192.168.1.1:8337/notify</code>.",
|
||||
"MessageBackupsDescription": "Les Sauvegardes incluent les utilisateurs, la progression de lecture par utilisateur, les détails des articles des bibliothèques, les paramètres du serveur et les images sauvegardées. Les Sauvegardes n'incluent pas les fichiers de votre bibliothèque.",
|
||||
"MessageBatchQuickMatchDescription": "La Recherche par Correspondance Rapide tentera d'ajouter les couvertures et les métadonnées manquantes pour les articles sélectionnés. Activer l'option suivante pour autoriser la Recherche par Correspondance à écraser les données existantes.",
|
||||
@@ -439,108 +440,108 @@
|
||||
"MessageChapterErrorStartLtPrev": "Horodatage invalide car il doit débuter au moins après le précédent chapitre",
|
||||
"MessageChapterStartIsAfter": "Le Chapitre Début est situé au début de votre Livre Audio",
|
||||
"MessageCheckingCron": "Vérification du cron...",
|
||||
"MessageConfirmDeleteBackup": "Etes vous certain de vouloir supprimer la Sauvegarde de {0}?",
|
||||
"MessageConfirmDeleteLibrary": "Etes vous certain de vouloir supprimer définitivement la bibliothèque \"{0}\"?",
|
||||
"MessageConfirmDeleteSession": "Etes vous certain de vouloir supprimer cette session?",
|
||||
"MessageConfirmForceReScan": "Etes vous certain de vouloir lancer une Analyse Forcée?",
|
||||
"MessageConfirmMarkSeriesFinished": "Etes vous certain de vouloir marquer comme terminé tous les livres de cette série?",
|
||||
"MessageConfirmMarkSeriesNotFinished": "Etes vous certain de vouloir marquer comme non terminé tous les livres de cette série?",
|
||||
"MessageConfirmRemoveCollection": "Etes vous certain de vouloir supprimer la collection \"{0}\"?",
|
||||
"MessageConfirmRemoveEpisode": "Etes vous certain de vouloir supprimer l'épisode \"{0}\"?",
|
||||
"MessageConfirmRemoveEpisodes": "Etes vous certain de vouloir supprimer {0} épisodes?",
|
||||
"MessageConfirmRemovePlaylist": "Etes vous certain de vouloir supprimer la liste de lecture \"{0}\"?",
|
||||
"MessageConfirmRenameGenre": "Etes vous certain de vouloir renommer le genre \"{0}\" vers \"{1}\" pour tous les articles?",
|
||||
"MessageConfirmDeleteBackup": "Êtes-vous sûr de vouloir supprimer la Sauvegarde de {0} ?",
|
||||
"MessageConfirmDeleteLibrary": "Êtes-vous sûr de vouloir supprimer définitivement la bibliothèque \"{0}\" ?",
|
||||
"MessageConfirmDeleteSession": "Êtes-vous sûr de vouloir supprimer cette session ?",
|
||||
"MessageConfirmForceReScan": "Êtes-vous sûr de vouloir lancer une Analyse Forcée ?",
|
||||
"MessageConfirmMarkSeriesFinished": "Êtes-vous sûr de vouloir marquer comme terminé tous les livres de cette série ?",
|
||||
"MessageConfirmMarkSeriesNotFinished": "Êtes-vous sûr de vouloir marquer comme non terminé tous les livres de cette série ?",
|
||||
"MessageConfirmRemoveCollection": "Êtes-vous sûr de vouloir supprimer la collection \"{0}\" ?",
|
||||
"MessageConfirmRemoveEpisode": "Êtes-vous sûr de vouloir supprimer l'épisode \"{0}\" ?",
|
||||
"MessageConfirmRemoveEpisodes": "Êtes-vous sûr de vouloir supprimer {0} épisodes ?",
|
||||
"MessageConfirmRemovePlaylist": "Êtes-vous sûr de vouloir supprimer la liste de lecture \"{0}\" ?",
|
||||
"MessageConfirmRenameGenre": "Êtes-vous sûr de vouloir renommer le genre \"{0}\" vers \"{1}\" pour tous les articles ?",
|
||||
"MessageConfirmRenameGenreMergeNote": "Information: Ce genre existe déjà et sera fusionné.",
|
||||
"MessageConfirmRenameGenreWarning": "Attention! Un genre similaire avec une casse différente existe déjà \"{0}\".",
|
||||
"MessageConfirmRenameTag": "Etes vous certain de vouloir renommer l'étiquette \"{0}\" vers \"{1}\" pour tous les articles?",
|
||||
"MessageConfirmRenameGenreWarning": "Attention ! Un genre similaire avec une casse différente existe déjà \"{0}\".",
|
||||
"MessageConfirmRenameTag": "Êtes-vous sûr de vouloir renommer l'étiquette \"{0}\" vers \"{1}\" pour tous les articles ?",
|
||||
"MessageConfirmRenameTagMergeNote": "Information: Cette étiquette existe déjà et sera fusionnée.",
|
||||
"MessageConfirmRenameTagWarning": "Attention! Une étiquette similaire avec une casse différente existe déjà \"{0}\".",
|
||||
"MessageConfirmRenameTagWarning": "Attention ! Une étiquette similaire avec une casse différente existe déjà \"{0}\".",
|
||||
"MessageDownloadingEpisode": "Téléchargement de l'épisode",
|
||||
"MessageDragFilesIntoTrackOrder": "Faire glisser les fichiers dans l'ordre correct",
|
||||
"MessageEmbedFinished": "Intégration Terminée!",
|
||||
"MessageEpisodesQueuedForDownload": "{0} Episode(s) mis en file pour téléchargement",
|
||||
"MessageEmbedFinished": "Intégration Terminée !",
|
||||
"MessageEpisodesQueuedForDownload": "{0} épisode(s) mis en file pour téléchargement",
|
||||
"MessageFeedURLWillBe": "L'URL du Flux sera {0}",
|
||||
"MessageFetching": "Récupération...",
|
||||
"MessageForceReScanDescription": "Analysera tous les fichiers de nouveau. Les étiquettes ID3 des fichiers audios, Fichiers OPF, et les fichiers textes seront analysés comme s'ils étaient nouveaux.",
|
||||
"MessageImportantNotice": "Information Importante!",
|
||||
"MessageForceReScanDescription": "Analysera tous les fichiers de nouveau. Les étiquettes ID3 des fichiers audios, fichiers OPF, et les fichiers textes seront analysés comme s'ils étaient nouveaux.",
|
||||
"MessageImportantNotice": "Information Importante !",
|
||||
"MessageInsertChapterBelow": "Insérer le chapitre ci-dessous",
|
||||
"MessageItemsSelected": "{0} Articles Sélectionnés",
|
||||
"MessageItemsUpdated": "{0} Articles Mis à Jour",
|
||||
"MessageItemsSelected": "{0} articles sélectionnés",
|
||||
"MessageItemsUpdated": "{0} articles mis à jour",
|
||||
"MessageJoinUsOn": "Rejoignez-nous sur",
|
||||
"MessageListeningSessionsInTheLastYear": "{0} sessions d'écoute l'an dernier",
|
||||
"MessageLoading": "Chargement...",
|
||||
"MessageLoadingFolders": "Chargement des Dossiers...",
|
||||
"MessageM4BFailed": "M4B en échec!",
|
||||
"MessageM4BFinished": "M4B terminé!",
|
||||
"MessageMapChapterTitles": "Faire correspondre les titres des chapitres aux chapitres existants de votre Livre Audio sans ajuster l'horodatage.",
|
||||
"MessageMarkAsFinished": "Marquer comme Terminé",
|
||||
"MessageLoadingFolders": "Chargement des dossiers...",
|
||||
"MessageM4BFailed": "M4B en échec !",
|
||||
"MessageM4BFinished": "M4B terminé !",
|
||||
"MessageMapChapterTitles": "Faire correspondre les titres des chapitres aux chapitres existants de votre livre audio sans ajuster l'horodatage.",
|
||||
"MessageMarkAsFinished": "Marquer comme terminé",
|
||||
"MessageMarkAsNotFinished": "Marquer comme non Terminé",
|
||||
"MessageMatchBooksDescription": "tentera de faire correspondre les livres de la bibliothèque avec les livres du fournisseur sélectionné pour combler les détails et couverture manquants. N'écrase pas les données existantes.",
|
||||
"MessageNoAudioTracks": "Pas de pistes audio",
|
||||
"MessageNoAuthors": "Pas d'Auteurs",
|
||||
"MessageNoBackups": "Pas de Sauvegardes",
|
||||
"MessageNoBookmarks": "Pas de Signets",
|
||||
"MessageNoChapters": "Pas de Chapitres",
|
||||
"MessageNoCollections": "Pas de Collections",
|
||||
"MessageNoCoversFound": "Pas de Couvertures Trouvées",
|
||||
"MessageNoDescription": "Pas de Description",
|
||||
"MessageNoBookmarks": "Pas de signets",
|
||||
"MessageNoChapters": "Pas de chapitres",
|
||||
"MessageNoCollections": "Pas de collections",
|
||||
"MessageNoCoversFound": "Aucune couverture trouvée",
|
||||
"MessageNoDescription": "Pas de description",
|
||||
"MessageNoEpisodeMatchesFound": "Pas de correspondance d'épisode trouvée",
|
||||
"MessageNoEpisodes": "Pas d'Episodes",
|
||||
"MessageNoFoldersAvailable": "Pas de Dossiers Disponibles",
|
||||
"MessageNoGenres": "Pas de Genres",
|
||||
"MessageNoIssues": "Pas de Parution",
|
||||
"MessageNoEpisodes": "Aucun épisode",
|
||||
"MessageNoFoldersAvailable": "Aucun dossier disponible",
|
||||
"MessageNoGenres": "Pas de genres",
|
||||
"MessageNoIssues": "Pas de parution",
|
||||
"MessageNoItems": "Pas d'Articles",
|
||||
"MessageNoItemsFound": "Pas d'Articles Trouvés",
|
||||
"MessageNoListeningSessions": "Pas de Sessions d'Ecoutes",
|
||||
"MessageNoLogs": "Pas de Journaux",
|
||||
"MessageNoListeningSessions": "Pas de sessions d'écoutes",
|
||||
"MessageNoLogs": "Pas de journaux",
|
||||
"MessageNoMediaProgress": "Pas de Média en cours",
|
||||
"MessageNoNotifications": "Pas de Notifications",
|
||||
"MessageNoPodcastsFound": "Pas de podcasts trouvés",
|
||||
"MessageNoResults": "Pas de Résultats",
|
||||
"MessageNoResults": "Pas de résultats",
|
||||
"MessageNoSearchResultsFor": "Pas de résultats de recherche pour \"{0}\"",
|
||||
"MessageNoSeries": "Pas de Séries",
|
||||
"MessageNoTags": "Pas d'Etiquettes",
|
||||
"MessageNoSeries": "Pas de séries",
|
||||
"MessageNoTags": "Pas d'étiquettes",
|
||||
"MessageNotYetImplemented": "Non implémenté",
|
||||
"MessageNoUpdateNecessary": "Pas de mise à jour nécessaire",
|
||||
"MessageNoUpdatesWereNecessary": "Aucune mise à jour n'était nécessaire",
|
||||
"MessageNoUserPlaylists": "Vous n'avez aucune liste de lecture",
|
||||
"MessageOr": "ou",
|
||||
"MessagePauseChapter": "Suspendre la lecture du chapitre",
|
||||
"MessagePlayChapter": "Ecouter depuis le début du chapitre",
|
||||
"MessagePlayChapter": "Écouter depuis le début du chapitre",
|
||||
"MessagePlaylistCreateFromCollection": "Créer une liste de lecture depuis la collection",
|
||||
"MessagePodcastHasNoRSSFeedForMatching": "Le Podcast n'a pas d'URL de flux RSS à utiliser pour la correspondance",
|
||||
"MessageQuickMatchDescription": "Renseigne les détails manquants ainsi que la couverture avec la première correspondance de '{0}'. N'écrase pas les données présentes à moins que le paramètre 'Préférer les Métadonnées par correspondance' soit activé.",
|
||||
"MessageRemoveAllItemsWarning": "ATTENTION! Cette action supprimera toute la base de données de la bibliothèque ainsi que les mises à jour ou correspondances qui auraient été effectuées. Cela n'a aucune incidence sur les fichiers de la bibliothèque. Voulez-vous continuer?",
|
||||
"MessageRemoveAllItemsWarning": "ATTENTION ! Cette action supprimera toute la base de données de la bibliothèque ainsi que les mises à jour ou correspondances qui auraient été effectuées. Cela n'a aucune incidence sur les fichiers de la bibliothèque. Souhaitez-vous continuer ?",
|
||||
"MessageRemoveChapter": "Supprimer le chapitre",
|
||||
"MessageRemoveEpisodes": "Suppression de {0} épisode(s)",
|
||||
"MessageRemoveFromPlayerQueue": "Supprimer de la liste d'écoute",
|
||||
"MessageRemoveUserWarning": "Etes-vous certain de vouloir supprimer définitivement l'utilisateur \"{0}\"?",
|
||||
"MessageRemoveUserWarning": "Êtes-vous certain de vouloir supprimer définitivement l'utilisateur \"{0}\" ?",
|
||||
"MessageReportBugsAndContribute": "Remonter des anomalies, demander des fonctionnalités et contribuer sur",
|
||||
"MessageResetChaptersConfirm": "Etes-vous certain de vouloir réinitialiser les chapitres et annuler les changements effectués?",
|
||||
"MessageRestoreBackupConfirm": "Etes-vous certain de vouloir restaurer la sauvegarde créée le",
|
||||
"MessageResetChaptersConfirm": "Êtes-vous certain de vouloir réinitialiser les chapitres et annuler les changements effectués ?",
|
||||
"MessageRestoreBackupConfirm": "Êtes-vous certain de vouloir restaurer la sauvegarde créée le",
|
||||
"MessageRestoreBackupWarning": "Restaurer la sauvegarde écrasera la base de donnée située dans le dossier /config ainsi que les images sur /metadata/items & /metadata/authors.<br /><br />Les sauvegardes ne touchent pas aux fichiers de la bibliothèque. Si vous avez activé le paramètre pour sauvegarder les métadonnées et les images de couverture dans le même dossier que les fichiers, ceux-ci ne ni sauvegardés, ni écrasés lors de la restauration.<br /><br />Tous les clients utilisant votre serveur seront automatiquement mis à jour.",
|
||||
"MessageSearchResultsFor": "Résultats de recherche pour",
|
||||
"MessageServerCouldNotBeReached": "Serveur inaccessible",
|
||||
"MessageSetChaptersFromTracksDescription": "Positionne un chapitre par fichier audio, avec le titre du fichier comme titre de chapitre",
|
||||
"MessageStartPlaybackAtTime": "Démarrer la lecture pour \"{0}\" à {1}?",
|
||||
"MessageStartPlaybackAtTime": "Démarrer la lecture pour \"{0}\" à {1} ?",
|
||||
"MessageThinking": "On réfléchit...",
|
||||
"MessageUploaderItemFailed": "Échec du téléversement",
|
||||
"MessageUploaderItemSuccess": "Téléversement effectué!",
|
||||
"MessageUploaderItemSuccess": "Téléversement effectué !",
|
||||
"MessageUploading": "Téléversement...",
|
||||
"MessageValidCronExpression": "Expression cron valide",
|
||||
"MessageWatcherIsDisabledGlobally": "La Surveillance est désactivée par un paramètre global du serveur",
|
||||
"MessageXLibraryIsEmpty": "La Bibliothèque {0} est vide!",
|
||||
"MessageXLibraryIsEmpty": "La bibliothèque {0} est vide !",
|
||||
"MessageYourAudiobookDurationIsLonger": "La durée de votre Livre Audio est plus longue que la durée trouvée",
|
||||
"MessageYourAudiobookDurationIsShorter": "La durée de votre Livre Audio est plus courte que la durée trouvée",
|
||||
"NoteChangeRootPassword": "L'utilisateur Root est le seul a pouvoir utiliser un mote de passe vide",
|
||||
"NoteChapterEditorTimes": "Information: L'horodatage du premier chapitre doit être à 0:00 et celui du dernier chapitre ne peut se situer au-delà de la durée du Livre Audio.",
|
||||
"NoteFolderPicker": "Information: Les dossiers déjà surveillés ne sont pas affichés",
|
||||
"NoteFolderPickerDebian": "Information: La sélection de dossier sur une installation debian n'est pas finalisée. Merci de renseigner le chemin complet vers votre bibliothèque manuellement.",
|
||||
"NoteRSSFeedPodcastAppsHttps": "Attention: La majorité des application de podcast nécessite une URL de flux en HTTPS.",
|
||||
"NoteRSSFeedPodcastAppsPubDate": "Warning: Un ou plus de vos épisodes ne possèdent pas de Pub Date. Certaines applications de podcast le requièrent.",
|
||||
"NoteUploaderFoldersWithMediaFiles": "Les dossiers avec des fichiers médias seront traités en tant qu'articles séparés.",
|
||||
"NoteUploaderOnlyAudioFiles": "En téléversant seulement des fichiers audio, chaque fichier sera traité comme un Livre Audio séparé.",
|
||||
"NoteUploaderUnsupportedFiles": "Les fichiers non-supportés seront ignorés. En sélectionnant ou déponsant un dossier, les autres fichiers qui ne sont pas un dossier contenant un article seront ignorés.",
|
||||
"NoteRSSFeedPodcastAppsHttps": "Attention : la majorité des application de podcast nécessite une adresse de flux en HTTPS.",
|
||||
"NoteRSSFeedPodcastAppsPubDate": "Attention : un ou plusieurs de vos épisodes ne possèdent pas de date de publication. Certaines applications de podcast le requièrent.",
|
||||
"NoteUploaderFoldersWithMediaFiles": "Les dossiers contenant des fichiers multimédias seront traités comme des éléments distincts de la bibliothèque.",
|
||||
"NoteUploaderOnlyAudioFiles": "Si vous téléverser uniquement des fichiers audio, chaque fichier audio sera traité comme un livre audio distinct.",
|
||||
"NoteUploaderUnsupportedFiles": "Les fichiers non pris en charge sont ignorés. Lorsque vous choisissez ou déposez un dossier, les autres fichiers qui ne sont pas dans un dossier d'élément sont ignorés.",
|
||||
"PlaceholderNewCollection": "Nom de la nouvelle collection",
|
||||
"PlaceholderNewFolderPath": "Nouveau chemin de dossier",
|
||||
"PlaceholderNewPlaylist": "Nouveau nom de liste de lecture",
|
||||
@@ -613,12 +614,5 @@
|
||||
"ToastSocketDisconnected": "WebSocket déconnecté",
|
||||
"ToastSocketFailedToConnect": "Échec de la connexion WebSocket",
|
||||
"ToastUserDeleteFailed": "Échec de la suppression de l'utilisateur",
|
||||
"ToastUserDeleteSuccess": "Utilisateur supprimé",
|
||||
"WeekdayFriday": "Vendredi",
|
||||
"WeekdayMonday": "Lundi",
|
||||
"WeekdaySaturday": "Samedi",
|
||||
"WeekdaySunday": "Dimanche",
|
||||
"WeekdayThursday": "Jeudi",
|
||||
"WeekdayTuesday": "Mardi",
|
||||
"WeekdayWednesday": "Mercredi"
|
||||
}
|
||||
"ToastUserDeleteSuccess": "Utilisateur supprimé"
|
||||
}
|
||||
|
||||
@@ -308,6 +308,7 @@
|
||||
"LabelPublishYear": "Godina izdavanja",
|
||||
"LabelRecentlyAdded": "Nedavno dodano",
|
||||
"LabelRecentSeries": "Nedavne serije",
|
||||
"LabelRecommended": "Recommended",
|
||||
"LabelRegion": "Regija",
|
||||
"LabelReleaseDate": "Datum izlaska",
|
||||
"LabelRemoveCover": "Remove cover",
|
||||
@@ -613,12 +614,5 @@
|
||||
"ToastSocketDisconnected": "Socket disconnected",
|
||||
"ToastSocketFailedToConnect": "Socket failed to connect",
|
||||
"ToastUserDeleteFailed": "Neuspješno brisanje korisnika",
|
||||
"ToastUserDeleteSuccess": "Korisnik obrisan",
|
||||
"WeekdayFriday": "Petak",
|
||||
"WeekdayMonday": "Ponedjeljak",
|
||||
"WeekdaySaturday": "Subota",
|
||||
"WeekdaySunday": "Nedjelja",
|
||||
"WeekdayThursday": "Četvrtak",
|
||||
"WeekdayTuesday": "Utorak",
|
||||
"WeekdayWednesday": "Srijeda"
|
||||
"ToastUserDeleteSuccess": "Korisnik obrisan"
|
||||
}
|
||||
@@ -76,8 +76,8 @@
|
||||
"ButtonUploadBackup": "Carica Backup",
|
||||
"ButtonUploadCover": "Carica Cover",
|
||||
"ButtonUploadOPMLFile": "Carica File OPML",
|
||||
"ButtonUserDelete": "Delete user {0}",
|
||||
"ButtonUserEdit": "Edit user {0}",
|
||||
"ButtonUserDelete": "Cancella Utente {0}",
|
||||
"ButtonUserEdit": "Modifica Utente {0}",
|
||||
"ButtonViewAll": "Mostra Tutto",
|
||||
"ButtonYes": "Si",
|
||||
"HeaderAccount": "Account",
|
||||
@@ -244,7 +244,7 @@
|
||||
"LabelLastSeen": "Ultimi Visti",
|
||||
"LabelLastTime": "Ultima Volta",
|
||||
"LabelLastUpdate": "Ultimo Aggiornamento",
|
||||
"LabelLess": "Meno",
|
||||
"LabelLess": "Poco",
|
||||
"LabelLibrariesAccessibleToUser": "Librerie Accessibili agli Utenti",
|
||||
"LabelLibrary": "Libreria",
|
||||
"LabelLibraryItem": "Elementi della Library",
|
||||
@@ -262,7 +262,7 @@
|
||||
"LabelMinute": "Minuto",
|
||||
"LabelMissing": "Altro",
|
||||
"LabelMissingParts": "Parti rimantenti",
|
||||
"LabelMore": "Espandi",
|
||||
"LabelMore": "Molto",
|
||||
"LabelName": "Nome",
|
||||
"LabelNarrator": "Narratore",
|
||||
"LabelNarrators": "Narratori",
|
||||
@@ -308,6 +308,7 @@
|
||||
"LabelPublishYear": "Anno Pubblicazione",
|
||||
"LabelRecentlyAdded": "Aggiunti Recentemente",
|
||||
"LabelRecentSeries": "Serie Recenti",
|
||||
"LabelRecommended": "Recommended",
|
||||
"LabelRegion": "Regione",
|
||||
"LabelReleaseDate": "Data Release",
|
||||
"LabelRemoveCover": "Remove cover",
|
||||
@@ -430,7 +431,7 @@
|
||||
"MessageBackupsDescription": "I backup includono utenti, progressi degli utenti, dettagli sugli elementi della libreria, impostazioni del server e immagini archiviate in <code>/metadata/items</code> & <code>/metadata/authors</code>. I backup non includono i file archiviati nelle cartelle della libreria.",
|
||||
"MessageBatchQuickMatchDescription": "Quick Match tenterà di aggiungere copertine e metadati mancanti per gli elementi selezionati. Attiva l'opzione per consentire a Quick Match di sovrascrivere copertine e/o metadati esistenti.",
|
||||
"MessageBookshelfNoCollections": "Non hai ancora creato nessuna raccolta ",
|
||||
"MessageBookshelfNoResultsForFilter": "Nessul risultato per il filtro \"{0}: {1}\"",
|
||||
"MessageBookshelfNoResultsForFilter": "Nessun risultato per il filtro \"{0}: {1}\"",
|
||||
"MessageBookshelfNoRSSFeeds": "Nessun RSS feeds aperto",
|
||||
"MessageBookshelfNoSeries": "Non c'è nessuna Serie",
|
||||
"MessageChapterEndIsAfter": "La fine del capitolo è dopo la fine del tuo audiolibro",
|
||||
@@ -443,8 +444,8 @@
|
||||
"MessageConfirmDeleteLibrary": "Sei sicuro di voler eliminare definitivamente la libreria \"{0}\"?",
|
||||
"MessageConfirmDeleteSession": "Sei sicuro di voler eliminare questa sessione?",
|
||||
"MessageConfirmForceReScan": "Sei sicuro di voler forzare una nuova scansione?",
|
||||
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
|
||||
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
|
||||
"MessageConfirmMarkSeriesFinished": "Sei sicuro di voler contrassegnare tutti i libri di questa serie come completati?",
|
||||
"MessageConfirmMarkSeriesNotFinished": "Sei sicuro di voler contrassegnare tutti i libri di questa serie come non completati?",
|
||||
"MessageConfirmRemoveCollection": "Sei sicuro di voler rimuovere la Raccolta \"{0}\"?",
|
||||
"MessageConfirmRemoveEpisode": "Sei sicuro di voler rimuovere l'episodio \"{0}\"?",
|
||||
"MessageConfirmRemoveEpisodes": "Sei sicuro di voler rimuovere {0} episodi?",
|
||||
@@ -453,7 +454,7 @@
|
||||
"MessageConfirmRenameGenreMergeNote": "Note: Questo genere esiste già quindi verra unito.",
|
||||
"MessageConfirmRenameGenreWarning": "Avvertimento! Esiste già un genere simile con un nome simile \"{0}\".",
|
||||
"MessageConfirmRenameTag": "Sei sicuro di voler rinominare il tag \"{0}\" in \"{1}\" per tutti gli oggetti?",
|
||||
"MessageConfirmRenameTagMergeNote": "Note: Questo tag esiste già e verrà unito nel vecchio.",
|
||||
"MessageConfirmRenameTagMergeNote": "Nota: Questo tag esiste già e verrà unito nel vecchio.",
|
||||
"MessageConfirmRenameTagWarning": "Avvertimento! Esiste già un tag simile con un nome simile \"{0}\".",
|
||||
"MessageDownloadingEpisode": "Download episodio in corso",
|
||||
"MessageDragFilesIntoTrackOrder": "Trascina i file nell'ordine di traccia corretto",
|
||||
@@ -600,25 +601,18 @@
|
||||
"ToastPlaylistUpdateFailed": "Aggiornamento Playlist Fallita",
|
||||
"ToastPlaylistUpdateSuccess": "Playlist Aggiornata",
|
||||
"ToastPodcastCreateFailed": "Errore Creazione podcast",
|
||||
"ToastPodcastCreateSuccess": "Podcast creato Correttamwnte",
|
||||
"ToastPodcastCreateSuccess": "Podcast creato Correttamente",
|
||||
"ToastRemoveItemFromCollectionFailed": "Errore rimozione file dalla Raccolta",
|
||||
"ToastRemoveItemFromCollectionSuccess": "Oggetto rimosso dalla Raccolta",
|
||||
"ToastRSSFeedCloseFailed": "Errore chiusura RSS feed",
|
||||
"ToastRSSFeedCloseSuccess": "RSS feed chiuso",
|
||||
"ToastSeriesUpdateFailed": "Series update failed",
|
||||
"ToastSeriesUpdateSuccess": "Series update success",
|
||||
"ToastSeriesUpdateFailed": "Aggiornaemnto Serie Fallito",
|
||||
"ToastSeriesUpdateSuccess": "Serie Aggornate",
|
||||
"ToastSessionDeleteFailed": "Errore eliminazione sessione",
|
||||
"ToastSessionDeleteSuccess": "Sessione cancellata",
|
||||
"ToastSocketConnected": "Socket connesso",
|
||||
"ToastSocketDisconnected": "Socket disconnesso",
|
||||
"ToastSocketFailedToConnect": "Socket non riesce a connettersi",
|
||||
"ToastUserDeleteFailed": "Errore eliminazione utente",
|
||||
"ToastUserDeleteSuccess": "Utente eliminato",
|
||||
"WeekdayFriday": "Venerdì",
|
||||
"WeekdayMonday": "Lunedì",
|
||||
"WeekdaySaturday": "Sabato",
|
||||
"WeekdaySunday": "Domenica",
|
||||
"WeekdayThursday": "Giovedi",
|
||||
"WeekdayTuesday": "Martedì",
|
||||
"WeekdayWednesday": "Mercoledì"
|
||||
"ToastUserDeleteSuccess": "Utente eliminato"
|
||||
}
|
||||
@@ -308,6 +308,7 @@
|
||||
"LabelPublishYear": "Rok publikacji",
|
||||
"LabelRecentlyAdded": "Niedawno dodany",
|
||||
"LabelRecentSeries": "Ostatnie serie",
|
||||
"LabelRecommended": "Recommended",
|
||||
"LabelRegion": "Region",
|
||||
"LabelReleaseDate": "Data wydania",
|
||||
"LabelRemoveCover": "Remove cover",
|
||||
@@ -613,12 +614,5 @@
|
||||
"ToastSocketDisconnected": "Połączenie z serwerem zostało zamknięte",
|
||||
"ToastSocketFailedToConnect": "Poączenie z serwerem nie powiodło się",
|
||||
"ToastUserDeleteFailed": "Nie udało się usunąć użytkownika",
|
||||
"ToastUserDeleteSuccess": "Użytkownik usunięty",
|
||||
"WeekdayFriday": "Piątek",
|
||||
"WeekdayMonday": "Poniedziałek",
|
||||
"WeekdaySaturday": "Sobota",
|
||||
"WeekdaySunday": "Niedziela",
|
||||
"WeekdayThursday": "Czwartek",
|
||||
"WeekdayTuesday": "Wtorek",
|
||||
"WeekdayWednesday": "Środa"
|
||||
"ToastUserDeleteSuccess": "Użytkownik usunięty"
|
||||
}
|
||||
618
client/strings/ru.json
Normal file
618
client/strings/ru.json
Normal file
@@ -0,0 +1,618 @@
|
||||
{
|
||||
"ButtonAdd": "Добавить",
|
||||
"ButtonAddChapters": "Добавить Главы",
|
||||
"ButtonAddPodcasts": "Добавить Подкасты",
|
||||
"ButtonAddYourFirstLibrary": "Добавьте Вашу первую библиотеку",
|
||||
"ButtonApply": "Применить",
|
||||
"ButtonApplyChapters": "Применить Главы",
|
||||
"ButtonAuthors": "Авторы",
|
||||
"ButtonBrowseForFolder": "Выбрать Папку",
|
||||
"ButtonCancel": "Отмена",
|
||||
"ButtonCancelEncode": "Отменить Кодирование",
|
||||
"ButtonChangeRootPassword": "Поменять Мастер Пароль",
|
||||
"ButtonCheckAndDownloadNewEpisodes": "Проверка и Загрузка Новых Эпизодов",
|
||||
"ButtonChooseAFolder": "Выбор папки",
|
||||
"ButtonChooseFiles": "Выбор файлов",
|
||||
"ButtonClearFilter": "Очистить Фильтр",
|
||||
"ButtonCloseFeed": "Закрыть Канал",
|
||||
"ButtonCollections": "Коллекции",
|
||||
"ButtonConfigureScanner": "Конфигурация Сканера",
|
||||
"ButtonCreate": "Создать",
|
||||
"ButtonCreateBackup": "Создать бэкап",
|
||||
"ButtonDelete": "Удалить",
|
||||
"ButtonEdit": "Редактировать",
|
||||
"ButtonEditChapters": "Редактировать Главы",
|
||||
"ButtonEditPodcast": "Редактировать Подкаст",
|
||||
"ButtonForceReScan": "Принудительно Пере сканировать",
|
||||
"ButtonFullPath": "Полный Путь",
|
||||
"ButtonHide": "Скрыть",
|
||||
"ButtonHome": "Домой",
|
||||
"ButtonIssues": "Проблемы",
|
||||
"ButtonLatest": "Последнее",
|
||||
"ButtonLibrary": "Библиотека",
|
||||
"ButtonLogout": "Выход",
|
||||
"ButtonLookup": "Найти",
|
||||
"ButtonManageTracks": "Управление Треками",
|
||||
"ButtonMapChapterTitles": "Найти Названия Глав",
|
||||
"ButtonMatchAllAuthors": "Найти Всех Авторов",
|
||||
"ButtonMatchBooks": "Найти Книги",
|
||||
"ButtonNevermind": "Не важно",
|
||||
"ButtonOk": "Ok",
|
||||
"ButtonOpenFeed": "Открыть Канал",
|
||||
"ButtonOpenManager": "Открыть Менеджер",
|
||||
"ButtonPlay": "Слушать",
|
||||
"ButtonPlaying": "Проигрывается",
|
||||
"ButtonPlaylists": "Плейлисты",
|
||||
"ButtonPurgeAllCache": "Очистить Весь Кэш",
|
||||
"ButtonPurgeItemsCache": "Очистить Кэш Элементов",
|
||||
"ButtonPurgeMediaProgress": "Очистить Прогресс Медиа",
|
||||
"ButtonQueueAddItem": "Добавить в очередь",
|
||||
"ButtonQueueRemoveItem": "Удалить из очереди",
|
||||
"ButtonQuickMatch": "Быстрый Поиск",
|
||||
"ButtonRead": "Читать",
|
||||
"ButtonRemove": "Удалить",
|
||||
"ButtonRemoveAll": "Удалить Всё",
|
||||
"ButtonRemoveAllLibraryItems": "Удалить Все Элементы Библиотеки",
|
||||
"ButtonRemoveFromContinueListening": "Удалить из Продолжить Слушать",
|
||||
"ButtonRemoveSeriesFromContinueSeries": "Удалить Серию из Продолжить Серию",
|
||||
"ButtonReScan": "Пере сканировать",
|
||||
"ButtonReset": "Сбросить",
|
||||
"ButtonRestore": "Восстановить",
|
||||
"ButtonSave": "Сохранить",
|
||||
"ButtonSaveAndClose": "Сохранить и Закрыть",
|
||||
"ButtonSaveTracklist": "Сохранить Список треков",
|
||||
"ButtonScan": "Сканировать",
|
||||
"ButtonScanLibrary": "Сканировать Библиотеку",
|
||||
"ButtonSearch": "Поиск",
|
||||
"ButtonSelectFolderPath": "Выберите Путь Папки",
|
||||
"ButtonSeries": "Серии",
|
||||
"ButtonSetChaptersFromTracks": "Установить главы из треков",
|
||||
"ButtonShiftTimes": "Смещение",
|
||||
"ButtonShow": "Показать",
|
||||
"ButtonStartM4BEncode": "Начать Кодирование M4B",
|
||||
"ButtonStartMetadataEmbed": "Начать Встраивание Метаданных",
|
||||
"ButtonSubmit": "Применить",
|
||||
"ButtonUpload": "Загрузить",
|
||||
"ButtonUploadBackup": "Загрузить Бэкап",
|
||||
"ButtonUploadCover": "Загрузить Обложку",
|
||||
"ButtonUploadOPMLFile": "Загрузить Файл OPML",
|
||||
"ButtonUserDelete": "Удалить пользователя {0}",
|
||||
"ButtonUserEdit": "Редактировать пользователя {0}",
|
||||
"ButtonViewAll": "Посмотреть Все",
|
||||
"ButtonYes": "Да",
|
||||
"HeaderAccount": "Учетная запись",
|
||||
"HeaderAdvanced": "Дополнительно",
|
||||
"HeaderAppriseNotificationSettings": "Настройки Оповещений",
|
||||
"HeaderAudiobookTools": "Инструменты Файлов Аудиокниг",
|
||||
"HeaderAudioTracks": "Аудио Треки",
|
||||
"HeaderBackups": "Бэкапы",
|
||||
"HeaderChangePassword": "Изменить Пароль",
|
||||
"HeaderChapters": "Главы",
|
||||
"HeaderChooseAFolder": "Выберите Папку",
|
||||
"HeaderCollection": "Коллекция",
|
||||
"HeaderCollectionItems": "Элементы Коллекции",
|
||||
"HeaderCover": "Обложка",
|
||||
"HeaderDetails": "Подробности",
|
||||
"HeaderEpisodes": "Эпизоды",
|
||||
"HeaderFiles": "Файлы",
|
||||
"HeaderFindChapters": "Найти Главы",
|
||||
"HeaderIgnoredFiles": "Игнорируемые Файлы",
|
||||
"HeaderItemFiles": "Файлы Элемента",
|
||||
"HeaderItemMetadataUtils": "Утилиты",
|
||||
"HeaderLastListeningSession": "Последний Сеанс Прослушивания",
|
||||
"HeaderLatestEpisodes": "Последние эпизоды",
|
||||
"HeaderLibraries": "Библиотеки",
|
||||
"HeaderLibraryFiles": "Файлы Библиотеки",
|
||||
"HeaderLibraryStats": "Статистика Библиотеки",
|
||||
"HeaderListeningSessions": "Сеансы",
|
||||
"HeaderListeningStats": "Статистика Прослушивания",
|
||||
"HeaderLogin": "Логин",
|
||||
"HeaderLogs": "Логи",
|
||||
"HeaderManageGenres": "Редактировать Жанры",
|
||||
"HeaderManageTags": "Редактировать Теги",
|
||||
"HeaderMapDetails": "Найти подробности",
|
||||
"HeaderMatch": "Поиск",
|
||||
"HeaderMetadataToEmbed": "Метаинформация для встраивания",
|
||||
"HeaderNewAccount": "Новая Учетная запись",
|
||||
"HeaderNewLibrary": "Новая Библиотека",
|
||||
"HeaderNotifications": "Уведомления",
|
||||
"HeaderOpenRSSFeed": "Открыть RSS-канал",
|
||||
"HeaderOtherFiles": "Другие Файлы",
|
||||
"HeaderPermissions": "Разрешения",
|
||||
"HeaderPlayerQueue": "Очередь Воспроизведения",
|
||||
"HeaderPlaylist": "Плейлист",
|
||||
"HeaderPlaylistItems": "Элементы Списка Воспроизведения",
|
||||
"HeaderPodcastsToAdd": "Подкасты для Добавления",
|
||||
"HeaderPreviewCover": "Предпросмотр Обложки",
|
||||
"HeaderRemoveEpisode": "Удалить Эпизод",
|
||||
"HeaderRemoveEpisodes": "Удалить {0} Эпизодов",
|
||||
"HeaderRSSFeedIsOpen": "RSS-канал Открыт",
|
||||
"HeaderSavedMediaProgress": "Прогресс Медиа Сохранен",
|
||||
"HeaderSchedule": "Планировщик",
|
||||
"HeaderScheduleLibraryScans": "Планировщик Автоматического Сканирования Библиотеки",
|
||||
"HeaderSession": "Сеансы",
|
||||
"HeaderSetBackupSchedule": "Установить Планировщик Бэкапов",
|
||||
"HeaderSettings": "Настройки",
|
||||
"HeaderSettingsDisplay": "Дисплей",
|
||||
"HeaderSettingsExperimental": "Экспериментальные Функции",
|
||||
"HeaderSettingsGeneral": "Основные",
|
||||
"HeaderSettingsScanner": "Сканер",
|
||||
"HeaderSleepTimer": "Таймер Сна",
|
||||
"HeaderStatsLongestItems": "Самые Длинные Книги (часов)",
|
||||
"HeaderStatsMinutesListeningChart": "Минут прослушивания (последние 7 дней)",
|
||||
"HeaderStatsRecentSessions": "Последние Сеансы",
|
||||
"HeaderStatsTop10Authors": "Топ 10 Авторов",
|
||||
"HeaderStatsTop5Genres": "Топ 5 Жанров",
|
||||
"HeaderTools": "Инструменты",
|
||||
"HeaderUpdateAccount": "Обновить Учетную запись",
|
||||
"HeaderUpdateAuthor": "Обновить Автора",
|
||||
"HeaderUpdateDetails": "Обновить Детали",
|
||||
"HeaderUpdateLibrary": "Обновить Библиотеку",
|
||||
"HeaderUsers": "Пользователи",
|
||||
"HeaderYourStats": "Ваша Статистика",
|
||||
"LabelAccountType": "Тип Учетной записи",
|
||||
"LabelAccountTypeAdmin": "Администратор",
|
||||
"LabelAccountTypeGuest": "Гость",
|
||||
"LabelAccountTypeUser": "Пользователь",
|
||||
"LabelActivity": "Активность",
|
||||
"LabelAddedAt": "Добавить В",
|
||||
"LabelAddToCollection": "Добавить в Коллекцию",
|
||||
"LabelAddToCollectionBatch": "Добавить {0} Книг в Коллекцию",
|
||||
"LabelAddToPlaylist": "Добавить в Плейлист",
|
||||
"LabelAddToPlaylistBatch": "Добавить {0} Элементов в Плейлист",
|
||||
"LabelAll": "Все",
|
||||
"LabelAllUsers": "Все пользователи",
|
||||
"LabelAppend": "Добавить",
|
||||
"LabelAuthor": "Автор",
|
||||
"LabelAuthorFirstLast": "Автор (Имя Фамилия)",
|
||||
"LabelAuthorLastFirst": "Автор (Фамилия, Имя)",
|
||||
"LabelAuthors": "Авторы",
|
||||
"LabelAutoDownloadEpisodes": "Скачивать Эпизоды Автоматически",
|
||||
"LabelBackToUser": "Назад к Пользователю",
|
||||
"LabelBackupsEnableAutomaticBackups": "Включить автоматическое бэкапирование",
|
||||
"LabelBackupsEnableAutomaticBackupsHelp": "Бэкапы сохраняются в /metadata/backups",
|
||||
"LabelBackupsMaxBackupSize": "Максимальный размер бэкапа (в GB)",
|
||||
"LabelBackupsMaxBackupSizeHelp": "В качестве защиты процесс бэкапирования будет завершаться ошибкой, если будет превышен настроенный размер.",
|
||||
"LabelBackupsNumberToKeep": "Сохранять бэкапов",
|
||||
"LabelBackupsNumberToKeepHelp": "За один раз только 1 бэкап будет удален, так что если у вас будет больше бэкапов, то их нужно удалить вручную.",
|
||||
"LabelBooks": "Книги",
|
||||
"LabelChangePassword": "Изменить Пароль",
|
||||
"LabelChaptersFound": "глав найдено",
|
||||
"LabelChapterTitle": "Название Главы",
|
||||
"LabelClosePlayer": "Закрыть проигрыватель",
|
||||
"LabelCollapseSeries": "Свернуть Серии",
|
||||
"LabelCollections": "Коллекции",
|
||||
"LabelComplete": "Завершить",
|
||||
"LabelConfirmPassword": "Подтвердить Пароль",
|
||||
"LabelContinueListening": "Продолжить Слушать",
|
||||
"LabelContinueSeries": "Продолжить Серию",
|
||||
"LabelCover": "Обложка",
|
||||
"LabelCoverImageURL": "URL Изображения Обложки",
|
||||
"LabelCreatedAt": "Создано",
|
||||
"LabelCronExpression": "Выражение Cron",
|
||||
"LabelCurrent": "Текущий",
|
||||
"LabelCurrently": "Текущее:",
|
||||
"LabelDatetime": "Дата и время",
|
||||
"LabelDescription": "Описание",
|
||||
"LabelDeselectAll": "Снять Выделение",
|
||||
"LabelDevice": "Устройство",
|
||||
"LabelDeviceInfo": "Информация об Устройстве",
|
||||
"LabelDirectory": "Каталог",
|
||||
"LabelDiscFromFilename": "Диск из Имени файла",
|
||||
"LabelDiscFromMetadata": "Диск из Метаданных",
|
||||
"LabelDownload": "Скачать",
|
||||
"LabelDuration": "Длина",
|
||||
"LabelDurationFound": "Найденная длина:",
|
||||
"LabelEdit": "Редактировать",
|
||||
"LabelEnable": "Включить",
|
||||
"LabelEnd": "Конец",
|
||||
"LabelEpisode": "Эпизод",
|
||||
"LabelEpisodeTitle": "Имя Эпизода",
|
||||
"LabelEpisodeType": "Тип Эпизода",
|
||||
"LabelExplicit": "Явный",
|
||||
"LabelFeedURL": "URL Канала",
|
||||
"LabelFile": "Файл",
|
||||
"LabelFileBirthtime": "Дата Создания",
|
||||
"LabelFileModified": "Дата Модификации",
|
||||
"LabelFilename": "Имя файла",
|
||||
"LabelFilterByUser": "Фильтр по Пользователю",
|
||||
"LabelFindEpisodes": "Найти Эпизоды",
|
||||
"LabelFinished": "Закончен",
|
||||
"LabelFolder": "Папка",
|
||||
"LabelFolders": "Папки",
|
||||
"LabelGenre": "Жанр",
|
||||
"LabelGenres": "Жанры",
|
||||
"LabelHardDeleteFile": "Жесткое удаление файла",
|
||||
"LabelHour": "Часы",
|
||||
"LabelIcon": "Иконка",
|
||||
"LabelIncludeInTracklist": "Включать в Список воспроизведения",
|
||||
"LabelIncomplete": "Не завершен",
|
||||
"LabelInProgress": "В процессе",
|
||||
"LabelInterval": "Интервал",
|
||||
"LabelIntervalCustomDailyWeekly": "Пользовательские ежедневно/еженедельно",
|
||||
"LabelIntervalEvery12Hours": "Каждые 12 часов",
|
||||
"LabelIntervalEvery15Minutes": "Каждые 15 минут",
|
||||
"LabelIntervalEvery2Hours": "Каждые 2 часа",
|
||||
"LabelIntervalEvery30Minutes": "Каждые 30 минут",
|
||||
"LabelIntervalEvery6Hours": "Каждые 6 часов",
|
||||
"LabelIntervalEveryDay": "Каждый день",
|
||||
"LabelIntervalEveryHour": "Каждый час",
|
||||
"LabelInvalidParts": "Неверные Части",
|
||||
"LabelItem": "Элемент",
|
||||
"LabelLanguage": "Язык",
|
||||
"LabelLanguageDefaultServer": "Язык Сервера по Умолчанию",
|
||||
"LabelLastSeen": "Последнее Сканирование",
|
||||
"LabelLastTime": "Последний по Времени",
|
||||
"LabelLastUpdate": "Последний Обновленный",
|
||||
"LabelLess": "Менее",
|
||||
"LabelLibrariesAccessibleToUser": "Библиотеки Доступные для Пользователя",
|
||||
"LabelLibrary": "Библиотека",
|
||||
"LabelLibraryItem": "Элемент Библиотеки",
|
||||
"LabelLibraryName": "Имя Библиотеки",
|
||||
"LabelLimit": "Лимит",
|
||||
"LabelListenAgain": "Послушать Снова",
|
||||
"LabelLogLevelDebug": "Debug",
|
||||
"LabelLogLevelInfo": "Info",
|
||||
"LabelLogLevelWarn": "Warn",
|
||||
"LabelLookForNewEpisodesAfterDate": "Искать новые эпизоды после этой даты",
|
||||
"LabelMediaPlayer": "Медиа Проигрыватель",
|
||||
"LabelMediaType": "Тип Медиа",
|
||||
"LabelMetadataProvider": "Провайдер",
|
||||
"LabelMetaTag": "Мета Тег",
|
||||
"LabelMinute": "Минуты",
|
||||
"LabelMissing": "Потеряно",
|
||||
"LabelMissingParts": "Потерянные Части",
|
||||
"LabelMore": "Еще",
|
||||
"LabelName": "Имя",
|
||||
"LabelNarrator": "Читает",
|
||||
"LabelNarrators": "Чтецы",
|
||||
"LabelNew": "Новый",
|
||||
"LabelNewestAuthors": "Новые Авторы",
|
||||
"LabelNewestEpisodes": "Новые Эпизоды",
|
||||
"LabelNewPassword": "Новый Пароль",
|
||||
"LabelNotes": "Заметки",
|
||||
"LabelNotFinished": "Не Завершено",
|
||||
"LabelNotificationAppriseURL": "URL(ы) для извещений",
|
||||
"LabelNotificationAvailableVariables": "Доступные переменные",
|
||||
"LabelNotificationBodyTemplate": "Шаблон Тела",
|
||||
"LabelNotificationEvent": "Событие Оповещения",
|
||||
"LabelNotificationsMaxFailedAttempts": "Макс. попыток",
|
||||
"LabelNotificationsMaxFailedAttemptsHelp": "Уведомления будут выключены если произойдет ошибка отправки данное количество раз",
|
||||
"LabelNotificationsMaxQueueSize": "Макс. размер очереди для событий уведомлений",
|
||||
"LabelNotificationsMaxQueueSizeHelp": "События ограничены 1 в секунду. События будут игнорированы если в очереди максимальное количество. Это предотвращает спам сообщениями.",
|
||||
"LabelNotificationTitleTemplate": "Шаблон Заголовка",
|
||||
"LabelNotStarted": "Не Запущено",
|
||||
"LabelNumberOfBooks": "Количество Книг",
|
||||
"LabelNumberOfEpisodes": "# Эпизодов",
|
||||
"LabelOpenRSSFeed": "Открыть RSS-канал",
|
||||
"LabelOverwrite": "Перезаписать",
|
||||
"LabelPassword": "Пароль",
|
||||
"LabelPath": "Путь",
|
||||
"LabelPermissionsAccessAllLibraries": "Есть Доступ ко всем Библиотекам",
|
||||
"LabelPermissionsAccessAllTags": "Есть Доступ ко всем Тегам",
|
||||
"LabelPermissionsAccessExplicitContent": "Есть Доступ к Явному Содержимому",
|
||||
"LabelPermissionsDelete": "Может Удалять",
|
||||
"LabelPermissionsDownload": "Может Скачивать",
|
||||
"LabelPermissionsUpdate": "Может Обновлять",
|
||||
"LabelPermissionsUpload": "Может Закачивать",
|
||||
"LabelPhotoPathURL": "Путь к Фото/URL",
|
||||
"LabelPlaylists": "Плейлисты",
|
||||
"LabelPlayMethod": "Метод Воспроизведения",
|
||||
"LabelPodcast": "Подкаст",
|
||||
"LabelPodcasts": "Подкасты",
|
||||
"LabelPrefixesToIgnore": "Игнорируемые Префиксы (без учета регистра)",
|
||||
"LabelProgress": "Прогресс",
|
||||
"LabelProvider": "Провайдер",
|
||||
"LabelPubDate": "Дата Публикации",
|
||||
"LabelPublisher": "Издатель",
|
||||
"LabelPublishYear": "Год Публикации",
|
||||
"LabelRecentlyAdded": "Недавно Добавленные",
|
||||
"LabelRecentSeries": "Последние Серии",
|
||||
"LabelRecommended": "Рекомендованное",
|
||||
"LabelRegion": "Регион",
|
||||
"LabelReleaseDate": "Дата Выхода",
|
||||
"LabelRemoveCover": "Удалить обложку",
|
||||
"LabelRSSFeedOpen": "Открыть RSS-канал",
|
||||
"LabelRSSFeedSlug": "Встроить RSS-канал",
|
||||
"LabelRSSFeedURL": "URL RSS-канала",
|
||||
"LabelSearchTerm": "Поисковый Запрос",
|
||||
"LabelSearchTitle": "Поиск по Названию",
|
||||
"LabelSearchTitleOrASIN": "Поиск по Названию или ASIN",
|
||||
"LabelSeason": "Сезон",
|
||||
"LabelSequence": "Последовательность",
|
||||
"LabelSeries": "Серия",
|
||||
"LabelSeriesName": "Имя Серии",
|
||||
"LabelSeriesProgress": "Прогресс Серии",
|
||||
"LabelSettingsBookshelfViewHelp": "Конструкция с деревянными полками",
|
||||
"LabelSettingsChromecastSupport": "Поддержка Chromecast",
|
||||
"LabelSettingsDateFormat": "Формат Даты",
|
||||
"LabelSettingsDisableWatcher": "Отключить Отслеживание",
|
||||
"LabelSettingsDisableWatcherForLibrary": "Отключить отслеживание для библиотеки",
|
||||
"LabelSettingsDisableWatcherHelp": "Отключает автоматическое добавление/обновление элементов, когда обнаружено изменение файлов. *Требуется перезапуск сервера",
|
||||
"LabelSettingsEnableEReader": "Включить e-reader для всех пользователей",
|
||||
"LabelSettingsEnableEReaderHelp": "E-reader все еще находится в стадии разработки, используйте эту настройку, чтобы открыть его для всех ваших пользователей (Только для Вас используйте переключатель \"Экспериментальные Функции\")",
|
||||
"LabelSettingsExperimentalFeatures": "Экспериментальные функции",
|
||||
"LabelSettingsExperimentalFeaturesHelp": "Функционал в разработке на который Вы могли бы дать отзыв или помочь в тестировании. Нажмите для открытия обсуждения на github.",
|
||||
"LabelSettingsFindCovers": "Найти обложки",
|
||||
"LabelSettingsFindCoversHelp": "Если у Ваших аудиокниг нет встроенной обложки или файла обложки в папке книги, то сканер попробует найти обложку.<br>Примечание: Это увеличит время сканирования",
|
||||
"LabelSettingsHomePageBookshelfView": "Вид книжной полки на Домашней Странице",
|
||||
"LabelSettingsLibraryBookshelfView": "Вид книжной полки в Библиотеке",
|
||||
"LabelSettingsOverdriveMediaMarkers": "Overdrive Media Markers для глав",
|
||||
"LabelSettingsOverdriveMediaMarkersHelp": "MP3 файлы из Overdrive поставляется с таймингами глав, встроенными в виде пользовательских метаданных. При включении этого параметра эти теги будут автоматически использоваться для таймингов глав",
|
||||
"LabelSettingsParseSubtitles": "Разбор подзаголовков",
|
||||
"LabelSettingsParseSubtitlesHelp": "Извлечение подзаголовков из имен папок аудиокниг.<br>Подзаголовок должны быть отделен \" - \"<br>например \"Название Книги - Тут Подзаголовок\" подзаголовок будет \"Тут Подзаголовок\"",
|
||||
"LabelSettingsPreferAudioMetadata": "Предпочитать аудио метаданные",
|
||||
"LabelSettingsPreferAudioMetadataHelp": "ID3 мета теги будут использоваться для данных книг вместо имен папок",
|
||||
"LabelSettingsPreferMatchedMetadata": "Предпочитать метаданные поиска",
|
||||
"LabelSettingsPreferMatchedMetadataHelp": "Данные поиска будут перезаписывать данные книг при использовании Быстрого Поиска. По умолчанию Быстрый Поиск будет использоваться только при отсутствии данных",
|
||||
"LabelSettingsPreferOPFMetadata": "Предпочитать OPF метаданные",
|
||||
"LabelSettingsPreferOPFMetadataHelp": "Метаданные из файла OPF будут использованы для данных книги вместо имен папок",
|
||||
"LabelSettingsSkipMatchingBooksWithASIN": "Пропускать Поиск книг у которых уже заполнен ASIN",
|
||||
"LabelSettingsSkipMatchingBooksWithISBN": "Пропускать Поиск книг у которых уже заполнен ISBN",
|
||||
"LabelSettingsSortingIgnorePrefixes": "Игнорировать префиксы при сортировке",
|
||||
"LabelSettingsSortingIgnorePrefixesHelp": "Например \"the\", книга с названием \"The Book Title\" будет сортироваться как \"Book Title, The\"",
|
||||
"LabelSettingsSquareBookCovers": "Использовать квадратные обложки книг",
|
||||
"LabelSettingsSquareBookCoversHelp": "Использовать квадратные обложки вместо стандартных для книг 1.6:1 обложек",
|
||||
"LabelSettingsStoreCoversWithItem": "Хранить обложки с элементом",
|
||||
"LabelSettingsStoreCoversWithItemHelp": "По умолчанию обложки сохраняются в папке /metadata/items, при включении этой настройки обложка будет храниться в папке элемента. Будет сохраняться только один файл с именем \"cover\"",
|
||||
"LabelSettingsStoreMetadataWithItem": "Хранить метаинформацию с элементом",
|
||||
"LabelSettingsStoreMetadataWithItemHelp": "По умолчанию метаинформация сохраняется в папке /metadata/items, при включении этой настройки метаинформация будет храниться в папке элемента. Используется расширение файла .abs",
|
||||
"LabelShowAll": "Показать Все",
|
||||
"LabelSize": "Размер",
|
||||
"LabelSleepTimer": "Таймер сна",
|
||||
"LabelStart": "Начало",
|
||||
"LabelStarted": "Начат",
|
||||
"LabelStartedAt": "Начато В",
|
||||
"LabelStartTime": "Время Начала",
|
||||
"LabelStatsAudioTracks": "Аудио Треки",
|
||||
"LabelStatsAuthors": "Авторы",
|
||||
"LabelStatsBestDay": "Лучший День",
|
||||
"LabelStatsDailyAverage": "В среднем в День",
|
||||
"LabelStatsDays": "Дней",
|
||||
"LabelStatsDaysListened": "Дней Прослушано",
|
||||
"LabelStatsHours": "Часов",
|
||||
"LabelStatsInARow": "в строке",
|
||||
"LabelStatsItemsFinished": "Элементов Завершено",
|
||||
"LabelStatsItemsInLibrary": "Элементов в Библиотеке",
|
||||
"LabelStatsMinutes": "минут",
|
||||
"LabelStatsMinutesListening": "Минут Прослушано",
|
||||
"LabelStatsOverallDays": "Всего Дней",
|
||||
"LabelStatsOverallHours": "Всего Часов",
|
||||
"LabelStatsWeekListening": "Недель Прослушано",
|
||||
"LabelSubtitle": "Подзаголовок",
|
||||
"LabelSupportedFileTypes": "Поддерживаемые типы файлов",
|
||||
"LabelTag": "Тег",
|
||||
"LabelTags": "Теги",
|
||||
"LabelTagsAccessibleToUser": "Теги Доступные для Пользователя",
|
||||
"LabelTimeListened": "Время Прослушивания",
|
||||
"LabelTimeListenedToday": "Время Прослушивания Сегодня",
|
||||
"LabelTimeRemaining": "{0} осталось",
|
||||
"LabelTimeToShift": "Время смещения в сек.",
|
||||
"LabelTitle": "Название",
|
||||
"LabelToolsEmbedMetadata": "Встроить Метаданные",
|
||||
"LabelToolsEmbedMetadataDescription": "Встроить метаданные в аудио файлы, включая обложку и главы.",
|
||||
"LabelToolsMakeM4b": "Создать M4B Файл Аудиокниги",
|
||||
"LabelToolsMakeM4bDescription": "Создает .M4B файл аудиокниги с встроенными метаданными, обложкой и главами.",
|
||||
"LabelToolsSplitM4b": "Разделить M4B на MP3 файлы",
|
||||
"LabelToolsSplitM4bDescription": "Создает MP3 файла из M4B, разделяет на главы с встроенными метаданными, обложкой и главами.",
|
||||
"LabelTotalDuration": "Общая Длина",
|
||||
"LabelTotalTimeListened": "Всего Прослушано",
|
||||
"LabelTrackFromFilename": "Трек из Имени файла",
|
||||
"LabelTrackFromMetadata": "Трек из Метаданных",
|
||||
"LabelTracks": "Треков",
|
||||
"LabelTracksMultiTrack": "Мультитрек",
|
||||
"LabelTracksSingleTrack": "Один трек",
|
||||
"LabelType": "Тип",
|
||||
"LabelUnknown": "Неизвестно",
|
||||
"LabelUpdateCover": "Обновить Обложку",
|
||||
"LabelUpdateCoverHelp": "Позволяет перезаписывать существующие обложки для выбранных книг если будут найдены",
|
||||
"LabelUpdatedAt": "Обновлено в",
|
||||
"LabelUpdateDetails": "Обновить Подробности",
|
||||
"LabelUpdateDetailsHelp": "Позволяет перезаписывать текущие подробности для выбранных книг если будут найдены",
|
||||
"LabelUploaderDragAndDrop": "Перетащите файлы или каталоги",
|
||||
"LabelUploaderDropFiles": "Перетащите файлы",
|
||||
"LabelUseChapterTrack": "Показывать время главы",
|
||||
"LabelUseFullTrack": "Показывать время книги",
|
||||
"LabelUser": "Пользователь",
|
||||
"LabelUsername": "Имя пользователя",
|
||||
"LabelValue": "Значение",
|
||||
"LabelVersion": "Версия",
|
||||
"LabelViewBookmarks": "Закладки",
|
||||
"LabelViewChapters": "Главы",
|
||||
"LabelViewQueue": "Очередь воспроизведения",
|
||||
"LabelVolume": "Громкость",
|
||||
"LabelWeekdaysToRun": "Дни недели для запуска",
|
||||
"LabelYourAudiobookDuration": "Продолжительность Вашей Книги",
|
||||
"LabelYourBookmarks": "Ваши Закладки",
|
||||
"LabelYourPlaylists": "Ваши Плейлисты",
|
||||
"LabelYourProgress": "Ваш Прогресс",
|
||||
"MessageAddToPlayerQueue": "Добавить в очередь проигрывателя",
|
||||
"MessageAppriseDescription": "Для использования этой функции необходимо иметь запущенный экземпляр <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> или api которое обрабатывает те же самые запросы. <br />URL-адрес API Apprise должен быть полным URL-адресом для отправки уведомления, т.е., если API запущено по адресу <code>http://192.168.1.1:8337</code> тогда нужно указать <code>http://192.168.1.1:8337/notify</code>.",
|
||||
"MessageBackupsDescription": "Бэкап включает пользователей, прогресс пользователей, данные элементов библиотеки, настройки сервера и изображения хранящиеся в <code>/metadata/items</code> и <code>/metadata/authors</code>. Бэкапы <strong>НЕ</strong> сохраняют файлы из папок библиотек.",
|
||||
"MessageBatchQuickMatchDescription": "Быстрый Поиск попытается добавить отсутствующие обложки и метаданные для выбранных элементов. Включите параметры ниже, чтобы разрешить Быстрому Поиску перезаписывать существующие обложки и/или метаданные.",
|
||||
"MessageBookshelfNoCollections": "Вы еще не создали ни одной коллекции",
|
||||
"MessageBookshelfNoResultsForFilter": "Нет Результатов для фильтра \"{0}: {1}\"",
|
||||
"MessageBookshelfNoRSSFeeds": "Нет открытых RSS-каналов",
|
||||
"MessageBookshelfNoSeries": "У вас нет серий",
|
||||
"MessageChapterEndIsAfter": "Конец главы после окончания вашей аудиокниги",
|
||||
"MessageChapterErrorFirstNotZero": "Первая глава должна начинаться с 0",
|
||||
"MessageChapterErrorStartGteDuration": "Неверное время начала, должно быть меньше продолжительности аудиокниги",
|
||||
"MessageChapterErrorStartLtPrev": "Неверное время начала, должно быть больше или равно времени начала предыдущей главы",
|
||||
"MessageChapterStartIsAfter": "Глава начинается после окончания аудиокниги",
|
||||
"MessageCheckingCron": "Проверка cron...",
|
||||
"MessageConfirmDeleteBackup": "Вы уверены, что хотите удалить бэкап для {0}?",
|
||||
"MessageConfirmDeleteLibrary": "Вы уверены, что хотите навсегда удалить библиотеку \"{0}\"?",
|
||||
"MessageConfirmDeleteSession": "Вы уверены, что хотите удалить этот сеанс?",
|
||||
"MessageConfirmForceReScan": "Вы уверены, что хотите принудительно выполнить повторное сканирование?",
|
||||
"MessageConfirmMarkSeriesFinished": "Вы уверены, что хотите отметить все книги этой серии как законченные?",
|
||||
"MessageConfirmMarkSeriesNotFinished": "Вы уверены, что хотите отметить все книги этой серии как незаконченные?",
|
||||
"MessageConfirmRemoveCollection": "Вы уверены, что хотите удалить коллекцию \"{0}\"?",
|
||||
"MessageConfirmRemoveEpisode": "Вы уверены, что хотите удалить эпизод \"{0}\"?",
|
||||
"MessageConfirmRemoveEpisodes": "Вы уверены, что хотите удалить {0} эпизодов?",
|
||||
"MessageConfirmRemovePlaylist": "Вы уверены, что хотите удалить плейлист \"{0}\"?",
|
||||
"MessageConfirmRenameGenre": "Вы уверены, что хотите переименовать жанр \"{0}\" в \"{1}\" для всех элементов?",
|
||||
"MessageConfirmRenameGenreMergeNote": "Примечание: Этот жанр уже существует, поэтому они будут объединены.",
|
||||
"MessageConfirmRenameGenreWarning": "Предупреждение! Похожий жанр с другими начальными буквами уже существует \"{0}\".",
|
||||
"MessageConfirmRenameTag": "Вы уверены, что хотите переименовать тег \"{0}\" в \"{1}\" для всех элементов?",
|
||||
"MessageConfirmRenameTagMergeNote": "Примечание: Этот тег уже существует, поэтому они будут объединены.",
|
||||
"MessageConfirmRenameTagWarning": "Предупреждение! Похожий тег с другими начальными буквами уже существует \"{0}\".",
|
||||
"MessageDownloadingEpisode": "Эпизод скачивается",
|
||||
"MessageDragFilesIntoTrackOrder": "Перетащите файлы для исправления порядка треков",
|
||||
"MessageEmbedFinished": "Встраивание завершено!",
|
||||
"MessageEpisodesQueuedForDownload": "{0} Эпизод(ов) запланировано для закачки",
|
||||
"MessageFeedURLWillBe": "URL канала будет {0}",
|
||||
"MessageFetching": "Завершается...",
|
||||
"MessageForceReScanDescription": "будет сканировать все файлы снова, как свежее сканирование. Теги ID3 аудиофайлов, OPF-файлы и текстовые файлы будут сканироваться как новые.",
|
||||
"MessageImportantNotice": "Важное замечание!",
|
||||
"MessageInsertChapterBelow": "Вставить главу ниже",
|
||||
"MessageItemsSelected": "{0} Элементов Выделено",
|
||||
"MessageItemsUpdated": "{0} Элементов Обновлено",
|
||||
"MessageJoinUsOn": "Присоединяйтесь к нам в",
|
||||
"MessageListeningSessionsInTheLastYear": "{0} сеансов прослушивания в прошлом году",
|
||||
"MessageLoading": "Загрузка...",
|
||||
"MessageLoadingFolders": "Загрузка каталогов...",
|
||||
"MessageM4BFailed": "M4B Ошибка!",
|
||||
"MessageM4BFinished": "M4B Завершено!",
|
||||
"MessageMapChapterTitles": "Сопоставление названий глав с существующими главами аудиокниги без корректировки временных меток",
|
||||
"MessageMarkAsFinished": "Отметить, как Завершенную",
|
||||
"MessageMarkAsNotFinished": "Отметить, как Не Завершенную",
|
||||
"MessageMatchBooksDescription": "попытается сопоставить книги в библиотеке с книгой из выбранного поставщика поиска и заполнить пустые детали и обложку. Не перезаписывает сведения.",
|
||||
"MessageNoAudioTracks": "Нет аудио треков",
|
||||
"MessageNoAuthors": "Нет Авторов",
|
||||
"MessageNoBackups": "Нет Бэкапов",
|
||||
"MessageNoBookmarks": "Нет Закладок",
|
||||
"MessageNoChapters": "Нет Глав",
|
||||
"MessageNoCollections": "Нет Коллекций",
|
||||
"MessageNoCoversFound": "Обложек не найдено",
|
||||
"MessageNoDescription": "Нет описания",
|
||||
"MessageNoEpisodeMatchesFound": "Совпадения эпизодов не найдены",
|
||||
"MessageNoEpisodes": "Нет Эпизодов",
|
||||
"MessageNoFoldersAvailable": "Нет доступных папок",
|
||||
"MessageNoGenres": "Нет Жанров",
|
||||
"MessageNoIssues": "Нет Проблем",
|
||||
"MessageNoItems": "Нет Элементов",
|
||||
"MessageNoItemsFound": "Элементы не найдены",
|
||||
"MessageNoListeningSessions": "Нет Сеансов Прослушивания",
|
||||
"MessageNoLogs": "Нет Логов",
|
||||
"MessageNoMediaProgress": "Нет Прогресса Медиа",
|
||||
"MessageNoNotifications": "Нет Уведомлений",
|
||||
"MessageNoPodcastsFound": "Подкасты не найдены",
|
||||
"MessageNoResults": "Нет Результатов",
|
||||
"MessageNoSearchResultsFor": "Нет результатов поиска для \"{0}\"",
|
||||
"MessageNoSeries": "Нет Серий",
|
||||
"MessageNoTags": "Нет Тегов",
|
||||
"MessageNotYetImplemented": "Пока не реализовано",
|
||||
"MessageNoUpdateNecessary": "Обновление не требуется",
|
||||
"MessageNoUpdatesWereNecessary": "Обновления не требовались",
|
||||
"MessageNoUserPlaylists": "У вас нет плейлистов",
|
||||
"MessageOr": "или",
|
||||
"MessagePauseChapter": "Пауза воспроизведения главы",
|
||||
"MessagePlayChapter": "Прослушать начало главы",
|
||||
"MessagePlaylistCreateFromCollection": "Создать плейлист из коллекции",
|
||||
"MessagePodcastHasNoRSSFeedForMatching": "Подкаст не имеет URL-адреса RSS-канала, который можно использовать для поиска",
|
||||
"MessageQuickMatchDescription": "Заполняет пустые детали элемента и обложку первым результатом поиска из «{0}». Не перезаписывает сведения, если не включен параметр сервера 'Предпочитать метаданные поиска'.",
|
||||
"MessageRemoveAllItemsWarning": "ПРЕДУПРЕЖДЕНИЕ! Это действие удалит все элементы библиотеки из базы данных, включая все сделанные обновления или совпадения. Ничего не произойдет с вашими фактическими файлами. Уверены?",
|
||||
"MessageRemoveChapter": "Удалить главу",
|
||||
"MessageRemoveEpisodes": "Удалить {0} эпизод(ов)",
|
||||
"MessageRemoveFromPlayerQueue": "Удалить из очереди воспроизведения",
|
||||
"MessageRemoveUserWarning": "Вы уверены, что хотите навсегда удалить пользователя \"{0}\"?",
|
||||
"MessageReportBugsAndContribute": "Сообщайте об ошибках, запрашивайте функции и вносите свой вклад на",
|
||||
"MessageResetChaptersConfirm": "Вы уверены, что хотите сбросить главы и отменить внесенные изменения?",
|
||||
"MessageRestoreBackupConfirm": "Вы уверены, что хотите восстановить резервную копию, созданную",
|
||||
"MessageRestoreBackupWarning": "Восстановление резервной копии перезапишет всю базу данных, расположенную в /config, и обложки изображений в /metadata/items и /metadata/authors.<br/><br/>Бэкапы не изменяют файлы в папках библиотеки. Если вы включили параметры сервера для хранения обложек и метаданных в папках библиотеки, то они не резервируются и не перезаписываются.<br/><br/>Все клиенты, использующие ваш сервер, будут автоматически обновлены.",
|
||||
"MessageSearchResultsFor": "Результаты поиска для",
|
||||
"MessageServerCouldNotBeReached": "Не удалось связаться с сервером",
|
||||
"MessageSetChaptersFromTracksDescription": "Установка глав с использованием каждого аудиофайла в качестве главы и заголовка главы в качестве имени аудиофайла",
|
||||
"MessageStartPlaybackAtTime": "Начать воспроизведение для \"{0}\" с {1}?",
|
||||
"MessageThinking": "Думаю...",
|
||||
"MessageUploaderItemFailed": "Не удалось загрузить",
|
||||
"MessageUploaderItemSuccess": "Успешно Загружено!",
|
||||
"MessageUploading": "Загрузка...",
|
||||
"MessageValidCronExpression": "Верное cron выражение",
|
||||
"MessageWatcherIsDisabledGlobally": "Наблюдатель отключен глобально в настройках сервера",
|
||||
"MessageXLibraryIsEmpty": "{0} Библиотека пуста!",
|
||||
"MessageYourAudiobookDurationIsLonger": "Продолжительность аудиокниги больше найденной продолжительности",
|
||||
"MessageYourAudiobookDurationIsShorter": "Продолжительность аудиокниги короче найденной продолжительности",
|
||||
"NoteChangeRootPassword": "Пользователь root — единственный пользователь, который может иметь пустой пароль",
|
||||
"NoteChapterEditorTimes": "Примечание: Время начала первой главы должно оставаться в 0:00, а время начала последней главы не может превышать продолжительность этой аудиокниги.",
|
||||
"NoteFolderPicker": "Примечание: папки, уже сопоставленные, не будут отображаться",
|
||||
"NoteFolderPickerDebian": "Примечание: Выбор папок debian не реализован полностью. Необходимо ввести путь к библиотеке напрямую.",
|
||||
"NoteRSSFeedPodcastAppsHttps": "Предупреждение: Большинству приложений подкастов потребуется, чтобы URL-адрес RSS-канала использовал HTTPS",
|
||||
"NoteRSSFeedPodcastAppsPubDate": "Предупреждение: 1 или более эпизодов не имеют даты публикации. Некоторые приложения для подкастов требуют этого.",
|
||||
"NoteUploaderFoldersWithMediaFiles": "Папки с медиафайлами будут обрабатываться как отдельные элементы библиотеки.",
|
||||
"NoteUploaderOnlyAudioFiles": "Если загружать только аудиофайлы, то каждый аудиофайл будет обрабатываться как отдельная аудиокнига.",
|
||||
"NoteUploaderUnsupportedFiles": "Неподдерживаемые файлы игнорируются. При выборе или удалении папки другие файлы, не находящиеся в папке элемента, игнорируются.",
|
||||
"PlaceholderNewCollection": "Новое имя коллекции",
|
||||
"PlaceholderNewFolderPath": "Путь к новой папке",
|
||||
"PlaceholderNewPlaylist": "Новое название плейлиста",
|
||||
"PlaceholderSearch": "Поиск...",
|
||||
"ToastAccountUpdateFailed": "Не удалось обновить учетную запись",
|
||||
"ToastAccountUpdateSuccess": "Учетная запись обновлена",
|
||||
"ToastAuthorImageRemoveFailed": "Не удалось удалить изображение",
|
||||
"ToastAuthorImageRemoveSuccess": "Изображение автора удалено",
|
||||
"ToastAuthorUpdateFailed": "Не удалось обновить автора",
|
||||
"ToastAuthorUpdateMerged": "Автор объединен",
|
||||
"ToastAuthorUpdateSuccess": "Автор обновлен",
|
||||
"ToastAuthorUpdateSuccessNoImageFound": "Автор обновлен (изображение не найдено)",
|
||||
"ToastBackupCreateFailed": "Не удалось создать бэкап",
|
||||
"ToastBackupCreateSuccess": "Бэкап создан",
|
||||
"ToastBackupDeleteFailed": "Не удалось удалить бэкап",
|
||||
"ToastBackupDeleteSuccess": "Бэкап удален",
|
||||
"ToastBackupRestoreFailed": "Не удалось восстановить из бэкапа",
|
||||
"ToastBackupUploadFailed": "Не удалось загрузить бэкап",
|
||||
"ToastBackupUploadSuccess": "Бэкап загружен",
|
||||
"ToastBatchUpdateFailed": "Сбой пакетного обновления",
|
||||
"ToastBatchUpdateSuccess": "Успешное пакетное обновление",
|
||||
"ToastBookmarkCreateFailed": "Не удалось создать закладку",
|
||||
"ToastBookmarkCreateSuccess": "Добавлена закладка",
|
||||
"ToastBookmarkRemoveFailed": "Не удалось удалить закладку",
|
||||
"ToastBookmarkRemoveSuccess": "Закладка удалена",
|
||||
"ToastBookmarkUpdateFailed": "Не удалось обновить закладку",
|
||||
"ToastBookmarkUpdateSuccess": "Закладка обновлена",
|
||||
"ToastChaptersHaveErrors": "Главы имеют ошибки",
|
||||
"ToastChaptersMustHaveTitles": "Главы должны содержать названия",
|
||||
"ToastCollectionItemsRemoveFailed": "Не удалось удалить элемент(ы) из коллекции",
|
||||
"ToastCollectionItemsRemoveSuccess": "Элемент(ы), удалены из коллекции",
|
||||
"ToastCollectionRemoveFailed": "Не удалось удалить коллекцию",
|
||||
"ToastCollectionRemoveSuccess": "Коллекция удалена",
|
||||
"ToastCollectionUpdateFailed": "Не удалось обновить коллекцию",
|
||||
"ToastCollectionUpdateSuccess": "Коллекция обновлена",
|
||||
"ToastItemCoverUpdateFailed": "Не удалось обновить обложку элемента",
|
||||
"ToastItemCoverUpdateSuccess": "Обложка элемента обновлена",
|
||||
"ToastItemDetailsUpdateFailed": "Не удалось обновить сведения об элементе",
|
||||
"ToastItemDetailsUpdateSuccess": "Обновлены сведения об элементе",
|
||||
"ToastItemDetailsUpdateUnneeded": "Для сведений об элементе не требуется никаких обновлений",
|
||||
"ToastItemMarkedAsFinishedFailed": "Не удалось пометить как Завершенный",
|
||||
"ToastItemMarkedAsFinishedSuccess": "Элемент помечен как Завершенный",
|
||||
"ToastItemMarkedAsNotFinishedFailed": "Не удалось пометить как Незавершенный",
|
||||
"ToastItemMarkedAsNotFinishedSuccess": "Элемент помечен как Незавершенный",
|
||||
"ToastLibraryCreateFailed": "Не удалось создать библиотеку",
|
||||
"ToastLibraryCreateSuccess": "Библиотека \"{0}\" создана",
|
||||
"ToastLibraryDeleteFailed": "Не удалось удалить библиотеку",
|
||||
"ToastLibraryDeleteSuccess": "Библиотека удалена",
|
||||
"ToastLibraryScanFailedToStart": "Не удалось запустить сканирование",
|
||||
"ToastLibraryScanStarted": "Запущено сканирование библиотеки",
|
||||
"ToastLibraryUpdateFailed": "Не удалось обновить библиотеку",
|
||||
"ToastLibraryUpdateSuccess": "Библиотека \"{0}\" обновлена",
|
||||
"ToastPlaylistCreateFailed": "Не удалось создать плейлист",
|
||||
"ToastPlaylistCreateSuccess": "Плейлист создан",
|
||||
"ToastPlaylistRemoveFailed": "Не удалось удалить плейлист",
|
||||
"ToastPlaylistRemoveSuccess": "Плейлист удален",
|
||||
"ToastPlaylistUpdateFailed": "Не удалось обновить плейлист",
|
||||
"ToastPlaylistUpdateSuccess": "Плейлист обновлен",
|
||||
"ToastPodcastCreateFailed": "Не удалось создать подкаст",
|
||||
"ToastPodcastCreateSuccess": "Подкаст успешно создан",
|
||||
"ToastRemoveItemFromCollectionFailed": "Не удалось удалить элемент из коллекции",
|
||||
"ToastRemoveItemFromCollectionSuccess": "Элемент удален из коллекции",
|
||||
"ToastRSSFeedCloseFailed": "Не удалось закрыть RSS-канал",
|
||||
"ToastRSSFeedCloseSuccess": "RSS-канал закрыт",
|
||||
"ToastSeriesUpdateFailed": "Не удалось обновить серию",
|
||||
"ToastSeriesUpdateSuccess": "Успешное обновление серии",
|
||||
"ToastSessionDeleteFailed": "Не удалось удалить сеанс",
|
||||
"ToastSessionDeleteSuccess": "Сеанс удален",
|
||||
"ToastSocketConnected": "Сокет подключен",
|
||||
"ToastSocketDisconnected": "Сокет отключен",
|
||||
"ToastSocketFailedToConnect": "Не удалось подключить сокет",
|
||||
"ToastUserDeleteFailed": "Не удалось удалить пользователя",
|
||||
"ToastUserDeleteSuccess": "Пользователь удален"
|
||||
}
|
||||
@@ -20,7 +20,7 @@
|
||||
"ButtonCreate": "创建",
|
||||
"ButtonCreateBackup": "创建备份",
|
||||
"ButtonDelete": "删除",
|
||||
"ButtonEdit": "Edit",
|
||||
"ButtonEdit": "编辑",
|
||||
"ButtonEditChapters": "编辑章节",
|
||||
"ButtonEditPodcast": "编辑播客",
|
||||
"ButtonForceReScan": "强制重新扫描",
|
||||
@@ -76,8 +76,8 @@
|
||||
"ButtonUploadBackup": "上传备份",
|
||||
"ButtonUploadCover": "上传封面",
|
||||
"ButtonUploadOPMLFile": "上传 OPML 文件",
|
||||
"ButtonUserDelete": "Delete user {0}",
|
||||
"ButtonUserEdit": "Edit user {0}",
|
||||
"ButtonUserDelete": "删除用户 {0}",
|
||||
"ButtonUserEdit": "编辑用户 {0}",
|
||||
"ButtonViewAll": "查看全部",
|
||||
"ButtonYes": "确定",
|
||||
"HeaderAccount": "帐户",
|
||||
@@ -308,6 +308,7 @@
|
||||
"LabelPublishYear": "发布年份",
|
||||
"LabelRecentlyAdded": "最近添加",
|
||||
"LabelRecentSeries": "最近添加系列",
|
||||
"LabelRecommended": "推荐内容",
|
||||
"LabelRegion": "区域",
|
||||
"LabelReleaseDate": "发布日期",
|
||||
"LabelRemoveCover": "移除封面",
|
||||
@@ -443,8 +444,8 @@
|
||||
"MessageConfirmDeleteLibrary": "你确定要永久删除媒体库 \"{0}\"?",
|
||||
"MessageConfirmDeleteSession": "你确定要删除此会话吗?",
|
||||
"MessageConfirmForceReScan": "你确定要强制重新扫描吗?",
|
||||
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
|
||||
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
|
||||
"MessageConfirmMarkSeriesFinished": "你确定要将此系列中的所有书籍都标记为已听完吗?",
|
||||
"MessageConfirmMarkSeriesNotFinished": "你确定要将此系列中的所有书籍都标记为未听完吗?",
|
||||
"MessageConfirmRemoveCollection": "您确定要移除收藏 \"{0}\"?",
|
||||
"MessageConfirmRemoveEpisode": "您确定要移除剧集 \"{0}\"?",
|
||||
"MessageConfirmRemoveEpisodes": "你确定要移除 {0} 剧集?",
|
||||
@@ -605,20 +606,13 @@
|
||||
"ToastRemoveItemFromCollectionSuccess": "项目已从收藏中删除",
|
||||
"ToastRSSFeedCloseFailed": "关闭 RSS 源失败",
|
||||
"ToastRSSFeedCloseSuccess": "RSS 源已关闭",
|
||||
"ToastSeriesUpdateFailed": "Series update failed",
|
||||
"ToastSeriesUpdateSuccess": "Series update success",
|
||||
"ToastSeriesUpdateFailed": "更新系列失败",
|
||||
"ToastSeriesUpdateSuccess": "系列已更新",
|
||||
"ToastSessionDeleteFailed": "删除会话失败",
|
||||
"ToastSessionDeleteSuccess": "会话已删除",
|
||||
"ToastSocketConnected": "网络已连接",
|
||||
"ToastSocketDisconnected": "网络已断开",
|
||||
"ToastSocketFailedToConnect": "网络连接失败",
|
||||
"ToastUserDeleteFailed": "删除用户失败",
|
||||
"ToastUserDeleteSuccess": "用户已删除",
|
||||
"WeekdayFriday": "星期五",
|
||||
"WeekdayMonday": "星期一",
|
||||
"WeekdaySaturday": "星期六",
|
||||
"WeekdaySunday": "星期日",
|
||||
"WeekdayThursday": "星期四",
|
||||
"WeekdayTuesday": "星期二",
|
||||
"WeekdayWednesday": "星期三"
|
||||
"ToastUserDeleteSuccess": "用户已删除"
|
||||
}
|
||||
@@ -91,8 +91,7 @@ module.exports = {
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Source Sans Pro'],
|
||||
mono: ['Ubuntu Mono'],
|
||||
book: ['Gentium Book Basic', 'serif']
|
||||
mono: ['Ubuntu Mono']
|
||||
},
|
||||
fontSize: {
|
||||
xxs: '0.625rem',
|
||||
|
||||
@@ -1,8 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 6702.7 1277.4" style="enable-background:new 0 0 6702.7 1277.4;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
|
||||
<svg
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 7501.0735 1237.1999"
|
||||
xml:space="preserve"
|
||||
width="7501.0737"
|
||||
height="1237.2"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"><defs
|
||||
id="defs104" />
|
||||
<style
|
||||
type="text/css"
|
||||
id="style70">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st1{fill:url(#SVGID_1_);}
|
||||
.st2{fill:#C9C9C9;}
|
||||
@@ -12,38 +28,82 @@
|
||||
.st6{font-family:'GentiumBasic';}
|
||||
.st7{font-size:305px;}
|
||||
</style>
|
||||
<title>bgAsset 6</title>
|
||||
<g id="Layer_2_1_">
|
||||
<g id="Layer_2-2">
|
||||
<g id="Layer_4">
|
||||
<g id="Layer_5">
|
||||
<circle class="st0" cx="618.6" cy="618.6" r="618.6"/>
|
||||
<title
|
||||
id="title72">bgAsset 6</title>
|
||||
<g
|
||||
id="Layer_2_1_">
|
||||
<g
|
||||
id="Layer_2-2">
|
||||
<g
|
||||
id="Layer_4">
|
||||
<g
|
||||
id="Layer_5">
|
||||
<circle
|
||||
class="st0"
|
||||
cx="618.59998"
|
||||
cy="618.59998"
|
||||
r="618.59998"
|
||||
id="circle74" />
|
||||
</g>
|
||||
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="617.37" y1="1257.3" x2="617.37" y2="61.4399" gradientTransform="matrix(1 0 0 -1 0 1278)">
|
||||
<stop offset="0.32" style="stop-color:#CD9D49"/>
|
||||
<stop offset="0.99" style="stop-color:#875D27"/>
|
||||
<linearGradient
|
||||
id="SVGID_1_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="617.37"
|
||||
y1="1257.3"
|
||||
x2="617.37"
|
||||
y2="61.439899"
|
||||
gradientTransform="matrix(1,0,0,-1,0,1278)">
|
||||
<stop
|
||||
offset="0.32"
|
||||
style="stop-color:#CD9D49"
|
||||
id="stop77" />
|
||||
<stop
|
||||
offset="0.99"
|
||||
style="stop-color:#875D27"
|
||||
id="stop79" />
|
||||
</linearGradient>
|
||||
<circle class="st1" cx="617.4" cy="618.6" r="597.9"/>
|
||||
<circle
|
||||
class="st1"
|
||||
cx="617.40002"
|
||||
cy="618.59998"
|
||||
r="597.90002"
|
||||
id="circle82"
|
||||
style="fill:url(#SVGID_1_)" />
|
||||
</g>
|
||||
<path class="st0" d="M1005.6,574.1c-4.8-4-12.4-10-22.6-17v-79.2c0-201.9-163.7-365.6-365.6-365.6l0,0
|
||||
c-201.9,0-365.6,163.7-365.6,365.6v79.2c-10.2,7-17.7,13-22.6,17c-4.1,3.4-6.5,8.5-6.5,13.9v94.9c0,5.4,2.4,10.5,6.5,14
|
||||
c11.3,9.4,37.2,29.1,77.5,49.3v9.2c0,24.9,16,45,35.8,45l0,0c19.8,0,35.8-20.2,35.8-45V527.8c0-24.9-16-45-35.8-45l0,0
|
||||
c-19,0-34.5,18.5-35.8,41.9h-0.1v-46.9c0-171.6,139.1-310.7,310.7-310.7l0,0C789,167.2,928,306.3,928,477.9v46.9H928
|
||||
c-1.3-23.4-16.8-41.9-35.8-41.9l0,0c-19.8,0-35.8,20.2-35.8,45v227.6c0,24.9,16,45,35.8,45l0,0c19.8,0,35.8-20.2,35.8-45v-9.2
|
||||
c40.3-20.2,66.2-39.9,77.5-49.3c4.2-3.5,6.5-8.6,6.5-14V588C1012.1,582.6,1009.7,577.5,1005.6,574.1z"/>
|
||||
<path class="st0" d="M489.9,969.7c23.9,0,43.3-19.4,43.3-43.3V441.6c0-23.9-19.4-43.3-43.3-43.3h-44.7
|
||||
c-23.9,0-43.3,19.4-43.3,43.3v484.8c0,23.9,19.4,43.3,43.3,43.3L489.9,969.7z M418.2,514.6h98.7v10.3h-98.7V514.6z"/>
|
||||
<path class="st0" d="M639.7,969.7c23.9,0,43.3-19.4,43.3-43.3V441.6c0-23.9-19.4-43.3-43.3-43.3H595c-23.9,0-43.3,19.4-43.3,43.3
|
||||
v484.8c0,23.9,19.4,43.3,43.3,43.3H639.7z M568,514.6h98.7v10.3H568V514.6z"/>
|
||||
<path class="st0" d="M789.6,969.7c23.9,0,43.3-19.4,43.3-43.3V441.6c0-23.9-19.4-43.3-43.3-43.3h-44.7
|
||||
c-23.9,0-43.3,19.4-43.3,43.3v484.8c0,23.9,19.4,43.3,43.3,43.3L789.6,969.7z M717.9,514.6h98.7v10.3h-98.7V514.6z"/>
|
||||
<path class="st0" d="M327.1,984.7h580.5c18,0,32.6,14.6,32.6,32.6v0c0,18-14.6,32.6-32.6,32.6H327.1c-18,0-32.6-14.6-32.6-32.6v0
|
||||
C294.5,999.3,309.1,984.7,327.1,984.7z"/>
|
||||
<path
|
||||
class="st0"
|
||||
d="m 1005.6,574.1 c -4.8,-4 -12.4,-10 -22.6,-17 V 477.9 C 983,276 819.3,112.3 617.4,112.3 v 0 C 415.5,112.3 251.8,276 251.8,477.9 v 79.2 c -10.2,7 -17.7,13 -22.6,17 -4.1,3.4 -6.5,8.5 -6.5,13.9 v 94.9 c 0,5.4 2.4,10.5 6.5,14 11.3,9.4 37.2,29.1 77.5,49.3 v 9.2 c 0,24.9 16,45 35.8,45 v 0 c 19.8,0 35.8,-20.2 35.8,-45 V 527.8 c 0,-24.9 -16,-45 -35.8,-45 v 0 c -19,0 -34.5,18.5 -35.8,41.9 h -0.1 v -46.9 c 0,-171.6 139.1,-310.7 310.7,-310.7 v 0 C 789,167.2 928,306.3 928,477.9 v 46.9 0 c -1.3,-23.4 -16.8,-41.9 -35.8,-41.9 v 0 c -19.8,0 -35.8,20.2 -35.8,45 v 227.6 c 0,24.9 16,45 35.8,45 v 0 c 19.8,0 35.8,-20.2 35.8,-45 v -9.2 c 40.3,-20.2 66.2,-39.9 77.5,-49.3 4.2,-3.5 6.5,-8.6 6.5,-14 v -95 c 0.1,-5.4 -2.3,-10.5 -6.4,-13.9 z"
|
||||
id="path85" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m 489.9,969.7 c 23.9,0 43.3,-19.4 43.3,-43.3 V 441.6 c 0,-23.9 -19.4,-43.3 -43.3,-43.3 h -44.7 c -23.9,0 -43.3,19.4 -43.3,43.3 v 484.8 c 0,23.9 19.4,43.3 43.3,43.3 z M 418.2,514.6 h 98.7 v 10.3 h -98.7 z"
|
||||
id="path87" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m 639.7,969.7 c 23.9,0 43.3,-19.4 43.3,-43.3 V 441.6 c 0,-23.9 -19.4,-43.3 -43.3,-43.3 H 595 c -23.9,0 -43.3,19.4 -43.3,43.3 v 484.8 c 0,23.9 19.4,43.3 43.3,43.3 z M 568,514.6 h 98.7 v 10.3 H 568 Z"
|
||||
id="path89" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m 789.6,969.7 c 23.9,0 43.3,-19.4 43.3,-43.3 V 441.6 c 0,-23.9 -19.4,-43.3 -43.3,-43.3 h -44.7 c -23.9,0 -43.3,19.4 -43.3,43.3 v 484.8 c 0,23.9 19.4,43.3 43.3,43.3 z M 717.9,514.6 h 98.7 v 10.3 h -98.7 z"
|
||||
id="path91" />
|
||||
<path
|
||||
class="st0"
|
||||
d="m 327.1,984.7 h 580.5 c 18,0 32.6,14.6 32.6,32.6 v 0 c 0,18 -14.6,32.6 -32.6,32.6 H 327.1 c -18,0 -32.6,-14.6 -32.6,-32.6 v 0 c 0,-18 14.6,-32.6 32.6,-32.6 z"
|
||||
id="path93" />
|
||||
</g>
|
||||
<g id="Layer_6">
|
||||
<text transform="matrix(1 0 0 1 1492.27 735.42)" class="st2 st3 st4">audiobookshelf</text>
|
||||
<text id="self-hosted_audiobook_and_podcast_server" transform="matrix(1 0 0 1 1492.27 1103.6899)" class="st5 st6 st7">self-hosted audiobook and podcast server</text>
|
||||
<g
|
||||
id="Layer_6">
|
||||
<text
|
||||
transform="translate(1492.27,735.42)"
|
||||
class="st2 st3 st4"
|
||||
id="text96">audiobookshelf</text>
|
||||
<text
|
||||
id="self-hosted_audiobook_and_podcast_server"
|
||||
transform="translate(1492.27,1103.6899)"
|
||||
class="st5 st6 st7">self-hosted audiobook and podcast server</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<metadata
|
||||
id="metadata210"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:title>bgAsset 6</dc:title></cc:Work></rdf:RDF></metadata></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 3.7 KiB |
4
index.js
4
index.js
@@ -18,8 +18,8 @@ const PORT = process.env.PORT || 80
|
||||
const HOST = process.env.HOST
|
||||
const CONFIG_PATH = process.env.CONFIG_PATH || '/config'
|
||||
const METADATA_PATH = process.env.METADATA_PATH || '/metadata'
|
||||
const UID = process.env.AUDIOBOOKSHELF_UID || 99
|
||||
const GID = process.env.AUDIOBOOKSHELF_GID || 100
|
||||
const UID = process.env.AUDIOBOOKSHELF_UID
|
||||
const GID = process.env.AUDIOBOOKSHELF_GID
|
||||
const SOURCE = process.env.SOURCE || 'docker'
|
||||
const ROUTER_BASE_PATH = process.env.ROUTER_BASE_PATH || ''
|
||||
|
||||
|
||||
177
package-lock.json
generated
177
package-lock.json
generated
@@ -1,20 +1,20 @@
|
||||
{
|
||||
"name": "audiobookshelf",
|
||||
"version": "2.2.12",
|
||||
"version": "2.2.15",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "audiobookshelf",
|
||||
"version": "2.2.12",
|
||||
"version": "2.2.15",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"axios": "^0.26.1",
|
||||
"axios": "^0.27.2",
|
||||
"express": "^4.17.1",
|
||||
"graceful-fs": "^4.2.10",
|
||||
"htmlparser2": "^8.0.1",
|
||||
"node-tone": "^1.0.1",
|
||||
"socket.io": "^4.4.1",
|
||||
"socket.io": "^4.5.4",
|
||||
"xml2js": "^0.4.23"
|
||||
},
|
||||
"bin": {
|
||||
@@ -35,14 +35,17 @@
|
||||
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
|
||||
},
|
||||
"node_modules/@types/cors": {
|
||||
"version": "2.8.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz",
|
||||
"integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw=="
|
||||
"version": "2.8.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz",
|
||||
"integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "18.11.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz",
|
||||
"integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg=="
|
||||
"version": "18.11.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
|
||||
"integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA=="
|
||||
},
|
||||
"node_modules/abbrev": {
|
||||
"version": "1.1.1",
|
||||
@@ -63,9 +66,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/anymatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
|
||||
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"normalize-path": "^3.0.0",
|
||||
@@ -80,12 +83,18 @@
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "0.26.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
|
||||
"integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
|
||||
"integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.14.8"
|
||||
"follow-redirects": "^1.14.9",
|
||||
"form-data": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
@@ -203,6 +212,17 @@
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
@@ -261,6 +281,14 @@
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
@@ -343,9 +371,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.0.tgz",
|
||||
"integrity": "sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==",
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.1.tgz",
|
||||
"integrity": "sha512-ECceEFcAaNRybd3lsGQKas3ZlMVjN3cyWwMP25D2i0zWfyiytVbTpRPa34qrr+FHddtpBVOmq4H/DCv1O0lZRA==",
|
||||
"dependencies": {
|
||||
"@types/cookie": "^0.4.1",
|
||||
"@types/cors": "^2.8.12",
|
||||
@@ -512,6 +540,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
@@ -1078,9 +1119,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/simple-update-notifier": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz",
|
||||
"integrity": "sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz",
|
||||
"integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"semver": "~7.0.0"
|
||||
@@ -1099,16 +1140,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io": {
|
||||
"version": "4.5.3",
|
||||
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.3.tgz",
|
||||
"integrity": "sha512-zdpnnKU+H6mOp7nYRXH4GNv1ux6HL6+lHL8g7Ds7Lj8CkdK1jJK/dlwsKDculbyOHifcJ0Pr/yeXnZQ5GeFrcg==",
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.4.tgz",
|
||||
"integrity": "sha512-m3GC94iK9MfIEeIBfbhJs5BqFibMtkRk8ZpKwG2QwxV0m/eEhPIV4ara6XCF1LWNAus7z58RodiZlAH71U3EhQ==",
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "~2.0.0",
|
||||
"debug": "~4.3.2",
|
||||
"engine.io": "~6.2.0",
|
||||
"engine.io": "~6.2.1",
|
||||
"socket.io-adapter": "~2.4.0",
|
||||
"socket.io-parser": "~4.2.0"
|
||||
"socket.io-parser": "~4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
@@ -1320,14 +1361,17 @@
|
||||
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
|
||||
},
|
||||
"@types/cors": {
|
||||
"version": "2.8.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz",
|
||||
"integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw=="
|
||||
"version": "2.8.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz",
|
||||
"integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "18.11.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz",
|
||||
"integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg=="
|
||||
"version": "18.11.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
|
||||
"integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA=="
|
||||
},
|
||||
"abbrev": {
|
||||
"version": "1.1.1",
|
||||
@@ -1345,9 +1389,9 @@
|
||||
}
|
||||
},
|
||||
"anymatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
|
||||
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"normalize-path": "^3.0.0",
|
||||
@@ -1359,12 +1403,18 @@
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
|
||||
},
|
||||
"asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.26.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
|
||||
"integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
|
||||
"integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.14.8"
|
||||
"follow-redirects": "^1.14.9",
|
||||
"form-data": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"balanced-match": {
|
||||
@@ -1452,6 +1502,14 @@
|
||||
"readdirp": "~3.6.0"
|
||||
}
|
||||
},
|
||||
"combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"requires": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
@@ -1498,6 +1556,11 @@
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
|
||||
},
|
||||
"depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
@@ -1552,9 +1615,9 @@
|
||||
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="
|
||||
},
|
||||
"engine.io": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.0.tgz",
|
||||
"integrity": "sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==",
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.1.tgz",
|
||||
"integrity": "sha512-ECceEFcAaNRybd3lsGQKas3ZlMVjN3cyWwMP25D2i0zWfyiytVbTpRPa34qrr+FHddtpBVOmq4H/DCv1O0lZRA==",
|
||||
"requires": {
|
||||
"@types/cookie": "^0.4.1",
|
||||
"@types/cors": "^2.8.12",
|
||||
@@ -1674,6 +1737,16 @@
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
|
||||
},
|
||||
"form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
},
|
||||
"forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
@@ -2080,9 +2153,9 @@
|
||||
}
|
||||
},
|
||||
"simple-update-notifier": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz",
|
||||
"integrity": "sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz",
|
||||
"integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"semver": "~7.0.0"
|
||||
@@ -2097,16 +2170,16 @@
|
||||
}
|
||||
},
|
||||
"socket.io": {
|
||||
"version": "4.5.3",
|
||||
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.3.tgz",
|
||||
"integrity": "sha512-zdpnnKU+H6mOp7nYRXH4GNv1ux6HL6+lHL8g7Ds7Lj8CkdK1jJK/dlwsKDculbyOHifcJ0Pr/yeXnZQ5GeFrcg==",
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.4.tgz",
|
||||
"integrity": "sha512-m3GC94iK9MfIEeIBfbhJs5BqFibMtkRk8ZpKwG2QwxV0m/eEhPIV4ara6XCF1LWNAus7z58RodiZlAH71U3EhQ==",
|
||||
"requires": {
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "~2.0.0",
|
||||
"debug": "~4.3.2",
|
||||
"engine.io": "~6.2.0",
|
||||
"engine.io": "~6.2.1",
|
||||
"socket.io-adapter": "~2.4.0",
|
||||
"socket.io-parser": "~4.2.0"
|
||||
"socket.io-parser": "~4.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "audiobookshelf",
|
||||
"version": "2.2.12",
|
||||
"version": "2.2.15",
|
||||
"description": "Self-hosted audiobook and podcast server",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
@@ -30,12 +30,12 @@
|
||||
"author": "advplyr",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"axios": "^0.26.1",
|
||||
"axios": "^0.27.2",
|
||||
"express": "^4.17.1",
|
||||
"graceful-fs": "^4.2.10",
|
||||
"htmlparser2": "^8.0.1",
|
||||
"node-tone": "^1.0.1",
|
||||
"socket.io": "^4.4.1",
|
||||
"socket.io": "^4.5.4",
|
||||
"xml2js": "^0.4.23"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
9
prod.js
9
prod.js
@@ -20,12 +20,13 @@ var inputConfig = options.config ? Path.resolve(options.config) : null
|
||||
var inputMetadata = options.metadata ? Path.resolve(options.metadata) : null
|
||||
|
||||
const PORT = options.port || process.env.PORT || 3333
|
||||
const HOST = options.host || process.env.HOST || "0.0.0.0"
|
||||
const HOST = options.host || process.env.HOST
|
||||
const CONFIG_PATH = inputConfig || process.env.CONFIG_PATH || Path.resolve('config')
|
||||
const METADATA_PATH = inputMetadata || process.env.METADATA_PATH || Path.resolve('metadata')
|
||||
const UID = 99
|
||||
const GID = 100
|
||||
const SOURCE = options.source || 'debian'
|
||||
const UID = process.env.AUDIOBOOKSHELF_UID
|
||||
const GID = process.env.AUDIOBOOKSHELF_GID
|
||||
const SOURCE = options.source || process.env.SOURCE || 'debian'
|
||||
|
||||
const ROUTER_BASE_PATH = process.env.ROUTER_BASE_PATH || ''
|
||||
|
||||
console.log(process.env.NODE_ENV, 'Config', CONFIG_PATH, METADATA_PATH)
|
||||
|
||||
@@ -43,11 +43,12 @@ class Server {
|
||||
this.Host = HOST
|
||||
global.Source = SOURCE
|
||||
global.isWin = process.platform === 'win32'
|
||||
global.Uid = isNaN(UID) ? 0 : Number(UID)
|
||||
global.Gid = isNaN(GID) ? 0 : Number(GID)
|
||||
global.Uid = isNaN(UID) ? undefined : Number(UID)
|
||||
global.Gid = isNaN(GID) ? undefined : Number(GID)
|
||||
global.ConfigPath = fileUtils.filePathToPOSIX(Path.normalize(CONFIG_PATH))
|
||||
global.MetadataPath = fileUtils.filePathToPOSIX(Path.normalize(METADATA_PATH))
|
||||
global.RouterBasePath = ROUTER_BASE_PATH
|
||||
global.XAccel = process.env.USE_X_ACCEL
|
||||
|
||||
if (!fs.pathExistsSync(global.ConfigPath)) {
|
||||
fs.mkdirSync(global.ConfigPath)
|
||||
@@ -142,6 +143,7 @@ class Server {
|
||||
const app = express()
|
||||
const router = express.Router()
|
||||
app.use(global.RouterBasePath, router)
|
||||
app.disable('x-powered-by')
|
||||
|
||||
this.server = http.createServer(app)
|
||||
|
||||
|
||||
@@ -319,7 +319,7 @@ class LibraryController {
|
||||
// series represents in the filtered series
|
||||
if (filterSeries) {
|
||||
json.collapsedSeries.seriesSequenceList =
|
||||
naturalSort(li.collapsedSeries.books.map(b => b.filterSeriesSequence)).asc()
|
||||
naturalSort(li.collapsedSeries.books.filter(b => b.filterSeriesSequence).map(b => b.filterSeriesSequence)).asc()
|
||||
.reduce((ranges, currentSequence) => {
|
||||
let lastRange = ranges.at(-1)
|
||||
let isNumber = /^(\d+|\d+\.\d*|\d*\.\d+)$/.test(currentSequence)
|
||||
@@ -684,13 +684,13 @@ class LibraryController {
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
// GET: api/libraries/:id/scan
|
||||
// POST: api/libraries/:id/scan
|
||||
async scan(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[LibraryController] Non-root user attempted to scan library`, req.user)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
var options = {
|
||||
const options = {
|
||||
forceRescan: req.query.force == 1
|
||||
}
|
||||
res.sendStatus(200)
|
||||
|
||||
@@ -201,6 +201,10 @@ class LibraryItemController {
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
|
||||
if (global.XAccel) {
|
||||
Logger.debug(`Use X-Accel to serve static file ${libraryItem.media.coverPath}`)
|
||||
return res.status(204).header({ 'X-Accel-Redirect': global.XAccel + libraryItem.media.coverPath }).send()
|
||||
}
|
||||
return res.sendFile(libraryItem.media.coverPath)
|
||||
}
|
||||
|
||||
@@ -385,7 +389,7 @@ class LibraryItemController {
|
||||
else res.sendStatus(500)
|
||||
}
|
||||
|
||||
// GET: api/items/:id/scan (admin)
|
||||
// POST: api/items/:id/scan (admin)
|
||||
async scan(req, res) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[LibraryItemController] Non-admin user attempted to scan library item`, req.user)
|
||||
|
||||
@@ -6,6 +6,10 @@ const { isObject, toNumber } = require('../utils/index')
|
||||
class MeController {
|
||||
constructor() { }
|
||||
|
||||
getCurrentUser(req, res) {
|
||||
res.json(req.user.toJSONForBrowser())
|
||||
}
|
||||
|
||||
// GET: api/me/listening-sessions
|
||||
async getListeningSessions(req, res) {
|
||||
var listeningSessions = await this.getUserListeningSessionsHelper(req.user.id)
|
||||
@@ -184,6 +188,7 @@ class MeController {
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: Deprecated. Removed from Android. Only used in iOS app now.
|
||||
// POST: api/me/sync-local-progress
|
||||
async syncLocalMediaProgress(req, res) {
|
||||
if (!req.body.localMediaProgress) {
|
||||
@@ -192,7 +197,8 @@ class MeController {
|
||||
}
|
||||
const updatedLocalMediaProgress = []
|
||||
var numServerProgressUpdates = 0
|
||||
var localMediaProgress = req.body.localMediaProgress || []
|
||||
const updatedServerMediaProgress = []
|
||||
const localMediaProgress = req.body.localMediaProgress || []
|
||||
|
||||
localMediaProgress.forEach(localProgress => {
|
||||
if (!localProgress.libraryItemId) {
|
||||
@@ -205,18 +211,22 @@ class MeController {
|
||||
return
|
||||
}
|
||||
|
||||
var mediaProgress = req.user.getMediaProgress(localProgress.libraryItemId, localProgress.episodeId)
|
||||
let mediaProgress = req.user.getMediaProgress(localProgress.libraryItemId, localProgress.episodeId)
|
||||
if (!mediaProgress) {
|
||||
// New media progress from mobile
|
||||
Logger.debug(`[MeController] syncLocalMediaProgress local progress is new - creating ${localProgress.id}`)
|
||||
req.user.createUpdateMediaProgress(libraryItem, localProgress, localProgress.episodeId)
|
||||
mediaProgress = req.user.getMediaProgress(localProgress.libraryItemId, localProgress.episodeId)
|
||||
updatedServerMediaProgress.push(mediaProgress)
|
||||
numServerProgressUpdates++
|
||||
} else if (mediaProgress.lastUpdate < localProgress.lastUpdate) {
|
||||
Logger.debug(`[MeController] syncLocalMediaProgress local progress is more recent - updating ${mediaProgress.id}`)
|
||||
req.user.createUpdateMediaProgress(libraryItem, localProgress, localProgress.episodeId)
|
||||
mediaProgress = req.user.getMediaProgress(localProgress.libraryItemId, localProgress.episodeId)
|
||||
updatedServerMediaProgress.push(mediaProgress)
|
||||
numServerProgressUpdates++
|
||||
} else if (mediaProgress.lastUpdate > localProgress.lastUpdate) {
|
||||
var updateTimeDifference = mediaProgress.lastUpdate - localProgress.lastUpdate
|
||||
const updateTimeDifference = mediaProgress.lastUpdate - localProgress.lastUpdate
|
||||
Logger.debug(`[MeController] syncLocalMediaProgress server progress is more recent by ${updateTimeDifference}ms - ${mediaProgress.id}`)
|
||||
|
||||
for (const key in localProgress) {
|
||||
@@ -240,7 +250,8 @@ class MeController {
|
||||
|
||||
res.json({
|
||||
numServerProgressUpdates,
|
||||
localProgressUpdates: updatedLocalMediaProgress
|
||||
localProgressUpdates: updatedLocalMediaProgress, // Array of LocalMediaProgress that were updated from server (server more recent)
|
||||
serverProgressUpdates: updatedServerMediaProgress // Array of MediaProgress that made updates to server (local more recent)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,10 @@ class MiscController {
|
||||
Logger.warn('User attempted to upload without permission', req.user)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
if (!req.files) {
|
||||
Logger.error('Invalid request, no files')
|
||||
return res.sendStatus(400)
|
||||
}
|
||||
var files = Object.values(req.files)
|
||||
var title = req.body.title
|
||||
var author = req.body.author
|
||||
|
||||
@@ -31,21 +31,24 @@ class PodcastController {
|
||||
}
|
||||
|
||||
const podcastPath = filePathToPOSIX(payload.path)
|
||||
if (await fs.pathExists(podcastPath)) {
|
||||
Logger.error(`[PodcastController] Podcast folder already exists "${podcastPath}"`)
|
||||
|
||||
// Check if a library item with this podcast folder exists already
|
||||
const existingLibraryItem = this.db.libraryItems.find(li => li.path === podcastPath && li.libraryId === library.id)
|
||||
if (existingLibraryItem) {
|
||||
Logger.error(`[PodcastController] Podcast already exists with name "${existingLibraryItem.media.metadata.title}" at path "${podcastPath}"`)
|
||||
return res.status(400).send('Podcast already exists')
|
||||
}
|
||||
|
||||
var success = await fs.ensureDir(podcastPath).then(() => true).catch((error) => {
|
||||
const success = await fs.ensureDir(podcastPath).then(() => true).catch((error) => {
|
||||
Logger.error(`[PodcastController] Failed to ensure podcast dir "${podcastPath}"`, error)
|
||||
return false
|
||||
})
|
||||
if (!success) return res.status(400).send('Invalid podcast path')
|
||||
await filePerms.setDefault(podcastPath)
|
||||
|
||||
var libraryItemFolderStats = await getFileTimestampsWithIno(podcastPath)
|
||||
const libraryItemFolderStats = await getFileTimestampsWithIno(podcastPath)
|
||||
|
||||
var relPath = payload.path.replace(folder.fullPath, '')
|
||||
let relPath = payload.path.replace(folder.fullPath, '')
|
||||
if (relPath.startsWith('/')) relPath = relPath.slice(1)
|
||||
|
||||
const libraryItemPayload = {
|
||||
@@ -60,14 +63,14 @@ class PodcastController {
|
||||
media: payload.media
|
||||
}
|
||||
|
||||
var libraryItem = new LibraryItem()
|
||||
const libraryItem = new LibraryItem()
|
||||
libraryItem.setData('podcast', libraryItemPayload)
|
||||
|
||||
// Download and save cover image
|
||||
if (payload.media.metadata.imageUrl) {
|
||||
// TODO: Scan cover image to library files
|
||||
// Podcast cover will always go into library item folder
|
||||
var coverResponse = await this.coverManager.downloadCoverFromUrl(libraryItem, payload.media.metadata.imageUrl, true)
|
||||
const coverResponse = await this.coverManager.downloadCoverFromUrl(libraryItem, payload.media.metadata.imageUrl, true)
|
||||
if (coverResponse) {
|
||||
if (coverResponse.error) {
|
||||
Logger.error(`[PodcastController] Download cover error from "${payload.media.metadata.imageUrl}": ${coverResponse.error}`)
|
||||
|
||||
@@ -75,6 +75,11 @@ class SessionController {
|
||||
this.playbackSessionManager.syncLocalSessionRequest(req.user, req.body, res)
|
||||
}
|
||||
|
||||
// POST: api/session/local-all
|
||||
syncLocalSessions(req, res) {
|
||||
this.playbackSessionManager.syncLocalSessionsRequest(req, res)
|
||||
}
|
||||
|
||||
openSessionMiddleware(req, res, next) {
|
||||
var playbackSession = this.playbackSessionManager.getSession(req.params.id)
|
||||
if (!playbackSession) return res.sendStatus(404)
|
||||
@@ -89,8 +94,11 @@ class SessionController {
|
||||
}
|
||||
|
||||
async middleware(req, res, next) {
|
||||
var playbackSession = await this.db.getPlaybackSession(req.params.id)
|
||||
if (!playbackSession) return res.sendStatus(404)
|
||||
const playbackSession = await this.db.getPlaybackSession(req.params.id)
|
||||
if (!playbackSession) {
|
||||
Logger.error(`[SessionController] Unable to find playback session with id=${req.params.id}`)
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
|
||||
if (req.method == 'DELETE' && !req.user.canDelete) {
|
||||
Logger.warn(`[SessionController] User attempted to delete without permission`, req.user)
|
||||
|
||||
@@ -3,6 +3,7 @@ const GoogleBooks = require('../providers/GoogleBooks')
|
||||
const Audible = require('../providers/Audible')
|
||||
const iTunes = require('../providers/iTunes')
|
||||
const Audnexus = require('../providers/Audnexus')
|
||||
const FantLab = require('../providers/FantLab')
|
||||
const Logger = require('../Logger')
|
||||
const { levenshteinDistance } = require('../utils/index')
|
||||
|
||||
@@ -13,6 +14,7 @@ class BookFinder {
|
||||
this.audible = new Audible()
|
||||
this.iTunesApi = new iTunes()
|
||||
this.audnexus = new Audnexus()
|
||||
this.fantLab = new FantLab()
|
||||
|
||||
this.verbose = false
|
||||
}
|
||||
@@ -146,6 +148,17 @@ class BookFinder {
|
||||
return books
|
||||
}
|
||||
|
||||
async getFantLabResults(title, author) {
|
||||
var books = await this.fantLab.search(title, author)
|
||||
if (this.verbose) Logger.debug(`FantLab Book Search Results: ${books.length || 0}`)
|
||||
if (books.errorCode) {
|
||||
Logger.error(`FantLab Search Error ${books.errorCode}`)
|
||||
return []
|
||||
}
|
||||
|
||||
return books
|
||||
}
|
||||
|
||||
async getiTunesAudiobooksResults(title, author) {
|
||||
return this.iTunesApi.searchAudiobooks(title)
|
||||
}
|
||||
@@ -172,7 +185,10 @@ class BookFinder {
|
||||
books = await this.getiTunesAudiobooksResults(title, author)
|
||||
} else if (provider === 'openlibrary') {
|
||||
books = await this.getOpenLibResults(title, author, maxTitleDistance, maxAuthorDistance)
|
||||
} else {
|
||||
} else if (provider === 'fantlab') {
|
||||
books = await this.getFantLabResults(title, author)
|
||||
}
|
||||
else {
|
||||
books = await this.getGoogleBooksResults(title, author)
|
||||
}
|
||||
|
||||
@@ -186,7 +202,7 @@ class BookFinder {
|
||||
return this.search(provider, cleanedTitle, cleanedAuthor, isbn, asin, options)
|
||||
}
|
||||
|
||||
if (["google", "audible", "itunes"].includes(provider)) return books
|
||||
if (["google", "audible", "itunes", 'fantlab'].includes(provider)) return books
|
||||
|
||||
return books.sort((a, b) => {
|
||||
return a.totalDistance - b.totalDistance
|
||||
|
||||
@@ -141,6 +141,10 @@ class AbMergeManager {
|
||||
'TrackNumber': 1,
|
||||
}
|
||||
|
||||
if (libraryItem.media.coverPath) {
|
||||
task.data.toneJsonObject['CoverFile'] = libraryItem.media.coverPath
|
||||
}
|
||||
|
||||
const workerData = {
|
||||
inputs: ffmpegInputs,
|
||||
options: ffmpegOptions,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user