mirror of
https://github.com/Tzahi12345/YoutubeDL-Material.git
synced 2026-03-08 04:20:08 +03:00
Compare commits
119 Commits
updated-pl
...
electron-i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c789ba9553 | ||
|
|
b8e1117ff6 | ||
|
|
b64a001ae1 | ||
|
|
c0a385ce78 | ||
|
|
258d5ff495 | ||
|
|
fb5c13db27 | ||
|
|
92413bd360 | ||
|
|
7174ef5f57 | ||
|
|
7ff906fd35 | ||
|
|
6e084bd94a | ||
|
|
21b97911e8 | ||
|
|
ccb4819a94 | ||
|
|
ce8f90ca1d | ||
|
|
8469ae10ad | ||
|
|
117255b0b7 | ||
|
|
f0e73c1708 | ||
|
|
aa1e36ae35 | ||
|
|
a1841e84ca | ||
|
|
05909877f4 | ||
|
|
90af895552 | ||
|
|
9f908aa3fc | ||
|
|
b56b371ece | ||
|
|
84e54cb4d5 | ||
|
|
42aaecc13a | ||
|
|
aac11b2105 | ||
|
|
bbf94ef982 | ||
|
|
2876cf55db | ||
|
|
375d3b4f38 | ||
|
|
160cffc737 | ||
|
|
7aad7b7d24 | ||
|
|
380475b33e | ||
|
|
384d365cf9 | ||
|
|
d6a43c76a4 | ||
|
|
407333a314 | ||
|
|
0fb01469c4 | ||
|
|
d10eb4f2eb | ||
|
|
148ed9aa65 | ||
|
|
1125de43d7 | ||
|
|
00b591a9a4 | ||
|
|
06d9793d1a | ||
|
|
0a2529330d | ||
|
|
19317dbddb | ||
|
|
3b74a2b5da | ||
|
|
a810628f15 | ||
|
|
a7d349a71a | ||
|
|
f8c4653ae0 | ||
|
|
bb6503e86d | ||
|
|
dbbfc041a4 | ||
|
|
342dafd52a | ||
|
|
984e990103 | ||
|
|
4ea239170e | ||
|
|
e2c31319cf | ||
|
|
b933af03e2 | ||
|
|
419fe3c3c6 | ||
|
|
07b48a4da1 | ||
|
|
a11445b80d | ||
|
|
297a4a3f34 | ||
|
|
1d2ab0dc41 | ||
|
|
46f8579439 | ||
|
|
b3744e616d | ||
|
|
de154a9c3e | ||
|
|
9e71b1ff12 | ||
|
|
6d318234b6 | ||
|
|
49925848ff | ||
|
|
356a807cad | ||
|
|
4e07440ed2 | ||
|
|
59c9237be5 | ||
|
|
4ba4710741 | ||
|
|
addd54fefd | ||
|
|
aefdde5401 | ||
|
|
4c1f975eae | ||
|
|
4c06bc750c | ||
|
|
4643efbae0 | ||
|
|
d11f77a6c9 | ||
|
|
1f0153b17e | ||
|
|
f32b394715 | ||
|
|
9d09eeffe3 | ||
|
|
c660c28422 | ||
|
|
669c87dd1b | ||
|
|
023df9c29d | ||
|
|
433d08e9df | ||
|
|
e34aa4d9d6 | ||
|
|
3f9314a0c3 | ||
|
|
00a0ab460b | ||
|
|
a1b32e2851 | ||
|
|
b8cab673ae | ||
|
|
6481102e01 | ||
|
|
af58854f0e | ||
|
|
d7d861ef0e | ||
|
|
1d5490c0ff | ||
|
|
28ee77cee0 | ||
|
|
133d848729 | ||
|
|
a78f4e99d0 | ||
|
|
539bc5094a | ||
|
|
f0f2faa398 | ||
|
|
7835185fe0 | ||
|
|
95bb69f16b | ||
|
|
a93aa080b3 | ||
|
|
ed1375d40b | ||
|
|
db78e4ad5e | ||
|
|
6ef0082563 | ||
|
|
b978007472 | ||
|
|
c09dd7a03b | ||
|
|
b6c09324d9 | ||
|
|
1a900399d8 | ||
|
|
ea959547fd | ||
|
|
085849c7ee | ||
|
|
cf1dd43d36 | ||
|
|
250f150587 | ||
|
|
dbf08e1276 | ||
|
|
f74ce4b865 | ||
|
|
8e4e0c7908 | ||
|
|
b0cb09309d | ||
|
|
75c1c9e9b7 | ||
|
|
c19e0bb881 | ||
|
|
a1af5496c7 | ||
|
|
3c206c31d5 | ||
|
|
3ffcfac28b | ||
|
|
1cc4df2829 |
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG]"
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Environment**
|
||||
- YoutubeDL-Material version
|
||||
- Docker tag: <tag> (optional)
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[FEATURE]"
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
16
.github/workflows/build.yml
vendored
16
.github/workflows/build.yml
vendored
@@ -15,7 +15,10 @@ jobs:
|
||||
- name: checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '12'
|
||||
cache: 'npm'
|
||||
- name: install dependencies
|
||||
run: |
|
||||
npm install
|
||||
@@ -69,17 +72,20 @@ jobs:
|
||||
with:
|
||||
name: youtubedl-material
|
||||
path: ${{runner.temp}}/youtubedl-material
|
||||
- name: extract tag name
|
||||
id: tag_name
|
||||
run: echo ::set-output name=tag_name::${GITHUB_REF#refs/tags/}
|
||||
- name: prepare release asset
|
||||
shell: pwsh
|
||||
run: Compress-Archive -Path ${{runner.temp}}/youtubedl-material -DestinationPath youtubedl-material-${{ github.ref }}.zip
|
||||
- name: upload build asset
|
||||
run: Compress-Archive -Path ${{runner.temp}}/youtubedl-material -DestinationPath youtubedl-material-${{ steps.tag_name.outputs.tag_name }}.zip
|
||||
- name: upload release asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./youtubedl-material-${{ github.ref }}.zip
|
||||
asset_name: youtubedl-material-${{ github.ref }}.zip
|
||||
asset_path: ./youtubedl-material-${{ steps.tag_name.outputs.tag_name }}.zip
|
||||
asset_name: youtubedl-material-${{ steps.tag_name.outputs.tag_name }}.zip
|
||||
asset_content_type: application/zip
|
||||
- name: upload docker-compose asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
|
||||
32
.github/workflows/docker-release.yml
vendored
Normal file
32
.github/workflows/docker-release.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: docker-release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tags:
|
||||
description: 'Docker tags'
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: setup platform emulator
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- name: setup multi-arch docker build
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: build & push images
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: linux/amd64,linux/arm,linux/arm64/v8
|
||||
push: true
|
||||
tags: ${{ github.event.inputs.tags }}
|
||||
25
.vscode/tasks.json
vendored
Normal file
25
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "start",
|
||||
"problemMatcher": [],
|
||||
"label": "Dev: start frontend",
|
||||
"detail": "ng serve"
|
||||
},
|
||||
{
|
||||
"label": "Dev: start backend",
|
||||
"type": "shell",
|
||||
"command": "set YTDL_MODE=debug && node app.js",
|
||||
"options": {
|
||||
"cwd": "./backend"
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "new"
|
||||
},
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -21,18 +21,23 @@ ENV UID=1000 \
|
||||
GID=1000 \
|
||||
USER=youtube
|
||||
|
||||
ENV NO_UPDATE_NOTIFIER=true
|
||||
ENV FOREVER_ROOT=/app/.forever
|
||||
|
||||
RUN addgroup -S $USER -g $GID && adduser -D -S $USER -G $USER -u $UID
|
||||
|
||||
RUN apk add --no-cache \
|
||||
ffmpeg \
|
||||
npm \
|
||||
python2 \
|
||||
python3 \
|
||||
su-exec \
|
||||
&& apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing/ \
|
||||
atomicparsley
|
||||
|
||||
WORKDIR /app
|
||||
COPY --chown=$UID:$GID [ "backend/package.json", "backend/package-lock.json", "/app/" ]
|
||||
RUN npm install forever -g
|
||||
RUN npm install && chown -R $UID:$GID ./
|
||||
|
||||
COPY --chown=$UID:$GID --from=frontend [ "/build/backend/public/", "/app/public/" ]
|
||||
@@ -40,4 +45,4 @@ COPY --chown=$UID:$GID [ "/backend/", "/app/" ]
|
||||
|
||||
EXPOSE 17442
|
||||
ENTRYPOINT [ "/app/entrypoint.sh" ]
|
||||
CMD [ "node", "app.js" ]
|
||||
CMD [ "forever", "app.js" ]
|
||||
|
||||
10
README.md
10
README.md
@@ -16,15 +16,11 @@ Check out the prerequisites, and go to the installation section. Easy as pie!
|
||||
|
||||
Here's an image of what it'll look like once you're done:
|
||||
|
||||

|
||||
|
||||
With optional file management enabled (default):
|
||||
|
||||

|
||||
<img src="https://i.imgur.com/C6vFGbL.png" width="800">
|
||||
|
||||
Dark mode:
|
||||
|
||||

|
||||
<img src="https://i.imgur.com/vOtvH5w.png" width="800">
|
||||
|
||||
### Prerequisites
|
||||
|
||||
@@ -33,7 +29,7 @@ NOTE: If you would like to use Docker, you can skip down to the [Docker](#Docker
|
||||
Debian/Ubuntu:
|
||||
|
||||
```bash
|
||||
sudo apt-get install nodejs youtube-dl ffmpeg
|
||||
sudo apt-get install nodejs youtube-dl ffmpeg unzip python npm
|
||||
```
|
||||
|
||||
CentOS 7:
|
||||
|
||||
1338
backend/app.js
1338
backend/app.js
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,8 @@
|
||||
"allow_quality_select": true,
|
||||
"download_only_mode": false,
|
||||
"allow_multi_download_mode": true,
|
||||
"enable_downloads_manager": true
|
||||
"enable_downloads_manager": true,
|
||||
"allow_playlist_categorization": true
|
||||
},
|
||||
"API": {
|
||||
"use_API_key": false,
|
||||
@@ -53,6 +54,10 @@
|
||||
"searchFilter": "(uid={{username}})"
|
||||
}
|
||||
},
|
||||
"Database": {
|
||||
"use_local_db": false,
|
||||
"mongodb_connection_string": "mongodb://127.0.0.1:27017/?compressors=zlib"
|
||||
},
|
||||
"Advanced": {
|
||||
"default_downloader": "youtube-dl",
|
||||
"use_default_downloading_agent": true,
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
const path = require('path');
|
||||
const config_api = require('../config');
|
||||
const consts = require('../consts');
|
||||
var subscriptions_api = require('../subscriptions')
|
||||
const fs = require('fs-extra');
|
||||
var jwt = require('jsonwebtoken');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { uuid } = require('uuidv4');
|
||||
var bcrypt = require('bcryptjs');
|
||||
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
var LocalStrategy = require('passport-local').Strategy;
|
||||
var LdapStrategy = require('passport-ldapauth');
|
||||
@@ -15,16 +13,15 @@ var JwtStrategy = require('passport-jwt').Strategy,
|
||||
|
||||
// other required vars
|
||||
let logger = null;
|
||||
let db = null;
|
||||
let users_db = null;
|
||||
let db_api = null;
|
||||
let SERVER_SECRET = null;
|
||||
let JWT_EXPIRATION = null;
|
||||
let opts = null;
|
||||
let saltRounds = null;
|
||||
|
||||
exports.initialize = function(input_db, input_users_db, input_logger) {
|
||||
exports.initialize = function(db_api, input_logger) {
|
||||
setLogger(input_logger)
|
||||
setDB(input_db, input_users_db);
|
||||
setDB(db_api);
|
||||
|
||||
/*************************
|
||||
* Authentication module
|
||||
@@ -34,21 +31,19 @@ exports.initialize = function(input_db, input_users_db, input_logger) {
|
||||
JWT_EXPIRATION = config_api.getConfigItem('ytdl_jwt_expiration');
|
||||
|
||||
SERVER_SECRET = null;
|
||||
if (users_db.get('jwt_secret').value()) {
|
||||
SERVER_SECRET = users_db.get('jwt_secret').value();
|
||||
if (db_api.users_db.get('jwt_secret').value()) {
|
||||
SERVER_SECRET = db_api.users_db.get('jwt_secret').value();
|
||||
} else {
|
||||
SERVER_SECRET = uuid();
|
||||
users_db.set('jwt_secret', SERVER_SECRET).write();
|
||||
db_api.users_db.set('jwt_secret', SERVER_SECRET).write();
|
||||
}
|
||||
|
||||
opts = {}
|
||||
opts.jwtFromRequest = ExtractJwt.fromUrlQueryParameter('jwt');
|
||||
opts.secretOrKey = SERVER_SECRET;
|
||||
/*opts.issuer = 'example.com';
|
||||
opts.audience = 'example.com';*/
|
||||
|
||||
exports.passport.use(new JwtStrategy(opts, function(jwt_payload, done) {
|
||||
const user = users_db.get('users').find({uid: jwt_payload.user}).value();
|
||||
exports.passport.use(new JwtStrategy(opts, async function(jwt_payload, done) {
|
||||
const user = await db_api.getRecord('users', {uid: jwt_payload.user});
|
||||
if (user) {
|
||||
return done(null, user);
|
||||
} else {
|
||||
@@ -62,9 +57,8 @@ function setLogger(input_logger) {
|
||||
logger = input_logger;
|
||||
}
|
||||
|
||||
function setDB(input_db, input_users_db) {
|
||||
db = input_db;
|
||||
users_db = input_users_db;
|
||||
function setDB(input_db_api) {
|
||||
db_api = input_db_api;
|
||||
}
|
||||
|
||||
exports.passport = require('passport');
|
||||
@@ -80,7 +74,7 @@ exports.passport.deserializeUser(function(user, done) {
|
||||
/***************************************
|
||||
* Register user with hashed password
|
||||
**************************************/
|
||||
exports.registerUser = function(req, res) {
|
||||
exports.registerUser = async function(req, res) {
|
||||
var userid = req.body.userid;
|
||||
var username = req.body.username;
|
||||
var plaintextPassword = req.body.password;
|
||||
@@ -98,20 +92,20 @@ exports.registerUser = function(req, res) {
|
||||
}
|
||||
|
||||
bcrypt.hash(plaintextPassword, saltRounds)
|
||||
.then(function(hash) {
|
||||
.then(async function(hash) {
|
||||
let new_user = generateUserObject(userid, username, hash);
|
||||
// check if user exists
|
||||
if (users_db.get('users').find({uid: userid}).value()) {
|
||||
if (await db_api.getRecord('users', {uid: userid})) {
|
||||
// user id is taken!
|
||||
logger.error('Registration failed: UID is already taken!');
|
||||
res.status(409).send('UID is already taken!');
|
||||
} else if (users_db.get('users').find({name: username}).value()) {
|
||||
} else if (await db_api.getRecord('users', {name: username})) {
|
||||
// user name is taken!
|
||||
logger.error('Registration failed: User name is already taken!');
|
||||
res.status(409).send('User name is already taken!');
|
||||
} else {
|
||||
// add to db
|
||||
users_db.get('users').push(new_user).write();
|
||||
await db_api.insertRecordIntoTable('users', new_user);
|
||||
logger.verbose(`New user created: ${new_user.name}`);
|
||||
res.send({
|
||||
user: new_user
|
||||
@@ -144,16 +138,18 @@ exports.registerUser = function(req, res) {
|
||||
************************************************/
|
||||
|
||||
|
||||
exports.login = async (username, password) => {
|
||||
const user = await db_api.getRecord('users', {name: username});
|
||||
if (!user) { logger.error(`User ${username} not found`); false }
|
||||
if (user.auth_method && user.auth_method !== 'internal') { return false }
|
||||
return await bcrypt.compare(password, user.passhash) ? user : false;
|
||||
}
|
||||
|
||||
exports.passport.use(new LocalStrategy({
|
||||
usernameField: 'username',
|
||||
passwordField: 'password'},
|
||||
async function(username, password, done) {
|
||||
const user = users_db.get('users').find({name: username}).value();
|
||||
if (!user) { logger.error(`User ${username} not found`); return done(null, false); }
|
||||
if (user.auth_method && user.auth_method !== 'internal') { return done(null, false); }
|
||||
if (user) {
|
||||
return done(null, (await bcrypt.compare(password, user.passhash)) ? user : false);
|
||||
}
|
||||
return done(null, await exports.login(username, password));
|
||||
}
|
||||
));
|
||||
|
||||
@@ -164,17 +160,17 @@ var getLDAPConfiguration = function(req, callback) {
|
||||
};
|
||||
|
||||
exports.passport.use(new LdapStrategy(getLDAPConfiguration,
|
||||
function(user, done) {
|
||||
async function(user, done) {
|
||||
// check if ldap auth is enabled
|
||||
const ldap_enabled = config_api.getConfigItem('ytdl_auth_method') === 'ldap';
|
||||
if (!ldap_enabled) return done(null, false);
|
||||
|
||||
const user_uid = user.uid;
|
||||
let db_user = users_db.get('users').find({uid: user_uid}).value();
|
||||
let db_user = await db_api.getRecord('users', {uid: user_uid});
|
||||
if (!db_user) {
|
||||
// generate DB user
|
||||
let new_user = generateUserObject(user_uid, user_uid, null, 'ldap');
|
||||
users_db.get('users').push(new_user).write();
|
||||
await db_api.insertRecordIntoTable('users', new_user);
|
||||
db_user = new_user;
|
||||
logger.verbose(`Generated new user ${user_uid} using LDAP`);
|
||||
}
|
||||
@@ -198,11 +194,11 @@ exports.generateJWT = function(req, res, next) {
|
||||
next();
|
||||
}
|
||||
|
||||
exports.returnAuthResponse = function(req, res) {
|
||||
exports.returnAuthResponse = async function(req, res) {
|
||||
res.status(200).json({
|
||||
user: req.user,
|
||||
token: req.token,
|
||||
permissions: exports.userPermissions(req.user.uid),
|
||||
permissions: await exports.userPermissions(req.user.uid),
|
||||
available_permissions: consts['AVAILABLE_PERMISSIONS']
|
||||
});
|
||||
}
|
||||
@@ -215,7 +211,7 @@ exports.returnAuthResponse = function(req, res) {
|
||||
* It also passes the user object to the next
|
||||
* middleware through res.locals
|
||||
**************************************/
|
||||
exports.ensureAuthenticatedElseError = function(req, res, next) {
|
||||
exports.ensureAuthenticatedElseError = (req, res, next) => {
|
||||
var token = getToken(req.query);
|
||||
if( token ) {
|
||||
try {
|
||||
@@ -233,10 +229,10 @@ exports.ensureAuthenticatedElseError = function(req, res, next) {
|
||||
}
|
||||
|
||||
// change password
|
||||
exports.changeUserPassword = async function(user_uid, new_pass) {
|
||||
exports.changeUserPassword = async (user_uid, new_pass) => {
|
||||
try {
|
||||
const hash = await bcrypt.hash(new_pass, saltRounds);
|
||||
users_db.get('users').find({uid: user_uid}).assign({passhash: hash}).write();
|
||||
await db_api.updateRecord('users', {uid: user_uid}, {passhash: hash});
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
@@ -244,16 +240,15 @@ exports.changeUserPassword = async function(user_uid, new_pass) {
|
||||
}
|
||||
|
||||
// change user permissions
|
||||
exports.changeUserPermissions = function(user_uid, permission, new_value) {
|
||||
exports.changeUserPermissions = async (user_uid, permission, new_value) => {
|
||||
try {
|
||||
const user_db_obj = users_db.get('users').find({uid: user_uid});
|
||||
user_db_obj.get('permissions').pull(permission).write();
|
||||
user_db_obj.get('permission_overrides').pull(permission).write();
|
||||
await db_api.pullFromRecordsArray('users', {uid: user_uid}, 'permissions', permission);
|
||||
await db_api.pullFromRecordsArray('users', {uid: user_uid}, 'permission_overrides', permission);
|
||||
if (new_value === 'yes') {
|
||||
user_db_obj.get('permissions').push(permission).write();
|
||||
user_db_obj.get('permission_overrides').push(permission).write();
|
||||
await db_api.pushToRecordsArray('users', {uid: user_uid}, 'permissions', permission);
|
||||
await db_api.pushToRecordsArray('users', {uid: user_uid}, 'permission_overrides', permission);
|
||||
} else if (new_value === 'no') {
|
||||
user_db_obj.get('permission_overrides').push(permission).write();
|
||||
await db_api.pushToRecordsArray('users', {uid: user_uid}, 'permission_overrides', permission);
|
||||
}
|
||||
return true;
|
||||
} catch (err) {
|
||||
@@ -263,12 +258,11 @@ exports.changeUserPermissions = function(user_uid, permission, new_value) {
|
||||
}
|
||||
|
||||
// change role permissions
|
||||
exports.changeRolePermissions = function(role, permission, new_value) {
|
||||
exports.changeRolePermissions = async (role, permission, new_value) => {
|
||||
try {
|
||||
const role_db_obj = users_db.get('roles').get(role);
|
||||
role_db_obj.get('permissions').pull(permission).write();
|
||||
await db_api.pullFromRecordsArray('roles', {key: role}, 'permissions', permission);
|
||||
if (new_value === 'yes') {
|
||||
role_db_obj.get('permissions').push(permission).write();
|
||||
await db_api.pushToRecordsArray('roles', {key: role}, 'permissions', permission);
|
||||
}
|
||||
return true;
|
||||
} catch (err) {
|
||||
@@ -277,19 +271,19 @@ exports.changeRolePermissions = function(role, permission, new_value) {
|
||||
}
|
||||
}
|
||||
|
||||
exports.adminExists = function() {
|
||||
return !!users_db.get('users').find({uid: 'admin'}).value();
|
||||
exports.adminExists = async function() {
|
||||
return !!(await db_api.getRecord('users', {uid: 'admin'}));
|
||||
}
|
||||
|
||||
// video stuff
|
||||
|
||||
exports.getUserVideos = function(user_uid, type) {
|
||||
const user = users_db.get('users').find({uid: user_uid}).value();
|
||||
return type ? user['files'].filter(file => file.isAudio = (type === 'audio')) : user['files'];
|
||||
exports.getUserVideos = async function(user_uid, type) {
|
||||
const files = await db_api.getRecords('files', {user_uid: user_uid});
|
||||
return type ? files.filter(file => file.isAudio === (type === 'audio')) : files;
|
||||
}
|
||||
|
||||
exports.getUserVideo = function(user_uid, file_uid, requireSharing = false) {
|
||||
let file = users_db.get('users').find({uid: user_uid}).get(`files`).find({uid: file_uid}).value();
|
||||
exports.getUserVideo = async function(user_uid, file_uid, requireSharing = false) {
|
||||
let file = await db_api.getRecord('files', {file_uid: file_uid});
|
||||
|
||||
// prevent unauthorized users from accessing the file info
|
||||
if (file && !file['sharingEnabled'] && requireSharing) file = null;
|
||||
@@ -297,58 +291,22 @@ exports.getUserVideo = function(user_uid, file_uid, requireSharing = false) {
|
||||
return file;
|
||||
}
|
||||
|
||||
exports.addPlaylist = function(user_uid, new_playlist) {
|
||||
users_db.get('users').find({uid: user_uid}).get(`playlists`).push(new_playlist).write();
|
||||
return true;
|
||||
}
|
||||
|
||||
exports.updatePlaylistFiles = function(user_uid, playlistID, new_filenames) {
|
||||
users_db.get('users').find({uid: user_uid}).get(`playlists`).find({id: playlistID}).assign({fileNames: new_filenames});
|
||||
return true;
|
||||
}
|
||||
|
||||
exports.removePlaylist = function(user_uid, playlistID) {
|
||||
users_db.get('users').find({uid: user_uid}).get(`playlists`).remove({id: playlistID}).write();
|
||||
exports.removePlaylist = async function(user_uid, playlistID) {
|
||||
await db_api.removeRecord('playlist', {playlistID: playlistID});
|
||||
return true;
|
||||
}
|
||||
|
||||
exports.getUserPlaylists = function(user_uid, user_files = null) {
|
||||
const user = users_db.get('users').find({uid: user_uid}).value();
|
||||
const playlists = JSON.parse(JSON.stringify(user['playlists']));
|
||||
const categories = db.get('categories').value();
|
||||
if (categories && user_files) {
|
||||
categories.forEach(category => {
|
||||
const audio_files = user_files && user_files.filter(file => file.category && file.category.uid === category.uid && file.isAudio);
|
||||
const video_files = user_files && user_files.filter(file => file.category && file.category.uid === category.uid && !file.isAudio);
|
||||
if (audio_files && audio_files.length > 0) {
|
||||
playlists.push({
|
||||
name: category['name'],
|
||||
thumbnailURL: audio_files[0].thumbnailURL,
|
||||
thumbnailPath: audio_files[0].thumbnailPath,
|
||||
fileNames: audio_files.map(file => file.id),
|
||||
type: 'audio',
|
||||
uid: user_uid,
|
||||
auto: true
|
||||
});
|
||||
}
|
||||
if (video_files && video_files.length > 0) {
|
||||
playlists.push({
|
||||
name: category['name'],
|
||||
thumbnailURL: video_files[0].thumbnailURL,
|
||||
thumbnailPath: video_files[0].thumbnailPath,
|
||||
fileNames: video_files.map(file => file.id),
|
||||
type: 'video',
|
||||
uid: user_uid,
|
||||
auto: true
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return playlists;
|
||||
exports.getUserPlaylists = async function(user_uid, user_files = null) {
|
||||
return await db_api.getRecords('playlists', {user_uid: user_uid});
|
||||
}
|
||||
|
||||
exports.getUserPlaylist = function(user_uid, playlistID, requireSharing = false) {
|
||||
let playlist = users_db.get('users').find({uid: user_uid}).get(`playlists`).find({id: playlistID}).value();
|
||||
exports.getUserPlaylist = async function(user_uid, playlistID, requireSharing = false) {
|
||||
let playlist = await db_api.getRecord('playlists', {id: playlistID});
|
||||
|
||||
// prevent unauthorized users from accessing the file info
|
||||
if (requireSharing && !playlist['sharingEnabled']) playlist = null;
|
||||
@@ -356,109 +314,23 @@ exports.getUserPlaylist = function(user_uid, playlistID, requireSharing = false)
|
||||
return playlist;
|
||||
}
|
||||
|
||||
exports.registerUserFile = function(user_uid, file_object) {
|
||||
users_db.get('users').find({uid: user_uid}).get(`files`)
|
||||
.remove({
|
||||
path: file_object['path']
|
||||
}).write();
|
||||
|
||||
users_db.get('users').find({uid: user_uid}).get(`files`)
|
||||
.push(file_object)
|
||||
.write();
|
||||
}
|
||||
|
||||
exports.deleteUserFile = async function(user_uid, file_uid, blacklistMode = false) {
|
||||
exports.changeSharingMode = async function(user_uid, file_uid, is_playlist, enabled) {
|
||||
let success = false;
|
||||
const file_obj = users_db.get('users').find({uid: user_uid}).get(`files`).find({uid: file_uid}).value();
|
||||
if (file_obj) {
|
||||
const type = file_obj.isAudio ? 'audio' : 'video';
|
||||
const usersFileFolder = config_api.getConfigItem('ytdl_users_base_path');
|
||||
const ext = type === 'audio' ? '.mp3' : '.mp4';
|
||||
|
||||
// close descriptors
|
||||
if (config_api.descriptors[file_obj.id]) {
|
||||
try {
|
||||
for (let i = 0; i < config_api.descriptors[file_obj.id].length; i++) {
|
||||
config_api.descriptors[file_obj.id][i].destroy();
|
||||
}
|
||||
} catch(e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const full_path = path.join(usersFileFolder, user_uid, type, file_obj.id + ext);
|
||||
users_db.get('users').find({uid: user_uid}).get(`files`)
|
||||
.remove({
|
||||
uid: file_uid
|
||||
}).write();
|
||||
if (await fs.pathExists(full_path)) {
|
||||
// remove json and file
|
||||
const json_path = path.join(usersFileFolder, user_uid, type, file_obj.id + '.info.json');
|
||||
const alternate_json_path = path.join(usersFileFolder, user_uid, type, file_obj.id + ext + '.info.json');
|
||||
let youtube_id = null;
|
||||
if (await fs.pathExists(json_path)) {
|
||||
youtube_id = await fs.readJSON(json_path).id;
|
||||
await fs.unlink(json_path);
|
||||
} else if (await fs.pathExists(alternate_json_path)) {
|
||||
youtube_id = await fs.readJSON(alternate_json_path).id;
|
||||
await fs.unlink(alternate_json_path);
|
||||
}
|
||||
|
||||
await fs.unlink(full_path);
|
||||
|
||||
// do archive stuff
|
||||
|
||||
let useYoutubeDLArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive');
|
||||
if (useYoutubeDLArchive) {
|
||||
const archive_path = path.join(usersFileFolder, user_uid, 'archives', `archive_${type}.txt`);
|
||||
|
||||
// use subscriptions API to remove video from the archive file, and write it to the blacklist
|
||||
if (await fs.pathExists(archive_path)) {
|
||||
const line = youtube_id ? await subscriptions_api.removeIDFromArchive(archive_path, youtube_id) : null;
|
||||
if (blacklistMode && line) {
|
||||
let blacklistPath = path.join(usersFileFolder, user_uid, 'archives', `blacklist_${type}.txt`);
|
||||
// adds newline to the beginning of the line
|
||||
line = '\n' + line;
|
||||
await fs.appendFile(blacklistPath, line);
|
||||
}
|
||||
} else {
|
||||
logger.info(`Could not find archive file for ${type} files. Creating...`);
|
||||
await fs.ensureFile(archive_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
success = true;
|
||||
} else {
|
||||
success = false;
|
||||
logger.warn(`User file ${file_uid} does not exist!`);
|
||||
}
|
||||
|
||||
is_playlist ? await db_api.updateRecord(`playlists`, {id: file_uid}, {sharingEnabled: enabled}) : await db_api.updateRecord(`files`, {uid: file_uid}, {sharingEnabled: enabled});
|
||||
success = true;
|
||||
return success;
|
||||
}
|
||||
|
||||
exports.changeSharingMode = function(user_uid, file_uid, is_playlist, enabled) {
|
||||
let success = false;
|
||||
const user_db_obj = users_db.get('users').find({uid: user_uid});
|
||||
if (user_db_obj.value()) {
|
||||
const file_db_obj = is_playlist ? user_db_obj.get(`playlists`).find({id: file_uid}) : user_db_obj.get(`files`).find({uid: file_uid});
|
||||
if (file_db_obj.value()) {
|
||||
success = true;
|
||||
file_db_obj.assign({sharingEnabled: enabled}).write();
|
||||
}
|
||||
}
|
||||
exports.userHasPermission = async function(user_uid, permission) {
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
exports.userHasPermission = function(user_uid, permission) {
|
||||
const user_obj = users_db.get('users').find({uid: user_uid}).value();
|
||||
const user_obj = await db_api.getRecord('users', ({uid: user_uid}));
|
||||
const role = user_obj['role'];
|
||||
if (!role) {
|
||||
// role doesn't exist
|
||||
logger.error('Invalid role ' + role);
|
||||
return false;
|
||||
}
|
||||
const role_permissions = (users_db.get('roles').value())['permissions'];
|
||||
const role_permissions = (await db_api.getRecords('roles'))['permissions'];
|
||||
|
||||
const user_has_explicit_permission = user_obj['permissions'].includes(permission);
|
||||
const permission_in_overrides = user_obj['permission_overrides'].includes(permission);
|
||||
@@ -481,16 +353,17 @@ exports.userHasPermission = function(user_uid, permission) {
|
||||
}
|
||||
}
|
||||
|
||||
exports.userPermissions = function(user_uid) {
|
||||
exports.userPermissions = async function(user_uid) {
|
||||
let user_permissions = [];
|
||||
const user_obj = users_db.get('users').find({uid: user_uid}).value();
|
||||
const user_obj = await db_api.getRecord('users', ({uid: user_uid}));
|
||||
const role = user_obj['role'];
|
||||
if (!role) {
|
||||
// role doesn't exist
|
||||
logger.error('Invalid role ' + role);
|
||||
return null;
|
||||
}
|
||||
const role_permissions = users_db.get('roles').get(role).get('permissions').value()
|
||||
const role_obj = await db_api.getRecord('roles', {key: role});
|
||||
const role_permissions = role_obj['permissions'];
|
||||
|
||||
for (let i = 0; i < consts['AVAILABLE_PERMISSIONS'].length; i++) {
|
||||
let permission = consts['AVAILABLE_PERMISSIONS'][i];
|
||||
@@ -536,14 +409,8 @@ function generateUserObject(userid, username, hash, auth_method = 'internal') {
|
||||
name: username,
|
||||
uid: userid,
|
||||
passhash: auth_method === 'internal' ? hash : null,
|
||||
files: {
|
||||
audio: [],
|
||||
video: []
|
||||
},
|
||||
playlists: {
|
||||
audio: [],
|
||||
video: []
|
||||
},
|
||||
files: [],
|
||||
playlists: [],
|
||||
subscriptions: [],
|
||||
created: Date.now(),
|
||||
role: userid === 'admin' && auth_method === 'internal' ? 'admin' : 'user',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const config_api = require('./config');
|
||||
const utils = require('./utils');
|
||||
|
||||
var logger = null;
|
||||
var db = null;
|
||||
@@ -33,35 +34,58 @@ Rules:
|
||||
|
||||
*/
|
||||
|
||||
async function categorize(file_json) {
|
||||
async function categorize(file_jsons) {
|
||||
// to make the logic easier, let's assume the file metadata is an array
|
||||
if (!Array.isArray(file_jsons)) file_jsons = [file_jsons];
|
||||
|
||||
let selected_category = null;
|
||||
const categories = getCategories();
|
||||
const categories = await getCategories();
|
||||
if (!categories) {
|
||||
logger.warn('Categories could not be found. Initializing categories...');
|
||||
db.assign({categories: []}).write();
|
||||
logger.warn('Categories could not be found.');
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < categories.length; i++) {
|
||||
const category = categories[i];
|
||||
const rules = category['rules'];
|
||||
|
||||
// if rules for current category apply, then that is the selected category
|
||||
if (applyCategoryRules(file_json, rules, category['name'])) {
|
||||
selected_category = category;
|
||||
logger.verbose(`Selected category ${category['name']} for ${file_json['webpage_url']}`);
|
||||
return selected_category;
|
||||
for (let i = 0; i < file_jsons.length; i++) {
|
||||
const file_json = file_jsons[i];
|
||||
for (let j = 0; j < categories.length; j++) {
|
||||
const category = categories[j];
|
||||
const rules = category['rules'];
|
||||
|
||||
// if rules for current category apply, then that is the selected category
|
||||
if (applyCategoryRules(file_json, rules, category['name'])) {
|
||||
selected_category = category;
|
||||
logger.verbose(`Selected category ${category['name']} for ${file_json['webpage_url']}`);
|
||||
return selected_category;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return selected_category;
|
||||
}
|
||||
|
||||
function getCategories() {
|
||||
const categories = db.get('categories').value();
|
||||
async function getCategories() {
|
||||
const categories = await db_api.getRecords('categories');
|
||||
return categories ? categories : null;
|
||||
}
|
||||
|
||||
async function getCategoriesAsPlaylists(files = null) {
|
||||
const categories_as_playlists = [];
|
||||
const available_categories = await getCategories();
|
||||
if (available_categories && files) {
|
||||
for (category of available_categories) {
|
||||
const files_that_match = utils.addUIDsToCategory(category, files);
|
||||
if (files_that_match && files_that_match.length > 0) {
|
||||
category['thumbnailURL'] = files_that_match[0].thumbnailURL;
|
||||
category['thumbnailPath'] = files_that_match[0].thumbnailPath;
|
||||
category['duration'] = files_that_match.reduce((a, b) => a + utils.durationStringToNumber(b.duration), 0);
|
||||
category['id'] = category['uid'];
|
||||
categories_as_playlists.push(category);
|
||||
}
|
||||
}
|
||||
}
|
||||
return categories_as_playlists;
|
||||
}
|
||||
|
||||
function applyCategoryRules(file_json, rules, category_name) {
|
||||
let rules_apply = false;
|
||||
for (let i = 0; i < rules.length; i++) {
|
||||
@@ -72,10 +96,10 @@ function applyCategoryRules(file_json, rules, category_name) {
|
||||
|
||||
switch (rule['comparator']) {
|
||||
case 'includes':
|
||||
rule_applies = file_json[rule['property']].includes(rule['value']);
|
||||
rule_applies = file_json[rule['property']].toLowerCase().includes(rule['value'].toLowerCase());
|
||||
break;
|
||||
case 'not_includes':
|
||||
rule_applies = !(file_json[rule['property']].includes(rule['value']));
|
||||
rule_applies = !(file_json[rule['property']].toLowerCase().includes(rule['value'].toLowerCase()));
|
||||
break;
|
||||
case 'equals':
|
||||
rule_applies = file_json[rule['property']] === rule['value'];
|
||||
@@ -120,4 +144,6 @@ async function addTagToExistingTags(tag) {
|
||||
module.exports = {
|
||||
initialize: initialize,
|
||||
categorize: categorize,
|
||||
getCategories: getCategories,
|
||||
getCategoriesAsPlaylists: getCategoriesAsPlaylists
|
||||
}
|
||||
@@ -197,7 +197,8 @@ DEFAULT_CONFIG = {
|
||||
"allow_quality_select": true,
|
||||
"download_only_mode": false,
|
||||
"allow_multi_download_mode": true,
|
||||
"enable_downloads_manager": true
|
||||
"enable_downloads_manager": true,
|
||||
"allow_playlist_categorization": true
|
||||
},
|
||||
"API": {
|
||||
"use_API_key": false,
|
||||
@@ -230,6 +231,10 @@ DEFAULT_CONFIG = {
|
||||
"searchFilter": "(uid={{username}})"
|
||||
}
|
||||
},
|
||||
"Database": {
|
||||
"use_local_db": false,
|
||||
"mongodb_connection_string": "mongodb://127.0.0.1:27017/?compressors=zlib"
|
||||
},
|
||||
"Advanced": {
|
||||
"default_downloader": "youtube-dl",
|
||||
"use_default_downloading_agent": true,
|
||||
|
||||
@@ -68,6 +68,10 @@ let CONFIG_ITEMS = {
|
||||
'key': 'ytdl_enable_downloads_manager',
|
||||
'path': 'YoutubeDLMaterial.Extra.enable_downloads_manager'
|
||||
},
|
||||
'ytdl_allow_playlist_categorization': {
|
||||
'key': 'ytdl_allow_playlist_categorization',
|
||||
'path': 'YoutubeDLMaterial.Extra.allow_playlist_categorization'
|
||||
},
|
||||
|
||||
// API
|
||||
'ytdl_use_api_key': {
|
||||
@@ -149,6 +153,16 @@ let CONFIG_ITEMS = {
|
||||
'path': 'YoutubeDLMaterial.Users.ldap_config'
|
||||
},
|
||||
|
||||
// Database
|
||||
'ytdl_use_local_db': {
|
||||
'key': 'ytdl_use_local_db',
|
||||
'path': 'YoutubeDLMaterial.Database.use_local_db'
|
||||
},
|
||||
'ytdl_mongodb_connection_string': {
|
||||
'key': 'ytdl_mongodb_connection_string',
|
||||
'path': 'YoutubeDLMaterial.Database.mongodb_connection_string'
|
||||
},
|
||||
|
||||
// Advanced
|
||||
'ytdl_default_downloader': {
|
||||
'key': 'ytdl_default_downloader',
|
||||
|
||||
945
backend/db.js
945
backend/db.js
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
CMD="node app.js"
|
||||
CMD="forever app.js"
|
||||
|
||||
# if the first arg starts with "-" pass it to program
|
||||
if [ "${1#-}" != "$1" ]; then
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const { app, BrowserWindow } = require('electron');
|
||||
const path = require('path');
|
||||
const url = require('url');
|
||||
const server = require('./app');
|
||||
|
||||
let win;
|
||||
|
||||
@@ -8,13 +9,7 @@ function createWindow() {
|
||||
win = new BrowserWindow({ width: 800, height: 600 });
|
||||
|
||||
// load the dist folder from Angular
|
||||
win.loadURL(
|
||||
url.format({
|
||||
pathname: path.join(__dirname, `/dist/index.html`),
|
||||
protocol: 'file:',
|
||||
slashes: true
|
||||
})
|
||||
);
|
||||
win.loadURL('http://localhost:17442') //ADD THIS
|
||||
|
||||
// The following is optional and will open the DevTools:
|
||||
// win.webContents.openDevTools()
|
||||
2838
backend/package-lock.json
generated
2838
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -2,10 +2,14 @@
|
||||
"name": "backend",
|
||||
"version": "1.0.0",
|
||||
"description": "backend for YoutubeDL-Material",
|
||||
"main": "index.js",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "nodemon -q app.js"
|
||||
"start": "nodemon app.js",
|
||||
"debug": "set YTDL_MODE=debug && node app.js",
|
||||
"electron": "electron main.js",
|
||||
"pack": "electron-builder --dir",
|
||||
"dist": "electron-builder"
|
||||
},
|
||||
"nodemonConfig": {
|
||||
"ignore": [
|
||||
@@ -14,9 +18,17 @@
|
||||
"public/*"
|
||||
],
|
||||
"watch": [
|
||||
"restart.json"
|
||||
"restart_update.json",
|
||||
"restart_general.json"
|
||||
]
|
||||
},
|
||||
"build": {
|
||||
"appId": "youtubedl.material",
|
||||
"mac": {
|
||||
"category": "public.app-category.utilities"
|
||||
},
|
||||
"files": ["!audio/*", "!video/*", "!users/*", "!subscriptions/*", "!appdata/*"]
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": ""
|
||||
@@ -30,7 +42,7 @@
|
||||
"dependencies": {
|
||||
"archiver": "^3.1.1",
|
||||
"async": "^3.1.0",
|
||||
"axios": "^0.21.0",
|
||||
"axios": "^0.21.1",
|
||||
"bcryptjs": "^2.4.0",
|
||||
"compression": "^1.7.4",
|
||||
"config": "^3.2.3",
|
||||
@@ -43,11 +55,13 @@
|
||||
"lowdb": "^1.0.0",
|
||||
"md5": "^2.2.1",
|
||||
"merge-files": "^0.1.2",
|
||||
"mocha": "^8.4.0",
|
||||
"moment": "^2.29.1",
|
||||
"mongodb": "^3.6.9",
|
||||
"multer": "^1.4.2",
|
||||
"node-fetch": "^2.6.1",
|
||||
"node-id3": "^0.1.14",
|
||||
"nodemon": "^2.0.2",
|
||||
"nodemon": "^2.0.7",
|
||||
"passport": "^0.4.1",
|
||||
"passport-http": "^0.3.0",
|
||||
"passport-jwt": "^4.0.0",
|
||||
@@ -61,5 +75,9 @@
|
||||
"uuidv4": "^6.0.6",
|
||||
"winston": "^3.2.1",
|
||||
"youtube-dl": "^3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "^13.1.7",
|
||||
"electron-builder": "^22.11.7"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,13 +14,13 @@ const debugMode = process.env.YTDL_MODE === 'debug';
|
||||
var logger = null;
|
||||
var db = null;
|
||||
var users_db = null;
|
||||
var db_api = null;
|
||||
let db_api = null;
|
||||
|
||||
function setDB(input_db, input_users_db, input_db_api) { db = input_db; users_db = input_users_db; db_api = input_db_api }
|
||||
function setDB(input_db_api) { db_api = input_db_api }
|
||||
function setLogger(input_logger) { logger = input_logger; }
|
||||
|
||||
function initialize(input_db, input_users_db, input_logger, input_db_api) {
|
||||
setDB(input_db, input_users_db, input_db_api);
|
||||
function initialize(input_db_api, input_logger) {
|
||||
setDB(input_db_api);
|
||||
setLogger(input_logger);
|
||||
}
|
||||
|
||||
@@ -34,12 +34,7 @@ async function subscribe(sub, user_uid = null) {
|
||||
sub.isPlaylist = sub.url.includes('playlist');
|
||||
sub.videos = [];
|
||||
|
||||
let url_exists = false;
|
||||
|
||||
if (user_uid)
|
||||
url_exists = !!users_db.get('users').find({uid: user_uid}).get('subscriptions').find({url: sub.url}).value()
|
||||
else
|
||||
url_exists = !!db.get('subscriptions').find({url: sub.url}).value();
|
||||
let url_exists = !!(await db_api.getRecord('subscriptions', {url: sub.url, user_uid: user_uid}));
|
||||
|
||||
if (!sub.name && url_exists) {
|
||||
logger.error(`Sub with the same URL "${sub.url}" already exists -- please provide a custom name for this new subscription.`);
|
||||
@@ -48,19 +43,12 @@ async function subscribe(sub, user_uid = null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// add sub to db
|
||||
let sub_db = null;
|
||||
if (user_uid) {
|
||||
users_db.get('users').find({uid: user_uid}).get('subscriptions').push(sub).write();
|
||||
sub_db = users_db.get('users').find({uid: user_uid}).get('subscriptions').find({id: sub.id});
|
||||
} else {
|
||||
db.get('subscriptions').push(sub).write();
|
||||
sub_db = db.get('subscriptions').find({id: sub.id});
|
||||
}
|
||||
sub['user_uid'] = user_uid ? user_uid : undefined;
|
||||
await db_api.insertRecordIntoTable('subscriptions', sub);
|
||||
|
||||
let success = await getSubscriptionInfo(sub, user_uid);
|
||||
|
||||
if (success) {
|
||||
sub = sub_db.value();
|
||||
getVideosForSub(sub, user_uid);
|
||||
} else {
|
||||
logger.error('Subscribe: Failed to get subscription info. Subscribe failed.')
|
||||
@@ -84,15 +72,15 @@ async function getSubscriptionInfo(sub, user_uid = null) {
|
||||
let downloadConfig = ['--dump-json', '--playlist-end', '1'];
|
||||
let useCookies = config_api.getConfigItem('ytdl_use_cookies');
|
||||
if (useCookies) {
|
||||
if (await fs.pathExists(path.join(__dirname, 'appdata', 'cookies.txt'))) {
|
||||
if (await fs.pathExists(path.join('appdata', 'cookies.txt'))) {
|
||||
downloadConfig.push('--cookies', path.join('appdata', 'cookies.txt'));
|
||||
} else {
|
||||
logger.warn('Cookies file could not be found. You can either upload one, or disable \'use cookies\' in the Advanced tab in the settings.');
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
youtubedl.exec(sub.url, downloadConfig, {}, function(err, output) {
|
||||
return new Promise(async resolve => {
|
||||
youtubedl.exec(sub.url, downloadConfig, {maxBuffer: Infinity}, async (err, output) => {
|
||||
if (debugMode) {
|
||||
logger.info('Subscribe: got info for subscription ' + sub.id);
|
||||
}
|
||||
@@ -122,17 +110,14 @@ async function getSubscriptionInfo(sub, user_uid = null) {
|
||||
}
|
||||
// if it's now valid, update
|
||||
if (sub.name) {
|
||||
if (user_uid)
|
||||
users_db.get('users').find({uid: user_uid}).get('subscriptions').find({id: sub.id}).assign({name: sub.name}).write();
|
||||
else
|
||||
db.get('subscriptions').find({id: sub.id}).assign({name: sub.name}).write();
|
||||
await db_api.updateRecord('subscriptions', {id: sub.id}, {name: sub.name});
|
||||
}
|
||||
}
|
||||
|
||||
const useArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive');
|
||||
if (useArchive && !sub.archive) {
|
||||
// must create the archive
|
||||
const archive_dir = path.join(__dirname, basePath, 'archives', sub.name);
|
||||
const archive_dir = path.join(basePath, 'archives', sub.name);
|
||||
const archive_path = path.join(archive_dir, 'archive.txt');
|
||||
|
||||
// creates archive directory and text file if it doesn't exist
|
||||
@@ -141,10 +126,8 @@ async function getSubscriptionInfo(sub, user_uid = null) {
|
||||
|
||||
// updates subscription
|
||||
sub.archive = archive_dir;
|
||||
if (user_uid)
|
||||
users_db.get('users').find({uid: user_uid}).get('subscriptions').find({id: sub.id}).assign({archive: archive_dir}).write();
|
||||
else
|
||||
db.get('subscriptions').find({id: sub.id}).assign({archive: archive_dir}).write();
|
||||
|
||||
await db_api.updateRecord('subscriptions', {id: sub.id}, {archive: archive_dir});
|
||||
}
|
||||
|
||||
// TODO: get even more info
|
||||
@@ -166,10 +149,8 @@ async function unsubscribe(sub, deleteMode, user_uid = null) {
|
||||
let result_obj = { success: false, error: '' };
|
||||
|
||||
let id = sub.id;
|
||||
if (user_uid)
|
||||
users_db.get('users').find({uid: user_uid}).get('subscriptions').remove({id: id}).write();
|
||||
else
|
||||
db.get('subscriptions').remove({id: id}).write();
|
||||
await db_api.removeRecord('subscriptions', {id: id});
|
||||
await db_api.removeAllRecords('files', {sub_id: id});
|
||||
|
||||
// failed subs have no name, on unsubscribe they shouldn't error
|
||||
if (!sub.name) {
|
||||
@@ -191,27 +172,23 @@ async function unsubscribe(sub, deleteMode, user_uid = null) {
|
||||
}
|
||||
|
||||
async function deleteSubscriptionFile(sub, file, deleteForever, file_uid = null, user_uid = null) {
|
||||
// TODO: combine this with deletefile
|
||||
let basePath = null;
|
||||
let sub_db = null;
|
||||
if (user_uid) {
|
||||
basePath = path.join(config_api.getConfigItem('ytdl_users_base_path'), user_uid, 'subscriptions');
|
||||
sub_db = users_db.get('users').find({uid: user_uid}).get('subscriptions').find({id: sub.id});
|
||||
} else {
|
||||
basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
|
||||
sub_db = db.get('subscriptions').find({id: sub.id});
|
||||
}
|
||||
basePath = user_uid ? path.join(config_api.getConfigItem('ytdl_users_base_path'), user_uid, 'subscriptions')
|
||||
: config_api.getConfigItem('ytdl_subscriptions_base_path');
|
||||
const useArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive');
|
||||
const appendedBasePath = getAppendedBasePath(sub, basePath);
|
||||
const name = file;
|
||||
let retrievedID = null;
|
||||
sub_db.get('videos').remove({uid: file_uid}).write();
|
||||
|
||||
await db_api.removeRecord('files', {uid: file_uid});
|
||||
|
||||
let filePath = appendedBasePath;
|
||||
const ext = (sub.type && sub.type === 'audio') ? '.mp3' : '.mp4'
|
||||
var jsonPath = path.join(__dirname,filePath,name+'.info.json');
|
||||
var videoFilePath = path.join(__dirname,filePath,name+ext);
|
||||
var imageFilePath = path.join(__dirname,filePath,name+'.jpg');
|
||||
var altImageFilePath = path.join(__dirname,filePath,name+'.jpg');
|
||||
var jsonPath = path.join(filePath,name+'.info.json');
|
||||
var videoFilePath = path.join(filePath,name+ext);
|
||||
var imageFilePath = path.join(filePath,name+'.jpg');
|
||||
var altImageFilePath = path.join(filePath,name+'.webp');
|
||||
|
||||
const [jsonExists, videoFileExists, imageFileExists, altImageFileExists] = await Promise.all([
|
||||
fs.pathExists(jsonPath),
|
||||
@@ -243,7 +220,7 @@ async function deleteSubscriptionFile(sub, file, deleteForever, file_uid = null,
|
||||
const archive_path = path.join(sub.archive, 'archive.txt')
|
||||
// if archive exists, remove line with video ID
|
||||
if (await fs.pathExists(archive_path)) {
|
||||
await removeIDFromArchive(archive_path, retrievedID);
|
||||
utils.removeIDFromArchive(archive_path, retrievedID);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -255,14 +232,7 @@ async function deleteSubscriptionFile(sub, file, deleteForever, file_uid = null,
|
||||
}
|
||||
|
||||
async function getVideosForSub(sub, user_uid = null) {
|
||||
// get sub_db
|
||||
let sub_db = null;
|
||||
if (user_uid)
|
||||
sub_db = users_db.get('users').find({uid: user_uid}).get('subscriptions').find({id: sub.id});
|
||||
else
|
||||
sub_db = db.get('subscriptions').find({id: sub.id});
|
||||
|
||||
const latest_sub_obj = sub_db.value();
|
||||
const latest_sub_obj = await getSubscription(sub.id);
|
||||
if (!latest_sub_obj || latest_sub_obj['downloading']) {
|
||||
return false;
|
||||
}
|
||||
@@ -277,6 +247,7 @@ async function getVideosForSub(sub, user_uid = null) {
|
||||
basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
|
||||
|
||||
let appendedBasePath = getAppendedBasePath(sub, basePath);
|
||||
fs.ensureDirSync(appendedBasePath);
|
||||
|
||||
let multiUserMode = null;
|
||||
if (user_uid) {
|
||||
@@ -291,9 +262,18 @@ async function getVideosForSub(sub, user_uid = null) {
|
||||
// get videos
|
||||
logger.verbose('Subscription: getting videos for subscription ' + sub.name);
|
||||
|
||||
return new Promise(resolve => {
|
||||
youtubedl.exec(sub.url, downloadConfig, {}, async function(err, output) {
|
||||
return new Promise(async resolve => {
|
||||
const preimported_file_paths = [];
|
||||
const PREIMPORT_INTERVAL = 5000;
|
||||
const preregister_check = setInterval(async () => {
|
||||
if (sub.streamingOnly) return;
|
||||
await db_api.preimportUnregisteredSubscriptionFile(sub, appendedBasePath);
|
||||
}, PREIMPORT_INTERVAL);
|
||||
youtubedl.exec(sub.url, downloadConfig, {maxBuffer: Infinity}, async function(err, output) {
|
||||
// cleanup
|
||||
updateSubscriptionProperty(sub, {downloading: false}, user_uid);
|
||||
clearInterval(preregister_check);
|
||||
|
||||
logger.verbose('Subscription: finished check for ' + sub.name);
|
||||
if (err && !output) {
|
||||
logger.error(err.stderr ? err.stderr : err.message);
|
||||
@@ -303,7 +283,7 @@ async function getVideosForSub(sub, user_uid = null) {
|
||||
const outputs = err.stdout.split(/\r\n|\r|\n/);
|
||||
for (let i = 0; i < outputs.length; i++) {
|
||||
const output = JSON.parse(outputs[i]);
|
||||
handleOutputJSON(sub, sub_db, output, i === 0, multiUserMode)
|
||||
await handleOutputJSON(sub, output, i === 0, multiUserMode)
|
||||
if (err.stderr.includes(output['id']) && archive_path) {
|
||||
// we found a video that errored! add it to the archive to prevent future errors
|
||||
if (sub.archive) {
|
||||
@@ -337,7 +317,7 @@ async function getVideosForSub(sub, user_uid = null) {
|
||||
}
|
||||
|
||||
const reset_videos = i === 0;
|
||||
handleOutputJSON(sub, sub_db, output_json, multiUserMode, reset_videos);
|
||||
await handleOutputJSON(sub, output_json, multiUserMode, preimported_file_paths, reset_videos);
|
||||
}
|
||||
|
||||
if (config_api.getConfigItem('ytdl_subscriptions_redownload_fresh_uploads')) {
|
||||
@@ -351,6 +331,7 @@ async function getVideosForSub(sub, user_uid = null) {
|
||||
}, err => {
|
||||
logger.error(err);
|
||||
updateSubscriptionProperty(sub, {downloading: false}, user_uid);
|
||||
clearInterval(preregister_check);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -366,7 +347,9 @@ async function generateArgsForSubscription(sub, user_uid, redownload = false, de
|
||||
|
||||
let appendedBasePath = getAppendedBasePath(sub, basePath);
|
||||
|
||||
let fullOutput = `${appendedBasePath}/%(title)s.%(ext)s`;
|
||||
const file_output = config_api.getConfigItem('ytdl_default_file_output') ? config_api.getConfigItem('ytdl_default_file_output') : '%(title)s';
|
||||
|
||||
let fullOutput = `${appendedBasePath}/${file_output}.%(ext)s`;
|
||||
if (desired_path) {
|
||||
fullOutput = `${desired_path}.%(ext)s`;
|
||||
} else if (sub.custom_output) {
|
||||
@@ -419,7 +402,7 @@ async function generateArgsForSubscription(sub, user_uid, redownload = false, de
|
||||
|
||||
let useCookies = config_api.getConfigItem('ytdl_use_cookies');
|
||||
if (useCookies) {
|
||||
if (await fs.pathExists(path.join(__dirname, 'appdata', 'cookies.txt'))) {
|
||||
if (await fs.pathExists(path.join('appdata', 'cookies.txt'))) {
|
||||
downloadConfig.push('--cookies', path.join('appdata', 'cookies.txt'));
|
||||
} else {
|
||||
logger.warn('Cookies file could not be found. You can either upload one, or disable \'use cookies\' in the Advanced tab in the settings.');
|
||||
@@ -433,8 +416,9 @@ async function generateArgsForSubscription(sub, user_uid, redownload = false, de
|
||||
return downloadConfig;
|
||||
}
|
||||
|
||||
function handleOutputJSON(sub, sub_db, output_json, multiUserMode = null, reset_videos = false) {
|
||||
if (sub.streamingOnly) {
|
||||
async function handleOutputJSON(sub, output_json, multiUserMode = null, reset_videos = false) {
|
||||
// TODO: remove streaming only mode
|
||||
if (false && sub.streamingOnly) {
|
||||
if (reset_videos) {
|
||||
sub_db.assign({videos: []}).write();
|
||||
}
|
||||
@@ -448,12 +432,15 @@ function handleOutputJSON(sub, sub_db, output_json, multiUserMode = null, reset_
|
||||
path_object = path.parse(output_json['_filename']);
|
||||
const path_string = path.format(path_object);
|
||||
|
||||
if (sub_db.get('videos').find({path: path_string}).value()) {
|
||||
const file_exists = await db_api.getRecord('files', {path: path_string, sub_id: sub.id});
|
||||
if (file_exists) {
|
||||
// TODO: fix issue where files of different paths due to custom path get downloaded multiple times
|
||||
// file already exists in DB, return early to avoid reseting the download date
|
||||
return;
|
||||
}
|
||||
|
||||
db_api.registerFileDB(path.basename(output_json['_filename']), sub.type, multiUserMode, sub);
|
||||
await db_api.registerFileDB2(output_json['_filename'], sub.type, sub.user_uid, null, sub.id);
|
||||
|
||||
const url = output_json['webpage_url'];
|
||||
if (sub.type === 'video' && url.includes('twitch.tv/videos/') && url.split('twitch.tv/videos/').length > 1
|
||||
&& config_api.getConfigItem('ytdl_use_twitch_api') && config_api.getConfigItem('ytdl_twitch_auto_download_chat')) {
|
||||
@@ -466,73 +453,41 @@ function handleOutputJSON(sub, sub_db, output_json, multiUserMode = null, reset_
|
||||
}
|
||||
}
|
||||
|
||||
function getSubscriptions(user_uid = null) {
|
||||
if (user_uid)
|
||||
return users_db.get('users').find({uid: user_uid}).get('subscriptions').value();
|
||||
else
|
||||
return db.get('subscriptions').value();
|
||||
async function getSubscriptions(user_uid = null) {
|
||||
return await db_api.getRecords('subscriptions', {user_uid: user_uid});
|
||||
}
|
||||
|
||||
function getAllSubscriptions() {
|
||||
let subscriptions = null;
|
||||
async function getAllSubscriptions() {
|
||||
const all_subs = await db_api.getRecords('subscriptions');
|
||||
const multiUserMode = config_api.getConfigItem('ytdl_multi_user_mode');
|
||||
if (multiUserMode) {
|
||||
subscriptions = [];
|
||||
let users = users_db.get('users').value();
|
||||
for (let i = 0; i < users.length; i++) {
|
||||
if (users[i]['subscriptions']) subscriptions = subscriptions.concat(users[i]['subscriptions']);
|
||||
}
|
||||
} else {
|
||||
subscriptions = getSubscriptions();
|
||||
}
|
||||
return subscriptions;
|
||||
return all_subs.filter(sub => !!(sub.user_uid) === multiUserMode);
|
||||
}
|
||||
|
||||
function getSubscription(subID, user_uid = null) {
|
||||
if (user_uid)
|
||||
return users_db.get('users').find({uid: user_uid}).get('subscriptions').find({id: subID}).value();
|
||||
else
|
||||
return db.get('subscriptions').find({id: subID}).value();
|
||||
async function getSubscription(subID) {
|
||||
return await db_api.getRecord('subscriptions', {id: subID});
|
||||
}
|
||||
|
||||
function getSubscriptionByName(subName, user_uid = null) {
|
||||
if (user_uid)
|
||||
return users_db.get('users').find({uid: user_uid}).get('subscriptions').find({name: subName}).value();
|
||||
else
|
||||
return db.get('subscriptions').find({name: subName}).value();
|
||||
async function getSubscriptionByName(subName, user_uid = null) {
|
||||
return await db_api.getRecord('subscriptions', {name: subName, user_uid: user_uid});
|
||||
}
|
||||
|
||||
function updateSubscription(sub, user_uid = null) {
|
||||
if (user_uid) {
|
||||
users_db.get('users').find({uid: user_uid}).get('subscriptions').find({id: sub.id}).assign(sub).write();
|
||||
} else {
|
||||
db.get('subscriptions').find({id: sub.id}).assign(sub).write();
|
||||
}
|
||||
async function updateSubscription(sub, user_uid = null) {
|
||||
await db_api.updateRecord('subscriptions', {id: sub.id}, sub);
|
||||
return true;
|
||||
}
|
||||
|
||||
function updateSubscriptionPropertyMultiple(subs, assignment_obj) {
|
||||
subs.forEach(sub => {
|
||||
updateSubscriptionProperty(sub, assignment_obj, sub.user_uid);
|
||||
async function updateSubscriptionPropertyMultiple(subs, assignment_obj) {
|
||||
subs.forEach(async sub => {
|
||||
await updateSubscriptionProperty(sub, assignment_obj, sub.user_uid);
|
||||
});
|
||||
}
|
||||
|
||||
function updateSubscriptionProperty(sub, assignment_obj, user_uid = null) {
|
||||
if (user_uid) {
|
||||
users_db.get('users').find({uid: user_uid}).get('subscriptions').find({id: sub.id}).assign(assignment_obj).write();
|
||||
} else {
|
||||
db.get('subscriptions').find({id: sub.id}).assign(assignment_obj).write();
|
||||
}
|
||||
async function updateSubscriptionProperty(sub, assignment_obj, user_uid = null) {
|
||||
// TODO: combine with updateSubscription
|
||||
await db_api.updateRecord('subscriptions', {id: sub.id}, assignment_obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
function subExists(subID, user_uid = null) {
|
||||
if (user_uid)
|
||||
return !!users_db.get('users').find({uid: user_uid}).get('subscriptions').find({id: subID}).value();
|
||||
else
|
||||
return !!db.get('subscriptions').find({id: subID}).value();
|
||||
}
|
||||
|
||||
async function setFreshUploads(sub, user_uid) {
|
||||
const current_date = new Date().toISOString().split('T')[0].replace(/-/g, '');
|
||||
sub.videos.forEach(async video => {
|
||||
@@ -548,7 +503,7 @@ async function checkVideosForFreshUploads(sub, user_uid) {
|
||||
const current_date = new Date().toISOString().split('T')[0].replace(/-/g, '');
|
||||
sub.videos.forEach(async video => {
|
||||
if (video['fresh_upload'] && current_date > video['upload_date'].replace(/-/g, '')) {
|
||||
checkVideoIfBetterExists(video, sub, user_uid)
|
||||
await checkVideoIfBetterExists(video, sub, user_uid)
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -558,14 +513,14 @@ async function checkVideoIfBetterExists(file_obj, sub, user_uid) {
|
||||
const downloadConfig = await generateArgsForSubscription(sub, user_uid, true, new_path);
|
||||
logger.verbose(`Checking if a better version of the fresh upload ${file_obj['id']} exists.`);
|
||||
// simulate a download to verify that a better version exists
|
||||
youtubedl.getInfo(file_obj['url'], downloadConfig, (err, output) => {
|
||||
youtubedl.getInfo(file_obj['url'], downloadConfig, async (err, output) => {
|
||||
if (err) {
|
||||
// video is not available anymore for whatever reason
|
||||
} else if (output) {
|
||||
const metric_to_compare = sub.type === 'audio' ? 'abr' : 'height';
|
||||
if (output[metric_to_compare] > file_obj[metric_to_compare]) {
|
||||
// download new video as the simulated one is better
|
||||
youtubedl.exec(file_obj['url'], downloadConfig, async (err, output) => {
|
||||
youtubedl.exec(file_obj['url'], downloadConfig, {maxBuffer: Infinity}, async (err, output) => {
|
||||
if (err) {
|
||||
logger.verbose(`Failed to download better version of video ${file_obj['id']}`);
|
||||
} else if (output) {
|
||||
@@ -586,33 +541,6 @@ function getAppendedBasePath(sub, base_path) {
|
||||
return path.join(base_path, (sub.isPlaylist ? 'playlists/' : 'channels/'), sub.name);
|
||||
}
|
||||
|
||||
async function removeIDFromArchive(archive_path, id) {
|
||||
let data = await fs.readFile(archive_path, {encoding: 'utf-8'});
|
||||
if (!data) {
|
||||
logger.error('Archive could not be found.');
|
||||
return;
|
||||
}
|
||||
|
||||
let dataArray = data.split('\n'); // convert file data in an array
|
||||
const searchKeyword = id; // we are looking for a line, contains, key word id in the file
|
||||
let lastIndex = -1; // let say, we have not found the keyword
|
||||
|
||||
for (let index=0; index<dataArray.length; index++) {
|
||||
if (dataArray[index].includes(searchKeyword)) { // check if a line contains the id keyword
|
||||
lastIndex = index; // found a line includes a id keyword
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const line = dataArray.splice(lastIndex, 1); // remove the keyword id from the data Array
|
||||
|
||||
// UPDATE FILE WITH NEW DATA
|
||||
const updatedData = dataArray.join('\n');
|
||||
await fs.writeFile(archive_path, updatedData);
|
||||
if (line) return line;
|
||||
if (err) throw err;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getSubscription : getSubscription,
|
||||
getSubscriptionByName : getSubscriptionByName,
|
||||
@@ -623,7 +551,6 @@ module.exports = {
|
||||
unsubscribe : unsubscribe,
|
||||
deleteSubscriptionFile : deleteSubscriptionFile,
|
||||
getVideosForSub : getVideosForSub,
|
||||
removeIDFromArchive : removeIDFromArchive,
|
||||
setLogger : setLogger,
|
||||
initialize : initialize,
|
||||
updateSubscriptionPropertyMultiple : updateSubscriptionPropertyMultiple
|
||||
|
||||
290
backend/test/tests.js
Normal file
290
backend/test/tests.js
Normal file
@@ -0,0 +1,290 @@
|
||||
var assert = require('assert');
|
||||
const low = require('lowdb')
|
||||
var winston = require('winston');
|
||||
|
||||
process.chdir('./backend')
|
||||
|
||||
const FileSync = require('lowdb/adapters/FileSync');
|
||||
|
||||
const adapter = new FileSync('./appdata/db.json');
|
||||
const db = low(adapter)
|
||||
|
||||
const users_adapter = new FileSync('./appdata/users.json');
|
||||
const users_db = low(users_adapter);
|
||||
|
||||
const defaultFormat = winston.format.printf(({ level, message, label, timestamp }) => {
|
||||
return `${timestamp} ${level.toUpperCase()}: ${message}`;
|
||||
});
|
||||
|
||||
let debugMode = process.env.YTDL_MODE === 'debug';
|
||||
|
||||
const logger = winston.createLogger({
|
||||
level: 'info',
|
||||
format: winston.format.combine(winston.format.timestamp(), defaultFormat),
|
||||
defaultMeta: {},
|
||||
transports: [
|
||||
//
|
||||
// - Write to all logs with level `info` and below to `combined.log`
|
||||
// - Write all logs error (and below) to `error.log`.
|
||||
//
|
||||
new winston.transports.File({ filename: 'appdata/logs/error.log', level: 'error' }),
|
||||
new winston.transports.File({ filename: 'appdata/logs/combined.log' }),
|
||||
new winston.transports.Console({level: 'debug', name: 'console'})
|
||||
]
|
||||
});
|
||||
|
||||
var auth_api = require('../authentication/auth');
|
||||
var db_api = require('../db');
|
||||
const utils = require('../utils');
|
||||
const subscriptions_api = require('../subscriptions');
|
||||
const fs = require('fs-extra');
|
||||
const { uuid } = require('uuidv4');
|
||||
|
||||
db_api.initialize(db, users_db, logger);
|
||||
|
||||
|
||||
describe('Database', async function() {
|
||||
describe('Import', async function() {
|
||||
it('Migrate', async function() {
|
||||
await db_api.connectToDB();
|
||||
await db_api.removeAllRecords();
|
||||
const success = await db_api.importJSONToDB(db.value(), users_db.value());
|
||||
assert(success);
|
||||
});
|
||||
|
||||
it('Transfer to remote', async function() {
|
||||
await db_api.removeAllRecords('test');
|
||||
await db_api.insertRecordIntoTable('test', {test: 'test'});
|
||||
|
||||
await db_api.transferDB(true);
|
||||
const success = await db_api.getRecord('test', {test: 'test'});
|
||||
assert(success);
|
||||
});
|
||||
|
||||
it('Transfer to local', async function() {
|
||||
await db_api.connectToDB();
|
||||
await db_api.removeAllRecords('test');
|
||||
await db_api.insertRecordIntoTable('test', {test: 'test'});
|
||||
|
||||
await db_api.transferDB(false);
|
||||
const success = await db_api.getRecord('test', {test: 'test'});
|
||||
assert(success);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Export', function() {
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('Basic functions', async function() {
|
||||
beforeEach(async function() {
|
||||
await db_api.connectToDB();
|
||||
await db_api.removeAllRecords('test');
|
||||
});
|
||||
it('Add and read record', async function() {
|
||||
await db_api.insertRecordIntoTable('test', {test_add: 'test', test_undefined: undefined, test_null: undefined});
|
||||
const added_record = await db_api.getRecord('test', {test_add: 'test', test_undefined: undefined, test_null: null});
|
||||
assert(added_record['test_add'] === 'test');
|
||||
await db_api.removeRecord('test', {test_add: 'test'});
|
||||
});
|
||||
|
||||
it('Update record', async function() {
|
||||
await db_api.insertRecordIntoTable('test', {test_update: 'test'});
|
||||
await db_api.updateRecord('test', {test_update: 'test'}, {added_field: true});
|
||||
const updated_record = await db_api.getRecord('test', {test_update: 'test'});
|
||||
assert(updated_record['added_field']);
|
||||
await db_api.removeRecord('test', {test_update: 'test'});
|
||||
});
|
||||
|
||||
it('Remove record', async function() {
|
||||
await db_api.insertRecordIntoTable('test', {test_remove: 'test'});
|
||||
const delete_succeeded = await db_api.removeRecord('test', {test_remove: 'test'});
|
||||
assert(delete_succeeded);
|
||||
const deleted_record = await db_api.getRecord('test', {test_remove: 'test'});
|
||||
assert(!deleted_record);
|
||||
});
|
||||
|
||||
it('Push to record array', async function() {
|
||||
await db_api.insertRecordIntoTable('test', {test: 'test', test_array: []});
|
||||
await db_api.pushToRecordsArray('test', {test: 'test'}, 'test_array', 'test_item');
|
||||
const record = await db_api.getRecord('test', {test: 'test'});
|
||||
assert(record);
|
||||
assert(record['test_array'].length === 1);
|
||||
});
|
||||
|
||||
it('Pull from record array', async function() {
|
||||
await db_api.insertRecordIntoTable('test', {test: 'test', test_array: ['test_item']});
|
||||
await db_api.pullFromRecordsArray('test', {test: 'test'}, 'test_array', 'test_item');
|
||||
const record = await db_api.getRecord('test', {test: 'test'});
|
||||
assert(record);
|
||||
assert(record['test_array'].length === 0);
|
||||
});
|
||||
|
||||
it('Bulk add', async function() {
|
||||
const NUM_RECORDS_TO_ADD = 2002; // max batch ops is 1000
|
||||
const test_records = [];
|
||||
for (let i = 0; i < NUM_RECORDS_TO_ADD; i++) {
|
||||
test_records.push({
|
||||
uid: uuid()
|
||||
});
|
||||
}
|
||||
const succcess = await db_api.bulkInsertRecordsIntoTable('test', test_records);
|
||||
|
||||
const received_records = await db_api.getRecords('test');
|
||||
assert(succcess && received_records && received_records.length === NUM_RECORDS_TO_ADD);
|
||||
});
|
||||
|
||||
it('Bulk update', async function() {
|
||||
// bulk add records
|
||||
const NUM_RECORDS_TO_ADD = 100; // max batch ops is 1000
|
||||
const test_records = [];
|
||||
const update_obj = {};
|
||||
for (let i = 0; i < NUM_RECORDS_TO_ADD; i++) {
|
||||
const test_uid = uuid();
|
||||
test_records.push({
|
||||
uid: test_uid
|
||||
});
|
||||
update_obj[test_uid] = {added_field: true};
|
||||
}
|
||||
let success = await db_api.bulkInsertRecordsIntoTable('test', test_records);
|
||||
assert(success);
|
||||
|
||||
// makes sure they are added
|
||||
const received_records = await db_api.getRecords('test');
|
||||
assert(received_records && received_records.length === NUM_RECORDS_TO_ADD);
|
||||
|
||||
success = await db_api.bulkUpdateRecords('test', 'uid', update_obj);
|
||||
assert(success);
|
||||
|
||||
const received_updated_records = await db_api.getRecords('test');
|
||||
for (let i = 0; i < received_updated_records.length; i++) {
|
||||
success &= received_updated_records[i]['added_field'];
|
||||
}
|
||||
assert(success);
|
||||
});
|
||||
|
||||
it('Stats', async function() {
|
||||
const stats = await db_api.getDBStats();
|
||||
assert(stats);
|
||||
});
|
||||
|
||||
it('Query speed', async function() {
|
||||
this.timeout(120000);
|
||||
const NUM_RECORDS_TO_ADD = 300004; // max batch ops is 1000
|
||||
const test_records = [];
|
||||
let random_uid = '06241f83-d1b8-4465-812c-618dfa7f2943';
|
||||
for (let i = 0; i < NUM_RECORDS_TO_ADD; i++) {
|
||||
const uid = uuid();
|
||||
if (i === NUM_RECORDS_TO_ADD/2) random_uid = uid;
|
||||
test_records.push({"id":"A$AP Mob - Yamborghini High (Official Music Video) ft. Juicy J","title":"A$AP Mob - Yamborghini High (Official Music Video) ft. Juicy J","thumbnailURL":"https://i.ytimg.com/vi/tt7gP_IW-1w/maxresdefault.jpg","isAudio":true,"duration":312,"url":"https://www.youtube.com/watch?v=tt7gP_IW-1w","uploader":"asapmobVEVO","size":5060157,"path":"audio\\A$AP Mob - Yamborghini High (Official Music Video) ft. Juicy J.mp3","upload_date":"2016-05-11","description":"A$AP Mob ft. Juicy J - \"Yamborghini High\" Get it now on:\niTunes: http://smarturl.it/iYAMH?IQid=yt\nListen on Spotify: http://smarturl.it/sYAMH?IQid=yt\nGoogle Play: http://smarturl.it/gYAMH?IQid=yt\nAmazon: http://smarturl.it/aYAMH?IQid=yt\n\nFollow A$AP Mob:\nhttps://www.facebook.com/asapmobofficial\nhttps://twitter.com/ASAPMOB\nhttp://instagram.com/asapmob \nhttp://www.asapmob.com/\n\n#AsapMob #YamborghiniHigh #Vevo #HipHop #OfficialMusicVideo #JuicyJ","view_count":118689353,"height":null,"abr":160,"uid": uid,"registered":1626672120632});
|
||||
}
|
||||
const insert_start = Date.now();
|
||||
let success = await db_api.bulkInsertRecordsIntoTable('test', test_records);
|
||||
const insert_end = Date.now();
|
||||
|
||||
console.log(`Insert time: ${(insert_end - insert_start)/1000}s`);
|
||||
|
||||
const query_start = Date.now();
|
||||
const random_record = await db_api.getRecord('test', {uid: random_uid});
|
||||
const query_end = Date.now();
|
||||
|
||||
console.log(random_record)
|
||||
|
||||
console.log(`Query time: ${(query_end - query_start)/1000}s`);
|
||||
|
||||
success = !!random_record;
|
||||
|
||||
assert(success);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Multi User', async function() {
|
||||
let user = null;
|
||||
const user_to_test = 'admin';
|
||||
const sub_to_test = 'dc834388-3454-41bf-a618-e11cb8c7de1c';
|
||||
const playlist_to_test = 'ysabVZz4x';
|
||||
beforeEach(async function() {
|
||||
await db_api.connectToDB();
|
||||
auth_api.initialize(db_api, logger);
|
||||
subscriptions_api.initialize(db_api, logger);
|
||||
user = await auth_api.login('admin', 'pass');
|
||||
});
|
||||
describe('Authentication', function() {
|
||||
it('login', async function() {
|
||||
assert(user);
|
||||
});
|
||||
});
|
||||
describe('Video player - normal', function() {
|
||||
const video_to_test = 'ebbcfffb-d6f1-4510-ad25-d1ec82e0477e';
|
||||
it('Get video', async function() {
|
||||
const video_obj = db_api.getVideo(video_to_test, 'admin');
|
||||
assert(video_obj);
|
||||
});
|
||||
|
||||
it('Video access - disallowed', async function() {
|
||||
await db_api.setVideoProperty(video_to_test, {sharingEnabled: false}, user_to_test);
|
||||
const video_obj = auth_api.getUserVideo('admin', video_to_test, true);
|
||||
assert(!video_obj);
|
||||
});
|
||||
|
||||
it('Video access - allowed', async function() {
|
||||
await db_api.setVideoProperty(video_to_test, {sharingEnabled: true}, user_to_test);
|
||||
const video_obj = auth_api.getUserVideo('admin', video_to_test, true);
|
||||
assert(video_obj);
|
||||
});
|
||||
});
|
||||
describe('Zip generators', function() {
|
||||
it('Playlist zip generator', async function() {
|
||||
const playlist = await db_api.getPlaylist(playlist_to_test, user_to_test);
|
||||
assert(playlist);
|
||||
const playlist_files_to_download = [];
|
||||
for (let i = 0; i < playlist['uids'].length; i++) {
|
||||
const uid = playlist['uids'][i];
|
||||
const playlist_file = await db_api.getVideo(uid, user_to_test);
|
||||
playlist_files_to_download.push(playlist_file);
|
||||
}
|
||||
const zip_path = await utils.createContainerZipFile(playlist, playlist_files_to_download);
|
||||
const zip_exists = fs.pathExistsSync(zip_path);
|
||||
assert(zip_exists);
|
||||
if (zip_exists) fs.unlinkSync(zip_path);
|
||||
});
|
||||
|
||||
it('Subscription zip generator', async function() {
|
||||
const sub = await subscriptions_api.getSubscription(sub_to_test, user_to_test);
|
||||
const sub_videos = await db_api.getRecords('files', {sub_id: sub.id});
|
||||
assert(sub);
|
||||
const sub_files_to_download = [];
|
||||
for (let i = 0; i < sub_videos.length; i++) {
|
||||
const sub_file = sub_videos[i];
|
||||
sub_files_to_download.push(sub_file);
|
||||
}
|
||||
const zip_path = await utils.createContainerZipFile(sub, sub_files_to_download);
|
||||
const zip_exists = fs.pathExistsSync(zip_path);
|
||||
assert(zip_exists);
|
||||
if (zip_exists) fs.unlinkSync(zip_path);
|
||||
});
|
||||
});
|
||||
// describe('Video player - subscription', function() {
|
||||
// const sub_to_test = '';
|
||||
// const video_to_test = 'ebbcfffb-d6f1-4510-ad25-d1ec82e0477e';
|
||||
// it('Get video', async function() {
|
||||
// const video_obj = db_api.getVideo(video_to_test, 'admin', );
|
||||
// assert(video_obj);
|
||||
// });
|
||||
|
||||
// it('Video access - disallowed', async function() {
|
||||
// await db_api.setVideoProperty(video_to_test, {sharingEnabled: false}, user_to_test, sub_to_test);
|
||||
// const video_obj = auth_api.getUserVideo('admin', video_to_test, true);
|
||||
// assert(!video_obj);
|
||||
// });
|
||||
|
||||
// it('Video access - allowed', async function() {
|
||||
// await db_api.setVideoProperty(video_to_test, {sharingEnabled: true}, user_to_test, sub_to_test);
|
||||
// const video_obj = auth_api.getUserVideo('admin', video_to_test, true);
|
||||
// assert(video_obj);
|
||||
// });
|
||||
// });
|
||||
|
||||
});
|
||||
209
backend/utils.js
209
backend/utils.js
@@ -1,6 +1,7 @@
|
||||
var fs = require('fs-extra')
|
||||
var path = require('path')
|
||||
const fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
const config_api = require('./config');
|
||||
const archiver = require('archiver');
|
||||
|
||||
const is_windows = process.platform === 'win32';
|
||||
|
||||
@@ -52,6 +53,43 @@ async function getDownloadedFilesByType(basePath, type, full_metadata = false) {
|
||||
return files;
|
||||
}
|
||||
|
||||
async function createContainerZipFile(container_obj, container_file_objs) {
|
||||
const container_files_to_download = [];
|
||||
for (let i = 0; i < container_file_objs.length; i++) {
|
||||
const container_file_obj = container_file_objs[i];
|
||||
container_files_to_download.push(container_file_obj.path);
|
||||
}
|
||||
return await createZipFile(path.join('appdata', container_obj.name + '.zip'), container_files_to_download);
|
||||
}
|
||||
|
||||
async function createZipFile(zip_file_path, file_paths) {
|
||||
let output = fs.createWriteStream(zip_file_path);
|
||||
|
||||
var archive = archiver('zip', {
|
||||
gzip: true,
|
||||
zlib: { level: 9 } // Sets the compression level.
|
||||
});
|
||||
|
||||
archive.on('error', function(err) {
|
||||
logger.error(err);
|
||||
throw err;
|
||||
});
|
||||
|
||||
// pipe archive data to the output file
|
||||
archive.pipe(output);
|
||||
|
||||
for (let file_path of file_paths) {
|
||||
const file_name = path.parse(file_path).base;
|
||||
archive.file(file_path, {name: file_name})
|
||||
}
|
||||
|
||||
await archive.finalize();
|
||||
|
||||
// wait a tiny bit for the zip to reload in fs
|
||||
await wait(100);
|
||||
return zip_file_path;
|
||||
}
|
||||
|
||||
function getJSONMp4(name, customPath, openReadPerms = false) {
|
||||
var obj = null; // output
|
||||
if (!customPath) customPath = config_api.getConfigItem('ytdl_video_folder_path');
|
||||
@@ -84,6 +122,21 @@ function getJSONMp3(name, customPath, openReadPerms = false) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
function getJSON(file_path, type) {
|
||||
const ext = type === 'audio' ? '.mp3' : '.mp4';
|
||||
let obj = null;
|
||||
var jsonPath = removeFileExtension(file_path) + '.info.json';
|
||||
var alternateJsonPath = removeFileExtension(file_path) + `${ext}.info.json`;
|
||||
if (fs.existsSync(jsonPath))
|
||||
{
|
||||
obj = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
|
||||
} else if (fs.existsSync(alternateJsonPath)) {
|
||||
obj = JSON.parse(fs.readFileSync(alternateJsonPath, 'utf8'));
|
||||
}
|
||||
else obj = 0;
|
||||
return obj;
|
||||
}
|
||||
|
||||
function getJSONByType(type, name, customPath, openReadPerms = false) {
|
||||
return type === 'audio' ? getJSONMp3(name, customPath, openReadPerms) : getJSONMp4(name, customPath, openReadPerms)
|
||||
}
|
||||
@@ -105,20 +158,43 @@ function getDownloadedThumbnail(name, type, customPath = null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function getExpectedFileSize(info_json) {
|
||||
if (info_json['filesize']) {
|
||||
return info_json['filesize'];
|
||||
}
|
||||
function getDownloadedThumbnail2(file_path, type) {
|
||||
const file_path_no_extension = removeFileExtension(file_path);
|
||||
|
||||
let jpgPath = file_path_no_extension + '.jpg';
|
||||
let webpPath = file_path_no_extension + '.webp';
|
||||
let pngPath = file_path_no_extension + '.png';
|
||||
|
||||
if (fs.existsSync(jpgPath))
|
||||
return jpgPath;
|
||||
else if (fs.existsSync(webpPath))
|
||||
return webpPath;
|
||||
else if (fs.existsSync(pngPath))
|
||||
return pngPath;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
function getExpectedFileSize(input_info_jsons) {
|
||||
// treat single videos as arrays to have the file sizes checked/added to. makes the code cleaner
|
||||
const info_jsons = Array.isArray(input_info_jsons) ? input_info_jsons : [input_info_jsons];
|
||||
|
||||
const formats = info_json['format_id'].split('+');
|
||||
let expected_filesize = 0;
|
||||
formats.forEach(format_id => {
|
||||
if (!info_json.formats) return expected_filesize;
|
||||
info_json.formats.forEach(available_format => {
|
||||
if (available_format.format_id === format_id && available_format.filesize) {
|
||||
expected_filesize += available_format.filesize;
|
||||
}
|
||||
info_jsons.forEach(info_json => {
|
||||
if (info_json['filesize']) {
|
||||
expected_filesize += info_json['filesize'];
|
||||
return;
|
||||
}
|
||||
const formats = info_json['format_id'].split('+');
|
||||
let individual_expected_filesize = 0;
|
||||
formats.forEach(format_id => {
|
||||
info_json.formats.forEach(available_format => {
|
||||
if (available_format.format_id === format_id && available_format.filesize) {
|
||||
individual_expected_filesize += available_format.filesize;
|
||||
}
|
||||
});
|
||||
});
|
||||
expected_filesize += individual_expected_filesize;
|
||||
});
|
||||
|
||||
return expected_filesize;
|
||||
@@ -146,6 +222,28 @@ function fixVideoMetadataPerms(name, type, customPath = null) {
|
||||
}
|
||||
}
|
||||
|
||||
function fixVideoMetadataPerms2(file_path, type) {
|
||||
if (is_windows) return;
|
||||
|
||||
const ext = type === 'audio' ? '.mp3' : '.mp4';
|
||||
|
||||
const file_path_no_extension = removeFileExtension(file_path);
|
||||
|
||||
const files_to_fix = [
|
||||
// JSONs
|
||||
file_path_no_extension + '.info.json',
|
||||
file_path_no_extension + ext + '.info.json',
|
||||
// Thumbnails
|
||||
file_path_no_extension + '.webp',
|
||||
file_path_no_extension + '.jpg'
|
||||
];
|
||||
|
||||
for (const file of files_to_fix) {
|
||||
if (!fs.existsSync(file)) continue;
|
||||
fs.chmodSync(file, 0o644);
|
||||
}
|
||||
}
|
||||
|
||||
function deleteJSONFile(name, type, customPath = null) {
|
||||
if (!customPath) customPath = type === 'audio' ? config_api.getConfigItem('ytdl_audio_folder_path')
|
||||
: config_api.getConfigItem('ytdl_video_folder_path');
|
||||
@@ -158,6 +256,64 @@ function deleteJSONFile(name, type, customPath = null) {
|
||||
if (fs.existsSync(alternate_json_path)) fs.unlinkSync(alternate_json_path);
|
||||
}
|
||||
|
||||
function deleteJSONFile2(file_path, type) {
|
||||
const ext = type === 'audio' ? '.mp3' : '.mp4';
|
||||
|
||||
const file_path_no_extension = removeFileExtension(file_path);
|
||||
|
||||
let json_path = file_path_no_extension + '.info.json';
|
||||
let alternate_json_path = file_path_no_extension + ext + '.info.json';
|
||||
|
||||
if (fs.existsSync(json_path)) fs.unlinkSync(json_path);
|
||||
if (fs.existsSync(alternate_json_path)) fs.unlinkSync(alternate_json_path);
|
||||
}
|
||||
|
||||
async function removeIDFromArchive(archive_path, id) {
|
||||
let data = await fs.readFile(archive_path, {encoding: 'utf-8'});
|
||||
if (!data) {
|
||||
logger.error('Archive could not be found.');
|
||||
return;
|
||||
}
|
||||
|
||||
let dataArray = data.split('\n'); // convert file data in an array
|
||||
const searchKeyword = id; // we are looking for a line, contains, key word id in the file
|
||||
let lastIndex = -1; // let say, we have not found the keyword
|
||||
|
||||
for (let index=0; index<dataArray.length; index++) {
|
||||
if (dataArray[index].includes(searchKeyword)) { // check if a line contains the id keyword
|
||||
lastIndex = index; // found a line includes a id keyword
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const line = dataArray.splice(lastIndex, 1); // remove the keyword id from the data Array
|
||||
|
||||
// UPDATE FILE WITH NEW DATA
|
||||
const updatedData = dataArray.join('\n');
|
||||
await fs.writeFile(archive_path, updatedData);
|
||||
if (line) return line;
|
||||
if (err) throw err;
|
||||
}
|
||||
|
||||
function durationStringToNumber(dur_str) {
|
||||
if (typeof dur_str === 'number') return dur_str;
|
||||
let num_sum = 0;
|
||||
const dur_str_parts = dur_str.split(':');
|
||||
for (let i = dur_str_parts.length-1; i >= 0; i--) {
|
||||
num_sum += parseInt(dur_str_parts[i])*(60**(dur_str_parts.length-1-i));
|
||||
}
|
||||
return num_sum;
|
||||
}
|
||||
|
||||
function getMatchingCategoryFiles(category, files) {
|
||||
return files && files.filter(file => file.category && file.category.uid === category.uid);
|
||||
}
|
||||
|
||||
function addUIDsToCategory(category, files) {
|
||||
const files_that_match = getMatchingCategoryFiles(category, files);
|
||||
category['uids'] = files_that_match.map(file => file.uid);
|
||||
return files_that_match;
|
||||
}
|
||||
|
||||
async function recFindByExt(base,ext,files,result)
|
||||
{
|
||||
@@ -181,6 +337,22 @@ async function recFindByExt(base,ext,files,result)
|
||||
return result
|
||||
}
|
||||
|
||||
function removeFileExtension(filename) {
|
||||
const filename_parts = filename.split('.');
|
||||
filename_parts.splice(filename_parts.length - 1);
|
||||
return filename_parts.join('.');
|
||||
}
|
||||
|
||||
/**
|
||||
* setTimeout, but its a promise.
|
||||
* @param {number} ms
|
||||
*/
|
||||
async function wait(ms) {
|
||||
await new Promise(resolve => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
// objects
|
||||
|
||||
function File(id, title, thumbnailURL, isAudio, duration, url, uploader, size, path, upload_date, description, view_count, height, abr) {
|
||||
@@ -203,12 +375,23 @@ function File(id, title, thumbnailURL, isAudio, duration, url, uploader, size, p
|
||||
module.exports = {
|
||||
getJSONMp3: getJSONMp3,
|
||||
getJSONMp4: getJSONMp4,
|
||||
getJSON: getJSON,
|
||||
getTrueFileName: getTrueFileName,
|
||||
getDownloadedThumbnail: getDownloadedThumbnail,
|
||||
getDownloadedThumbnail2: getDownloadedThumbnail2,
|
||||
getExpectedFileSize: getExpectedFileSize,
|
||||
fixVideoMetadataPerms: fixVideoMetadataPerms,
|
||||
fixVideoMetadataPerms2: fixVideoMetadataPerms2,
|
||||
deleteJSONFile: deleteJSONFile,
|
||||
deleteJSONFile2: deleteJSONFile2,
|
||||
removeIDFromArchive, removeIDFromArchive,
|
||||
getDownloadedFilesByType: getDownloadedFilesByType,
|
||||
createContainerZipFile: createContainerZipFile,
|
||||
durationStringToNumber: durationStringToNumber,
|
||||
getMatchingCategoryFiles: getMatchingCategoryFiles,
|
||||
addUIDsToCategory: addUIDsToCategory,
|
||||
recFindByExt: recFindByExt,
|
||||
removeFileExtension: removeFileExtension,
|
||||
wait: wait,
|
||||
File: File
|
||||
}
|
||||
|
||||
23
chart/.helmignore
Normal file
23
chart/.helmignore
Normal file
@@ -0,0 +1,23 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
||||
24
chart/Chart.yaml
Normal file
24
chart/Chart.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
apiVersion: v2
|
||||
name: youtubedl-material
|
||||
description: A Helm chart for Kubernetes
|
||||
|
||||
# A chart can be either an 'application' or a 'library' chart.
|
||||
#
|
||||
# Application charts are a collection of templates that can be packaged into versioned archives
|
||||
# to be deployed.
|
||||
#
|
||||
# Library charts provide useful utilities or functions for the chart developer. They're included as
|
||||
# a dependency of application charts to inject those utilities and functions into the rendering
|
||||
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
|
||||
type: application
|
||||
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 0.1.0
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: "4.2"
|
||||
22
chart/templates/NOTES.txt
Normal file
22
chart/templates/NOTES.txt
Normal file
@@ -0,0 +1,22 @@
|
||||
1. Get the application URL by running these commands:
|
||||
{{- if .Values.ingress.enabled }}
|
||||
{{- range $host := .Values.ingress.hosts }}
|
||||
{{- range .paths }}
|
||||
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- else if contains "NodePort" .Values.service.type }}
|
||||
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "youtubedl-material.fullname" . }})
|
||||
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
||||
echo http://$NODE_IP:$NODE_PORT
|
||||
{{- else if contains "LoadBalancer" .Values.service.type }}
|
||||
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
||||
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "youtubedl-material.fullname" . }}'
|
||||
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "youtubedl-material.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
|
||||
echo http://$SERVICE_IP:{{ .Values.service.port }}
|
||||
{{- else if contains "ClusterIP" .Values.service.type }}
|
||||
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "youtubedl-material.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
|
||||
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
|
||||
echo "Visit http://127.0.0.1:8080 to use your application"
|
||||
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
|
||||
{{- end }}
|
||||
62
chart/templates/_helpers.tpl
Normal file
62
chart/templates/_helpers.tpl
Normal file
@@ -0,0 +1,62 @@
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "youtubedl-material.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
If release name contains chart name it will be used as a full name.
|
||||
*/}}
|
||||
{{- define "youtubedl-material.fullname" -}}
|
||||
{{- if .Values.fullnameOverride }}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- if contains $name .Release.Name }}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "youtubedl-material.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "youtubedl-material.labels" -}}
|
||||
helm.sh/chart: {{ include "youtubedl-material.chart" . }}
|
||||
{{ include "youtubedl-material.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "youtubedl-material.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "youtubedl-material.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use
|
||||
*/}}
|
||||
{{- define "youtubedl-material.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create }}
|
||||
{{- default (include "youtubedl-material.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else }}
|
||||
{{- default "default" .Values.serviceAccount.name }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
21
chart/templates/appdata-pvc.yaml
Normal file
21
chart/templates/appdata-pvc.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
{{- if and .Values.persistence.appdata.enabled (not .Values.persistence.appdata.existingClaim) }}
|
||||
kind: PersistentVolumeClaim
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: {{ template "youtubedl-material.fullname" . }}-appdata
|
||||
labels:
|
||||
{{- include "youtubedl-material.labels" . | nindent 4 }}
|
||||
spec:
|
||||
accessModes:
|
||||
- {{ .Values.persistence.appdata.accessMode | quote }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.persistence.appdata.size | quote }}
|
||||
{{- if .Values.persistence.appdata.storageClass }}
|
||||
{{- if (eq "-" .Values.persistence.appdata.storageClass) }}
|
||||
storageClassName: ""
|
||||
{{- else }}
|
||||
storageClassName: "{{ .Values.persistence.appdata.storageClass }}"
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
21
chart/templates/audio-pvc.yaml
Normal file
21
chart/templates/audio-pvc.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
{{- if and .Values.persistence.audio.enabled (not .Values.persistence.audio.existingClaim) }}
|
||||
kind: PersistentVolumeClaim
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: {{ template "youtubedl-material.fullname" . }}-audio
|
||||
labels:
|
||||
{{- include "youtubedl-material.labels" . | nindent 4 }}
|
||||
spec:
|
||||
accessModes:
|
||||
- {{ .Values.persistence.audio.accessMode | quote }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.persistence.audio.size | quote }}
|
||||
{{- if .Values.persistence.audio.storageClass }}
|
||||
{{- if (eq "-" .Values.persistence.audio.storageClass) }}
|
||||
storageClassName: ""
|
||||
{{- else }}
|
||||
storageClassName: "{{ .Values.persistence.audio.storageClass }}"
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
121
chart/templates/deployment.yaml
Normal file
121
chart/templates/deployment.yaml
Normal file
@@ -0,0 +1,121 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "youtubedl-material.fullname" . }}
|
||||
labels:
|
||||
{{- include "youtubedl-material.labels" . | nindent 4 }}
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "youtubedl-material.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
{{- with .Values.podAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "youtubedl-material.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "youtubedl-material.serviceAccountName" . }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 17442
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
volumeMounts:
|
||||
- mountPath: /app/appdata
|
||||
name: appdata
|
||||
{{- if .Values.persistence.appdata.subPath }}
|
||||
subPath: {{ .Values.persistence.appdata.subPath }}
|
||||
{{- end }}
|
||||
- mountPath: /app/audio
|
||||
name: audio
|
||||
{{- if .Values.persistence.audio.subPath }}
|
||||
subPath: {{ .Values.persistence.audio.subPath }}
|
||||
{{- end }}
|
||||
- mountPath: /app/video
|
||||
name: video
|
||||
{{- if .Values.persistence.video.subPath }}
|
||||
subPath: {{ .Values.persistence.video.subPath }}
|
||||
{{- end }}
|
||||
- mountPath: /app/subscriptions
|
||||
name: subscriptions
|
||||
{{- if .Values.persistence.subscriptions.subPath }}
|
||||
subPath: {{ .Values.persistence.subscriptions.subPath }}
|
||||
{{- end }}
|
||||
- mountPath: /app/users
|
||||
name: users
|
||||
{{- if .Values.persistence.users.subPath }}
|
||||
subPath: {{ .Values.persistence.users.subPath }}
|
||||
{{- end }}
|
||||
volumes:
|
||||
- name: appdata
|
||||
{{- if .Values.persistence.appdata.enabled}}
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ if .Values.persistence.appdata.existingClaim }}{{ .Values.persistence.appdata.existingClaim }}{{- else }}{{ template "youtubedl-material.fullname" . }}-appdata{{- end }}
|
||||
{{- else }}
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
- name: audio
|
||||
{{- if .Values.persistence.audio.enabled}}
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ if .Values.persistence.audio.existingClaim }}{{ .Values.persistence.audio.existingClaim }}{{- else }}{{ template "youtubedl-material.fullname" . }}-audio{{- end }}
|
||||
{{- else }}
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
- name: subscriptions
|
||||
{{- if .Values.persistence.subscriptions.enabled}}
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ if .Values.persistence.subscriptions.existingClaim }}{{ .Values.persistence.subscriptions.existingClaim }}{{- else }}{{ template "youtubedl-material.fullname" . }}-subscriptions{{- end }}
|
||||
{{- else }}
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
- name: users
|
||||
{{- if .Values.persistence.users.enabled}}
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ if .Values.persistence.users.existingClaim }}{{ .Values.persistence.users.existingClaim }}{{- else }}{{ template "youtubedl-material.fullname" . }}-users{{- end }}
|
||||
{{- else }}
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
- name: video
|
||||
{{- if .Values.persistence.video.enabled}}
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ if .Values.persistence.video.existingClaim }}{{ .Values.persistence.video.existingClaim }}{{- else }}{{ template "youtubedl-material.fullname" . }}-video{{- end }}
|
||||
{{- else }}
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
41
chart/templates/ingress.yaml
Normal file
41
chart/templates/ingress.yaml
Normal file
@@ -0,0 +1,41 @@
|
||||
{{- if .Values.ingress.enabled -}}
|
||||
{{- $fullName := include "youtubedl-material.fullname" . -}}
|
||||
{{- $svcPort := .Values.service.port -}}
|
||||
{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
{{- else -}}
|
||||
apiVersion: extensions/v1beta1
|
||||
{{- end }}
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ $fullName }}
|
||||
labels:
|
||||
{{- include "youtubedl-material.labels" . | nindent 4 }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if .Values.ingress.tls }}
|
||||
tls:
|
||||
{{- range .Values.ingress.tls }}
|
||||
- hosts:
|
||||
{{- range .hosts }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
secretName: {{ .secretName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- range .Values.ingress.hosts }}
|
||||
- host: {{ .host | quote }}
|
||||
http:
|
||||
paths:
|
||||
{{- range .paths }}
|
||||
- path: {{ .path }}
|
||||
backend:
|
||||
serviceName: {{ $fullName }}
|
||||
servicePort: {{ $svcPort }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
15
chart/templates/service.yaml
Normal file
15
chart/templates/service.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "youtubedl-material.fullname" . }}
|
||||
labels:
|
||||
{{- include "youtubedl-material.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
ports:
|
||||
- port: {{ .Values.service.port }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
{{- include "youtubedl-material.selectorLabels" . | nindent 4 }}
|
||||
12
chart/templates/serviceaccount.yaml
Normal file
12
chart/templates/serviceaccount.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
{{- if .Values.serviceAccount.create -}}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ include "youtubedl-material.serviceAccountName" . }}
|
||||
labels:
|
||||
{{- include "youtubedl-material.labels" . | nindent 4 }}
|
||||
{{- with .Values.serviceAccount.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
21
chart/templates/subscriptions-pvc.yaml
Normal file
21
chart/templates/subscriptions-pvc.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
{{- if and .Values.persistence.subscriptions.enabled (not .Values.persistence.subscriptions.existingClaim) }}
|
||||
kind: PersistentVolumeClaim
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: {{ template "youtubedl-material.fullname" . }}-subscriptions
|
||||
labels:
|
||||
{{- include "youtubedl-material.labels" . | nindent 4 }}
|
||||
spec:
|
||||
accessModes:
|
||||
- {{ .Values.persistence.subscriptions.accessMode | quote }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.persistence.subscriptions.size | quote }}
|
||||
{{- if .Values.persistence.subscriptions.storageClass }}
|
||||
{{- if (eq "-" .Values.persistence.subscriptions.storageClass) }}
|
||||
storageClassName: ""
|
||||
{{- else }}
|
||||
storageClassName: "{{ .Values.persistence.subscriptions.storageClass }}"
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
15
chart/templates/tests/test-connection.yaml
Normal file
15
chart/templates/tests/test-connection.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: "{{ include "youtubedl-material.fullname" . }}-test-connection"
|
||||
labels:
|
||||
{{- include "youtubedl-material.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
"helm.sh/hook": test
|
||||
spec:
|
||||
containers:
|
||||
- name: wget
|
||||
image: busybox
|
||||
command: ['wget']
|
||||
args: ['{{ include "youtubedl-material.fullname" . }}:{{ .Values.service.port }}']
|
||||
restartPolicy: Never
|
||||
21
chart/templates/users-pvc.yaml
Normal file
21
chart/templates/users-pvc.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
{{- if and .Values.persistence.users.enabled (not .Values.persistence.users.existingClaim) }}
|
||||
kind: PersistentVolumeClaim
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: {{ template "youtubedl-material.fullname" . }}-users
|
||||
labels:
|
||||
{{- include "youtubedl-material.labels" . | nindent 4 }}
|
||||
spec:
|
||||
accessModes:
|
||||
- {{ .Values.persistence.users.accessMode | quote }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.persistence.users.size | quote }}
|
||||
{{- if .Values.persistence.users.storageClass }}
|
||||
{{- if (eq "-" .Values.persistence.users.storageClass) }}
|
||||
storageClassName: ""
|
||||
{{- else }}
|
||||
storageClassName: "{{ .Values.persistence.users.storageClass }}"
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
21
chart/templates/video-pvc.yaml
Normal file
21
chart/templates/video-pvc.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
{{- if and .Values.persistence.video.enabled (not .Values.persistence.video.existingClaim) }}
|
||||
kind: PersistentVolumeClaim
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: {{ template "youtubedl-material.fullname" . }}-video
|
||||
labels:
|
||||
{{- include "youtubedl-material.labels" . | nindent 4 }}
|
||||
spec:
|
||||
accessModes:
|
||||
- {{ .Values.persistence.video.accessMode | quote }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.persistence.video.size | quote }}
|
||||
{{- if .Values.persistence.video.storageClass }}
|
||||
{{- if (eq "-" .Values.persistence.video.storageClass) }}
|
||||
storageClassName: ""
|
||||
{{- else }}
|
||||
storageClassName: "{{ .Values.persistence.video.storageClass }}"
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
153
chart/values.yaml
Normal file
153
chart/values.yaml
Normal file
@@ -0,0 +1,153 @@
|
||||
# Default values for youtubedl-material.
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
repository: tzahi12345/youtubedl-material
|
||||
pullPolicy: IfNotPresent
|
||||
# Overrides the image tag whose default is the chart appVersion.
|
||||
tag: ""
|
||||
|
||||
imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
serviceAccount:
|
||||
# Specifies whether a service account should be created
|
||||
create: true
|
||||
# Annotations to add to the service account
|
||||
annotations: {}
|
||||
# The name of the service account to use.
|
||||
# If not set and create is true, a name is generated using the fullname template
|
||||
name: ""
|
||||
|
||||
podAnnotations: {}
|
||||
|
||||
podSecurityContext: {}
|
||||
# fsGroup: 2000
|
||||
|
||||
securityContext: {}
|
||||
# capabilities:
|
||||
# drop:
|
||||
# - ALL
|
||||
# readOnlyRootFilesystem: true
|
||||
# runAsNonRoot: true
|
||||
# runAsUser: 1000
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 17442
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
annotations: {}
|
||||
# kubernetes.io/ingress.class: nginx
|
||||
# kubernetes.io/tls-acme: "true"
|
||||
hosts:
|
||||
- host: chart-example.local
|
||||
paths: []
|
||||
tls: []
|
||||
# - secretName: chart-example-tls
|
||||
# hosts:
|
||||
# - chart-example.local
|
||||
|
||||
resources: {}
|
||||
# We usually recommend not to specify default resources and to leave this as a conscious
|
||||
# choice for the user. This also increases chances charts run on environments with little
|
||||
# resources, such as Minikube. If you do want to specify resources, uncomment the following
|
||||
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
|
||||
# limits:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
# requests:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
|
||||
persistence:
|
||||
appdata:
|
||||
enabled: true
|
||||
## If defined, storageClassName: <storageClass>
|
||||
## If set to "-", storageClassName: "", which disables dynamic provisioning
|
||||
## If undefined (the default) or set to null, no storageClassName spec is
|
||||
## set, choosing the default provisioner. (gp2 on AWS, standard on
|
||||
## GKE, AWS & OpenStack)
|
||||
##
|
||||
# storageClass: "-"
|
||||
## If you want to reuse an existing claim, you can pass the name of the PVC using
|
||||
## the existingClaim variable
|
||||
# existingClaim: your-claim
|
||||
# subPath: some-subpath
|
||||
accessMode: ReadWriteOnce
|
||||
size: 1Gi
|
||||
audio:
|
||||
enabled: true
|
||||
## If defined, storageClassName: <storageClass>
|
||||
## If set to "-", storageClassName: "", which disables dynamic provisioning
|
||||
## If undefined (the default) or set to null, no storageClassName spec is
|
||||
## set, choosing the default provisioner. (gp2 on AWS, standard on
|
||||
## GKE, AWS & OpenStack)
|
||||
##
|
||||
# storageClass: "-"
|
||||
##
|
||||
## If you want to reuse an existing claim, you can pass the name of the PVC using
|
||||
## the existingClaim variable
|
||||
# existingClaim: your-claim
|
||||
# subPath: some-subpath
|
||||
accessMode: ReadWriteOnce
|
||||
size: 50Gi
|
||||
video:
|
||||
enabled: true
|
||||
## If defined, storageClassName: <storageClass>
|
||||
## If set to "-", storageClassName: "", which disables dynamic provisioning
|
||||
## If undefined (the default) or set to null, no storageClassName spec is
|
||||
## set, choosing the default provisioner. (gp2 on AWS, standard on
|
||||
## GKE, AWS & OpenStack)
|
||||
##
|
||||
# storageClass: "-"
|
||||
##
|
||||
## If you want to reuse an existing claim, you can pass the name of the PVC using
|
||||
## the existingClaim variable
|
||||
# existingClaim: your-claim
|
||||
# subPath: some-subpath
|
||||
accessMode: ReadWriteOnce
|
||||
size: 50Gi
|
||||
subscriptions:
|
||||
enabled: true
|
||||
## If defined, storageClassName: <storageClass>
|
||||
## If set to "-", storageClassName: "", which disables dynamic provisioning
|
||||
## If undefined (the default) or set to null, no storageClassName spec is
|
||||
## set, choosing the default provisioner. (gp2 on AWS, standard on
|
||||
## GKE, AWS & OpenStack)
|
||||
##
|
||||
# storageClass: "-"
|
||||
##
|
||||
## If you want to reuse an existing claim, you can pass the name of the PVC using
|
||||
## the existingClaim variable
|
||||
# existingClaim: your-claim
|
||||
# subPath: some-subpath
|
||||
accessMode: ReadWriteOnce
|
||||
size: 50Gi
|
||||
users:
|
||||
enabled: true
|
||||
## If defined, storageClassName: <storageClass>
|
||||
## If set to "-", storageClassName: "", which disables dynamic provisioning
|
||||
## If undefined (the default) or set to null, no storageClassName spec is
|
||||
## set, choosing the default provisioner. (gp2 on AWS, standard on
|
||||
## GKE, AWS & OpenStack)
|
||||
##
|
||||
# storageClass: "-"
|
||||
##
|
||||
## If you want to reuse an existing claim, you can pass the name of the PVC using
|
||||
## the existingClaim variable
|
||||
# existingClaim: your-claim
|
||||
# subPath: some-subpath
|
||||
accessMode: ReadWriteOnce
|
||||
size: 50Gi
|
||||
|
||||
nodeSelector: {}
|
||||
|
||||
tolerations: []
|
||||
|
||||
affinity: {}
|
||||
@@ -3,6 +3,9 @@ services:
|
||||
ytdl_material:
|
||||
environment:
|
||||
ALLOW_CONFIG_MUTATIONS: 'true'
|
||||
ytdl_mongodb_connection_string: 'mongodb://ytdl-mongo-db:27017'
|
||||
ytdl_use_local_db: 'false'
|
||||
write_ytdl_config: 'true'
|
||||
restart: always
|
||||
volumes:
|
||||
- ./appdata:/app/appdata
|
||||
@@ -12,4 +15,13 @@ services:
|
||||
- ./users:/app/users
|
||||
ports:
|
||||
- "8998:17442"
|
||||
image: tzahi12345/youtubedl-material:latest
|
||||
image: tzahi12345/youtubedl-material:latest
|
||||
ytdl-mongo-db:
|
||||
image: mongo
|
||||
ports:
|
||||
- "27017:27017"
|
||||
logging:
|
||||
driver: "none"
|
||||
container_name: mongo-db
|
||||
volumes:
|
||||
- ./db/:/data/db
|
||||
323
package-lock.json
generated
323
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "youtube-dl-material",
|
||||
"version": "4.1.0",
|
||||
"version": "4.2.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -180,9 +180,9 @@
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.20",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
},
|
||||
"semver": {
|
||||
@@ -316,6 +316,12 @@
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
|
||||
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
|
||||
"dev": true
|
||||
},
|
||||
"resolve": {
|
||||
"version": "1.18.1",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz",
|
||||
@@ -432,9 +438,9 @@
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.20",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
},
|
||||
"semver": {
|
||||
@@ -705,9 +711,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash": {
|
||||
"version": "4.17.20",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
@@ -784,9 +790,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash": {
|
||||
"version": "4.17.20",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
@@ -1592,9 +1598,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash": {
|
||||
"version": "4.17.20",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1609,9 +1615,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash": {
|
||||
"version": "4.17.20",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1760,6 +1766,12 @@
|
||||
"semver-intersect": "1.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
|
||||
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
|
||||
"dev": true
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.3.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
|
||||
@@ -2616,15 +2628,6 @@
|
||||
"tweetnacl": "^0.14.3"
|
||||
}
|
||||
},
|
||||
"better-assert": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz",
|
||||
"integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"callsite": "1.0.0"
|
||||
}
|
||||
},
|
||||
"big.js": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
|
||||
@@ -3059,12 +3062,6 @@
|
||||
"caller-callsite": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"callsite": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
|
||||
"integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=",
|
||||
"dev": true
|
||||
},
|
||||
"callsites": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz",
|
||||
@@ -4513,24 +4510,24 @@
|
||||
"dev": true
|
||||
},
|
||||
"elliptic": {
|
||||
"version": "6.5.3",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
|
||||
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
|
||||
"version": "6.5.4",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
|
||||
"integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bn.js": "^4.4.0",
|
||||
"brorand": "^1.0.1",
|
||||
"bn.js": "^4.11.9",
|
||||
"brorand": "^1.1.0",
|
||||
"hash.js": "^1.0.0",
|
||||
"hmac-drbg": "^1.0.0",
|
||||
"inherits": "^2.0.1",
|
||||
"minimalistic-assert": "^1.0.0",
|
||||
"minimalistic-crypto-utils": "^1.0.0"
|
||||
"hmac-drbg": "^1.0.1",
|
||||
"inherits": "^2.0.4",
|
||||
"minimalistic-assert": "^1.0.1",
|
||||
"minimalistic-crypto-utils": "^1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"bn.js": {
|
||||
"version": "4.11.9",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
||||
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
@@ -4582,37 +4579,37 @@
|
||||
}
|
||||
},
|
||||
"engine.io": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.4.2.tgz",
|
||||
"integrity": "sha512-b4Q85dFkGw+TqgytGPrGgACRUhsdKc9S9ErRAXpPGy/CXKs4tYoHDkvIRdsseAF7NjfVwjRFIn6KTnbw7LwJZg==",
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.5.0.tgz",
|
||||
"integrity": "sha512-21HlvPUKaitDGE4GXNtQ7PLP0Sz4aWLddMPw2VTyFz1FVZqu/kZsJUO8WNpKuE/OCL7nkfRaOui2ZCJloGznGA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "2.0.0",
|
||||
"cookie": "0.3.1",
|
||||
"cookie": "~0.4.1",
|
||||
"debug": "~4.1.0",
|
||||
"engine.io-parser": "~2.2.0",
|
||||
"ws": "^7.1.2"
|
||||
"ws": "~7.4.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
|
||||
"integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=",
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
|
||||
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
|
||||
"dev": true
|
||||
},
|
||||
"ws": {
|
||||
"version": "7.4.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.1.tgz",
|
||||
"integrity": "sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ==",
|
||||
"version": "7.4.5",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz",
|
||||
"integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"engine.io-client": {
|
||||
"version": "3.4.4",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.4.4.tgz",
|
||||
"integrity": "sha512-iU4CRr38Fecj8HoZEnFtm2EiKGbYZcPn3cHxqNGl/tmdWRf60KhK+9vE0JeSjgnlS/0oynEfLgKbT9ALpim0sQ==",
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.5.2.tgz",
|
||||
"integrity": "sha512-QEqIp+gJ/kMHeUun7f5Vv3bteRHppHH/FMBQX/esFj/fuYfjyUKWGMo3VCvIP/V8bE9KcjHmRZrhIz2Z9oNsDA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"component-emitter": "~1.3.0",
|
||||
@@ -4623,8 +4620,8 @@
|
||||
"indexof": "0.0.1",
|
||||
"parseqs": "0.0.6",
|
||||
"parseuri": "0.0.6",
|
||||
"ws": "~6.1.0",
|
||||
"xmlhttprequest-ssl": "~1.5.4",
|
||||
"ws": "~7.4.2",
|
||||
"xmlhttprequest-ssl": "~1.6.2",
|
||||
"yeast": "0.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -4643,26 +4640,11 @@
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
|
||||
"dev": true
|
||||
},
|
||||
"parseqs": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz",
|
||||
"integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==",
|
||||
"dev": true
|
||||
},
|
||||
"parseuri": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz",
|
||||
"integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==",
|
||||
"dev": true
|
||||
},
|
||||
"ws": {
|
||||
"version": "6.1.4",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz",
|
||||
"integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"async-limiter": "~1.0.0"
|
||||
}
|
||||
"version": "7.4.5",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz",
|
||||
"integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -5930,9 +5912,9 @@
|
||||
}
|
||||
},
|
||||
"hosted-git-info": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.7.tgz",
|
||||
"integrity": "sha512-fWqc0IcuXs+BmE9orLDyVykAG9GJtGLGuZAAqgcckPgv5xad4AcXGIv8galtQvlwutxSlaMcdw7BUtq2EIvqCQ==",
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.8.tgz",
|
||||
"integrity": "sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lru-cache": "^6.0.0"
|
||||
@@ -6338,9 +6320,9 @@
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
|
||||
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
||||
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
|
||||
"dev": true
|
||||
},
|
||||
"inquirer": {
|
||||
@@ -6405,9 +6387,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.20",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
},
|
||||
"supports-color": {
|
||||
@@ -7498,9 +7480,9 @@
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.15",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"lodash.memoize": {
|
||||
"version": "4.1.2",
|
||||
@@ -7719,9 +7701,9 @@
|
||||
}
|
||||
},
|
||||
"ssri": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
|
||||
"integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz",
|
||||
"integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"figgy-pudding": "^3.5.1"
|
||||
@@ -7775,6 +7757,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"material-icons": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/material-icons/-/material-icons-0.5.4.tgz",
|
||||
"integrity": "sha512-5ycazkNmIOtV78Ff3WgvxQESoJuujdRm0cNbf18fmyJN20jHyqp9rpwi4EfQyGimag0ZLElxtVg3H9enIKdOOw=="
|
||||
},
|
||||
"md5.js": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
@@ -8284,9 +8271,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"hosted-git-info": {
|
||||
"version": "2.8.8",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
|
||||
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
|
||||
"version": "2.8.9",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
|
||||
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
@@ -8435,9 +8422,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"hosted-git-info": {
|
||||
"version": "2.8.8",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
|
||||
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
|
||||
"version": "2.8.9",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
|
||||
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
|
||||
"dev": true
|
||||
},
|
||||
"lru-cache": {
|
||||
@@ -8511,12 +8498,6 @@
|
||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
|
||||
"dev": true
|
||||
},
|
||||
"object-component": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz",
|
||||
"integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=",
|
||||
"dev": true
|
||||
},
|
||||
"object-copy": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
|
||||
@@ -8937,9 +8918,9 @@
|
||||
}
|
||||
},
|
||||
"hosted-git-info": {
|
||||
"version": "2.8.8",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
|
||||
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
|
||||
"version": "2.8.9",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
|
||||
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
|
||||
"dev": true
|
||||
},
|
||||
"lru-cache": {
|
||||
@@ -9003,9 +8984,9 @@
|
||||
}
|
||||
},
|
||||
"ssri": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
|
||||
"integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz",
|
||||
"integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"figgy-pudding": "^3.5.1"
|
||||
@@ -9139,22 +9120,16 @@
|
||||
}
|
||||
},
|
||||
"parseqs": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz",
|
||||
"integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"better-assert": "~1.0.0"
|
||||
}
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz",
|
||||
"integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==",
|
||||
"dev": true
|
||||
},
|
||||
"parseuri": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz",
|
||||
"integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"better-assert": "~1.0.0"
|
||||
}
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz",
|
||||
"integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==",
|
||||
"dev": true
|
||||
},
|
||||
"parseurl": {
|
||||
"version": "1.3.3",
|
||||
@@ -11672,16 +11647,16 @@
|
||||
}
|
||||
},
|
||||
"socket.io": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.3.0.tgz",
|
||||
"integrity": "sha512-2A892lrj0GcgR/9Qk81EaY2gYhCBxurV0PfmmESO6p27QPrUK1J3zdns+5QPqvUYK2q657nSj0guoIil9+7eFg==",
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.4.1.tgz",
|
||||
"integrity": "sha512-Si18v0mMXGAqLqCVpTxBa8MGqriHGQh8ccEOhmsmNS3thNCGBwO8WGrwMibANsWtQQ5NStdZwHqZR3naJVFc3w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "~4.1.0",
|
||||
"engine.io": "~3.4.0",
|
||||
"engine.io": "~3.5.0",
|
||||
"has-binary2": "~1.0.2",
|
||||
"socket.io-adapter": "~1.1.0",
|
||||
"socket.io-client": "2.3.0",
|
||||
"socket.io-client": "2.4.0",
|
||||
"socket.io-parser": "~3.4.0"
|
||||
}
|
||||
},
|
||||
@@ -11692,38 +11667,32 @@
|
||||
"dev": true
|
||||
},
|
||||
"socket.io-client": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.3.0.tgz",
|
||||
"integrity": "sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA==",
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.4.0.tgz",
|
||||
"integrity": "sha512-M6xhnKQHuuZd4Ba9vltCLT9oa+YvTsP8j9NcEiLElfIg8KeYPyhWOes6x4t+LTAC8enQbE/995AdTem2uNyKKQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"backo2": "1.0.2",
|
||||
"base64-arraybuffer": "0.1.5",
|
||||
"component-bind": "1.0.0",
|
||||
"component-emitter": "1.2.1",
|
||||
"debug": "~4.1.0",
|
||||
"engine.io-client": "~3.4.0",
|
||||
"component-emitter": "~1.3.0",
|
||||
"debug": "~3.1.0",
|
||||
"engine.io-client": "~3.5.0",
|
||||
"has-binary2": "~1.0.2",
|
||||
"has-cors": "1.1.0",
|
||||
"indexof": "0.0.1",
|
||||
"object-component": "0.0.3",
|
||||
"parseqs": "0.0.5",
|
||||
"parseuri": "0.0.5",
|
||||
"parseqs": "0.0.6",
|
||||
"parseuri": "0.0.6",
|
||||
"socket.io-parser": "~3.3.0",
|
||||
"to-array": "0.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"base64-arraybuffer": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz",
|
||||
"integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=",
|
||||
"dev": true
|
||||
},
|
||||
"component-emitter": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
|
||||
"integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=",
|
||||
"dev": true
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"isarray": {
|
||||
"version": "2.0.1",
|
||||
@@ -11738,31 +11707,14 @@
|
||||
"dev": true
|
||||
},
|
||||
"socket.io-parser": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.1.tgz",
|
||||
"integrity": "sha512-1QLvVAe8dTz+mKmZ07Swxt+LAo4Y1ff50rlyoEx00TQmDFVQYPfcqGvIDJLGaBdhdNCecXtyKpD+EgKGcmmbuQ==",
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.2.tgz",
|
||||
"integrity": "sha512-FJvDBuOALxdCI9qwRrO/Rfp9yfndRtc1jSgVgV8FDraihmSP/MLGD5PEuJrNfjALvcQ+vMDM/33AWOYP/JSjDg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"component-emitter": "~1.3.0",
|
||||
"debug": "~3.1.0",
|
||||
"isarray": "2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"component-emitter": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
|
||||
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
|
||||
"dev": true
|
||||
},
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12077,9 +12029,9 @@
|
||||
}
|
||||
},
|
||||
"ssri": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz",
|
||||
"integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==",
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz",
|
||||
"integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minipass": "^3.1.1"
|
||||
@@ -13069,9 +13021,9 @@
|
||||
}
|
||||
},
|
||||
"url-parse": {
|
||||
"version": "1.4.7",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz",
|
||||
"integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==",
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz",
|
||||
"integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"querystringify": "^2.1.1",
|
||||
@@ -13791,8 +13743,7 @@
|
||||
},
|
||||
"ssri": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
|
||||
"integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
|
||||
"resolved": "",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"figgy-pudding": "^3.5.1"
|
||||
@@ -14533,9 +14484,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"xmlhttprequest-ssl": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz",
|
||||
"integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=",
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.2.tgz",
|
||||
"integrity": "sha512-tYOaldF/0BLfKuoA39QMwD4j2m8lq4DIncqj1yuNELX4vz9+z/ieG/vwmctjJce+boFHXstqhWnHSxc4W8f4qg==",
|
||||
"dev": true
|
||||
},
|
||||
"xtend": {
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"file-saver": "^2.0.2",
|
||||
"filesize": "^6.1.0",
|
||||
"fingerprintjs2": "^2.1.0",
|
||||
"material-icons": "^0.5.4",
|
||||
"nan": "^2.14.1",
|
||||
"ng-lazyload-image": "^7.0.1",
|
||||
"ngx-avatar": "^4.0.0",
|
||||
|
||||
@@ -86,6 +86,7 @@ import { EditCategoryDialogComponent } from './dialogs/edit-category-dialog/edit
|
||||
import { TwitchChatComponent } from './components/twitch-chat/twitch-chat.component';
|
||||
import { LinkifyPipe, SeeMoreComponent } from './components/see-more/see-more.component';
|
||||
import { H401Interceptor } from './http.interceptor';
|
||||
import { ConcurrentStreamComponent } from './components/concurrent-stream/concurrent-stream.component';
|
||||
|
||||
registerLocaleData(es, 'es');
|
||||
|
||||
@@ -134,7 +135,8 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible
|
||||
CustomPlaylistsComponent,
|
||||
EditCategoryDialogComponent,
|
||||
TwitchChatComponent,
|
||||
SeeMoreComponent
|
||||
SeeMoreComponent,
|
||||
ConcurrentStreamComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<div class="buttons-container">
|
||||
<button (click)="startWatching()" *ngIf="!watch_together_clicked" mat-flat-button>Watch together</button>
|
||||
<button (click)="startServer()" *ngIf="watch_together_clicked && !started && server_mode && server_already_exists === false" mat-flat-button>Start stream</button>
|
||||
<button (click)="startClient()" *ngIf="watch_together_clicked && !started && server_already_exists === true" mat-flat-button>Join stream</button>
|
||||
<button style="margin-left: 10px;" (click)="stop()" *ngIf="watch_together_clicked" mat-flat-button>Stop</button>
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
.buttons-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ConcurrentStreamComponent } from './concurrent-stream.component';
|
||||
|
||||
describe('ConcurrentStreamComponent', () => {
|
||||
let component: ConcurrentStreamComponent;
|
||||
let fixture: ComponentFixture<ConcurrentStreamComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ ConcurrentStreamComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ConcurrentStreamComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,140 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { PostsService } from 'app/posts.services';
|
||||
|
||||
@Component({
|
||||
selector: 'app-concurrent-stream',
|
||||
templateUrl: './concurrent-stream.component.html',
|
||||
styleUrls: ['./concurrent-stream.component.scss']
|
||||
})
|
||||
export class ConcurrentStreamComponent implements OnInit {
|
||||
|
||||
@Input() server_mode = false;
|
||||
@Input() playback_timestamp;
|
||||
@Input() playing;
|
||||
@Input() uid;
|
||||
|
||||
@Output() setPlaybackTimestamp = new EventEmitter<any>();
|
||||
@Output() togglePlayback = new EventEmitter<boolean>();
|
||||
@Output() setPlaybackRate = new EventEmitter<number>();
|
||||
|
||||
started = false;
|
||||
server_started = false;
|
||||
watch_together_clicked = false;
|
||||
|
||||
server_already_exists = null;
|
||||
|
||||
check_timeout: any;
|
||||
update_timeout: any;
|
||||
|
||||
PLAYBACK_TIMESTAMP_DIFFERENCE_THRESHOLD_PLAYBACK_MODIFICATION = 0.5;
|
||||
PLAYBACK_TIMESTAMP_DIFFERENCE_THRESHOLD_SKIP = 2;
|
||||
|
||||
PLAYBACK_MODIFIER = 0.1;
|
||||
|
||||
playback_rate_modified = false;
|
||||
|
||||
constructor(private postsService: PostsService) { }
|
||||
|
||||
// flow: click start watching -> check for available stream to enable join button and if user, display "start stream"
|
||||
// users who join a stream will send continuous requests for info on playback
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
}
|
||||
|
||||
startServer() {
|
||||
this.started = true;
|
||||
this.server_started = true;
|
||||
this.update_timeout = setInterval(() => {
|
||||
this.updateStream();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
updateStream() {
|
||||
this.postsService.updateConcurrentStream(this.uid, this.playback_timestamp, Date.now()/1000, this.playing).subscribe(res => {
|
||||
});
|
||||
}
|
||||
|
||||
startClient() {
|
||||
this.started = true;
|
||||
}
|
||||
|
||||
checkStream() {
|
||||
if (this.server_started) { return; }
|
||||
const current_playback_timestamp = this.playback_timestamp;
|
||||
const current_unix_timestamp = Date.now()/1000;
|
||||
this.postsService.checkConcurrentStream(this.uid).subscribe(res => {
|
||||
const stream = res['stream'];
|
||||
|
||||
if (!stream) {
|
||||
this.server_already_exists = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.server_already_exists = true;
|
||||
|
||||
// check whether client has joined the stream
|
||||
if (!this.started) { return; }
|
||||
|
||||
if (!stream['playing'] && this.playing) {
|
||||
// tell client to pause and set the timestamp to sync
|
||||
this.togglePlayback.emit(false);
|
||||
this.setPlaybackTimestamp.emit(stream['playback_timestamp']);
|
||||
} else if (stream['playing']) {
|
||||
// sync unpause state
|
||||
if (!this.playing) { this.togglePlayback.emit(true); }
|
||||
|
||||
// sync time
|
||||
const zeroed_local_unix_timestamp = current_unix_timestamp - current_playback_timestamp;
|
||||
const zeroed_server_unix_timestamp = stream['unix_timestamp'] - stream['playback_timestamp'];
|
||||
|
||||
const seconds_behind_locally = zeroed_local_unix_timestamp - zeroed_server_unix_timestamp;
|
||||
|
||||
if (Math.abs(seconds_behind_locally) > this.PLAYBACK_TIMESTAMP_DIFFERENCE_THRESHOLD_SKIP) {
|
||||
// skip to playback timestamp because the difference is too high
|
||||
this.setPlaybackTimestamp.emit(this.playback_timestamp + seconds_behind_locally + 0.3);
|
||||
this.playback_rate_modified = false;
|
||||
} else if (!this.playback_rate_modified && Math.abs(seconds_behind_locally) > this.PLAYBACK_TIMESTAMP_DIFFERENCE_THRESHOLD_PLAYBACK_MODIFICATION) {
|
||||
// increase playback speed to avoid skipping
|
||||
let seconds_to_wait = (Math.abs(seconds_behind_locally)/this.PLAYBACK_MODIFIER);
|
||||
seconds_to_wait += 0.3/this.PLAYBACK_MODIFIER;
|
||||
|
||||
this.playback_rate_modified = true;
|
||||
|
||||
if (seconds_behind_locally > 0) {
|
||||
// increase speed
|
||||
this.setPlaybackRate.emit(1 + this.PLAYBACK_MODIFIER);
|
||||
setTimeout(() => {
|
||||
this.setPlaybackRate.emit(1);
|
||||
this.playback_rate_modified = false;
|
||||
}, seconds_to_wait * 1000);
|
||||
} else {
|
||||
// decrease speed
|
||||
this.setPlaybackRate.emit(1 - this.PLAYBACK_MODIFIER);
|
||||
setTimeout(() => {
|
||||
this.setPlaybackRate.emit(1);
|
||||
this.playback_rate_modified = false;
|
||||
}, seconds_to_wait * 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
startWatching() {
|
||||
this.watch_together_clicked = true;
|
||||
this.check_timeout = setInterval(() => {
|
||||
this.checkStream();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.check_timeout) { clearInterval(this.check_timeout); }
|
||||
if (this.update_timeout) { clearInterval(this.update_timeout); }
|
||||
this.started = false;
|
||||
this.server_started = false;
|
||||
this.watch_together_clicked = false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -24,10 +24,17 @@ export class CustomPlaylistsComponent implements OnInit {
|
||||
this.getAllPlaylists();
|
||||
}
|
||||
});
|
||||
|
||||
this.postsService.playlists_changed.subscribe(changed => {
|
||||
if (changed) {
|
||||
this.getAllPlaylists();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getAllPlaylists() {
|
||||
this.playlists_received = false;
|
||||
// must call getAllFiles as we need to get category playlists as well
|
||||
this.postsService.getAllFiles().subscribe(res => {
|
||||
this.playlists = res['playlists'];
|
||||
this.playlists_received = true;
|
||||
@@ -53,16 +60,15 @@ export class CustomPlaylistsComponent implements OnInit {
|
||||
goToPlaylist(info_obj) {
|
||||
const playlist = info_obj.file;
|
||||
const playlistID = playlist.id;
|
||||
const type = playlist.type;
|
||||
|
||||
if (playlist) {
|
||||
if (this.postsService.config['Extra']['download_only_mode']) {
|
||||
this.downloading_content[type][playlistID] = true;
|
||||
this.downloadPlaylist(playlist.fileNames, type, playlist.name, playlistID);
|
||||
this.downloadPlaylist(playlist.id, playlist.name);
|
||||
} else {
|
||||
localStorage.setItem('player_navigator', this.router.url);
|
||||
const fileNames = playlist.fileNames;
|
||||
this.router.navigate(['/player', {fileNames: fileNames.join('|nvr|'), type: type, id: playlistID, uid: playlistID, auto: playlist.auto}]);
|
||||
const routeParams = {playlist_id: playlistID};
|
||||
if (playlist.auto) { routeParams['auto'] = playlist.auto; }
|
||||
this.router.navigate(['/player', routeParams]);
|
||||
}
|
||||
} else {
|
||||
// playlist not found
|
||||
@@ -70,11 +76,12 @@ export class CustomPlaylistsComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
downloadPlaylist(fileNames, type, zipName = null, playlistID = null) {
|
||||
this.postsService.downloadFileFromServer(fileNames, type, zipName).subscribe(res => {
|
||||
if (playlistID) { this.downloading_content[type][playlistID] = false };
|
||||
const blob: Blob = res;
|
||||
saveAs(blob, zipName + '.zip');
|
||||
downloadPlaylist(playlist_id, playlist_name) {
|
||||
this.downloading_content[playlist_id] = true;
|
||||
this.postsService.downloadPlaylistFromServer(playlist_id).subscribe(res => {
|
||||
this.downloading_content[playlist_id] = false;
|
||||
const blob: any = res;
|
||||
saveAs(blob, playlist_name + '.zip');
|
||||
});
|
||||
|
||||
}
|
||||
@@ -97,7 +104,7 @@ export class CustomPlaylistsComponent implements OnInit {
|
||||
const index = args.index;
|
||||
const dialogRef = this.dialog.open(ModifyPlaylistComponent, {
|
||||
data: {
|
||||
playlist: playlist,
|
||||
playlist_id: playlist.id,
|
||||
width: '65vw'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
<div style="padding: 20px;">
|
||||
<div *ngFor="let session_downloads of downloads | keyvalue">
|
||||
<ng-container *ngIf="keys(session_downloads.value).length > 0">
|
||||
<div *ngFor="let session_downloads of downloads">
|
||||
<ng-container *ngIf="keys(session_downloads).length > 2">
|
||||
<mat-card style="padding-bottom: 30px; margin-bottom: 15px;">
|
||||
<h4 style="text-align: center;"><ng-container i18n="Session ID">Session ID:</ng-container> {{session_downloads.key}}
|
||||
<span *ngIf="session_downloads.key === postsService.session_id"> <ng-container i18n="Current session">(current)</ng-container></span>
|
||||
<h4 style="text-align: center;"><ng-container i18n="Session ID">Session ID:</ng-container> {{session_downloads['session_id']}}
|
||||
<span *ngIf="session_downloads['session_id'] === postsService.session_id"> <ng-container i18n="Current session">(current)</ng-container></span>
|
||||
</h4>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div *ngFor="let download of session_downloads.value | keyvalue: sort_downloads; let i = index;" class="col-12 my-1">
|
||||
<mat-card *ngIf="download.value" class="mat-elevation-z3">
|
||||
<app-download-item [download]="download.value" [queueNumber]="i+1" (cancelDownload)="clearDownload(session_downloads.key, download.value.uid)"></app-download-item>
|
||||
<div *ngFor="let download of session_downloads | keyvalue: sort_downloads; let i = index;" class="col-12 my-1">
|
||||
<mat-card *ngIf="download.key !== 'session_id' && download.key !== '_id' && download.value" class="mat-elevation-z3">
|
||||
<app-download-item [download]="download.value" [queueNumber]="i+1" (cancelDownload)="clearDownload(session_downloads['session_id'], download.value.uid)"></app-download-item>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button style="top: 15px;" (click)="clearDownloads(session_downloads.key)" mat-stroked-button color="warn"><ng-container i18n="clear all downloads action button">Clear all downloads</ng-container></button>
|
||||
<button style="top: 15px;" (click)="clearDownloads(session_downloads['session_id'])" mat-stroked-button color="warn"><ng-container i18n="clear all downloads action button">Clear all downloads</ng-container></button>
|
||||
</div>
|
||||
</mat-card>
|
||||
</ng-container>
|
||||
|
||||
@@ -35,7 +35,7 @@ import { Router } from '@angular/router';
|
||||
export class DownloadsComponent implements OnInit, OnDestroy {
|
||||
|
||||
downloads_check_interval = 1000;
|
||||
downloads = {};
|
||||
downloads = [];
|
||||
interval_id = null;
|
||||
|
||||
keys = Object.keys;
|
||||
@@ -137,6 +137,7 @@ export class DownloadsComponent implements OnInit, OnDestroy {
|
||||
this.downloads[session_id] = session_downloads_by_id;
|
||||
} else {
|
||||
for (let j = 0; j < session_download_ids.length; j++) {
|
||||
if (session_download_ids[j] === 'session_id' || session_download_ids[j] === '_id') continue;
|
||||
const download_id = session_download_ids[j];
|
||||
const download = new_downloads_by_session[session_id][download_id]
|
||||
if (!this.downloads[session_id][download_id]) {
|
||||
@@ -156,11 +157,10 @@ export class DownloadsComponent implements OnInit, OnDestroy {
|
||||
|
||||
downloadsValid() {
|
||||
let valid = false;
|
||||
const keys = this.keys(this.downloads);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
const value = this.downloads[key];
|
||||
if (this.keys(value).length > 0) {
|
||||
for (let i = 0; i < this.downloads.length; i++) {
|
||||
const session_downloads = this.downloads[i];
|
||||
if (!session_downloads) continue;
|
||||
if (this.keys(session_downloads).length > 2) {
|
||||
valid = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<mat-list-item role="listitem" *ngFor="let permission of available_permissions">
|
||||
<h3 matLine>{{permissionToLabel[permission] ? permissionToLabel[permission] : permission}}</h3>
|
||||
<span matLine>
|
||||
<mat-radio-group [disabled]="permission === 'settings' && role.name === 'admin'" (change)="changeRolePermissions($event, permission, permissions[permission])" [(ngModel)]="permissions[permission]" [attr.aria-label]="'Give role permission for ' + permission">
|
||||
<mat-radio-group [disabled]="permission === 'settings' && role.key === 'admin'" (change)="changeRolePermissions($event, permission, permissions[permission])" [(ngModel)]="permissions[permission]" [attr.aria-label]="'Give role permission for ' + permission">
|
||||
<mat-radio-button value="yes"><ng-container i18n="Yes">Yes</ng-container></mat-radio-button>
|
||||
<mat-radio-button value="no"><ng-container i18n="No">No</ng-container></mat-radio-button>
|
||||
</mat-radio-group>
|
||||
|
||||
@@ -47,7 +47,7 @@ export class ManageRoleComponent implements OnInit {
|
||||
}
|
||||
|
||||
changeRolePermissions(change, permission) {
|
||||
this.postsService.setRolePermission(this.role.name, permission, change.value).subscribe(res => {
|
||||
this.postsService.setRolePermission(this.role.key, permission, change.value).subscribe(res => {
|
||||
if (res['success']) {
|
||||
|
||||
} else {
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
</div>
|
||||
<button color="primary" [matMenuTriggerFor]="edit_roles_menu" class="edit-role" mat-raised-button><ng-container i18n="Edit role">Edit Role</ng-container></button>
|
||||
<mat-menu #edit_roles_menu="matMenu">
|
||||
<button (click)="openModifyRole(role)" mat-menu-item *ngFor="let role of roles">{{role.name}}</button>
|
||||
<button (click)="openModifyRole(role)" mat-menu-item *ngFor="let role of roles">{{role.key}}</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -78,16 +78,7 @@ export class ModifyUsersComponent implements OnInit, AfterViewInit {
|
||||
|
||||
getRoles() {
|
||||
this.postsService.getRoles().subscribe(res => {
|
||||
this.roles = [];
|
||||
const roles = res['roles'];
|
||||
const role_names = Object.keys(roles);
|
||||
for (let i = 0; i < role_names.length; i++) {
|
||||
const role_name = role_names[i];
|
||||
this.roles.push({
|
||||
name: role_name,
|
||||
permissions: roles[role_name]['permissions']
|
||||
});
|
||||
}
|
||||
this.roles = res['roles'];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<div class="row justify-content-center">
|
||||
<ng-container *ngIf="normal_files_received && paged_data">
|
||||
<div *ngFor="let file of paged_data; let i = index" class="mb-2 mt-2 d-flex justify-content-center" [ngClass]="[ postsService.card_size === 'small' ? 'col-2 small-col' : '', postsService.card_size === 'medium' ? 'col-6 col-lg-4 medium-col' : '', postsService.card_size === 'large' ? 'col-12 large-col' : '' ]">
|
||||
<app-unified-file-card [index]="i" [card_size]="postsService.card_size" [locale]="postsService.locale" (goToFile)="goToFile($event)" (goToSubscription)="goToSubscription($event)" [file_obj]="file" [use_youtubedl_archive]="postsService.config['Downloader']['use_youtubedl_archive']" [loading]="false" (deleteFile)="deleteFile($event)" [baseStreamPath]="postsService.path" [jwtString]="postsService.isLoggedIn ? '?jwt=' + this.postsService.token : ''"></app-unified-file-card>
|
||||
<app-unified-file-card [index]="i" [card_size]="postsService.card_size" [locale]="postsService.locale" (goToFile)="goToFile($event)" (goToSubscription)="goToSubscription($event)" [file_obj]="file" [use_youtubedl_archive]="postsService.config['Downloader']['use_youtubedl_archive']" [availablePlaylists]="playlists" (addFileToPlaylist)="addFileToPlaylist($event)" [loading]="false" (deleteFile)="deleteFile($event)" [baseStreamPath]="postsService.path" [jwtString]="postsService.isLoggedIn ? '?jwt=' + this.postsService.token : ''"></app-unified-file-card>
|
||||
</div>
|
||||
<div *ngIf="filtered_files.length === 0">
|
||||
<ng-container i18n="No videos found">No videos found.</ng-container>
|
||||
|
||||
@@ -50,6 +50,8 @@ export class RecentVideosComponent implements OnInit {
|
||||
}
|
||||
};
|
||||
filterProperty = this.filterProperties['upload_date'];
|
||||
|
||||
playlists = null;
|
||||
|
||||
pageSize = 10;
|
||||
paged_data = null;
|
||||
@@ -68,14 +70,27 @@ export class RecentVideosComponent implements OnInit {
|
||||
ngOnInit(): void {
|
||||
if (this.postsService.initialized) {
|
||||
this.getAllFiles();
|
||||
this.getAllPlaylists();
|
||||
}
|
||||
|
||||
this.postsService.service_initialized.subscribe(init => {
|
||||
if (init) {
|
||||
this.getAllFiles();
|
||||
this.getAllPlaylists();
|
||||
}
|
||||
});
|
||||
|
||||
this.postsService.files_changed.subscribe(changed => {
|
||||
if (changed) {
|
||||
this.getAllFiles();
|
||||
}
|
||||
});
|
||||
|
||||
this.postsService.playlists_changed.subscribe(changed => {
|
||||
if (changed) {
|
||||
this.getAllPlaylists();
|
||||
}
|
||||
});
|
||||
|
||||
// set filter property to cached
|
||||
const cached_filter_property = localStorage.getItem('filter_property');
|
||||
@@ -84,6 +99,12 @@ export class RecentVideosComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
getAllPlaylists() {
|
||||
this.postsService.getPlaylists().subscribe(res => {
|
||||
this.playlists = res['playlists'];
|
||||
});
|
||||
}
|
||||
|
||||
// search
|
||||
|
||||
onSearchInputChanged(newvalue) {
|
||||
@@ -127,12 +148,11 @@ export class RecentVideosComponent implements OnInit {
|
||||
this.normal_files_received = false;
|
||||
this.postsService.getAllFiles().subscribe(res => {
|
||||
this.files = res['files'];
|
||||
this.files.sort(this.sortFiles);
|
||||
for (let i = 0; i < this.files.length; i++) {
|
||||
const file = this.files[i];
|
||||
file.duration = typeof file.duration !== 'string' ? file.duration : this.durationStringToNumber(file.duration);
|
||||
file.index = i;
|
||||
}
|
||||
this.files.sort(this.sortFiles);
|
||||
if (this.search_mode) {
|
||||
this.filterFiles(this.search_text);
|
||||
} else {
|
||||
@@ -167,15 +187,14 @@ export class RecentVideosComponent implements OnInit {
|
||||
const sub = this.postsService.getSubscriptionByID(file.sub_id);
|
||||
if (sub.streamingOnly) {
|
||||
// streaming only mode subscriptions
|
||||
!new_tab ? this.router.navigate(['/player', {name: file.id,
|
||||
url: file.requested_formats ? file.requested_formats[0].url : file.url}])
|
||||
: window.open(`/#/player;name=${file.id};url=${file.requested_formats ? file.requested_formats[0].url : file.url}`);
|
||||
// !new_tab ? this.router.navigate(['/player', {name: file.id,
|
||||
// url: file.requested_formats ? file.requested_formats[0].url : file.url}])
|
||||
// : window.open(`/#/player;name=${file.id};url=${file.requested_formats ? file.requested_formats[0].url : file.url}`);
|
||||
} else {
|
||||
// normal subscriptions
|
||||
!new_tab ? this.router.navigate(['/player', {fileNames: file.id,
|
||||
type: file.isAudio ? 'audio' : 'video', subscriptionName: sub.name,
|
||||
subPlaylist: sub.isPlaylist}])
|
||||
: window.open(`/#/player;fileNames=${file.id};type=${file.isAudio ? 'audio' : 'video'};subscriptionName=${sub.name};subPlaylist=${sub.isPlaylist}`);
|
||||
!new_tab ? this.router.navigate(['/player', {uid: file.uid,
|
||||
type: file.isAudio ? 'audio' : 'video', sub_id: sub.id}])
|
||||
: window.open(`/#/player;uid=${file.uid};type=${file.isAudio ? 'audio' : 'video'};sub_id=${sub.id}`);
|
||||
}
|
||||
} else {
|
||||
// normal files
|
||||
@@ -202,8 +221,7 @@ export class RecentVideosComponent implements OnInit {
|
||||
const type = file.isAudio ? 'audio' : 'video';
|
||||
const ext = type === 'audio' ? '.mp3' : '.mp4'
|
||||
const sub = this.postsService.getSubscriptionByID(file.sub_id);
|
||||
this.postsService.downloadFileFromServer(file.id, type, null, null, sub.name, sub.isPlaylist,
|
||||
this.postsService.user ? this.postsService.user.uid : null, null).subscribe(res => {
|
||||
this.postsService.downloadFileFromServer(file.uid).subscribe(res => {
|
||||
const blob: Blob = res;
|
||||
saveAs(blob, file.id + ext);
|
||||
}, err => {
|
||||
@@ -216,14 +234,14 @@ export class RecentVideosComponent implements OnInit {
|
||||
const ext = type === 'audio' ? '.mp3' : '.mp4'
|
||||
const name = file.id;
|
||||
this.downloading_content[type][name] = true;
|
||||
this.postsService.downloadFileFromServer(name, type).subscribe(res => {
|
||||
this.postsService.downloadFileFromServer(file.uid).subscribe(res => {
|
||||
this.downloading_content[type][name] = false;
|
||||
const blob: Blob = res;
|
||||
saveAs(blob, decodeURIComponent(name) + ext);
|
||||
|
||||
if (!this.postsService.config.Extra.file_manager_enabled) {
|
||||
// tell server to delete the file once downloaded
|
||||
this.postsService.deleteFile(name, type).subscribe(delRes => {
|
||||
this.postsService.deleteFile(file.uid).subscribe(delRes => {
|
||||
// reload mp4s
|
||||
this.getAllFiles();
|
||||
});
|
||||
@@ -239,19 +257,17 @@ export class RecentVideosComponent implements OnInit {
|
||||
const blacklistMode = args.blacklistMode;
|
||||
|
||||
if (file.sub_id) {
|
||||
this.deleteSubscriptionFile(file, index, blacklistMode);
|
||||
this.deleteSubscriptionFile(file, blacklistMode);
|
||||
} else {
|
||||
this.deleteNormalFile(file, index, blacklistMode);
|
||||
this.deleteNormalFile(file, blacklistMode);
|
||||
}
|
||||
}
|
||||
|
||||
deleteNormalFile(file, index, blacklistMode = false) {
|
||||
this.postsService.deleteFile(file.uid, file.isAudio ? 'audio' : 'video', blacklistMode).subscribe(result => {
|
||||
deleteNormalFile(file, blacklistMode = false) {
|
||||
this.postsService.deleteFile(file.uid, blacklistMode).subscribe(result => {
|
||||
if (result) {
|
||||
this.postsService.openSnackBar('Delete success!', 'OK.');
|
||||
this.files.splice(file.index, 1);
|
||||
for (let i = 0; i < this.files.length; i++) { this.files[i].index = i }
|
||||
this.filterByProperty(this.filterProperty['property']);
|
||||
this.removeFileCard(file);
|
||||
} else {
|
||||
this.postsService.openSnackBar('Delete failed!', 'OK.');
|
||||
}
|
||||
@@ -260,27 +276,53 @@ export class RecentVideosComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
deleteSubscriptionFile(file, index, blacklistMode = false) {
|
||||
deleteSubscriptionFile(file, blacklistMode = false) {
|
||||
if (blacklistMode) {
|
||||
this.deleteForever(file, index);
|
||||
this.deleteForever(file);
|
||||
} else {
|
||||
this.deleteAndRedownload(file, index);
|
||||
this.deleteAndRedownload(file);
|
||||
}
|
||||
}
|
||||
|
||||
deleteAndRedownload(file, index) {
|
||||
deleteAndRedownload(file) {
|
||||
const sub = this.postsService.getSubscriptionByID(file.sub_id);
|
||||
this.postsService.deleteSubscriptionFile(sub, file.id, false, file.uid).subscribe(res => {
|
||||
this.postsService.openSnackBar(`Successfully deleted file: '${file.id}'`);
|
||||
this.files.splice(index, 1);
|
||||
this.removeFileCard(file);
|
||||
});
|
||||
}
|
||||
|
||||
deleteForever(file, index) {
|
||||
deleteForever(file) {
|
||||
const sub = this.postsService.getSubscriptionByID(file.sub_id);
|
||||
this.postsService.deleteSubscriptionFile(sub, file.id, true, file.uid).subscribe(res => {
|
||||
this.postsService.openSnackBar(`Successfully deleted file: '${file.id}'`);
|
||||
this.files.splice(index, 1);
|
||||
this.removeFileCard(file);
|
||||
});
|
||||
}
|
||||
|
||||
removeFileCard(file_to_remove) {
|
||||
const index = this.files.map(e => e.uid).indexOf(file_to_remove.uid);
|
||||
this.files.splice(index, 1);
|
||||
if (this.search_mode) {
|
||||
this.filterFiles(this.search_text);
|
||||
}
|
||||
this.filterByProperty(this.filterProperty['property']);
|
||||
}
|
||||
|
||||
addFileToPlaylist(info_obj) {
|
||||
const file = info_obj['file'];
|
||||
const playlist_id = info_obj['playlist_id'];
|
||||
const playlist = this.playlists.find(potential_playlist => potential_playlist['id'] === playlist_id);
|
||||
this.postsService.addFileToPlaylist(playlist_id, file['uid']).subscribe(res => {
|
||||
if (res['success']) {
|
||||
this.postsService.openSnackBar(`Successfully added ${file.title} to ${playlist.title}!`);
|
||||
this.postsService.playlists_changed.next(true);
|
||||
} else {
|
||||
this.postsService.openSnackBar(`Failed to add ${file.title} to ${playlist.title}! Unknown error.`);
|
||||
}
|
||||
}, err => {
|
||||
console.error(err);
|
||||
this.postsService.openSnackBar(`Failed to add ${file.title} to ${playlist.title}! See browser console for error.`);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,12 @@
|
||||
<ng-container *ngIf="!is_playlist && !loading">
|
||||
<button (click)="openFileInfoDialog()" mat-menu-item><mat-icon>info</mat-icon><ng-container i18n="Video info button">Info</ng-container></button>
|
||||
<button (click)="navigateToSubscription()" mat-menu-item *ngIf="file_obj.sub_id"><mat-icon>{{file_obj.isAudio ? 'library_music' : 'video_library'}}</mat-icon> <ng-container i18n="Go to subscription menu item">Go to subscription</ng-container></button>
|
||||
<button *ngIf="availablePlaylists" [matMenuTriggerFor]="addtoplaylist" mat-menu-item><mat-icon>playlist_add</mat-icon> <ng-container i18n="Add to playlist menu item">Add to playlist</ng-container></button>
|
||||
<mat-menu #addtoplaylist="matMenu">
|
||||
<ng-container *ngFor="let playlist of availablePlaylists">
|
||||
<button *ngIf="(playlist.type === 'audio') === file_obj.isAudio" [disabled]="playlist.uids?.includes(file_obj.uid)" (click)="emitAddFileToPlaylist(playlist.id)" mat-menu-item>{{playlist.name}}</button>
|
||||
</ng-container>
|
||||
</mat-menu>
|
||||
<mat-divider></mat-divider>
|
||||
<button *ngIf="file_obj.sub_id" (click)="emitDeleteFile()" mat-menu-item>
|
||||
<mat-icon>restore</mat-icon><ng-container i18n="Delete and redownload subscription video button">Delete and redownload</ng-container>
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 5px;
|
||||
z-index: 99999;
|
||||
z-index: 999;
|
||||
width: calc(100% - 8px);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -46,9 +46,11 @@ export class UnifiedFileCardComponent implements OnInit {
|
||||
@Input() locale = null;
|
||||
@Input() baseStreamPath = null;
|
||||
@Input() jwtString = null;
|
||||
@Input() availablePlaylists = null;
|
||||
@Output() goToFile = new EventEmitter<any>();
|
||||
@Output() goToSubscription = new EventEmitter<any>();
|
||||
@Output() deleteFile = new EventEmitter<any>();
|
||||
@Output() addFileToPlaylist = new EventEmitter<any>();
|
||||
@Output() editPlaylist = new EventEmitter<any>();
|
||||
|
||||
|
||||
@@ -70,7 +72,7 @@ export class UnifiedFileCardComponent implements OnInit {
|
||||
}
|
||||
|
||||
if (this.file_obj && this.file_obj.thumbnailPath) {
|
||||
this.thumbnailBlobURL = `${this.baseStreamPath}thumbnail/${encodeURIComponent(this.file_obj.thumbnailPath)}${this.jwtString}`;
|
||||
this.thumbnailBlobURL = `${this.baseStreamPath}thumbnail/${this.file_obj.uid}${this.jwtString}`;
|
||||
/*const mime = getMimeByFilename(this.file_obj.thumbnailPath);
|
||||
const blob = new Blob([new Uint8Array(this.file_obj.thumbnailBlob.data)], {type: mime});
|
||||
const bloburl = URL.createObjectURL(blob);
|
||||
@@ -86,6 +88,13 @@ export class UnifiedFileCardComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
emitAddFileToPlaylist(playlist_id) {
|
||||
this.addFileToPlaylist.emit({
|
||||
file: this.file_obj,
|
||||
playlist_id: playlist_id
|
||||
});
|
||||
}
|
||||
|
||||
navigateToFile(event) {
|
||||
this.goToFile.emit({file: this.file_obj, event: event});
|
||||
}
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
<mat-label *ngIf="type === 'audio'"><ng-container i18n="Audio files title">Audio files</ng-container></mat-label>
|
||||
<mat-label *ngIf="type === 'video'"><ng-container i18n="Videos title">Videos</ng-container></mat-label>
|
||||
<mat-select [formControl]="filesSelect" multiple required aria-required>
|
||||
<ng-container *ngIf="filesToSelectFrom"><mat-option *ngFor="let file of filesToSelectFrom" [value]="file.id">{{file.id}}</mat-option></ng-container>
|
||||
<ng-container *ngIf="audiosToSelectFrom && type === 'audio'"><mat-option *ngFor="let file of audiosToSelectFrom" [value]="file.id">{{file.id}}</mat-option></ng-container>
|
||||
<ng-container *ngIf="videosToSelectFrom && type === 'video'"><mat-option *ngFor="let file of videosToSelectFrom" [value]="file.id">{{file.id}}</mat-option></ng-container>
|
||||
<ng-container *ngIf="filesToSelectFrom"><mat-option *ngFor="let file of filesToSelectFrom" [value]="file.uid">{{file.id}}</mat-option></ng-container>
|
||||
<ng-container *ngIf="audiosToSelectFrom && type === 'audio'"><mat-option *ngFor="let file of audiosToSelectFrom" [value]="file.uid">{{file.id}}</mat-option></ng-container>
|
||||
<ng-container *ngIf="videosToSelectFrom && type === 'video'"><mat-option *ngFor="let file of videosToSelectFrom" [value]="file.uid">{{file.id}}</mat-option></ng-container>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<!-- No videos available -->
|
||||
|
||||
@@ -51,9 +51,8 @@ export class CreatePlaylistComponent implements OnInit {
|
||||
|
||||
createPlaylist() {
|
||||
const thumbnailURL = this.getThumbnailURL();
|
||||
const duration = this.calculateDuration();
|
||||
this.create_in_progress = true;
|
||||
this.postsService.createPlaylist(this.name, this.filesSelect.value, this.type, thumbnailURL, duration).subscribe(res => {
|
||||
this.postsService.createPlaylist(this.name, this.filesSelect.value, this.type, thumbnailURL).subscribe(res => {
|
||||
this.create_in_progress = false;
|
||||
if (res['success']) {
|
||||
this.dialogRef.close(true);
|
||||
@@ -78,36 +77,4 @@ export class CreatePlaylistComponent implements OnInit {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getDuration(file_id) {
|
||||
let properFilesToSelectFrom = this.filesToSelectFrom;
|
||||
if (!this.filesToSelectFrom) {
|
||||
properFilesToSelectFrom = this.type === 'audio' ? this.audiosToSelectFrom : this.videosToSelectFrom;
|
||||
}
|
||||
for (let i = 0; i < properFilesToSelectFrom.length; i++) {
|
||||
const file = properFilesToSelectFrom[i];
|
||||
if (file.id === file_id) {
|
||||
return file.duration;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
calculateDuration() {
|
||||
let sum = 0;
|
||||
for (let i = 0; i < this.filesSelect.value.length; i++) {
|
||||
const duration_val = this.getDuration(this.filesSelect.value[i]);
|
||||
sum += typeof duration_val === 'string' ? this.durationStringToNumber(duration_val) : duration_val;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
durationStringToNumber(dur_str) {
|
||||
let num_sum = 0;
|
||||
const dur_str_parts = dur_str.split(':');
|
||||
for (let i = dur_str_parts.length-1; i >= 0; i--) {
|
||||
num_sum += parseInt(dur_str_parts[i])*(60**(dur_str_parts.length-1-i));
|
||||
}
|
||||
return num_sum;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,44 @@
|
||||
<h4 mat-dialog-title><ng-container i18n="Modify playlist dialog title">Modify playlist</ng-container></h4>
|
||||
|
||||
<mat-dialog-content>
|
||||
<!-- Playlist info -->
|
||||
<div>
|
||||
<mat-form-field color="accent">
|
||||
<input matInput placeholder="Name" i18n-placeholder="Name" [(ngModel)]="playlist.name">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 10px; height: 40px;">
|
||||
<div style="float: left">
|
||||
<span *ngIf="reverse_order === false" i18n="Normal order">Normal order </span>
|
||||
<span *ngIf="reverse_order === true" i18n="Reverse order">Reverse order </span>
|
||||
<button (click)="togglePlaylistOrder()" mat-icon-button><mat-icon>{{!reverse_order ? 'arrow_downward' : 'arrow_upward'}}</mat-icon></button>
|
||||
<div *ngIf="playlist">
|
||||
<!-- Playlist info -->
|
||||
<div>
|
||||
<mat-form-field color="accent">
|
||||
<input matInput placeholder="Name" i18n-placeholder="Name" [(ngModel)]="playlist.name">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div style="float: right">
|
||||
<button [disabled]="available_files.length === 0" mat-stroked-button [matMenuTriggerFor]="menu"><ng-container i18n="Add content">Add content</ng-container></button>
|
||||
<div>
|
||||
<mat-checkbox [(ngModel)]="playlist.randomize_order"><ng-container i18n="Randomize order when playing checkbox label">Randomize order when playing</ng-container></mat-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Playlist order -->
|
||||
<mat-button-toggle-group class="media-list" cdkDropList (cdkDropListDropped)="drop($event)" style="width: 80%; left: 9%" vertical name="videoSelect" aria-label="Video Select" #group="matButtonToggleGroup">
|
||||
<!-- The following for loop can be optimized but it requires a pipe https://stackoverflow.com/a/35703364/8088021 -->
|
||||
<mat-button-toggle class="media-box" cdkDrag *ngFor="let playlist_item of (reverse_order ? playlist.fileNames.slice().reverse() : playlist.fileNames); let i = index" [checked]="false"><div><div class="playlist-item-text">{{playlist_item}}</div> <button (click)="removeContent(i)" class="remove-item-button" mat-icon-button><mat-icon>cancel</mat-icon></button></div></mat-button-toggle>
|
||||
</mat-button-toggle-group>
|
||||
|
||||
<mat-menu #menu="matMenu">
|
||||
<button *ngFor="let file of available_files" (click)="addContent(file)" mat-menu-item>{{file}}</button>
|
||||
</mat-menu>
|
||||
<div style="margin-bottom: 10px; height: 40px;">
|
||||
<div style="float: left">
|
||||
<span *ngIf="reverse_order === false" i18n="Normal order">Normal order </span>
|
||||
<span *ngIf="reverse_order === true" i18n="Reverse order">Reverse order </span>
|
||||
<button (click)="togglePlaylistOrder()" mat-icon-button><mat-icon>{{!reverse_order ? 'arrow_downward' : 'arrow_upward'}}</mat-icon></button>
|
||||
</div>
|
||||
|
||||
<div style="float: right">
|
||||
<button [disabled]="available_files.length === 0" mat-stroked-button [matMenuTriggerFor]="menu"><ng-container i18n="Add content">Add content</ng-container></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Playlist order -->
|
||||
<mat-button-toggle-group class="media-list" cdkDropList (cdkDropListDropped)="drop($event)" style="width: 80%; left: 9%" vertical name="videoSelect" aria-label="Video Select" #group="matButtonToggleGroup">
|
||||
<!-- The following for loop can be optimized but it requires a pipe https://stackoverflow.com/a/35703364/8088021 -->
|
||||
<mat-button-toggle class="media-box" cdkDrag *ngFor="let playlist_item of (reverse_order ? playlist_file_objs.slice().reverse() : playlist_file_objs); let i = index" [checked]="false"><div><div class="playlist-item-text">{{playlist_item.title}}</div> <button (click)="removeContent(i)" class="remove-item-button" mat-icon-button><mat-icon>cancel</mat-icon></button></div></mat-button-toggle>
|
||||
</mat-button-toggle-group>
|
||||
|
||||
<mat-menu #menu="matMenu">
|
||||
<button *ngFor="let file of available_files" (click)="addContent(file)" mat-menu-item>{{file.title}}</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions>
|
||||
<!-- Save -->
|
||||
<button [disabled]="!playlistChanged()" (click)="updatePlaylist()" [disabled]="playlistChanged()" mat-raised-button color="accent"><ng-container i18n="Save">Save</ng-container></button>
|
||||
<button [disabled]="!playlist || !playlistChanged()" (click)="updatePlaylist()" mat-raised-button color="accent"><ng-container i18n="Save">Save</ng-container></button>
|
||||
</mat-dialog-actions>
|
||||
@@ -10,8 +10,12 @@ import { PostsService } from 'app/posts.services';
|
||||
})
|
||||
export class ModifyPlaylistComponent implements OnInit {
|
||||
|
||||
playlist_id = null;
|
||||
|
||||
original_playlist = null;
|
||||
playlist = null;
|
||||
playlist_file_objs = null;
|
||||
|
||||
available_files = [];
|
||||
all_files = [];
|
||||
playlist_updated = false;
|
||||
@@ -23,9 +27,8 @@ export class ModifyPlaylistComponent implements OnInit {
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.data) {
|
||||
this.playlist = JSON.parse(JSON.stringify(this.data.playlist));
|
||||
this.original_playlist = JSON.parse(JSON.stringify(this.data.playlist));
|
||||
this.getFiles();
|
||||
this.playlist_id = this.data.playlist_id;
|
||||
this.getPlaylist();
|
||||
}
|
||||
|
||||
this.reverse_order = localStorage.getItem('default_playlist_order_reversed') === 'true';
|
||||
@@ -44,41 +47,47 @@ export class ModifyPlaylistComponent implements OnInit {
|
||||
}
|
||||
|
||||
processFiles(new_files = null) {
|
||||
if (new_files) { this.all_files = new_files.map(file => file.id); }
|
||||
this.available_files = this.all_files.filter(e => !this.playlist.fileNames.includes(e))
|
||||
if (new_files) { this.all_files = new_files; }
|
||||
this.available_files = this.all_files.filter(e => !this.playlist_file_objs.includes(e))
|
||||
}
|
||||
|
||||
updatePlaylist() {
|
||||
this.playlist['uids'] = this.playlist_file_objs.map(playlist_file_obj => playlist_file_obj['uid'])
|
||||
this.postsService.updatePlaylist(this.playlist).subscribe(res => {
|
||||
this.playlist_updated = true;
|
||||
this.postsService.openSnackBar('Playlist updated successfully.');
|
||||
this.getPlaylist();
|
||||
this.postsService.playlists_changed.next(true);
|
||||
});
|
||||
}
|
||||
|
||||
playlistChanged() {
|
||||
return JSON.stringify(this.playlist) === JSON.stringify(this.original_playlist);
|
||||
return JSON.stringify(this.playlist) !== JSON.stringify(this.original_playlist);
|
||||
}
|
||||
|
||||
getPlaylist() {
|
||||
this.postsService.getPlaylist(this.playlist.id, this.playlist.type, null).subscribe(res => {
|
||||
this.postsService.getPlaylist(this.playlist_id, null, true).subscribe(res => {
|
||||
if (res['playlist']) {
|
||||
this.playlist = res['playlist'];
|
||||
this.playlist_file_objs = res['file_objs'];
|
||||
this.original_playlist = JSON.parse(JSON.stringify(this.playlist));
|
||||
this.getFiles();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
addContent(file) {
|
||||
this.playlist.fileNames.push(file);
|
||||
this.playlist_file_objs.push(file);
|
||||
this.playlist.uids.push(file.uid);
|
||||
this.processFiles();
|
||||
}
|
||||
|
||||
removeContent(index) {
|
||||
if (this.reverse_order) {
|
||||
index = this.playlist.fileNames.length - 1 - index;
|
||||
index = this.playlist_file_objs.length - 1 - index;
|
||||
}
|
||||
this.playlist.fileNames.splice(index, 1);
|
||||
this.playlist_file_objs.splice(index, 1);
|
||||
this.playlist.uids.splice(index, 1);
|
||||
this.processFiles();
|
||||
}
|
||||
|
||||
@@ -89,10 +98,10 @@ export class ModifyPlaylistComponent implements OnInit {
|
||||
|
||||
drop(event: CdkDragDrop<string[]>) {
|
||||
if (this.reverse_order) {
|
||||
event.previousIndex = this.playlist.fileNames.length - 1 - event.previousIndex;
|
||||
event.currentIndex = this.playlist.fileNames.length - 1 - event.currentIndex;
|
||||
event.previousIndex = this.playlist_file_objs.length - 1 - event.previousIndex;
|
||||
event.currentIndex = this.playlist_file_objs.length - 1 - event.currentIndex;
|
||||
}
|
||||
moveItemInArray(this.playlist.fileNames, event.previousIndex, event.currentIndex);
|
||||
moveItemInArray(this.playlist_file_objs, event.previousIndex, event.currentIndex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<h4 mat-dialog-title>
|
||||
<ng-container *ngIf="is_playlist" i18n="Share playlist dialog title">Share playlist</ng-container>
|
||||
<ng-container *ngIf="!is_playlist && type === 'video'" i18n="Share video dialog title">Share video</ng-container>
|
||||
<ng-container *ngIf="!is_playlist && type === 'audio'" i18n="Share audio dialog title">Share audio</ng-container>
|
||||
<ng-container *ngIf="!is_playlist" i18n="Share video dialog title">Share file</ng-container>
|
||||
</h4>
|
||||
|
||||
<mat-dialog-content>
|
||||
|
||||
@@ -11,7 +11,6 @@ import { PostsService } from 'app/posts.services';
|
||||
})
|
||||
export class ShareMediaDialogComponent implements OnInit {
|
||||
|
||||
type = null;
|
||||
uid = null;
|
||||
uuid = null;
|
||||
share_url = null;
|
||||
@@ -26,14 +25,13 @@ export class ShareMediaDialogComponent implements OnInit {
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.data) {
|
||||
this.type = this.data.type;
|
||||
this.uid = this.data.uid;
|
||||
this.uuid = this.data.uuid;
|
||||
this.sharing_enabled = this.data.sharing_enabled;
|
||||
this.is_playlist = this.data.is_playlist;
|
||||
this.current_timestamp = (this.data.current_timestamp / 1000).toFixed(2);
|
||||
|
||||
const arg = (this.is_playlist ? ';id=' : ';uid=');
|
||||
const arg = (this.is_playlist ? ';playlist_id=' : ';uid=');
|
||||
this.default_share_url = window.location.href.split(';')[0] + arg + this.uid;
|
||||
if (this.uuid) {
|
||||
this.default_share_url += ';uuid=' + this.uuid;
|
||||
@@ -65,7 +63,7 @@ export class ShareMediaDialogComponent implements OnInit {
|
||||
|
||||
sharingChanged(event) {
|
||||
if (event.checked) {
|
||||
this.postsService.enableSharing(this.uid, this.type, this.is_playlist).subscribe(res => {
|
||||
this.postsService.enableSharing(this.uid, this.is_playlist).subscribe(res => {
|
||||
if (res['success']) {
|
||||
this.openSnackBar('Sharing enabled.');
|
||||
this.sharing_enabled = true;
|
||||
@@ -76,7 +74,7 @@ export class ShareMediaDialogComponent implements OnInit {
|
||||
this.openSnackBar('Failed to enable sharing - server error.');
|
||||
});
|
||||
} else {
|
||||
this.postsService.disableSharing(this.uid, this.type, this.is_playlist).subscribe(res => {
|
||||
this.postsService.disableSharing(this.uid, this.is_playlist).subscribe(res => {
|
||||
if (res['success']) {
|
||||
this.openSnackBar('Sharing disabled.');
|
||||
this.sharing_enabled = false;
|
||||
|
||||
@@ -56,7 +56,7 @@ export class FileCardComponent implements OnInit {
|
||||
|
||||
deleteFile(blacklistMode = false) {
|
||||
if (!this.playlist) {
|
||||
this.postsService.deleteFile(this.uid, this.isAudio ? 'audio' : 'video', blacklistMode).subscribe(result => {
|
||||
this.postsService.deleteFile(this.uid, blacklistMode).subscribe(result => {
|
||||
if (result) {
|
||||
this.openSnackBar('Delete success!', 'OK.');
|
||||
this.removeFile.emit(this.name);
|
||||
@@ -84,7 +84,7 @@ export class FileCardComponent implements OnInit {
|
||||
editPlaylistDialog() {
|
||||
const dialogRef = this.dialog.open(ModifyPlaylistComponent, {
|
||||
data: {
|
||||
playlist: this.playlist,
|
||||
playlist_id: this.playlist.id,
|
||||
width: '65vw'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ export class H401Interceptor implements HttpInterceptor {
|
||||
return next.handle(request).pipe(catchError(err => {
|
||||
if (err.status === 401) {
|
||||
localStorage.setItem('jwt_token', null);
|
||||
if (this.router.url !== '/login') {
|
||||
if (this.router.url !== '/login' && !this.router.url.includes('player')) {
|
||||
this.router.navigate(['/login']).then(() => {
|
||||
this.openSnackBar('Login expired, please login again.');
|
||||
});
|
||||
|
||||
@@ -124,6 +124,10 @@ mat-form-field.mat-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.advanced-input-time {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.edit-button {
|
||||
margin-left: 10px;
|
||||
top: -5px;
|
||||
|
||||
@@ -20,11 +20,16 @@
|
||||
</ng-container>
|
||||
</mat-label>
|
||||
<mat-select [ngModelOptions]="{standalone: true}" [(ngModel)]="selectedQuality">
|
||||
<ng-container *ngFor="let option of qualityOptions[(audioOnly) ? 'audio' : 'video']">
|
||||
<mat-option *ngIf="option.value === '' || url && cachedAvailableFormats[url] && cachedAvailableFormats[url]['formats'] && cachedAvailableFormats[url]['formats'][(audioOnly) ? 'audio' : 'video'][option.value]" [value]="option.value">
|
||||
{{option.label}}
|
||||
<mat-option [value]="''">
|
||||
Max
|
||||
</mat-option>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="url && cachedAvailableFormats && cachedAvailableFormats[url]?.formats">
|
||||
<ng-container *ngFor="let option of cachedAvailableFormats[url]['formats'][audioOnly ? 'audio' : 'video']">
|
||||
<mat-option *ngIf="option.key !== 'best_audio_format'" [value]="option">
|
||||
{{option.key}}
|
||||
</mat-option>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</mat-select>
|
||||
<div class="spinner-div" *ngIf="url !== '' && cachedAvailableFormats[url] && cachedAvailableFormats[url]['formats_loading']">
|
||||
<mat-spinner [diameter]="25"></mat-spinner>
|
||||
@@ -129,7 +134,7 @@
|
||||
</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div *ngIf="!youtubeAuthDisabledOverride" class="col-12 col-sm-6 mt-2">
|
||||
<div *ngIf="!youtubeAuthDisabledOverride" class="col-12 col-sm-6 mt-3">
|
||||
<mat-checkbox color="accent" [disabled]="current_download" (change)="youtubeAuthEnabledChanged($event)" [(ngModel)]="youtubeAuthEnabled" style="z-index: 999" [ngModelOptions]="{standalone: true}">
|
||||
<ng-container i18n="Use authentication checkbox">
|
||||
Use authentication
|
||||
@@ -139,11 +144,26 @@
|
||||
<input [(ngModel)]="youtubeUsername" [ngModelOptions]="{standalone: true}" [disabled]="!youtubeAuthEnabled" matInput placeholder="Username" i18n-placeholder="YT Username placeholder">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div *ngIf="!youtubeAuthDisabledOverride" class="col-12 col-sm-6 mt-2">
|
||||
<div *ngIf="!youtubeAuthDisabledOverride" class="col-12 col-sm-6 mt-3">
|
||||
<mat-form-field style="margin-top: 31px;" color="accent" class="advanced-input">
|
||||
<input [(ngModel)]="youtubePassword" type="password" [ngModelOptions]="{standalone: true}" [disabled]="!youtubeAuthEnabled" matInput placeholder="Password" i18n-placeholder="YT Password placeholder">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-12 col-sm-6 mt-3">
|
||||
<mat-checkbox color="accent" [disabled]="current_download" [(ngModel)]="cropFile" style="z-index: 999" [ngModelOptions]="{standalone: true}">
|
||||
<ng-container i18n="Crop video checkbox">
|
||||
Crop file
|
||||
</ng-container>
|
||||
</mat-checkbox>
|
||||
<mat-form-field color="accent" class="advanced-input">
|
||||
<input [(ngModel)]="cropFileStart" type="number" [ngModelOptions]="{standalone: true}" [disabled]="!cropFile" matInput placeholder="Crop from (seconds)" i18n-placeholder="Crop from placeholder">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-12 col-sm-6 mt-3">
|
||||
<mat-form-field style="margin-top: 31px;" color="accent" class="advanced-input">
|
||||
<input [(ngModel)]="cropFileEnd" type="number" [ngModelOptions]="{standalone: true}" [disabled]="!cropFile" matInput placeholder="Crop to (seconds)" i18n-placeholder="Crop to placeholder">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</mat-expansion-panel>
|
||||
|
||||
@@ -54,6 +54,9 @@ export class MainComponent implements OnInit {
|
||||
youtubeAuthEnabled = false;
|
||||
youtubeUsername = null;
|
||||
youtubePassword = null;
|
||||
cropFile = false;
|
||||
cropFileStart = null;
|
||||
cropFileEnd = null;
|
||||
urlError = false;
|
||||
path = '';
|
||||
url = '';
|
||||
@@ -339,12 +342,8 @@ export class MainComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
public goToFile(name, isAudio, uid) {
|
||||
if (isAudio) {
|
||||
this.downloadHelperMp3(name, uid, false, false, null, true);
|
||||
} else {
|
||||
this.downloadHelperMp4(name, uid, false, false, null, true);
|
||||
}
|
||||
public goToFile(container, isAudio, uid) {
|
||||
this.downloadHelper(container, isAudio ? 'audio' : 'video', false, false, null, true);
|
||||
}
|
||||
|
||||
public goToPlaylist(playlistID, type) {
|
||||
@@ -352,7 +351,7 @@ export class MainComponent implements OnInit {
|
||||
if (playlist) {
|
||||
if (this.downloadOnlyMode) {
|
||||
this.downloading_content[type][playlistID] = true;
|
||||
this.downloadPlaylist(playlist.fileNames, type, playlist.name, playlistID);
|
||||
this.downloadPlaylist(playlist);
|
||||
} else {
|
||||
localStorage.setItem('player_navigator', this.router.url);
|
||||
const fileNames = playlist.fileNames;
|
||||
@@ -376,56 +375,26 @@ export class MainComponent implements OnInit {
|
||||
|
||||
// download helpers
|
||||
|
||||
downloadHelperMp3(name, uid, is_playlist = false, forceView = false, new_download = null, navigate_mode = false) {
|
||||
downloadHelper(container, type, is_playlist = false, force_view = false, new_download = null, navigate_mode = false) {
|
||||
this.downloadingfile = false;
|
||||
if (this.multiDownloadMode && !this.downloadOnlyMode && !navigate_mode) {
|
||||
// do nothing
|
||||
this.reloadRecentVideos();
|
||||
} else {
|
||||
// if download only mode, just download the file. no redirect
|
||||
if (forceView === false && this.downloadOnlyMode && !this.iOS) {
|
||||
if (force_view === false && this.downloadOnlyMode && !this.iOS) {
|
||||
if (is_playlist) {
|
||||
const zipName = name[0].split(' ')[0] + name[1].split(' ')[0];
|
||||
this.downloadPlaylist(name, 'audio', zipName);
|
||||
this.downloadPlaylist(container['uid']);
|
||||
} else {
|
||||
this.downloadAudioFile(decodeURI(name));
|
||||
this.downloadFileFromServer(container, type);
|
||||
}
|
||||
this.reloadRecentVideos();
|
||||
} else {
|
||||
localStorage.setItem('player_navigator', this.router.url.split(';')[0]);
|
||||
if (is_playlist) {
|
||||
this.router.navigate(['/player', {fileNames: name.join('|nvr|'), type: 'audio'}]);
|
||||
this.router.navigate(['/player', {playlist_id: container['id'], type: type}]);
|
||||
} else {
|
||||
this.router.navigate(['/player', {type: 'audio', uid: uid}]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove download from current downloads
|
||||
this.removeDownloadFromCurrentDownloads(new_download);
|
||||
}
|
||||
|
||||
downloadHelperMp4(name, uid, is_playlist = false, forceView = false, new_download = null, navigate_mode = false) {
|
||||
this.downloadingfile = false;
|
||||
if (this.multiDownloadMode && !this.downloadOnlyMode && !navigate_mode) {
|
||||
// do nothing
|
||||
this.reloadRecentVideos();
|
||||
} else {
|
||||
// if download only mode, just download the file. no redirect
|
||||
if (forceView === false && this.downloadOnlyMode) {
|
||||
if (is_playlist) {
|
||||
const zipName = name[0].split(' ')[0] + name[1].split(' ')[0];
|
||||
this.downloadPlaylist(name, 'video', zipName);
|
||||
} else {
|
||||
this.downloadVideoFile(decodeURI(name));
|
||||
}
|
||||
this.reloadRecentVideos();
|
||||
} else {
|
||||
localStorage.setItem('player_navigator', this.router.url.split(';')[0]);
|
||||
if (is_playlist) {
|
||||
this.router.navigate(['/player', {fileNames: name.join('|nvr|'), type: 'video'}]);
|
||||
} else {
|
||||
this.router.navigate(['/player', {type: 'video', uid: uid}]);
|
||||
this.router.navigate(['/player', {type: type, uid: container['uid']}]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -436,124 +405,85 @@ export class MainComponent implements OnInit {
|
||||
|
||||
// download click handler
|
||||
downloadClicked() {
|
||||
if (this.ValidURL(this.url)) {
|
||||
this.urlError = false;
|
||||
this.path = '';
|
||||
|
||||
// get common args
|
||||
const customArgs = (this.customArgsEnabled ? this.customArgs : null);
|
||||
const customOutput = (this.customOutputEnabled ? this.customOutput : null);
|
||||
const youtubeUsername = (this.youtubeAuthEnabled && this.youtubeUsername ? this.youtubeUsername : null);
|
||||
const youtubePassword = (this.youtubeAuthEnabled && this.youtubePassword ? this.youtubePassword : null);
|
||||
|
||||
// set advanced inputs
|
||||
if (this.allowAdvancedDownload) {
|
||||
if (customArgs) {
|
||||
localStorage.setItem('customArgs', customArgs);
|
||||
}
|
||||
if (customOutput) {
|
||||
localStorage.setItem('customOutput', customOutput);
|
||||
}
|
||||
if (youtubeUsername) {
|
||||
localStorage.setItem('youtubeUsername', youtubeUsername);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.audioOnly) {
|
||||
// create download object
|
||||
const new_download: Download = {
|
||||
uid: uuid(),
|
||||
type: 'audio',
|
||||
percent_complete: 0,
|
||||
url: this.url,
|
||||
downloading: true,
|
||||
is_playlist: this.url.includes('playlist'),
|
||||
error: false
|
||||
};
|
||||
this.downloads.push(new_download);
|
||||
if (!this.current_download && !this.multiDownloadMode) { this.current_download = new_download };
|
||||
this.downloadingfile = true;
|
||||
|
||||
let customQualityConfiguration = null;
|
||||
if (this.selectedQuality !== '') {
|
||||
customQualityConfiguration = this.getSelectedAudioFormat();
|
||||
}
|
||||
|
||||
this.postsService.makeMP3(this.url, (this.selectedQuality === '' ? null : this.selectedQuality),
|
||||
customQualityConfiguration, customArgs, customOutput, youtubeUsername, youtubePassword, new_download.uid).subscribe(posts => {
|
||||
// update download object
|
||||
new_download.downloading = false;
|
||||
new_download.percent_complete = 100;
|
||||
|
||||
const is_playlist = !!(posts['file_names']);
|
||||
this.path = is_playlist ? posts['file_names'] : posts['audiopathEncoded'];
|
||||
|
||||
this.current_download = null;
|
||||
|
||||
if (this.path !== '-1') {
|
||||
this.downloadHelperMp3(this.path, posts['uid'], is_playlist, false, new_download);
|
||||
}
|
||||
}, error => { // can't access server or failed to download for other reasons
|
||||
this.downloadingfile = false;
|
||||
this.current_download = null;
|
||||
new_download['downloading'] = false;
|
||||
// removes download from list of downloads
|
||||
const downloads_index = this.downloads.indexOf(new_download);
|
||||
if (downloads_index !== -1) {
|
||||
this.downloads.splice(downloads_index)
|
||||
}
|
||||
this.openSnackBar('Download failed!', 'OK.');
|
||||
});
|
||||
} else {
|
||||
// create download object
|
||||
const new_download: Download = {
|
||||
uid: uuid(),
|
||||
type: 'video',
|
||||
percent_complete: 0,
|
||||
url: this.url,
|
||||
downloading: true,
|
||||
is_playlist: this.url.includes('playlist'),
|
||||
error: false
|
||||
};
|
||||
this.downloads.push(new_download);
|
||||
if (!this.current_download && !this.multiDownloadMode) { this.current_download = new_download };
|
||||
this.downloadingfile = true;
|
||||
|
||||
const customQualityConfiguration = this.getSelectedVideoFormat();
|
||||
|
||||
this.postsService.makeMP4(this.url, (this.selectedQuality === '' ? null : this.selectedQuality),
|
||||
customQualityConfiguration, customArgs, customOutput, youtubeUsername, youtubePassword, new_download.uid).subscribe(posts => {
|
||||
// update download object
|
||||
new_download.downloading = false;
|
||||
new_download.percent_complete = 100;
|
||||
|
||||
const is_playlist = !!(posts['file_names']);
|
||||
this.path = is_playlist ? posts['file_names'] : posts['videopathEncoded'];
|
||||
|
||||
this.current_download = null;
|
||||
|
||||
if (this.path !== '-1') {
|
||||
this.downloadHelperMp4(this.path, posts['uid'], is_playlist, false, new_download);
|
||||
}
|
||||
}, error => { // can't access server
|
||||
this.downloadingfile = false;
|
||||
this.current_download = null;
|
||||
new_download['downloading'] = false;
|
||||
// removes download from list of downloads
|
||||
const downloads_index = this.downloads.indexOf(new_download);
|
||||
if (downloads_index !== -1) {
|
||||
this.downloads.splice(downloads_index)
|
||||
}
|
||||
this.openSnackBar('Download failed!', 'OK.');
|
||||
});
|
||||
}
|
||||
|
||||
if (this.multiDownloadMode) {
|
||||
this.url = '';
|
||||
this.downloadingfile = false;
|
||||
}
|
||||
} else {
|
||||
if (!this.ValidURL(this.url)) {
|
||||
this.urlError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this.urlError = false;
|
||||
|
||||
// get common args
|
||||
const customArgs = (this.customArgsEnabled ? this.customArgs : null);
|
||||
const customOutput = (this.customOutputEnabled ? this.customOutput : null);
|
||||
const youtubeUsername = (this.youtubeAuthEnabled && this.youtubeUsername ? this.youtubeUsername : null);
|
||||
const youtubePassword = (this.youtubeAuthEnabled && this.youtubePassword ? this.youtubePassword : null);
|
||||
|
||||
// set advanced inputs
|
||||
if (this.allowAdvancedDownload) {
|
||||
if (customArgs) {
|
||||
localStorage.setItem('customArgs', customArgs);
|
||||
}
|
||||
if (customOutput) {
|
||||
localStorage.setItem('customOutput', customOutput);
|
||||
}
|
||||
if (youtubeUsername) {
|
||||
localStorage.setItem('youtubeUsername', youtubeUsername);
|
||||
}
|
||||
}
|
||||
|
||||
const type = this.audioOnly ? 'audio' : 'video';
|
||||
// create download object
|
||||
const new_download: Download = {
|
||||
uid: uuid(),
|
||||
type: type,
|
||||
percent_complete: 0,
|
||||
url: this.url,
|
||||
downloading: true,
|
||||
is_playlist: this.url.includes('playlist'),
|
||||
error: false
|
||||
};
|
||||
this.downloads.push(new_download);
|
||||
if (!this.current_download && !this.multiDownloadMode) { this.current_download = new_download };
|
||||
this.downloadingfile = true;
|
||||
|
||||
let customQualityConfiguration = type === 'audio' ? this.getSelectedAudioFormat() : this.getSelectedVideoFormat();
|
||||
|
||||
let cropFileSettings = null;
|
||||
|
||||
if (this.cropFile) {
|
||||
cropFileSettings = {
|
||||
cropFileStart: this.cropFileStart,
|
||||
cropFileEnd: this.cropFileEnd
|
||||
}
|
||||
}
|
||||
|
||||
this.postsService.downloadFile(this.url, type, (this.selectedQuality === '' ? null : this.selectedQuality),
|
||||
customQualityConfiguration, customArgs, customOutput, youtubeUsername, youtubePassword, new_download.uid, cropFileSettings).subscribe(res => {
|
||||
// update download object
|
||||
new_download.downloading = false;
|
||||
new_download.percent_complete = 100;
|
||||
|
||||
const container = res['container'];
|
||||
const is_playlist = res['file_uids'].length > 1;
|
||||
|
||||
this.current_download = null;
|
||||
|
||||
this.downloadHelper(container, type, is_playlist, false, new_download);
|
||||
}, error => { // can't access server
|
||||
this.downloadingfile = false;
|
||||
this.current_download = null;
|
||||
new_download['downloading'] = false;
|
||||
// removes download from list of downloads
|
||||
const downloads_index = this.downloads.indexOf(new_download);
|
||||
if (downloads_index !== -1) {
|
||||
this.downloads.splice(downloads_index)
|
||||
}
|
||||
this.openSnackBar('Download failed!', 'OK.');
|
||||
});
|
||||
|
||||
if (this.multiDownloadMode) {
|
||||
this.url = '';
|
||||
this.downloadingfile = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -570,23 +500,26 @@ export class MainComponent implements OnInit {
|
||||
}
|
||||
|
||||
getSelectedAudioFormat() {
|
||||
if (this.selectedQuality === '') { return null };
|
||||
if (this.selectedQuality === '') { return null; }
|
||||
const cachedFormatsExists = this.cachedAvailableFormats[this.url] && this.cachedAvailableFormats[this.url]['formats'];
|
||||
if (cachedFormatsExists) {
|
||||
const audio_formats = this.cachedAvailableFormats[this.url]['formats']['audio'];
|
||||
return audio_formats[this.selectedQuality]['format_id'];
|
||||
return this.selectedQuality['format_id'];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
getSelectedVideoFormat() {
|
||||
if (this.selectedQuality === '') { return null };
|
||||
const cachedFormatsExists = this.cachedAvailableFormats[this.url] && this.cachedAvailableFormats[this.url]['formats'];
|
||||
if (cachedFormatsExists) {
|
||||
const video_formats = this.cachedAvailableFormats[this.url]['formats']['video'];
|
||||
if (video_formats['best_audio_format'] && this.selectedQuality !== '') {
|
||||
return video_formats[this.selectedQuality]['format_id'] + '+' + video_formats['best_audio_format'];
|
||||
if (this.selectedQuality === '') { return null; }
|
||||
const cachedFormats = this.cachedAvailableFormats[this.url] && this.cachedAvailableFormats[this.url]['formats'];
|
||||
if (cachedFormats) {
|
||||
const video_formats = cachedFormats['video'];
|
||||
if (this.selectedQuality) {
|
||||
let selected_video_format = this.selectedQuality['format_id'];
|
||||
// add in audio format if necessary
|
||||
if (!this.selectedQuality['acodec'] && cachedFormats['best_audio_format']) selected_video_format += `+${cachedFormats['best_audio_format']}`;
|
||||
return selected_video_format;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -614,41 +547,27 @@ export class MainComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
downloadAudioFile(name) {
|
||||
this.downloading_content['audio'][name] = true;
|
||||
this.postsService.downloadFileFromServer(name, 'audio').subscribe(res => {
|
||||
this.downloading_content['audio'][name] = false;
|
||||
downloadFileFromServer(file, type) {
|
||||
const ext = type === 'audio' ? 'mp3' : 'mp4'
|
||||
this.downloading_content[type][file.id] = true;
|
||||
this.postsService.downloadFileFromServer(file.uid).subscribe(res => {
|
||||
this.downloading_content[type][file.id] = false;
|
||||
const blob: Blob = res;
|
||||
saveAs(blob, decodeURIComponent(name) + '.mp3');
|
||||
saveAs(blob, decodeURIComponent(file.id) + `.${ext}`);
|
||||
|
||||
if (!this.fileManagerEnabled) {
|
||||
// tell server to delete the file once downloaded
|
||||
this.postsService.deleteFile(name, 'video').subscribe(delRes => {
|
||||
this.postsService.deleteFile(file.uid).subscribe(delRes => {
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
downloadVideoFile(name) {
|
||||
this.downloading_content['video'][name] = true;
|
||||
this.postsService.downloadFileFromServer(name, 'video').subscribe(res => {
|
||||
this.downloading_content['video'][name] = false;
|
||||
downloadPlaylist(playlist) {
|
||||
this.postsService.downloadPlaylistFromServer(playlist.id).subscribe(res => {
|
||||
if (playlist.id) { this.downloading_content[playlist.type][playlist.id] = false };
|
||||
const blob: Blob = res;
|
||||
saveAs(blob, decodeURIComponent(name) + '.mp4');
|
||||
|
||||
if (!this.fileManagerEnabled) {
|
||||
// tell server to delete the file once downloaded
|
||||
this.postsService.deleteFile(name, 'audio').subscribe(delRes => {
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
downloadPlaylist(fileNames, type, zipName = null, playlistID = null) {
|
||||
this.postsService.downloadFileFromServer(fileNames, type, zipName).subscribe(res => {
|
||||
if (playlistID) { this.downloading_content[type][playlistID] = false };
|
||||
const blob: Blob = res;
|
||||
saveAs(blob, zipName + '.zip');
|
||||
saveAs(blob, playlist.name + '.zip');
|
||||
});
|
||||
|
||||
}
|
||||
@@ -728,9 +647,8 @@ export class MainComponent implements OnInit {
|
||||
this.errorFormats(url);
|
||||
return;
|
||||
}
|
||||
const parsed_infos = this.getAudioAndVideoFormats(infos.formats);
|
||||
const available_formats = {audio: parsed_infos[0], video: parsed_infos[1]};
|
||||
this.cachedAvailableFormats[url]['formats'] = available_formats;
|
||||
this.cachedAvailableFormats[url]['formats'] = this.getAudioAndVideoFormats(infos.formats);
|
||||
console.log(this.cachedAvailableFormats[url]['formats']);
|
||||
}, err => {
|
||||
this.errorFormats(url);
|
||||
});
|
||||
@@ -773,7 +691,7 @@ export class MainComponent implements OnInit {
|
||||
if (audio_format) {
|
||||
format_array.push('-f', audio_format);
|
||||
} else if (this.selectedQuality) {
|
||||
format_array.push('--audio-quality', this.selectedQuality);
|
||||
format_array.push('--audio-quality', this.selectedQuality['format_id']);
|
||||
}
|
||||
|
||||
// pushes formats
|
||||
@@ -789,7 +707,7 @@ export class MainComponent implements OnInit {
|
||||
if (video_format) {
|
||||
format_array = ['-f', video_format];
|
||||
} else if (this.selectedQuality) {
|
||||
format_array = [`bestvideo[height=${this.selectedQuality}]+bestaudio/best[height=${this.selectedQuality}]`];
|
||||
format_array = [`bestvideo[height=${this.selectedQuality['format_id']}]+bestaudio/best[height=${this.selectedQuality}]`];
|
||||
}
|
||||
|
||||
// pushes formats
|
||||
@@ -886,9 +804,11 @@ export class MainComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
getAudioAndVideoFormats(formats): any[] {
|
||||
const audio_formats = {};
|
||||
const video_formats = {};
|
||||
getAudioAndVideoFormats(formats) {
|
||||
const audio_formats: any = {};
|
||||
const video_formats: any = {};
|
||||
|
||||
console.log(formats);
|
||||
|
||||
for (let i = 0; i < formats.length; i++) {
|
||||
const format_obj = {type: null};
|
||||
@@ -899,9 +819,12 @@ export class MainComponent implements OnInit {
|
||||
format_obj.type = format_type;
|
||||
if (format_obj.type === 'audio' && format.abr) {
|
||||
const key = format.abr.toString() + 'K';
|
||||
format_obj['key'] = key;
|
||||
format_obj['bitrate'] = format.abr;
|
||||
format_obj['format_id'] = format.format_id;
|
||||
format_obj['ext'] = format.ext;
|
||||
format_obj['label'] = key;
|
||||
|
||||
// don't overwrite if not m4a
|
||||
if (audio_formats[key]) {
|
||||
if (format.ext === 'm4a') {
|
||||
@@ -912,11 +835,14 @@ export class MainComponent implements OnInit {
|
||||
}
|
||||
} else if (format_obj.type === 'video') {
|
||||
// check if video format is mp4
|
||||
const key = format.format_note.replace('p', '');
|
||||
const key = `${format.height}p${Math.round(format.fps)}`;
|
||||
if (format.ext === 'mp4' || format.ext === 'mkv' || format.ext === 'webm') {
|
||||
format_obj['key'] = key;
|
||||
format_obj['height'] = format.height;
|
||||
format_obj['acodec'] = format.acodec;
|
||||
format_obj['format_id'] = format.format_id;
|
||||
format_obj['label'] = key;
|
||||
format_obj['fps'] = Math.round(format.fps);
|
||||
|
||||
// no acodec means no overwrite
|
||||
if (!(video_formats[key]) || format_obj['acodec'] !== 'none') {
|
||||
@@ -926,9 +852,17 @@ export class MainComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
video_formats['best_audio_format'] = this.getBestAudioFormatForMp4(audio_formats);
|
||||
const parsed_formats: any = {};
|
||||
|
||||
return [audio_formats, video_formats]
|
||||
parsed_formats['best_audio_format'] = this.getBestAudioFormatForMp4(audio_formats);
|
||||
|
||||
parsed_formats['video'] = Object.values(video_formats);
|
||||
parsed_formats['audio'] = Object.values(audio_formats);
|
||||
|
||||
parsed_formats['video'] = parsed_formats['video'].sort((a, b) => b.height - a.height || b.fps - a.fps);
|
||||
parsed_formats['audio'] = parsed_formats['audio'].sort((a, b) => b.bitrate - a.bitrate);
|
||||
|
||||
return parsed_formats;
|
||||
}
|
||||
|
||||
getBestAudioFormatForMp4(audio_formats) {
|
||||
@@ -1017,8 +951,6 @@ export class MainComponent implements OnInit {
|
||||
}
|
||||
|
||||
reloadRecentVideos() {
|
||||
if (this.recentVideos) {
|
||||
this.recentVideos.getAllFiles();
|
||||
}
|
||||
this.postsService.files_changed.next(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
.audio-styles {
|
||||
height: 50px;
|
||||
background-color: transparent;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<div style="height: 100%" *ngIf="playlist.length > 0 && show_player">
|
||||
<div style="height: 100%" [ngClass]="(type === 'audio') ? null : 'container-video'">
|
||||
<div style="height: 100%" [ngClass]="(currentItem.type === 'audio/mp3') ? null : 'container-video'">
|
||||
<div style="max-width: 100%; margin-left: 0px; height: 100%">
|
||||
<mat-drawer-container style="height: 100%" class="example-container" autosize>
|
||||
<div style="height: fit-content" [ngClass]="(type === 'audio') ? 'audio-col' : 'video-col'">
|
||||
<vg-player style="height: fit-content; max-height: 75vh" (onPlayerReady)="onPlayerReady($event)" [style.background-color]="(type === 'audio') ? 'transparent' : 'black'">
|
||||
<video [ngClass]="(type === 'audio') ? 'audio-styles' : 'video-styles'" #media class="video-player" [vgMedia]="media" [src]="currentItem.src" id="singleVideo" preload="auto" controls>
|
||||
<div style="height: fit-content" [ngClass]="(currentItem.type === 'audio/mp3') ? 'audio-col' : 'video-col'">
|
||||
<vg-player style="height: fit-content; max-height: 75vh" (onPlayerReady)="onPlayerReady($event)" [style.background-color]="(currentItem.type === 'audio/mp3') ? postsService.theme.drawer_color : 'black'">
|
||||
<video [ngClass]="(currentItem.type === 'audio/mp3') ? 'audio-styles' : 'video-styles'" #media class="video-player" [vgMedia]="media" [src]="currentItem.src" id="singleVideo" preload="auto" controls>
|
||||
</video>
|
||||
</vg-player>
|
||||
</div>
|
||||
<div *ngIf="db_file" style="height: fit-content; width: 100%; margin-top: 10px;">
|
||||
<div style="height: fit-content; width: 100%; margin-top: 10px;">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-2 col-lg-1">
|
||||
@@ -27,14 +27,14 @@
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<ng-container *ngIf="playlist.length > 1">
|
||||
<ng-container *ngIf="db_playlist">
|
||||
<button (click)="downloadContent()" [disabled]="downloading" mat-icon-button><mat-icon>save</mat-icon><mat-spinner *ngIf="downloading" class="spinner" [diameter]="35"></mat-spinner></button>
|
||||
<button *ngIf="!id" color="accent" (click)="namePlaylistDialog()" mat-icon-button><mat-icon>favorite</mat-icon></button>
|
||||
<button *ngIf="!is_shared && id && (!postsService.isLoggedIn || postsService.permissions.includes('sharing'))" (click)="openShareDialog()" mat-icon-button><mat-icon>share</mat-icon></button>
|
||||
<button *ngIf="(!postsService.isLoggedIn || postsService.permissions.includes('sharing')) && !auto" (click)="openShareDialog()" mat-icon-button><mat-icon>share</mat-icon></button>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="playlist.length === 1">
|
||||
<ng-container *ngIf="db_file">
|
||||
<button (click)="downloadFile()" [disabled]="downloading" mat-icon-button><mat-icon>save</mat-icon><mat-spinner *ngIf="downloading" class="spinner" [diameter]="35"></mat-spinner></button>
|
||||
<button *ngIf="!is_shared && uid && uid !== 'false' && type !== 'subscription' && (!postsService.isLoggedIn || postsService.permissions.includes('sharing'))" (click)="openShareDialog()" mat-icon-button><mat-icon>share</mat-icon></button>
|
||||
<button *ngIf="type !== 'subscription' && (!postsService.isLoggedIn || postsService.permissions.includes('sharing'))" (click)="openShareDialog()" mat-icon-button><mat-icon>share</mat-icon></button>
|
||||
<button (click)="openFileInfoDialog()" *ngIf="db_file" mat-icon-button><mat-icon>info</mat-icon></button>
|
||||
</ng-container>
|
||||
<button *ngIf="db_file && db_file.url.includes('twitch.tv/videos/') && postsService['config']['API']['use_twitch_API']" (click)="drawer.toggle()" mat-icon-button><mat-icon>chat</mat-icon></button>
|
||||
</div>
|
||||
@@ -46,19 +46,22 @@
|
||||
<mat-button-toggle cdkDrag *ngFor="let playlist_item of playlist; let i = index" [checked]="currentItem.title === playlist_item.title" (click)="onClickPlaylistItem(playlist_item, i)" class="toggle-button" [value]="playlist_item.title">{{playlist_item.label}}</mat-button-toggle>
|
||||
</mat-button-toggle-group>
|
||||
</div>
|
||||
|
||||
<app-concurrent-stream *ngIf="db_file && api && postsService.config" (setPlaybackRate)="setPlaybackRate($event)" (togglePlayback)="togglePlayback($event)" (setPlaybackTimestamp)="setPlaybackTimestamp($event)" [playing]="api.state === 'playing'" [uid]="uid" [playback_timestamp]="api.time.current/1000" [server_mode]="!postsService.config.Advanced.multi_user_mode || postsService.isLoggedIn"></app-concurrent-stream>
|
||||
|
||||
<mat-drawer #drawer class="example-sidenav" mode="side" position="end" [opened]="db_file && db_file['chat_exists'] && postsService['config']['API']['use_twitch_API']">
|
||||
<ng-container *ngIf="api_ready && db_file && db_file.url.includes('twitch.tv/videos/')">
|
||||
<app-twitch-chat #twitchchat [db_file]="db_file" [current_timestamp]="api.currentTime" [sub]="subscription"></app-twitch-chat>
|
||||
</ng-container>
|
||||
</mat-drawer>
|
||||
|
||||
<div class="update-playlist-button-div" *ngIf="id && playlistChanged()">
|
||||
<!-- <div class="update-playlist-button-div" *ngIf="id && playlistChanged()">
|
||||
<div class="spinner-div">
|
||||
<mat-spinner *ngIf="playlist_updating" [diameter]="25"></mat-spinner>
|
||||
</div>
|
||||
<button color="primary" [disabled]="playlist_updating" (click)="updatePlaylist()" mat-raised-button><ng-container i18n="Playlist save changes button">Save changes</ng-container> <mat-icon>update</mat-icon></button>
|
||||
|
||||
</div>
|
||||
</div> -->
|
||||
</mat-drawer-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, OnInit, HostListener, EventEmitter, OnDestroy, AfterViewInit, ViewChild } from '@angular/core';
|
||||
import { Component, OnInit, HostListener, EventEmitter, OnDestroy, AfterViewInit, ViewChild, ChangeDetectorRef } from '@angular/core';
|
||||
import { VgApiService } from '@videogular/ngx-videogular/core';
|
||||
import { PostsService } from 'app/posts.services';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
@@ -8,6 +8,7 @@ import { InputDialogComponent } from 'app/input-dialog/input-dialog.component';
|
||||
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
|
||||
import { ShareMediaDialogComponent } from '../dialogs/share-media-dialog/share-media-dialog.component';
|
||||
import { TwitchChatComponent } from 'app/components/twitch-chat/twitch-chat.component';
|
||||
import { VideoInfoDialogComponent } from 'app/dialogs/video-info-dialog/video-info-dialog.component';
|
||||
|
||||
export interface IMedia {
|
||||
title: string;
|
||||
@@ -35,17 +36,16 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
api_ready = false;
|
||||
|
||||
// params
|
||||
fileNames: string[];
|
||||
uids: string[];
|
||||
type: string;
|
||||
id = null; // used for playlists (not subscription)
|
||||
playlist_id = null; // used for playlists (not subscription)
|
||||
uid = null; // used for non-subscription files (audio, video, playlist)
|
||||
subscription = null;
|
||||
subscriptionName = null;
|
||||
sub_id = null;
|
||||
subPlaylist = null;
|
||||
uuid = null; // used for sharing in multi-user mode, uuid is the user that downloaded the video
|
||||
timestamp = null;
|
||||
|
||||
is_shared = false;
|
||||
auto = null;
|
||||
|
||||
db_playlist = null;
|
||||
db_file = null;
|
||||
@@ -55,8 +55,6 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
videoFolderPath = null;
|
||||
subscriptionFolderPath = null;
|
||||
|
||||
sharingEnabled = null;
|
||||
|
||||
// url-mode params
|
||||
url = null;
|
||||
name = null;
|
||||
@@ -78,15 +76,14 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
ngOnInit(): void {
|
||||
this.innerWidth = window.innerWidth;
|
||||
|
||||
this.type = this.route.snapshot.paramMap.get('type');
|
||||
this.id = this.route.snapshot.paramMap.get('id');
|
||||
this.playlist_id = this.route.snapshot.paramMap.get('playlist_id');
|
||||
this.uid = this.route.snapshot.paramMap.get('uid');
|
||||
this.subscriptionName = this.route.snapshot.paramMap.get('subscriptionName');
|
||||
this.subPlaylist = this.route.snapshot.paramMap.get('subPlaylist');
|
||||
this.sub_id = this.route.snapshot.paramMap.get('sub_id');
|
||||
this.url = this.route.snapshot.paramMap.get('url');
|
||||
this.name = this.route.snapshot.paramMap.get('name');
|
||||
this.uuid = this.route.snapshot.paramMap.get('uuid');
|
||||
this.timestamp = this.route.snapshot.paramMap.get('timestamp');
|
||||
this.auto = this.route.snapshot.paramMap.get('auto');
|
||||
|
||||
// loading config
|
||||
if (this.postsService.initialized) {
|
||||
@@ -101,6 +98,7 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.cdr.detectChanges();
|
||||
this.postsService.sidenav.close();
|
||||
}
|
||||
|
||||
@@ -110,7 +108,7 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
}
|
||||
|
||||
constructor(public postsService: PostsService, private route: ActivatedRoute, private dialog: MatDialog, private router: Router,
|
||||
public snackBar: MatSnackBar) {
|
||||
public snackBar: MatSnackBar, private cdr: ChangeDetectorRef) {
|
||||
|
||||
}
|
||||
|
||||
@@ -119,19 +117,14 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
this.audioFolderPath = this.postsService.config['Downloader']['path-audio'];
|
||||
this.videoFolderPath = this.postsService.config['Downloader']['path-video'];
|
||||
this.subscriptionFolderPath = this.postsService.config['Subscriptions']['subscriptions_base_path'];
|
||||
this.fileNames = this.route.snapshot.paramMap.get('fileNames') ? this.route.snapshot.paramMap.get('fileNames').split('|nvr|') : null;
|
||||
|
||||
if (!this.fileNames && !this.type) {
|
||||
this.is_shared = true;
|
||||
}
|
||||
|
||||
if (this.uid && !this.id) {
|
||||
this.getFile();
|
||||
} else if (this.id) {
|
||||
this.getPlaylistFiles();
|
||||
} else if (this.subscriptionName) {
|
||||
if (this.sub_id) {
|
||||
this.getSubscription();
|
||||
}
|
||||
} else if (this.playlist_id) {
|
||||
this.getPlaylistFiles();
|
||||
} else if (this.uid) {
|
||||
this.getFile();
|
||||
}
|
||||
|
||||
if (this.url) {
|
||||
// if a url is given, just stream the URL
|
||||
@@ -146,14 +139,10 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
this.currentItem = this.playlist[0];
|
||||
this.currentIndex = 0;
|
||||
this.show_player = true;
|
||||
} else if (this.fileNames && !this.subscriptionName) {
|
||||
this.show_player = true;
|
||||
this.parseFileNames();
|
||||
}
|
||||
}
|
||||
|
||||
getFile() {
|
||||
const already_has_filenames = !!this.fileNames;
|
||||
this.postsService.getFile(this.uid, null, this.uuid).subscribe(res => {
|
||||
this.db_file = res['file'];
|
||||
if (!this.db_file) {
|
||||
@@ -164,57 +153,41 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
console.error('Failed to increment view count');
|
||||
console.error(err);
|
||||
});
|
||||
this.sharingEnabled = this.db_file.sharingEnabled;
|
||||
if (!this.fileNames) {
|
||||
// means it's a shared video
|
||||
if (!this.id) {
|
||||
// regular video/audio file (not playlist)
|
||||
this.fileNames = [this.db_file['id']];
|
||||
this.type = this.db_file['isAudio'] ? 'audio' : 'video';
|
||||
if (!already_has_filenames) { this.parseFileNames(); }
|
||||
}
|
||||
}
|
||||
if (this.db_file['sharingEnabled'] || !this.uuid) {
|
||||
this.show_player = true;
|
||||
} else if (!already_has_filenames) {
|
||||
this.openSnackBar('Error: Sharing has been disabled for this video!', 'Dismiss');
|
||||
}
|
||||
// regular video/audio file (not playlist)
|
||||
this.uids = [this.db_file['uid']];
|
||||
this.type = this.db_file['isAudio'] ? 'audio' : 'video';
|
||||
this.parseFileNames();
|
||||
});
|
||||
}
|
||||
|
||||
getSubscription() {
|
||||
this.postsService.getSubscription(null, this.subscriptionName).subscribe(res => {
|
||||
this.postsService.getSubscription(this.sub_id).subscribe(res => {
|
||||
const subscription = res['subscription'];
|
||||
this.subscription = subscription;
|
||||
if (this.fileNames) {
|
||||
subscription.videos.forEach(video => {
|
||||
if (video['id'] === this.fileNames[0]) {
|
||||
this.db_file = video;
|
||||
this.postsService.incrementViewCount(this.db_file['uid'], this.subscription['id'], this.uuid).subscribe(res => {}, err => {
|
||||
console.error('Failed to increment view count');
|
||||
console.error(err);
|
||||
});
|
||||
this.show_player = true;
|
||||
this.parseFileNames();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log('no file name specified');
|
||||
}
|
||||
this.type === this.subscription.type;
|
||||
subscription.videos.forEach(video => {
|
||||
if (video['uid'] === this.uid) {
|
||||
this.db_file = video;
|
||||
this.postsService.incrementViewCount(this.db_file['uid'], this.sub_id, this.uuid).subscribe(res => {}, err => {
|
||||
console.error('Failed to increment view count');
|
||||
console.error(err);
|
||||
});
|
||||
this.uids = [this.db_file['uid']];
|
||||
this.show_player = true;
|
||||
this.parseFileNames();
|
||||
}
|
||||
});
|
||||
}, err => {
|
||||
this.openSnackBar(`Failed to find subscription ${this.subscriptionName}`, 'Dismiss');
|
||||
this.openSnackBar(`Failed to find subscription ${this.sub_id}`, 'Dismiss');
|
||||
});
|
||||
}
|
||||
|
||||
getPlaylistFiles() {
|
||||
if (this.route.snapshot.paramMap.get('auto') === 'true') {
|
||||
this.show_player = true;
|
||||
return;
|
||||
}
|
||||
this.postsService.getPlaylist(this.id, null, this.uuid).subscribe(res => {
|
||||
this.postsService.getPlaylist(this.playlist_id, this.uuid, true).subscribe(res => {
|
||||
if (res['playlist']) {
|
||||
this.db_playlist = res['playlist'];
|
||||
this.fileNames = this.db_playlist['fileNames'];
|
||||
this.db_playlist['file_objs'] = res['file_objs'];
|
||||
this.uids = this.db_playlist.uids;
|
||||
this.type = res['type'];
|
||||
this.show_player = true;
|
||||
this.parseFileNames();
|
||||
@@ -226,69 +199,52 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
parseFileNames() {
|
||||
let fileType = null;
|
||||
if (this.type === 'audio') {
|
||||
fileType = 'audio/mp3';
|
||||
} else if (this.type === 'video') {
|
||||
fileType = 'video/mp4';
|
||||
} else {
|
||||
// error
|
||||
console.error('Must have valid file type! Use \'audio\', \'video\', or \'subscription\'.');
|
||||
}
|
||||
parseFileNames() {
|
||||
this.playlist = [];
|
||||
for (let i = 0; i < this.fileNames.length; i++) {
|
||||
const fileName = this.fileNames[i];
|
||||
let baseLocation = null;
|
||||
let fullLocation = null;
|
||||
for (let i = 0; i < this.uids.length; i++) {
|
||||
const uid = this.uids[i];
|
||||
|
||||
// adds user token if in multi-user-mode
|
||||
const uuid_str = this.uuid ? `&uuid=${this.uuid}` : '';
|
||||
const uid_str = (this.id || !this.db_file) ? '' : `&uid=${this.db_file.uid}`;
|
||||
const type_str = (this.type || !this.db_file) ? `&type=${this.type}` : `&type=${this.db_file.type}`
|
||||
const id_str = this.id ? `&id=${this.id}` : '';
|
||||
const file_path_str = (!this.db_file) ? '' : `&file_path=${encodeURIComponent(this.db_file.path)}`;
|
||||
const file_obj = this.playlist_id ? this.db_playlist['file_objs'][i] : this.db_file;
|
||||
|
||||
if (!this.subscriptionName) {
|
||||
baseLocation = 'stream/';
|
||||
fullLocation = this.baseStreamPath + baseLocation + encodeURIComponent(fileName) + `?test=test${type_str}${file_path_str}`;
|
||||
} else {
|
||||
// default to video but include subscription name param
|
||||
baseLocation = 'stream/';
|
||||
fullLocation = this.baseStreamPath + baseLocation + encodeURIComponent(fileName) + '?subName=' + this.subscriptionName +
|
||||
'&subPlaylist=' + this.subPlaylist + `${file_path_str}${type_str}`;
|
||||
}
|
||||
const mime_type = file_obj.isAudio ? 'audio/mp3' : 'video/mp4'
|
||||
|
||||
let baseLocation = 'stream/';
|
||||
let fullLocation = this.baseStreamPath + baseLocation + `?test=test&uid=${file_obj['uid']}`;
|
||||
|
||||
if (this.postsService.isLoggedIn) {
|
||||
fullLocation += (this.subscriptionName ? '&' : '&') + `jwt=${this.postsService.token}`;
|
||||
if (this.is_shared) { fullLocation += `${uuid_str}${uid_str}${type_str}${id_str}`; }
|
||||
} else if (this.is_shared) {
|
||||
fullLocation += (this.subscriptionName ? '&' : '?') + `test=test${uuid_str}${uid_str}${type_str}${id_str}`;
|
||||
fullLocation += `&jwt=${this.postsService.token}`;
|
||||
}
|
||||
// if it has a slash (meaning it's in a directory), only get the file name for the label
|
||||
let label = null;
|
||||
const decodedName = decodeURIComponent(fileName);
|
||||
const hasSlash = decodedName.includes('/') || decodedName.includes('\\');
|
||||
if (hasSlash) {
|
||||
label = decodedName.replace(/^.*[\\\/]/, '');
|
||||
} else {
|
||||
label = decodedName;
|
||||
|
||||
if (this.uuid) {
|
||||
fullLocation += `&uuid=${this.uuid}`;
|
||||
}
|
||||
|
||||
if (this.sub_id) {
|
||||
fullLocation += `&sub_id=${this.sub_id}`;
|
||||
} else if (this.playlist_id) {
|
||||
fullLocation += `&playlist_id=${this.playlist_id}`;
|
||||
}
|
||||
|
||||
const mediaObject: IMedia = {
|
||||
title: fileName,
|
||||
title: file_obj['title'],
|
||||
src: fullLocation,
|
||||
type: fileType,
|
||||
label: label
|
||||
type: mime_type,
|
||||
label: file_obj['title']
|
||||
}
|
||||
this.playlist.push(mediaObject);
|
||||
}
|
||||
if (this.db_playlist && this.db_playlist['randomize_order']) {
|
||||
this.shuffleArray(this.playlist);
|
||||
}
|
||||
this.currentItem = this.playlist[this.currentIndex];
|
||||
this.original_playlist = JSON.stringify(this.playlist);
|
||||
this.show_player = true;
|
||||
}
|
||||
|
||||
onPlayerReady(api: VgApiService) {
|
||||
this.api = api;
|
||||
this.api_ready = true;
|
||||
this.cdr.detectChanges();
|
||||
|
||||
// checks if volume has been previously set. if so, use that as default
|
||||
if (localStorage.getItem('player_volume')) {
|
||||
@@ -353,15 +309,9 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
}
|
||||
|
||||
downloadContent() {
|
||||
const fileNames = [];
|
||||
for (let i = 0; i < this.playlist.length; i++) {
|
||||
fileNames.push(this.playlist[i].title);
|
||||
}
|
||||
|
||||
const zipName = fileNames[0].split(' ')[0] + fileNames[1].split(' ')[0];
|
||||
const zipName = this.db_playlist.name;
|
||||
this.downloading = true;
|
||||
this.postsService.downloadFileFromServer(fileNames, this.type, zipName, null, null, null, null,
|
||||
!this.uuid ? this.postsService.user.uid : this.uuid, this.id).subscribe(res => {
|
||||
this.postsService.downloadPlaylistFromServer(this.playlist_id, this.uuid).subscribe(res => {
|
||||
this.downloading = false;
|
||||
const blob: Blob = res;
|
||||
saveAs(blob, zipName + '.zip');
|
||||
@@ -372,11 +322,10 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
}
|
||||
|
||||
downloadFile() {
|
||||
const ext = (this.type === 'audio') ? '.mp3' : '.mp4';
|
||||
const filename = this.playlist[0].title;
|
||||
const ext = (this.playlist[0].type === 'audio/mp3') ? '.mp3' : '.mp4';
|
||||
this.downloading = true;
|
||||
this.postsService.downloadFileFromServer(filename, this.type, null, null, this.subscriptionName, this.subPlaylist,
|
||||
this.is_shared ? this.db_file['uid'] : null, this.uuid).subscribe(res => {
|
||||
this.postsService.downloadFileFromServer(this.uid, this.uuid, this.sub_id).subscribe(res => {
|
||||
this.downloading = false;
|
||||
const blob: Blob = res;
|
||||
saveAs(blob, filename + ext);
|
||||
@@ -386,50 +335,10 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
namePlaylistDialog() {
|
||||
const done = new EventEmitter<any>();
|
||||
const dialogRef = this.dialog.open(InputDialogComponent, {
|
||||
width: '300px',
|
||||
data: {
|
||||
inputTitle: 'Name the playlist',
|
||||
inputPlaceholder: 'Name',
|
||||
submitText: 'Favorite',
|
||||
doneEmitter: done
|
||||
}
|
||||
});
|
||||
|
||||
done.subscribe(name => {
|
||||
|
||||
// Eventually do additional checks on name
|
||||
if (name) {
|
||||
const fileNames = this.getFileNames();
|
||||
this.postsService.createPlaylist(name, fileNames, this.type, null).subscribe(res => {
|
||||
if (res['success']) {
|
||||
dialogRef.close();
|
||||
const new_playlist = res['new_playlist'];
|
||||
this.db_playlist = new_playlist;
|
||||
this.openSnackBar('Playlist \'' + name + '\' successfully created!', '')
|
||||
this.playlistPostCreationHandler(new_playlist.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
createPlaylist(name) {
|
||||
this.postsService.createPlaylist(name, this.fileNames, this.type, null).subscribe(res => {
|
||||
if (res['success']) {
|
||||
console.log('Success!');
|
||||
}
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
playlistPostCreationHandler(playlistID) {
|
||||
// changes the route without moving from the current view or
|
||||
// triggering a navigation event
|
||||
this.id = playlistID;
|
||||
this.playlist_id = playlistID;
|
||||
this.router.navigateByUrl(this.router.url + ';id=' + playlistID);
|
||||
}
|
||||
|
||||
@@ -444,11 +353,11 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
updatePlaylist() {
|
||||
const fileNames = this.getFileNames();
|
||||
this.playlist_updating = true;
|
||||
this.postsService.updatePlaylistFiles(this.id, fileNames, this.type).subscribe(res => {
|
||||
this.postsService.updatePlaylistFiles(this.playlist_id, fileNames, this.type).subscribe(res => {
|
||||
this.playlist_updating = false;
|
||||
if (res['success']) {
|
||||
const fileNamesEncoded = fileNames.join('|nvr|');
|
||||
this.router.navigate(['/player', {fileNames: fileNamesEncoded, type: this.type, id: this.id}]);
|
||||
this.router.navigate(['/player', {fileNames: fileNamesEncoded, type: this.type, id: this.playlist_id}]);
|
||||
this.openSnackBar('Successfully updated playlist.', '');
|
||||
this.original_playlist = JSON.stringify(this.playlist);
|
||||
} else {
|
||||
@@ -460,10 +369,9 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
openShareDialog() {
|
||||
const dialogRef = this.dialog.open(ShareMediaDialogComponent, {
|
||||
data: {
|
||||
uid: this.id ? this.id : this.uid,
|
||||
type: this.type,
|
||||
sharing_enabled: this.id ? this.db_playlist.sharingEnabled : this.db_file.sharingEnabled,
|
||||
is_playlist: !!this.id,
|
||||
uid: this.playlist_id ? this.playlist_id : this.uid,
|
||||
sharing_enabled: this.playlist_id ? this.db_playlist.sharingEnabled : this.db_file.sharingEnabled,
|
||||
is_playlist: !!this.playlist_id,
|
||||
uuid: this.postsService.isLoggedIn ? this.postsService.user.uid : null,
|
||||
current_timestamp: this.api.time.current
|
||||
},
|
||||
@@ -471,13 +379,45 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(res => {
|
||||
if (!this.id) {
|
||||
if (!this.playlist_id) {
|
||||
this.getFile();
|
||||
} else {
|
||||
this.getPlaylistFiles();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
openFileInfoDialog() {
|
||||
this.dialog.open(VideoInfoDialogComponent, {
|
||||
data: {
|
||||
file: this.db_file,
|
||||
},
|
||||
minWidth: '50vw'
|
||||
})
|
||||
}
|
||||
|
||||
setPlaybackTimestamp(time) {
|
||||
this.api.seekTime(time);
|
||||
}
|
||||
|
||||
togglePlayback(to_play) {
|
||||
if (to_play) {
|
||||
this.api.play();
|
||||
} else {
|
||||
this.api.pause();
|
||||
}
|
||||
}
|
||||
|
||||
setPlaybackRate(speed) {
|
||||
this.api.playbackRate = speed;
|
||||
}
|
||||
|
||||
shuffleArray(array) {
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[array[i], array[j]] = [array[j], array[i]];
|
||||
}
|
||||
}
|
||||
|
||||
// snackbar helper
|
||||
public openSnackBar(message: string, action: string) {
|
||||
|
||||
@@ -48,6 +48,9 @@ export class PostsService implements CanActivate {
|
||||
settings_changed = new BehaviorSubject<boolean>(false);
|
||||
open_create_default_admin_dialog = new BehaviorSubject<boolean>(false);
|
||||
|
||||
files_changed = new BehaviorSubject<boolean>(false);
|
||||
playlists_changed = new BehaviorSubject<boolean>(false);
|
||||
|
||||
// app status
|
||||
initialized = false;
|
||||
|
||||
@@ -171,33 +174,39 @@ export class PostsService implements CanActivate {
|
||||
}
|
||||
|
||||
// tslint:disable-next-line: max-line-length
|
||||
makeMP3(url: string, selectedQuality: string, customQualityConfiguration: string, customArgs: string = null, customOutput: string = null, youtubeUsername: string = null, youtubePassword: string = null, ui_uid = null) {
|
||||
return this.http.post(this.path + 'tomp3', {url: url,
|
||||
maxBitrate: selectedQuality,
|
||||
customQualityConfiguration: customQualityConfiguration,
|
||||
customArgs: customArgs,
|
||||
customOutput: customOutput,
|
||||
youtubeUsername: youtubeUsername,
|
||||
youtubePassword: youtubePassword,
|
||||
ui_uid: ui_uid}, this.httpOptions);
|
||||
}
|
||||
|
||||
// tslint:disable-next-line: max-line-length
|
||||
makeMP4(url: string, selectedQuality: string, customQualityConfiguration: string, customArgs: string = null, customOutput: string = null, youtubeUsername: string = null, youtubePassword: string = null, ui_uid = null) {
|
||||
return this.http.post(this.path + 'tomp4', {url: url,
|
||||
downloadFile(url: string, type: string, selectedQuality: string, customQualityConfiguration: string, customArgs: string = null, customOutput: string = null, youtubeUsername: string = null, youtubePassword: string = null, ui_uid = null, cropFileSettings = null) {
|
||||
return this.http.post(this.path + 'downloadFile', {url: url,
|
||||
selectedHeight: selectedQuality,
|
||||
customQualityConfiguration: customQualityConfiguration,
|
||||
customArgs: customArgs,
|
||||
customOutput: customOutput,
|
||||
youtubeUsername: youtubeUsername,
|
||||
youtubePassword: youtubePassword,
|
||||
ui_uid: ui_uid}, this.httpOptions);
|
||||
ui_uid: ui_uid,
|
||||
type: type,
|
||||
cropFileSettings: cropFileSettings}, this.httpOptions);
|
||||
}
|
||||
|
||||
getDBInfo() {
|
||||
return this.http.post(this.path + 'getDBInfo', {}, this.httpOptions);
|
||||
}
|
||||
|
||||
transferDB(local_to_remote) {
|
||||
return this.http.post(this.path + 'transferDB', {local_to_remote: local_to_remote}, this.httpOptions);
|
||||
}
|
||||
|
||||
testConnectionString(connection_string) {
|
||||
return this.http.post(this.path + 'testConnectionString', {connection_string: connection_string}, this.httpOptions);
|
||||
}
|
||||
|
||||
killAllDownloads() {
|
||||
return this.http.post(this.path + 'killAllDownloads', {}, this.httpOptions);
|
||||
}
|
||||
|
||||
restartServer() {
|
||||
return this.http.post(this.path + 'restartServer', {}, this.httpOptions);
|
||||
}
|
||||
|
||||
loadNavItems() {
|
||||
if (isDevMode()) {
|
||||
return this.http.get('./assets/default.json');
|
||||
@@ -214,8 +223,8 @@ export class PostsService implements CanActivate {
|
||||
return this.http.post(this.path + 'setConfig', {new_config_file: config}, this.httpOptions);
|
||||
}
|
||||
|
||||
deleteFile(uid: string, type: string, blacklistMode = false) {
|
||||
return this.http.post(this.path + 'deleteFile', {uid: uid, type: type, blacklistMode: blacklistMode}, this.httpOptions);
|
||||
deleteFile(uid: string, blacklistMode = false) {
|
||||
return this.http.post(this.path + 'deleteFile', {uid: uid, blacklistMode: blacklistMode}, this.httpOptions);
|
||||
}
|
||||
|
||||
getMp3s() {
|
||||
@@ -242,22 +251,43 @@ export class PostsService implements CanActivate {
|
||||
return this.http.post(this.path + 'downloadTwitchChatByVODID', {id: id, type: type, vodId: vodId, uuid: uuid, sub: sub}, this.httpOptions);
|
||||
}
|
||||
|
||||
downloadFileFromServer(fileName, type, outputName = null, fullPathProvided = null, subscriptionName = null, subPlaylist = null,
|
||||
uid = null, uuid = null, id = null) {
|
||||
return this.http.post(this.path + 'downloadFile', {fileNames: fileName,
|
||||
type: type,
|
||||
zip_mode: Array.isArray(fileName),
|
||||
outputName: outputName,
|
||||
fullPathProvided: fullPathProvided,
|
||||
subscriptionName: subscriptionName,
|
||||
subPlaylist: subPlaylist,
|
||||
uuid: uuid,
|
||||
downloadFileFromServer(uid, uuid = null, sub_id = null, is_playlist = null) {
|
||||
return this.http.post(this.path + 'downloadFileFromServer', {
|
||||
uid: uid,
|
||||
id: id
|
||||
uuid: uuid,
|
||||
sub_id: sub_id,
|
||||
is_playlist: is_playlist
|
||||
},
|
||||
{responseType: 'blob', params: this.httpOptions.params});
|
||||
}
|
||||
|
||||
downloadPlaylistFromServer(playlist_id, uuid = null) {
|
||||
return this.http.post(this.path + 'downloadFileFromServer', {
|
||||
uuid: uuid,
|
||||
playlist_id: playlist_id
|
||||
},
|
||||
{responseType: 'blob', params: this.httpOptions.params});
|
||||
}
|
||||
|
||||
downloadSubFromServer(sub_id, uuid = null) {
|
||||
return this.http.post(this.path + 'downloadFileFromServer', {
|
||||
uuid: uuid,
|
||||
sub_id: sub_id
|
||||
},
|
||||
{responseType: 'blob', params: this.httpOptions.params});
|
||||
}
|
||||
|
||||
checkConcurrentStream(uid) {
|
||||
return this.http.post(this.path + 'checkConcurrentStream', {uid: uid}, this.httpOptions);
|
||||
}
|
||||
|
||||
updateConcurrentStream(uid, playback_timestamp, unix_timestamp, playing) {
|
||||
return this.http.post(this.path + 'updateConcurrentStream', {uid: uid,
|
||||
playback_timestamp: playback_timestamp,
|
||||
unix_timestamp: unix_timestamp,
|
||||
playing: playing}, this.httpOptions);
|
||||
}
|
||||
|
||||
uploadCookiesFile(fileFormData) {
|
||||
return this.http.post(this.path + 'uploadCookies', fileFormData, this.httpOptions);
|
||||
}
|
||||
@@ -282,43 +312,53 @@ export class PostsService implements CanActivate {
|
||||
return this.http.post(this.path + 'generateNewAPIKey', {}, this.httpOptions);
|
||||
}
|
||||
|
||||
enableSharing(uid, type, is_playlist) {
|
||||
return this.http.post(this.path + 'enableSharing', {uid: uid, type: type, is_playlist: is_playlist}, this.httpOptions);
|
||||
enableSharing(uid, is_playlist) {
|
||||
return this.http.post(this.path + 'enableSharing', {uid: uid, is_playlist: is_playlist}, this.httpOptions);
|
||||
}
|
||||
|
||||
disableSharing(uid, is_playlist) {
|
||||
return this.http.post(this.path + 'disableSharing', {uid: uid, is_playlist: is_playlist}, this.httpOptions);
|
||||
}
|
||||
|
||||
incrementViewCount(file_uid, sub_id, uuid) {
|
||||
return this.http.post(this.path + 'incrementViewCount', {file_uid: file_uid, sub_id: sub_id, uuid: uuid}, this.httpOptions);
|
||||
}
|
||||
|
||||
disableSharing(uid, type, is_playlist) {
|
||||
return this.http.post(this.path + 'disableSharing', {uid: uid, type: type, is_playlist: is_playlist}, this.httpOptions);
|
||||
}
|
||||
|
||||
createPlaylist(playlistName, fileNames, type, thumbnailURL, duration = null) {
|
||||
createPlaylist(playlistName, uids, type, thumbnailURL) {
|
||||
return this.http.post(this.path + 'createPlaylist', {playlistName: playlistName,
|
||||
fileNames: fileNames,
|
||||
uids: uids,
|
||||
type: type,
|
||||
thumbnailURL: thumbnailURL,
|
||||
duration: duration}, this.httpOptions);
|
||||
thumbnailURL: thumbnailURL}, this.httpOptions);
|
||||
}
|
||||
|
||||
getPlaylist(playlistID, type, uuid = null) {
|
||||
return this.http.post(this.path + 'getPlaylist', {playlistID: playlistID,
|
||||
type: type, uuid: uuid}, this.httpOptions);
|
||||
getPlaylist(playlist_id, uuid = null, include_file_metadata = false) {
|
||||
return this.http.post(this.path + 'getPlaylist', {playlist_id: playlist_id,
|
||||
uuid: uuid,
|
||||
include_file_metadata: include_file_metadata}, this.httpOptions);
|
||||
}
|
||||
|
||||
getPlaylists() {
|
||||
return this.http.post(this.path + 'getPlaylists', {}, this.httpOptions);
|
||||
}
|
||||
|
||||
updatePlaylist(playlist) {
|
||||
return this.http.post(this.path + 'updatePlaylist', {playlist: playlist}, this.httpOptions);
|
||||
}
|
||||
|
||||
updatePlaylistFiles(playlistID, fileNames, type) {
|
||||
return this.http.post(this.path + 'updatePlaylistFiles', {playlistID: playlistID,
|
||||
updatePlaylistFiles(playlist_id, fileNames, type) {
|
||||
return this.http.post(this.path + 'updatePlaylistFiles', {playlist_id: playlist_id,
|
||||
fileNames: fileNames,
|
||||
type: type}, this.httpOptions);
|
||||
}
|
||||
|
||||
removePlaylist(playlistID, type) {
|
||||
return this.http.post(this.path + 'deletePlaylist', {playlistID: playlistID, type: type}, this.httpOptions);
|
||||
addFileToPlaylist(playlist_id, file_uid) {
|
||||
return this.http.post(this.path + 'addFileToPlaylist', {playlist_id: playlist_id,
|
||||
file_uid: file_uid},
|
||||
this.httpOptions);
|
||||
}
|
||||
|
||||
removePlaylist(playlist_id, type) {
|
||||
return this.http.post(this.path + 'deletePlaylist', {playlist_id: playlist_id, type: type}, this.httpOptions);
|
||||
}
|
||||
|
||||
// categories
|
||||
@@ -357,14 +397,17 @@ export class PostsService implements CanActivate {
|
||||
}
|
||||
|
||||
updateSubscription(subscription) {
|
||||
delete subscription['videos'];
|
||||
return this.http.post(this.path + 'updateSubscription', {subscription: subscription}, this.httpOptions);
|
||||
}
|
||||
|
||||
unsubscribe(sub, deleteMode = false) {
|
||||
delete sub['videos'];
|
||||
return this.http.post(this.path + 'unsubscribe', {sub: sub, deleteMode: deleteMode}, this.httpOptions)
|
||||
}
|
||||
|
||||
deleteSubscriptionFile(sub, file, deleteForever, file_uid) {
|
||||
delete sub['videos'];
|
||||
return this.http.post(this.path + 'deleteSubscriptionFile', {sub: sub, file: file, deleteForever: deleteForever,
|
||||
file_uid: file_uid}, this.httpOptions)
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ export const isoLangs = {
|
||||
'nativeName': 'ဗမာစာ'
|
||||
},
|
||||
'ca': {
|
||||
'name': 'Catalan; Valencian',
|
||||
'name': 'Catalan',
|
||||
'nativeName': 'Català'
|
||||
},
|
||||
'ch': {
|
||||
@@ -159,7 +159,7 @@ export const isoLangs = {
|
||||
},
|
||||
'nl': {
|
||||
'name': 'Dutch',
|
||||
'nativeName': 'Nederlands, Vlaams'
|
||||
'nativeName': 'Nederlands'
|
||||
},
|
||||
'en': {
|
||||
'name': 'English',
|
||||
|
||||
@@ -140,7 +140,7 @@
|
||||
<mat-divider></mat-divider>
|
||||
<div *ngIf="new_config" class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12 mt-3 mb-2">
|
||||
<div class="col-12 mt-3">
|
||||
<h6 i18n="Categories">Categories</h6>
|
||||
<div cdkDropList class="category-list" (cdkDropListDropped)="dropCategory($event)">
|
||||
<div class="category-box" *ngFor="let category of postsService.categories" cdkDrag>
|
||||
@@ -154,6 +154,9 @@
|
||||
</div>
|
||||
<button style="margin-top: 10px;" mat-mini-fab (click)="openAddCategoryDialog()"><mat-icon>add</mat-icon></button>
|
||||
</div>
|
||||
<div class="col-12 mt-2 mb-2">
|
||||
<mat-checkbox [(ngModel)]="new_config['Extra']['allow_playlist_categorization']" matTooltip="With this setting enabled, if a single video matches a category, the entire playlist will receive that category." i18n-matTooltip="Allow playlist categorization setting tooltip"><ng-container i18n="Allow playlist categorization setting label">Allow playlist categorization</ng-container></mat-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider></mat-divider>
|
||||
@@ -216,7 +219,7 @@
|
||||
<div class="enable-api-key-div">
|
||||
<mat-form-field class="text-field" color="accent">
|
||||
<input [disabled]="!new_config['API']['use_API_key']" [(ngModel)]="new_config['API']['API_key']" matInput placeholder="Public API Key" i18n-placeholder="Public API Key setting placeholder" required>
|
||||
<mat-hint><a target="_blank" href="https://stoplight.io/p/docs/gh/tzahi12345/youtubedl-material"><ng-container i18n="View API docs setting hint">View documentation</ng-container></a></mat-hint>
|
||||
<mat-hint><a target="_blank" href="https://youtubedl-material.stoplight.io/docs/youtubedl-material/Public%20API%20v1.yaml"><ng-container i18n="View API docs setting hint">View documentation</ng-container></a></mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="api-key-div">
|
||||
@@ -277,6 +280,42 @@
|
||||
</div>
|
||||
</ng-template>
|
||||
</mat-tab>
|
||||
<!-- Database -->
|
||||
<mat-tab label="Database" i18n-label="Database settings label">
|
||||
<ng-template matTabContent>
|
||||
<div *ngIf="new_config" class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12 mt-3">
|
||||
<div *ngIf="db_info">
|
||||
<p><ng-container i18n="Database location label">Database location:</ng-container> <strong>{{db_info['using_local_db'] ? 'Local' : 'MongoDB'}}</strong></p>
|
||||
<h6 i18n="Records per table label">Records per table</h6>
|
||||
<mat-list style="padding-top: 0px">
|
||||
<mat-list-item style="height: 28px" *ngFor="let table_stats of db_info['stats_by_table'] | keyvalue">
|
||||
{{table_stats.key}}: {{table_stats.value.records_count}}
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
|
||||
<mat-form-field style="width: 100%; margin-top: 15px; margin-bottom: 10px" color="accent">
|
||||
<input [(ngModel)]="new_config['Database']['mongodb_connection_string']" matInput placeholder="MongoDB Connection String" i18n-placeholder="MongoDB Connection String" required>
|
||||
<mat-hint><ng-container i18n="MongoDB Connection String setting hint AKA preamble">Example:</ng-container> mongodb://127.0.0.1:27017/?compressors=zlib<br>Docker: mongodb://<container name>:27017/?compressors=zlib</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<div class="test-connection-div">
|
||||
<button (click)="testConnectionString(new_config['Database']['mongodb_connection_string'])" [disabled]="testing_connection_string" mat-flat-button color="accent"><ng-container i18n="Test connection string button">Test connection string</ng-container></button>
|
||||
</div>
|
||||
|
||||
<div class="transfer-db-div">
|
||||
<button [disabled]="db_transferring" color="accent" (click)="transferDB()" mat-raised-button><ng-container i18n="Transfer DB button">Transfer DB to </ng-container>{{db_info['using_local_db'] ? 'MongoDB' : 'Local'}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="!db_info">
|
||||
<ng-container i18n="Database info not retrieved error message">Database information could not be retrieved. Check the server logs for more information.</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</mat-tab>
|
||||
<!-- Advanced -->
|
||||
<mat-tab label="Advanced" i18n-label="Host settings label">
|
||||
<ng-template matTabContent>
|
||||
@@ -286,8 +325,9 @@
|
||||
<mat-form-field>
|
||||
<mat-label><ng-container i18n="Default downloader select label">Select a downloader</ng-container></mat-label>
|
||||
<mat-select color="accent" [(ngModel)]="new_config['Advanced']['default_downloader']">
|
||||
<mat-option value="youtube-dlc">youtube-dlc</mat-option>
|
||||
<mat-option value="youtube-dl">youtube-dl</mat-option>
|
||||
<mat-option value="youtube-dlc">youtube-dlc</mat-option>
|
||||
<mat-option value="yt-dlp">yt-dlp</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
@@ -350,6 +390,14 @@
|
||||
<div *ngIf="new_config" class="container-fluid mt-1">
|
||||
<app-updater></app-updater>
|
||||
</div>
|
||||
<mat-divider></mat-divider>
|
||||
<div *ngIf="new_config" class="container">
|
||||
<div class="row">
|
||||
<div class="col-12 mt-4">
|
||||
<button (click)="restartServer()" mat-stroked-button color="warn"><ng-container i18n="Restart server button">Restart server</ng-container></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</mat-tab>
|
||||
<mat-tab *ngIf="postsService.config && postsService.config.Advanced.multi_user_mode" label="Users" i18n-label="Users settings label">
|
||||
|
||||
@@ -77,8 +77,17 @@
|
||||
}
|
||||
|
||||
.category-custom-placeholder {
|
||||
background: #ccc;
|
||||
border: dotted 3px #999;
|
||||
min-height: 60px;
|
||||
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
|
||||
background: #ccc;
|
||||
border: dotted 3px #999;
|
||||
min-height: 60px;
|
||||
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.test-connection-div {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.transfer-db-div {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
@@ -20,7 +20,7 @@ import { EditCategoryDialogComponent } from 'app/dialogs/edit-category-dialog/ed
|
||||
})
|
||||
export class SettingsComponent implements OnInit {
|
||||
all_locales = isoLangs;
|
||||
supported_locales = ['en', 'es', 'de', 'fr', 'zh', 'nb', 'en-GB'];
|
||||
supported_locales = ['en', 'es', 'de', 'fr', 'nl', 'pt', 'it', 'ca', 'cs', 'nb', 'ru', 'zh', 'id', 'en-GB'];
|
||||
initialLocale = localStorage.getItem('locale');
|
||||
|
||||
initial_config = null;
|
||||
@@ -29,6 +29,10 @@ export class SettingsComponent implements OnInit {
|
||||
generated_bookmarklet_code = null;
|
||||
bookmarkletAudioOnly = false;
|
||||
|
||||
db_info = null;
|
||||
db_transferring = false;
|
||||
testing_connection_string = false;
|
||||
|
||||
_settingsSame = true;
|
||||
|
||||
latestGithubRelease = null;
|
||||
@@ -48,6 +52,7 @@ export class SettingsComponent implements OnInit {
|
||||
|
||||
ngOnInit() {
|
||||
this.getConfig();
|
||||
this.getDBInfo();
|
||||
|
||||
this.generated_bookmarklet_code = this.sanitizer.bypassSecurityTrustUrl(this.generateBookmarkletCode());
|
||||
|
||||
@@ -255,6 +260,68 @@ export class SettingsComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
restartServer() {
|
||||
this.postsService.restartServer().subscribe(res => {
|
||||
this.postsService.openSnackBar('Restarting!');
|
||||
}, err => {
|
||||
this.postsService.openSnackBar('Failed to restart the server.');
|
||||
});
|
||||
}
|
||||
|
||||
getDBInfo() {
|
||||
this.postsService.getDBInfo().subscribe(res => {
|
||||
this.db_info = res['db_info'];
|
||||
});
|
||||
}
|
||||
|
||||
transferDB() {
|
||||
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
|
||||
data: {
|
||||
dialogTitle: 'Transfer DB',
|
||||
dialogText: `Are you sure you want to transfer the DB?`,
|
||||
submitText: 'Transfer',
|
||||
}
|
||||
});
|
||||
dialogRef.afterClosed().subscribe(confirmed => {
|
||||
if (confirmed) {
|
||||
this._transferDB();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_transferDB() {
|
||||
this.db_transferring = true;
|
||||
this.postsService.transferDB(this.db_info['using_local_db']).subscribe(res => {
|
||||
this.db_transferring = false;
|
||||
const success = res['success'];
|
||||
if (success) {
|
||||
this.openSnackBar('Successfully transfered DB! Reloading info...');
|
||||
this.getDBInfo();
|
||||
} else {
|
||||
this.openSnackBar('Failed to transfer DB -- transfer was aborted. Error: ' + res['error']);
|
||||
}
|
||||
}, err => {
|
||||
this.db_transferring = false;
|
||||
this.openSnackBar('Failed to transfer DB -- API call failed. See browser logs for details.');
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
testConnectionString(connection_string) {
|
||||
this.testing_connection_string = true;
|
||||
this.postsService.testConnectionString(connection_string).subscribe(res => {
|
||||
this.testing_connection_string = false;
|
||||
if (res['success']) {
|
||||
this.postsService.openSnackBar('Connection successful!');
|
||||
} else {
|
||||
this.postsService.openSnackBar('Connection failed! Error: ' + res['error']);
|
||||
}
|
||||
}, err => {
|
||||
this.testing_connection_string = false;
|
||||
this.postsService.openSnackBar('Connection failed! Error: Server error. See logs for more info.');
|
||||
});
|
||||
}
|
||||
|
||||
// snackbar helper
|
||||
public openSnackBar(message: string, action: string = '') {
|
||||
this.snackBar.open(message, action, {
|
||||
|
||||
@@ -42,7 +42,7 @@ export class SubscriptionFileCardComponent implements OnInit {
|
||||
|
||||
goToFile() {
|
||||
const emit_obj = {
|
||||
name: this.file.id,
|
||||
uid: this.file.uid,
|
||||
url: this.file.requested_formats ? this.file.requested_formats[0].url : this.file.url
|
||||
}
|
||||
this.goToFileEmit.emit(emit_obj);
|
||||
|
||||
@@ -103,15 +103,14 @@ export class SubscriptionComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
goToFile(emit_obj) {
|
||||
const name = emit_obj['name'];
|
||||
const uid = emit_obj['uid'];
|
||||
const url = emit_obj['url'];
|
||||
localStorage.setItem('player_navigator', this.router.url);
|
||||
if (this.subscription.streamingOnly) {
|
||||
this.router.navigate(['/player', {name: name, url: url}]);
|
||||
this.router.navigate(['/player', {uid: uid, url: url}]);
|
||||
} else {
|
||||
this.router.navigate(['/player', {fileNames: name,
|
||||
type: this.subscription.type ? this.subscription.type : 'video', subscriptionName: this.subscription.name,
|
||||
subPlaylist: this.subscription.isPlaylist}]);
|
||||
this.router.navigate(['/player', {uid: uid,
|
||||
sub_id: this.subscription.id}]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +153,7 @@ export class SubscriptionComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
this.downloading = true;
|
||||
this.postsService.downloadFileFromServer(fileNames, 'video', this.subscription.name, true).subscribe(res => {
|
||||
this.postsService.downloadSubFromServer(this.subscription.id).subscribe(res => {
|
||||
this.downloading = false;
|
||||
const blob: Blob = res;
|
||||
saveAs(blob, this.subscription.name + '.zip');
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
"download_only_mode": false,
|
||||
"allow_multi_download_mode": true,
|
||||
"settings_pin_required": false,
|
||||
"enable_downloads_manager": true
|
||||
"enable_downloads_manager": true,
|
||||
"allow_playlist_categorization": true
|
||||
},
|
||||
"API": {
|
||||
"use_API_key": false,
|
||||
@@ -28,7 +29,8 @@
|
||||
"use_youtube_API": false,
|
||||
"youtube_API_key": "",
|
||||
"use_twitch_API": false,
|
||||
"twitch_API_key": ""
|
||||
"twitch_API_key": "",
|
||||
"twitch_auto_download_chat": true
|
||||
},
|
||||
"Themes": {
|
||||
"default_theme": "default",
|
||||
@@ -37,7 +39,7 @@
|
||||
"Subscriptions": {
|
||||
"allow_subscriptions": true,
|
||||
"subscriptions_base_path": "subscriptions/",
|
||||
"subscriptions_check_interval": "300",
|
||||
"subscriptions_check_interval": "86400",
|
||||
"subscriptions_use_youtubedl_archive": true
|
||||
},
|
||||
"Users": {
|
||||
@@ -52,6 +54,10 @@
|
||||
"searchFilter": "(uid={{username}})"
|
||||
}
|
||||
},
|
||||
"Database": {
|
||||
"mongodb_connection_string": "mongodb://127.0.0.1:27017/?compressors=zlib",
|
||||
"use_local_db": false
|
||||
},
|
||||
"Advanced": {
|
||||
"use_default_downloading_agent": true,
|
||||
"custom_downloading_agent": "",
|
||||
@@ -60,7 +66,7 @@
|
||||
"jwt_expiration": 86400,
|
||||
"logger_level": "debug",
|
||||
"use_cookies": false,
|
||||
"default_downloader": "youtube-dlc"
|
||||
"default_downloader": "youtube-dl"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
248
src/assets/i18n/messages.ca.json
Normal file
248
src/assets/i18n/messages.ca.json
Normal file
@@ -0,0 +1,248 @@
|
||||
{
|
||||
"004b222ff9ef9dd4771b777950ca1d0e4cd4348a": "Quant a",
|
||||
"994363f08f9fbfa3b3994ff7b35c6904fdff18d8": "Perfil",
|
||||
"adb4562d2dbd3584370e44496969d58c511ecb63": "Fosc",
|
||||
"121cc5391cd2a5115bc2b3160379ee5b36cd7716": "Paràmetres",
|
||||
"92eee6be6de0b11c924e3ab27db30257159c0a7c": "Inici",
|
||||
"6765b4c916060f6bc42d9bb69e80377dbcb5e4e9": "Inicia la sessió",
|
||||
"357064ca9d9ac859eb618e28e8126fa32be049e2": "Subscripcions",
|
||||
"822fab38216f64e8166d368b59fe756ca39d301b": "Baixades",
|
||||
"4a9889d36910edc8323d7bab60858ab3da6d91df": "Només àudio",
|
||||
"6a21ba5fb0ac804a525bf9ab168038c3ee88e661": "Baixa",
|
||||
"a38ae1082fec79ba1f379978337385a539a28e73": "Qualitat",
|
||||
"4be966a9dcfbc9b54dfcc604b831c0289f847fa4": "Utilitza un URL",
|
||||
"d3f02f845e62cebd75fde451ab8479d2a8ad784d": "Visualitza",
|
||||
"96a01fafe135afc58b0f8071a4ab00234495ce18": "Mode de baixades múltiples",
|
||||
"6a3777f913cf3f288664f0632b9f24794fdcc24e": "Cancel·la",
|
||||
"322ed150e02666fe2259c5b4614eac7066f4ffa0": "Avançat",
|
||||
"4e4c721129466be9c3862294dc40241b64045998": "Utilitza arguments personalitzats",
|
||||
"ad2f8ac8b7de7945b80c8e424484da94e597125f": "Arguments personalitzats",
|
||||
"a6911c2157f1b775284bbe9654ce5eb30cf45d7f": "No cal incloure l’URL, només tot el que hi ha després. Separeu els arguments amb dues comes: ,,",
|
||||
"3a92a3443c65a52f37ca7efb8f453b35dbefbf29": "Utilitza una sortida personalitzada",
|
||||
"d9c02face477f2f9cdaae318ccee5f89856851fb": "Sortida personalitzada",
|
||||
"fcfd4675b4c90f08d18d3abede9a9a4dff4cfdc7": "Documentació",
|
||||
"19d1ae64d94d28a29b2c57ae8671aace906b5401": "El camí és relatiu al camí de baixada de la configuració. No hi inclogueu l’extensió.",
|
||||
"b7ffe7c6586d6f3f18a9246806a7c7d5538ab43e": "Ordre simulada:",
|
||||
"8fad10737d3e3735a6699a4d89cbf6c20f6bb55f": "Utilitza autenticació",
|
||||
"08c74dc9762957593b91f6eb5d65efdfc975bf48": "Nom d’usuari",
|
||||
"c32ef07f8803a223a83ed17024b38e8d82292407": "Contrasenya",
|
||||
"17f0ea5d2d7a262b0e875acc70475f102aee84e6": "Crea una llista de reproducció",
|
||||
"cff1428d10d59d14e45edec3c735a27b5482db59": "Nom",
|
||||
"f61c6867295f3b53d23557021f2f4e0aa1d0b8fc": "Tipus",
|
||||
"f0baeb8b69d120073b6d60d34785889b0c3232c8": "Àudio",
|
||||
"2d1ea268a6a9f483dbc2cbfe19bf4256a57a6af4": "Vídeo",
|
||||
"f47e2d56dd8a145b2e9599da9730c049d52962a2": "Fitxers d’àudio",
|
||||
"a52dae09be10ca3a65da918533ced3d3f4992238": "Vídeos",
|
||||
"a9806cf78ce00eb2613eeca11354a97e033377b8": "Subscriviu-vos a la llista o al canal",
|
||||
"801b98c6f02fe3b32f6afa3ee854c99ed83474e6": "URL",
|
||||
"93efc99ae087fc116de708ecd3ace86ca237cf30": "L’URL de la llista o el canal",
|
||||
"08f5d0ef937ae17feb1b04aff15ad88911e87baf": "Nom personalitzat",
|
||||
"ea30873bd3f0d5e4fb2378eec3f0a1db77634a28": "Baixa totes les pujades",
|
||||
"d641b8fa5ac5e85114c733b1f7de6976bd091f70": "Qualitat màxima",
|
||||
"c76a955642714b8949ff3e4b4990864a2e2cac95": "Mode només d’àudio",
|
||||
"408ca4911457e84a348cecf214f02c69289aa8f1": "Mode només de transmissió",
|
||||
"f432e1a8d6adb12e612127978ce2e0ced933959c": "Aquestes s’afegeixen després dels arguments estàndards.",
|
||||
"98b6ec9ec138186d663e64770267b67334353d63": "Sortida de fitxer personalitzada",
|
||||
"d7b35c384aecd25a516200d6921836374613dfe7": "Cancel·la",
|
||||
"d0336848b0c375a1c25ba369b3481ee383217a4f": "Subscriviu-vos",
|
||||
"28a678e9cabf86e44c32594c43fa0e890135c20f": "Baixeu els vídeos penjats a la darrera",
|
||||
"e78c0d60ac39787f62c9159646fe0b3c1ed55a1d": "Tipus:",
|
||||
"c52db455cca9109ee47e1a612c3f4117c09eb71b": "URL:",
|
||||
"ca3dbbc7f3e011bffe32a10a3ea45cc84f30ecf1": "Id.:",
|
||||
"f4e529ae5ffd73001d1ff4bbdeeb0a72e342e5c8": "Tanca",
|
||||
"8efc77bf327659c0fec1f518cf48a98cdcd9dddf": "Exporta l’arxiu",
|
||||
"3042bd3ad8dffcfeca5fd1ae6159fd1047434e95": "Cancel·la la subscripció",
|
||||
"303e45ffae995c9817e510e38cb969e6bb3adcbf": "(En pausa)",
|
||||
"a44d86aa1e6c20ced07aca3a7c081d8db9ded1c6": "Arxiu:",
|
||||
"616e206cb4f25bd5885fc35925365e43cf5fb929": "Nom:",
|
||||
"c6eb45d085384903e53ab001a3513d1de6a1dbac": "Carregador:",
|
||||
"109c6f4a5e46efb933612ededfaf52a13178b7e0": "Mida del fitxer:",
|
||||
"bd630d8669b16e5f264ec4649d9b469fe03e5ff4": "Camí:",
|
||||
"a67e7d843cef735c79d5ef1c8ba4af3e758912bb": "Data de pujada:",
|
||||
"0cc1dec590ecd74bef71a865fb364779bc42a749": "Categoria:",
|
||||
"d9e83ac17026e70ef6e9c0f3240a3b2450367f40": "Modifica arguments del youtube-dl",
|
||||
"7fc1946abe2b40f60059c6cd19975d677095fd19": "Arguments nous simulats",
|
||||
"0b71824ae71972f236039bed43f8d2323e8fd570": "Afegeix un argument",
|
||||
"c8b0e59eb491f2ac7505f0fbab747062e6b32b23": "Cerca per categoria",
|
||||
"9eeb91caef5a50256dd87e1c4b7b3e8216479377": "Utilitza el valor de l’argument",
|
||||
"7de2451ed3fb8d8b847979bd3f0c740b970f167b": "Afegeix un argument",
|
||||
"b2623aee44b70c9a4ba1fce16c8a593b0a4c7974": "Modifica",
|
||||
"25d8ad5eba2ec24e68295a27d6a4bb9b49e3dacd": "Valor de l’argument",
|
||||
"91ecce65f1d23f9419d1c953cd6b7bc7f91c110e": "Actualitzador",
|
||||
"b7ff2e2b909c53abe088fe60b9f4b6ac7757247f": "Registra un usuari",
|
||||
"024886ca34a6f309e3e51c2ed849320592c3faaa": "Nom d’usuari",
|
||||
"cfc2f436ec2beffb042e7511a73c89c372e86a6c": "Registra",
|
||||
"ebadf946ae90f13ecd0c70f09edbc0f983af8a0f": "Puja galetes noves",
|
||||
"a8b7b9c168fd936a75e500806a8c0d7755ef1198": "NOTA: La càrrega de galetes noves anul·larà les galetes anteriors. Tingueu en compte també que les galetes són de tota la instància i no per usuari.",
|
||||
"98a8a42e5efffe17ab786636ed0139b4c7032d0e": "Arrossegar i deixar anar",
|
||||
"4f389e41e4592f7f9bb76abdd8af4afdfb13f4f1": "Modifica la llista de reproducció",
|
||||
"5caadefa4143cf6766a621b0f54f91f373a1f164": "Afegeix contingut",
|
||||
"52c9a103b812f258bcddc3d90a6e3f46871d25fe": "Desa",
|
||||
"33026f57ea65cd9c8a5d917a08083f71a718933a": "Ordre normal",
|
||||
"29376982b1205d9d6ea3d289e8e2f8e1ac2839b1": "Ordre invers",
|
||||
"d02888c485d3aeab6de628508f4a00312a722894": "Els meus vídeos",
|
||||
"7e892ba15f2c6c17e83510e273b3e10fc32ea016": "Cerca",
|
||||
"73423607944a694ce6f9e55cfee329681bb4d9f9": "No s’ha trobat cap vídeo.",
|
||||
"3697f8583ea42868aa269489ad366103d94aece7": "Editant",
|
||||
"07db550ae114d9faad3a0cbb68bcc16ab6cd31fc": "Pausat",
|
||||
"c3b0b86523f1d10e84a71f9b188d54913a11af3b": "Editant la categoria",
|
||||
"2489eefea00931942b91f4a1ae109514b591e2e1": "Regles",
|
||||
"e4eeb9106dbcbc91ca1ac3fb4068915998a70f37": "Afegeix una regla nova",
|
||||
"792dc6a57f28a1066db283f2e736484f066005fd": "Baixa el xat del Twitch",
|
||||
"28f86ffd419b869711aa13f5e5ff54be6d70731c": "Edita",
|
||||
"826b25211922a1b46436589233cb6f1a163d89b7": "Suprimeix",
|
||||
"321e4419a943044e674beb55b8039f42a9761ca5": "Informació",
|
||||
"e684046d73bcee88e82f7ff01e2852789a05fc32": "Recompte:",
|
||||
"34504b488c24c27e68089be549f0eeae6ebaf30b": "Suprimeix i afegeix a la llista negra",
|
||||
"dad95154dcef3509b8cc705046061fd24994bbb7": "visualitzacions",
|
||||
"5b3075e8dc3f3921ec316b0bd83b6d14a06c1a4f": "Desa els canvis",
|
||||
"4d8a18b04a1f785ecd8021ac824e0dfd5881dbfc": "La descàrrega ha estat correcta",
|
||||
"348cc5d553b18e862eb1c1770e5636f6b05ba130": "S'ha produït un error",
|
||||
"4f8b2bb476981727ab34ed40fde1218361f92c45": "Detalls",
|
||||
"e9aff8e6df2e2bf6299ea27bb2894c70bc48bd4d": "S’ha produït un error:",
|
||||
"77b0c73840665945b25bd128709aa64c8f017e1c": "Inici de la baixada:",
|
||||
"08ff9375ec078065bcdd7637b7ea65fce2979266": "Final de la baixada:",
|
||||
"ad127117f9471612f47d01eae09709da444a36a4": "Camins de fitxers:",
|
||||
"e2319dec5b4ccfb6ed9f55ccabd63650a8fdf547": "Les vostres subscripcions",
|
||||
"807cf11e6ac1cde912496f764c176bdfdd6b7e19": "Canals",
|
||||
"47546e45bbb476baaaad38244db444c427ddc502": "Llistes de reproducció",
|
||||
"29b89f751593e1b347eef103891b7a1ff36ec03f": "El nom no està disponible. S’està recuperant el canal.",
|
||||
"4636cd4a1379c50d471e98786098c4d39e1e82ad": "No tens cap subscripció al canal.",
|
||||
"2e0a410652cb07d069f576b61eab32586a18320d": "El nom no està disponible. Recuperant la llista de reproducció.",
|
||||
"587b57ced54965d8874c3fd0e9dfedb987e5df04": "No tens cap subscripció a la llista de reproducció.",
|
||||
"82421c3e46a0453a70c42900eab51d58d79e6599": "Principal",
|
||||
"0ba25ad86a240576c4f20a2fada4722ebba77b1e": "Descarregador",
|
||||
"d5f69691f9f05711633128b5a3db696783266b58": "Extra",
|
||||
"bc2e854e111ecf2bd7db170da5e3c2ed08181d88": "Avançat",
|
||||
"4d13a9cd5ed3dcee0eab22cb25198d43886942be": "Usuaris",
|
||||
"eb3d5aefff38a814b76da74371cbf02c0789a1ef": "Registres",
|
||||
"fe8fd36dbf5deee1d56564965787a782a66eba44": "{VAR_SELECT, select, true {Close} false {Cancel} other {otha}}",
|
||||
"54c512cca1923ab72faf1a0bd98d3d172469629a": "URL des d’on s’accedirà a aquesta aplicació, sense el port.",
|
||||
"cb2741a46e3560f6bc6dfd99d385e86b08b26d72": "Port",
|
||||
"22e8f1d0423a3b784fe40fab187b92c06541b577": "El port desitjat. Per defecte és 17442.",
|
||||
"d4477669a560750d2064051a510ef4d7679e2f3e": "Mode multiusuari",
|
||||
"2eb03565fcdce7a7a67abc277a936a32fcf51557": "Ruta base dels usuaris",
|
||||
"a64505c41150663968e277ec9b3ddaa5f4838798": "Ruta base per als usuaris i els seus vídeos descarregats.",
|
||||
"4e3120311801c4acd18de7146add2ee4a4417773": "Permet les subscripcions",
|
||||
"4bee2a4bef2d26d37c9b353c278e24e5cd309ce3": "Ruta base de subscripcions",
|
||||
"bc9892814ee2d119ae94378c905ea440a249b84a": "Ruta base per als vídeos dels vostres canals i llistes de reproducció subscrits. És relatiu a la carpeta arrel de YTDL-Material.",
|
||||
"5bef4b25ba680da7fff06b86a91b1fc7e6a926e3": "Interval de comprovació",
|
||||
"0f56a7449b77630c114615395bbda4cab398efd8": "La unitat és de segons, només inclou números.",
|
||||
"13759b09a7f4074ceee8fa2f968f9815fdf63295": "De vegades, es descarreguen vídeos nous abans de processar-los completament. Aquesta configuració fa que, pels vídeos nous, es comprovi si hi ha una versió de més qualitat l'endemà.",
|
||||
"3d1a47dc18b7bd8b5d9e1eb44b235ed9c4a2b513": "Torneu a descarregar les càrregues noves",
|
||||
"27a56aad79d8b61269ed303f11664cc78bcc2522": "Tema",
|
||||
"ff7cee38a2259526c519f878e71b964f41db4348": "Per defecte",
|
||||
"7a6bacee4c31cb5c0ac2d24274fb4610d8858602": "Permet canviar el tema",
|
||||
"fe46ccaae902ce974e2441abe752399288298619": "Idioma",
|
||||
"ab2756805742e84ad0cc0468f4be2d8aa9f855a5": "Camí a la carpeta d’àudio",
|
||||
"c2c89cdf45d46ea64d2ed2f9ac15dfa4d77e26ca": "Ruta de descàrregues de només d'àudio. És relatiu a la carpeta arrel de YTDL-Material.",
|
||||
"46826331da1949bd6fb74624447057099c9d20cd": "Ruta de la carpeta de vídeo",
|
||||
"17c92e6d47a213fa95b5aa344b3f258147123f93": "Ruta de descàrregues de vídeo. És relatiu a la carpeta arrel de YTDL-Material.",
|
||||
"cfe829634b1144bc44b6d38cf5584ea65db9804f": "Sortida de fitxer per defecte",
|
||||
"1148fd45287ff09955b938756bc302042bcb29c7": "La ruta és relativa a les rutes de descàrrega anteriors. No inclogueu l'extensió.",
|
||||
"ef418d4ece7c844f3a5e431da1aa59bedd88da7b": "Arguments personalitzats globals",
|
||||
"6b995e7130b4d667eaab6c5f61b362ace486d26d": "Arguments personalitzats globals per a descàrregues a la pàgina inicial. Els arguments es delimiten amb dues comes, així: ,,",
|
||||
"04201f9d27abd7d6f58a4328ab98063ce1072006": "Categories",
|
||||
"78e49b7339b4fa7184dd21bcaae107ce9b7076f6": "Utilitzeu l'arxiu youtube-dl",
|
||||
"ffc19f32b1cba0daefc0e5668f89346db1db83ad": "Inclou la miniatura",
|
||||
"384de8f8f112c9e6092eb2698706d391553f3e8d": "Inclou-hi metadades",
|
||||
"fb35145bfb84521e21b6385363d59221f436a573": "Mata totes les descàrregues",
|
||||
"61f8fd90b5f8cb20c70371feb2ee5e1fac5a9095": "Títol superior",
|
||||
"78d3531417c0d4ba4c90f0d4ae741edc261ec8df": "Gestor de fitxers habilitat",
|
||||
"a5a1be0a5df07de9eec57f5d2a86ed0204b2e75a": "Gestor de descàrregues habilitat",
|
||||
"c33bd5392b39dbed36b8e5a1145163a15d45835f": "Permet seleccionar la qualitat",
|
||||
"bda5508e24e0d77debb28bcd9194d8fefb1cfb92": "Mode de només baixades",
|
||||
"09d31c803a7252658694e1e3176b97f5655a3fe3": "Permet el mode de descàrrega múltiple",
|
||||
"1c4dbce56d96b8974aac24a02f7ab2ee81415014": "Habilita l'API pública",
|
||||
"23bd81dcc30b74d06279a26d7a42e8901c1b124e": "Clau API pública",
|
||||
"41016a73d8ad85e6cb26dffa0a8fab9fe8f60d8e": "Mostra la documentació",
|
||||
"00a94f58d9eb2e3aa561440eabea616d0c937fa2": "Això suprimirà la vostra clau de l’API anterior!",
|
||||
"1b258b258b4cc475ceb2871305b61756b0134f4a": "Genera",
|
||||
"d5d7c61349f3b0859336066e6d453fc35d334fe5": "Utilitza l’API del YouTube",
|
||||
"ce10d31febb3d9d60c160750570310f303a22c22": "Clau de l’API del YouTube",
|
||||
"8602e313cdfa7c4cc475ccbe86459fce3c3fd986": "És senzill generar una clau!",
|
||||
"d162f9fcd6a7187b391e004f072ab3da8377c47d": "Utilitza l’API del Twitch",
|
||||
"8ae23bc4302a479f687f4b20a84c276182e2519c": "Clau de l’API del Twitch",
|
||||
"84ffcebac2709ca0785f4a1d5ba274433b5beabc": "També coneguda com a identificador de client.",
|
||||
"5fb1e0083c9b2a40ac8ae7dcb2618311c291b8b9": "Baixa automàticament el xat del Twitch",
|
||||
"9b3cedfa83c6d7acb3210953289d1be4aab115c7": "Feu clic aquí",
|
||||
"7f09776373995003161235c0c8d02b7f91dbc4df": "per a baixar manualment l’extensió YouTubeDL-Material per al Chrome.",
|
||||
"5b5296423906ab3371fdb2b5a5aaa83acaa2ee52": "Heu de carregar manualment l'extensió i modificar la seva configuració per configurar l'URL de la interfície.",
|
||||
"9a2ec6da48771128384887525bdcac992632c863": "per instal·lar l'extensió oficial de YoutubeDL-Material per a Firefox directament des de la pàgina d'extensions de Firefox.",
|
||||
"eb81be6b49e195e5307811d1d08a19259d411f37": "Instruccions detallades de configuració.",
|
||||
"cb17ff8fe3961cf90f44bee97c88a3f3347a7e55": "No cal gaire cosa, a part de canviar la configuració de l'extensió per establir l'URL de la interfície.",
|
||||
"61b81b11aad0b9d970ece2fce18405f07eac69c2": "Arrossegueu l’enllaç següent als vostres adreces d'interès i ja podreu començar! Només cal que aneu al vídeo de YouTube que vulgueu baixar i feu clic al marcador.",
|
||||
"c505d6c5de63cc700f0aaf8a4b31fae9e18024e5": "Genereu el marcador 'només àudio'",
|
||||
"ec71e08aee647ea4a71fd6b7510c54d84a797ca6": "Seleccioneu un baixador",
|
||||
"5fab47f146b0a4b809dcebf3db9da94df6299ea1": "Utilitzeu l'agent de descàrrega predeterminat",
|
||||
"c776eb4992b6c98f58cd89b20c1ea8ac37888521": "Seleccioneu un agent de descàrrega",
|
||||
"0c43af932e6a4ee85500e28f01b3538b4eb27bc4": "Nivell de registre",
|
||||
"db6c192032f4cab809aad35215f0aa4765761897": "Caducitat de l'inici de sessió",
|
||||
"dc3d990391c944d1fbfc7cfb402f7b5e112fb3a8": "Permet la descàrrega avançada",
|
||||
"431e5f3a0dde88768d1074baedd65266412b3f02": "Utilitza les galetes",
|
||||
"80651a7ad1229ea6613557d3559f702cfa5aecf5": "Defineix les galetes",
|
||||
"37224420db54d4bc7696f157b779a7225f03ca9d": "Permetre el registre d’usuari",
|
||||
"fa548cee6ea11c160a416cac3e6bdec0363883dc": "Mètode d’autenticació",
|
||||
"4f56ced9d6b85aeb1d4346433361d47ea72dac1a": "Intern",
|
||||
"e3d7c5f019e79a3235a28ba24df24f11712c7627": "LDAP",
|
||||
"1db9789b93069861019bd0ccaa5d4706b00afc61": "URL LDAP",
|
||||
"f50fa6c09c8944aed504f6325f2913ee6c7a296a": "DN de vinculació",
|
||||
"080cc6abcba236390fc22e79792d0d3443a3bd2a": "Credencials de vinculació",
|
||||
"cfa67d14d84fe0e9fadf251dc51ffc181173b662": "Base de Cerca",
|
||||
"e01d54ecc1a0fcf9525a3c100ed8b83d94e61c23": "Filtre de cerca",
|
||||
"cec82c0a545f37420d55a9b6c45c20546e82f94e": "Quant al YoutubeDL-Material",
|
||||
"199c17e5d6a419313af3c325f06dcbb9645ca618": "és un descarregador de YouTube de codi obert construït segons les especificacions de Material Design de Google. Podeu descarregar sense problemes els vostres vídeos preferits com a fitxers de vídeo o àudio i, fins i tot, subscriure-us als vostres canals i llistes de reproducció preferits per estar al dia amb els nous vídeos.",
|
||||
"bc0ad0ee6630acb7fcb7802ec79f5a0ee943c1a7": "té algunes funcions increïbles incloses. Una àmplia API, assistència de Docker i suport de localització (traducció). Feu clic a la icona de GitHub de més amunt per llegir totes les funcions compatibles.",
|
||||
"a45e3b05f0529dc5246d70ef62304c94426d4c81": "Versió instal·lada:",
|
||||
"b33536f59b94ec935a16bd6869d836895dc5300c": "Heu trobat un error o teniu un suggeriment?",
|
||||
"e1f398f38ff1534303d4bb80bd6cece245f24016": "per crear un report de problema!",
|
||||
"e22f3a5351944f3a1a10cfc7da6f65dfbe0037fe": "S’està comprovant si hi ha actualitzacions…",
|
||||
"a16e92385b4fd9677bb830a4b796b8b79c113290": "Hi ha una actualització disponible",
|
||||
"189b28aaa19b3c51c6111ad039c4fd5e2a22e370": "Podeu actualitzar des del menú de paràmetres.",
|
||||
"1372e61c5bd06100844bd43b98b016aabc468f62": "Seleccioneu una versió:",
|
||||
"1f6d14a780a37a97899dc611881e6bc971268285": "Activa la compartició",
|
||||
"6580b6a950d952df847cb3d8e7176720a740adc8": "Usa la marca de temps",
|
||||
"4f2ed9e71a7c981db3e50ae2fedb28aff2ec4e6c": "Segons",
|
||||
"3a6e5a6aa78ca864f6542410c5dafb6334538106": "Copia al porta-retalls",
|
||||
"a249a5ae13e0835383885aaf697d2890cc3e53e9": "Comparteix la llista",
|
||||
"15da89490e04496ca9ea1e1b3d44fb5efd4a75d9": "Comparteix el vídeo",
|
||||
"1d540dcd271b316545d070f9d182c372d923aadd": "Comparteix l’àudio",
|
||||
"a1ad8b1be9be43b5183bd2c3186d4e19496f2a0b": "Id. de la sessió:",
|
||||
"b6c453e0e61faea184bbaf5c5b0a1e164f4de2a2": "Neteja totes les baixades",
|
||||
"eb98135e35af26a9a326ee69bd8ff104d36dd8ec": "(actual)",
|
||||
"7117fc42f860e86d983bfccfcf2654e5750f3406": "No hi ha cap descàrrega disponible!",
|
||||
"42ff677ec14f111e88bd6cdd30145378e994d1bf": "El teu perfil",
|
||||
"bb694b49d408265c91c62799c2b3a7e3151c824d": "Tancar sessió",
|
||||
"ac9d09de42edca1296371e4d801349c9096ac8de": "UID:",
|
||||
"a5ed099ffc9e96f6970df843289ade8a7d20ab9f": "Creat:",
|
||||
"fa96f2137af0a24e6d6d54c598c0af7d5d5ad344": "No heu iniciat la sessió.",
|
||||
"a1dbca87b9f36d2b06a5cbcffb5814c4ae9b798a": "Crea un compte d'administrador",
|
||||
"2d2adf3ca26a676bca2269295b7455a26fd26980": "No s'ha detectat cap compte d'administrador predeterminat. Això crearà i definirà la contrasenya d'un compte d'administrador amb el nom d'usuari 'admin'.",
|
||||
"70a67e04629f6d412db0a12d51820b480788d795": "Crea",
|
||||
"4d92a0395dd66778a931460118626c5794a3fc7a": "Afegeix usuaris",
|
||||
"b0d7dd8a1b0349622d6e0c6e643e24a9ea0efa1d": "Edita el rol",
|
||||
"746f64ddd9001ac456327cd9a3d5152203a4b93c": "Nom d’usuari",
|
||||
"52c1447c1ec9570a2a3025c7e566557b8d19ed92": "Rol",
|
||||
"59a8c38db3091a63ac1cb9590188dc3a972acfb3": "Accions",
|
||||
"2bd201aea09e43fbfd3cd15ec0499b6755302329": "Gestiona l'usuari",
|
||||
"95b95a9c79e4fd9ed41f6855e37b3b06af25bcab": "Suprimeix l'usuari",
|
||||
"632e8b20c98e8eec4059a605a4b011bb476137af": "Edita l'usuari",
|
||||
"29c97c8e76763bb15b6d515648fa5bd1eb0f7510": "UID d'usuari:",
|
||||
"e70e209561583f360b1e9cefd2cbb1fe434b6229": "Nova contrasenya",
|
||||
"6498fa1b8f563988f769654a75411bb8060134b9": "Establir una contrasenya nova",
|
||||
"544e09cdc99a8978f48521d45f62db0da6dcf742": "Utilitzeu el rol per defecte",
|
||||
"4f20f2d5a6882190892e58b85f6ccbedfa737952": "Sí",
|
||||
"3d3ae7deebc5949b0c1c78b9847886a94321d9fd": "No",
|
||||
"57c6c05d8ebf4ef1180c2705033c044f655bb2c4": "Gestiona el rol",
|
||||
"5009630cdf32ab4f1c78737b9617b8773512c05a": "Línies:",
|
||||
"8a0bda4c47f10b2423ff183acefbf70d4ab52ea2": "Esborra els registres",
|
||||
"24dc3ecf7ec2c2144910c4f3d38343828be03a4c": "Generat automàticament",
|
||||
"ccf5ea825526ac490974336cb5c24352886abc07": "Obrir fitxer",
|
||||
"5656a06f17c24b2d7eae9c221567b209743829a9": "Obre el fitxer en una pestanya nova",
|
||||
"a0720c36ee1057e5c54a86591b722485c62d7b1a": "Anar a la subscripció",
|
||||
"94e01842dcee90531caa52e4147f70679bac87fe": "Suprimeix i torna a descarregar",
|
||||
"2031adb51e07a41844e8ba7704b054e98345c9c1": "Esborra per sempre",
|
||||
"ddc31f2885b1b33a7651963254b0c197f2a64086": "Veure més.",
|
||||
"56a2a773fbd5a6b9ac2e6b89d29d70a2ed0f3227": "Veure menys.",
|
||||
"2054791b822475aeaea95c0119113de3200f5e1c": "Llargada:"
|
||||
}
|
||||
2517
src/assets/i18n/messages.ca.xlf
Normal file
2517
src/assets/i18n/messages.ca.xlf
Normal file
File diff suppressed because it is too large
Load Diff
248
src/assets/i18n/messages.cs.json
Normal file
248
src/assets/i18n/messages.cs.json
Normal file
@@ -0,0 +1,248 @@
|
||||
{
|
||||
"004b222ff9ef9dd4771b777950ca1d0e4cd4348a": "O aplikaci",
|
||||
"994363f08f9fbfa3b3994ff7b35c6904fdff18d8": "Účet",
|
||||
"adb4562d2dbd3584370e44496969d58c511ecb63": "Tmavý vzhled",
|
||||
"121cc5391cd2a5115bc2b3160379ee5b36cd7716": "Nastavení",
|
||||
"92eee6be6de0b11c924e3ab27db30257159c0a7c": "Domů",
|
||||
"6765b4c916060f6bc42d9bb69e80377dbcb5e4e9": "Přihlásit se",
|
||||
"357064ca9d9ac859eb618e28e8126fa32be049e2": "Odběry",
|
||||
"822fab38216f64e8166d368b59fe756ca39d301b": "Stažené",
|
||||
"4a9889d36910edc8323d7bab60858ab3da6d91df": "Pouze Zvuk",
|
||||
"6a21ba5fb0ac804a525bf9ab168038c3ee88e661": "Stáhnout",
|
||||
"a38ae1082fec79ba1f379978337385a539a28e73": "Kvalita",
|
||||
"4be966a9dcfbc9b54dfcc604b831c0289f847fa4": "Použijte URL adresu",
|
||||
"d3f02f845e62cebd75fde451ab8479d2a8ad784d": "Zobrazit",
|
||||
"96a01fafe135afc58b0f8071a4ab00234495ce18": "Stahovat více zároveň",
|
||||
"6a3777f913cf3f288664f0632b9f24794fdcc24e": "Zrušit",
|
||||
"322ed150e02666fe2259c5b4614eac7066f4ffa0": "Pokročilé nastavení",
|
||||
"4e4c721129466be9c3862294dc40241b64045998": "Použít vlastní argumenty",
|
||||
"ad2f8ac8b7de7945b80c8e424484da94e597125f": "Vlastní argumenty",
|
||||
"a6911c2157f1b775284bbe9654ce5eb30cf45d7f": "Není třeba vkládat URL adresu, jen to co je za ní. Argumenty se oddělují pomocí dvou čárek za sebou takto: ,,",
|
||||
"3a92a3443c65a52f37ca7efb8f453b35dbefbf29": "Použít vlastní výstup",
|
||||
"d9c02face477f2f9cdaae318ccee5f89856851fb": "Vlastní výstup",
|
||||
"fcfd4675b4c90f08d18d3abede9a9a4dff4cfdc7": "Dokumentace",
|
||||
"19d1ae64d94d28a29b2c57ae8671aace906b5401": "Cesta je relativní ke nakonfigurované cestě pro stahování. Nezahrnujte koncovky souborů.",
|
||||
"b7ffe7c6586d6f3f18a9246806a7c7d5538ab43e": "Simulovaný příkaz:",
|
||||
"8fad10737d3e3735a6699a4d89cbf6c20f6bb55f": "Použit autentifikaci",
|
||||
"08c74dc9762957593b91f6eb5d65efdfc975bf48": "Uživatelské jméno",
|
||||
"c32ef07f8803a223a83ed17024b38e8d82292407": "Heslo",
|
||||
"17f0ea5d2d7a262b0e875acc70475f102aee84e6": "Vytvořit playlist",
|
||||
"cff1428d10d59d14e45edec3c735a27b5482db59": "Název",
|
||||
"f61c6867295f3b53d23557021f2f4e0aa1d0b8fc": "Typ",
|
||||
"f0baeb8b69d120073b6d60d34785889b0c3232c8": "Audio",
|
||||
"2d1ea268a6a9f483dbc2cbfe19bf4256a57a6af4": "Video",
|
||||
"f47e2d56dd8a145b2e9599da9730c049d52962a2": "Audio soubory",
|
||||
"a52dae09be10ca3a65da918533ced3d3f4992238": "Videa",
|
||||
"a9806cf78ce00eb2613eeca11354a97e033377b8": "Odebírat playlist nebo kanál",
|
||||
"801b98c6f02fe3b32f6afa3ee854c99ed83474e6": "URL adresa",
|
||||
"93efc99ae087fc116de708ecd3ace86ca237cf30": "URL adresa playlistu nebo kanálu",
|
||||
"08f5d0ef937ae17feb1b04aff15ad88911e87baf": "Vlastní název",
|
||||
"ea30873bd3f0d5e4fb2378eec3f0a1db77634a28": "Stáhnout všechny nahrávky",
|
||||
"d641b8fa5ac5e85114c733b1f7de6976bd091f70": "Nejvyšší kvalita",
|
||||
"c76a955642714b8949ff3e4b4990864a2e2cac95": "Stahovat pouze audio",
|
||||
"408ca4911457e84a348cecf214f02c69289aa8f1": "Režim pouze pro streamování",
|
||||
"f432e1a8d6adb12e612127978ce2e0ced933959c": "Toto je přidáno za standartní argumenty.",
|
||||
"98b6ec9ec138186d663e64770267b67334353d63": "Vlastní výstup souboru",
|
||||
"d7b35c384aecd25a516200d6921836374613dfe7": "Zrušit odběr",
|
||||
"d0336848b0c375a1c25ba369b3481ee383217a4f": "Odebírat",
|
||||
"28a678e9cabf86e44c32594c43fa0e890135c20f": "Stahovat videa nahraná naposled v",
|
||||
"e78c0d60ac39787f62c9159646fe0b3c1ed55a1d": "Typ:",
|
||||
"c52db455cca9109ee47e1a612c3f4117c09eb71b": "URL:",
|
||||
"ca3dbbc7f3e011bffe32a10a3ea45cc84f30ecf1": "ID:",
|
||||
"f4e529ae5ffd73001d1ff4bbdeeb0a72e342e5c8": "Zavřít",
|
||||
"8efc77bf327659c0fec1f518cf48a98cdcd9dddf": "Exportovat Archiv",
|
||||
"3042bd3ad8dffcfeca5fd1ae6159fd1047434e95": "Zrušit odběr",
|
||||
"303e45ffae995c9817e510e38cb969e6bb3adcbf": "(Pozastaveno)",
|
||||
"a44d86aa1e6c20ced07aca3a7c081d8db9ded1c6": "Archiv:",
|
||||
"616e206cb4f25bd5885fc35925365e43cf5fb929": "Název:",
|
||||
"c6eb45d085384903e53ab001a3513d1de6a1dbac": "Autor:",
|
||||
"109c6f4a5e46efb933612ededfaf52a13178b7e0": "Velikost souboru:",
|
||||
"bd630d8669b16e5f264ec4649d9b469fe03e5ff4": "Cesta:",
|
||||
"a67e7d843cef735c79d5ef1c8ba4af3e758912bb": "Datum Nahrání:",
|
||||
"0cc1dec590ecd74bef71a865fb364779bc42a749": "Kategorie:",
|
||||
"d9e83ac17026e70ef6e9c0f3240a3b2450367f40": "Upravid youtube-dl argumenty",
|
||||
"7fc1946abe2b40f60059c6cd19975d677095fd19": "Nové simulované argumenty",
|
||||
"0b71824ae71972f236039bed43f8d2323e8fd570": "Přidat argument",
|
||||
"c8b0e59eb491f2ac7505f0fbab747062e6b32b23": "Hledat podle kategorie",
|
||||
"9eeb91caef5a50256dd87e1c4b7b3e8216479377": "Použít hodnotu argumentu",
|
||||
"7de2451ed3fb8d8b847979bd3f0c740b970f167b": "Přidat argument",
|
||||
"b2623aee44b70c9a4ba1fce16c8a593b0a4c7974": "Upravit",
|
||||
"25d8ad5eba2ec24e68295a27d6a4bb9b49e3dacd": "Hodnota argumentu",
|
||||
"91ecce65f1d23f9419d1c953cd6b7bc7f91c110e": "Aktualizace",
|
||||
"b7ff2e2b909c53abe088fe60b9f4b6ac7757247f": "Registrovat uživatele",
|
||||
"024886ca34a6f309e3e51c2ed849320592c3faaa": "Uživatelské jméno",
|
||||
"cfc2f436ec2beffb042e7511a73c89c372e86a6c": "Registrovat",
|
||||
"ebadf946ae90f13ecd0c70f09edbc0f983af8a0f": "Nahrání nových cookies",
|
||||
"a8b7b9c168fd936a75e500806a8c0d7755ef1198": "POZNÁMKA: Nahráním nových cookies přepíšete vaše předchozí cookie. Vezměte na vědomí, že cookies jsou společné pro celou instanci, nikoliv rozdělené podle uživatelů.",
|
||||
"98a8a42e5efffe17ab786636ed0139b4c7032d0e": "Přetáhněte sem",
|
||||
"4f389e41e4592f7f9bb76abdd8af4afdfb13f4f1": "Upravit playlist",
|
||||
"5caadefa4143cf6766a621b0f54f91f373a1f164": "Přidat obsah",
|
||||
"52c9a103b812f258bcddc3d90a6e3f46871d25fe": "Uložit",
|
||||
"33026f57ea65cd9c8a5d917a08083f71a718933a": "Normální řazení",
|
||||
"29376982b1205d9d6ea3d289e8e2f8e1ac2839b1": "Opačné řazení",
|
||||
"d02888c485d3aeab6de628508f4a00312a722894": "Moje videa",
|
||||
"7e892ba15f2c6c17e83510e273b3e10fc32ea016": "Vyhledávání",
|
||||
"73423607944a694ce6f9e55cfee329681bb4d9f9": "Nebyla nalezena žádná videa.",
|
||||
"3697f8583ea42868aa269489ad366103d94aece7": "Úprava",
|
||||
"07db550ae114d9faad3a0cbb68bcc16ab6cd31fc": "Pozastaveno",
|
||||
"c3b0b86523f1d10e84a71f9b188d54913a11af3b": "Úprava kategorie",
|
||||
"2489eefea00931942b91f4a1ae109514b591e2e1": "Pravidla",
|
||||
"e4eeb9106dbcbc91ca1ac3fb4068915998a70f37": "Přidat nové pravidlo",
|
||||
"792dc6a57f28a1066db283f2e736484f066005fd": "Stáhnout Twitch Chat",
|
||||
"28f86ffd419b869711aa13f5e5ff54be6d70731c": "Upravit",
|
||||
"826b25211922a1b46436589233cb6f1a163d89b7": "Odstranit",
|
||||
"321e4419a943044e674beb55b8039f42a9761ca5": "Informace",
|
||||
"e684046d73bcee88e82f7ff01e2852789a05fc32": "Počet:",
|
||||
"34504b488c24c27e68089be549f0eeae6ebaf30b": "Odstranit a zablokovat",
|
||||
"dad95154dcef3509b8cc705046061fd24994bbb7": "zhlédnutí",
|
||||
"5b3075e8dc3f3921ec316b0bd83b6d14a06c1a4f": "Uložit změny",
|
||||
"4d8a18b04a1f785ecd8021ac824e0dfd5881dbfc": "Stažení proběhlo úspěšně",
|
||||
"348cc5d553b18e862eb1c1770e5636f6b05ba130": "Došlo k chybě",
|
||||
"4f8b2bb476981727ab34ed40fde1218361f92c45": "Podrobnosti",
|
||||
"e9aff8e6df2e2bf6299ea27bb2894c70bc48bd4d": "Došlo k chybě:",
|
||||
"77b0c73840665945b25bd128709aa64c8f017e1c": "Zahájení stahování:",
|
||||
"08ff9375ec078065bcdd7637b7ea65fce2979266": "Konec stahování:",
|
||||
"ad127117f9471612f47d01eae09709da444a36a4": "Cesta k souboru:",
|
||||
"e2319dec5b4ccfb6ed9f55ccabd63650a8fdf547": "Vaše odběry",
|
||||
"807cf11e6ac1cde912496f764c176bdfdd6b7e19": "Kanály",
|
||||
"47546e45bbb476baaaad38244db444c427ddc502": "Playlisty",
|
||||
"29b89f751593e1b347eef103891b7a1ff36ec03f": "Název není dostupný. Probíhá vyhledávání kanálu.",
|
||||
"4636cd4a1379c50d471e98786098c4d39e1e82ad": "Nemáte žádné odběry.",
|
||||
"2e0a410652cb07d069f576b61eab32586a18320d": "Název není dostupný. Probíhá vyhledávání playlistu.",
|
||||
"587b57ced54965d8874c3fd0e9dfedb987e5df04": "Nemáte žádné odběry playlistů.",
|
||||
"82421c3e46a0453a70c42900eab51d58d79e6599": "Obecné",
|
||||
"0ba25ad86a240576c4f20a2fada4722ebba77b1e": "Stahování",
|
||||
"d5f69691f9f05711633128b5a3db696783266b58": "Ostatní",
|
||||
"bc2e854e111ecf2bd7db170da5e3c2ed08181d88": "Pokročilé",
|
||||
"4d13a9cd5ed3dcee0eab22cb25198d43886942be": "Uživatelé",
|
||||
"eb3d5aefff38a814b76da74371cbf02c0789a1ef": "Záznamy",
|
||||
"fe8fd36dbf5deee1d56564965787a782a66eba44": "{VAR_SELECT, select, true {Zavřít} false {Zrušit} other {Ostatní}}",
|
||||
"54c512cca1923ab72faf1a0bd98d3d172469629a": "URL adresa této aplikace, bez čísla portu.",
|
||||
"cb2741a46e3560f6bc6dfd99d385e86b08b26d72": "Číslo portu",
|
||||
"22e8f1d0423a3b784fe40fab187b92c06541b577": "Momentálně nastavený port. Výchozí je 17442.",
|
||||
"d4477669a560750d2064051a510ef4d7679e2f3e": "Režim více uživatelů",
|
||||
"2eb03565fcdce7a7a67abc277a936a32fcf51557": "Cesta pro uživatele",
|
||||
"a64505c41150663968e277ec9b3ddaa5f4838798": "Základní cesta pro uživatele a jejich stažená videa.",
|
||||
"4e3120311801c4acd18de7146add2ee4a4417773": "Povolit odběry",
|
||||
"4bee2a4bef2d26d37c9b353c278e24e5cd309ce3": "Základní cesta pro odběry",
|
||||
"bc9892814ee2d119ae94378c905ea440a249b84a": "Základní cesta k videím z odebíraných kanálů a playlistů. Cesta je relativní k hlavní složce YTDL-Material.",
|
||||
"5bef4b25ba680da7fff06b86a91b1fc7e6a926e3": "Interval kontroly",
|
||||
"0f56a7449b77630c114615395bbda4cab398efd8": "Hodnota ve vteřinách, použijte pouze číslice.",
|
||||
"13759b09a7f4074ceee8fa2f968f9815fdf63295": "Někdy jsou nová videa stažena dříve než mohou být plně zpracována. Toto nastavení znamená, že nová videa budou zkontrolována následující den pro zjištění, zda existují verze ve vyšší kvalitě.",
|
||||
"3d1a47dc18b7bd8b5d9e1eb44b235ed9c4a2b513": "Znovu stáhnout čersvé nahrávky",
|
||||
"27a56aad79d8b61269ed303f11664cc78bcc2522": "Vzhled",
|
||||
"ff7cee38a2259526c519f878e71b964f41db4348": "Výchozí",
|
||||
"7a6bacee4c31cb5c0ac2d24274fb4610d8858602": "Povolit změnu vzhledu",
|
||||
"fe46ccaae902ce974e2441abe752399288298619": "Jazyk",
|
||||
"ab2756805742e84ad0cc0468f4be2d8aa9f855a5": "Cesta pro složku s audio soubory",
|
||||
"c2c89cdf45d46ea64d2ed2f9ac15dfa4d77e26ca": "Cesta pro složku se zvukovými soubory. Cesta je relativní k hlavní složce YTDL-Material.",
|
||||
"46826331da1949bd6fb74624447057099c9d20cd": "Cesta pro složku s video soubory",
|
||||
"17c92e6d47a213fa95b5aa344b3f258147123f93": "Cesta pro složku s video soubory. Cesta je relativní k hlavní složce YTDL-Material.",
|
||||
"cfe829634b1144bc44b6d38cf5584ea65db9804f": "Výchozí výstup souboru",
|
||||
"1148fd45287ff09955b938756bc302042bcb29c7": "Cesta je relativní k cestám pro stažení výše. Nezahrnujte koncovky souborů.",
|
||||
"ef418d4ece7c844f3a5e431da1aa59bedd88da7b": "Globální vlastní argumenty",
|
||||
"6b995e7130b4d667eaab6c5f61b362ace486d26d": "Globální vlastní argumenty pro stahování na hlavní stránce. Argumenty se oddělují pomocí dvou čárek za sebou takto: ,,",
|
||||
"04201f9d27abd7d6f58a4328ab98063ce1072006": "Kategorie",
|
||||
"78e49b7339b4fa7184dd21bcaae107ce9b7076f6": "Použít archiv youtube-dl",
|
||||
"ffc19f32b1cba0daefc0e5668f89346db1db83ad": "Zahrnout náhledový obrázek",
|
||||
"384de8f8f112c9e6092eb2698706d391553f3e8d": "Zahrnout metadata",
|
||||
"fb35145bfb84521e21b6385363d59221f436a573": "Zrušit všechna stahování",
|
||||
"61f8fd90b5f8cb20c70371feb2ee5e1fac5a9095": "Hlavní název",
|
||||
"78d3531417c0d4ba4c90f0d4ae741edc261ec8df": "Povolit správce souborů",
|
||||
"a5a1be0a5df07de9eec57f5d2a86ed0204b2e75a": "Povolit správce stahování",
|
||||
"c33bd5392b39dbed36b8e5a1145163a15d45835f": "Povolit výběr kvality",
|
||||
"bda5508e24e0d77debb28bcd9194d8fefb1cfb92": "Režim pouze stahování",
|
||||
"09d31c803a7252658694e1e3176b97f5655a3fe3": "Povolit režim více stahování zároveň",
|
||||
"1c4dbce56d96b8974aac24a02f7ab2ee81415014": "Povolit Veřejné API",
|
||||
"23bd81dcc30b74d06279a26d7a42e8901c1b124e": "Veřejný API Klíč",
|
||||
"41016a73d8ad85e6cb26dffa0a8fab9fe8f60d8e": "Zobrazit dokumentaci",
|
||||
"00a94f58d9eb2e3aa561440eabea616d0c937fa2": "Tímto odstraníte svůj starý API klíč!",
|
||||
"1b258b258b4cc475ceb2871305b61756b0134f4a": "Vygenerovat",
|
||||
"d5d7c61349f3b0859336066e6d453fc35d334fe5": "Použít YouTube API",
|
||||
"ce10d31febb3d9d60c160750570310f303a22c22": "YouTube API Klíč",
|
||||
"8602e313cdfa7c4cc475ccbe86459fce3c3fd986": "Vygenerovat klič je snadné!",
|
||||
"d162f9fcd6a7187b391e004f072ab3da8377c47d": "Použít Twitch API",
|
||||
"8ae23bc4302a479f687f4b20a84c276182e2519c": "Twitch API Klíč",
|
||||
"84ffcebac2709ca0785f4a1d5ba274433b5beabc": "Také známý jako ID Klienta.",
|
||||
"5fb1e0083c9b2a40ac8ae7dcb2618311c291b8b9": "Automaticky stahovat Twitch Chat",
|
||||
"9b3cedfa83c6d7acb3210953289d1be4aab115c7": "Klikněte zde",
|
||||
"7f09776373995003161235c0c8d02b7f91dbc4df": "pro ruční stažení oficiálního Chrome doplňku YoutubeDL-Material.",
|
||||
"5b5296423906ab3371fdb2b5a5aaa83acaa2ee52": "Musíte ručně nahrát rozšíření a upravit jeho nastavení tak, aby odkazovalo na URL adresu vaší domény.",
|
||||
"9a2ec6da48771128384887525bdcac992632c863": "pro instalaci oficiálního doplňku YouTubeDL-Material přímo z obchodu Firefox.",
|
||||
"eb81be6b49e195e5307811d1d08a19259d411f37": "Podrobné instrukce k nastavení.",
|
||||
"cb17ff8fe3961cf90f44bee97c88a3f3347a7e55": "Není toho potřeba mnoho, stačí v nastavení rozšíření změnit odkaz na URL adresu Vaší domény.",
|
||||
"61b81b11aad0b9d970ece2fce18405f07eac69c2": "Přetáhněte odkaz níže do svých záložek a je to! Poté si najděte na YouTube video, které byste chtěli stáhnout a klikněte na uloženou záložku.",
|
||||
"c505d6c5de63cc700f0aaf8a4b31fae9e18024e5": "Vygenerovat záložku pro 'pouze zvuk'",
|
||||
"ec71e08aee647ea4a71fd6b7510c54d84a797ca6": "Vybrat službu pro stahování",
|
||||
"5fab47f146b0a4b809dcebf3db9da94df6299ea1": "Použít výchozího agenta pro stahování",
|
||||
"c776eb4992b6c98f58cd89b20c1ea8ac37888521": "Vybrat agenta pro stahování",
|
||||
"0c43af932e6a4ee85500e28f01b3538b4eb27bc4": "Úroveň Záznamů",
|
||||
"db6c192032f4cab809aad35215f0aa4765761897": "Vypršení přihlášení",
|
||||
"dc3d990391c944d1fbfc7cfb402f7b5e112fb3a8": "Povolit pokročilé nastavení stahování",
|
||||
"431e5f3a0dde88768d1074baedd65266412b3f02": "Použít Cookies",
|
||||
"80651a7ad1229ea6613557d3559f702cfa5aecf5": "Nastavit Cookies",
|
||||
"37224420db54d4bc7696f157b779a7225f03ca9d": "Povolit registraci uživatelů",
|
||||
"fa548cee6ea11c160a416cac3e6bdec0363883dc": "Autentifikační metoda",
|
||||
"4f56ced9d6b85aeb1d4346433361d47ea72dac1a": "Interní",
|
||||
"e3d7c5f019e79a3235a28ba24df24f11712c7627": "LDAP",
|
||||
"1db9789b93069861019bd0ccaa5d4706b00afc61": "URL adresa LDAP",
|
||||
"f50fa6c09c8944aed504f6325f2913ee6c7a296a": "Bind-DN",
|
||||
"080cc6abcba236390fc22e79792d0d3443a3bd2a": "Přihlašovací údaje pro Bind",
|
||||
"cfa67d14d84fe0e9fadf251dc51ffc181173b662": "Vyhledávací Základna",
|
||||
"e01d54ecc1a0fcf9525a3c100ed8b83d94e61c23": "Vyhledávací Filtr",
|
||||
"cec82c0a545f37420d55a9b6c45c20546e82f94e": "O YoutubeDL-Material",
|
||||
"199c17e5d6a419313af3c325f06dcbb9645ca618": "je open-source projekt pro stahování z YouTube postavený na specifikaci Material Designu od Google. Můžete pohodlně a bez problémů stahovat Vaše oblíbená videa jako video soubory nebo pouze zvukové soubory a dokonce odebírat Vaše oblíbené kanály a playlisty, aby Vám neunikla ta nejnovější videa.",
|
||||
"bc0ad0ee6630acb7fcb7802ec79f5a0ee943c1a7": "má spoustu skvělých funkcí! Rozšířitelné API, podporu Dockeru, překladů a lokalizace. Pro více informací o všech funkcích klikněte na GitHub ikonu výše.",
|
||||
"a45e3b05f0529dc5246d70ef62304c94426d4c81": "Nainstalovaná verze:",
|
||||
"b33536f59b94ec935a16bd6869d836895dc5300c": "Našli jste chybu nebo máte připomínku?",
|
||||
"e1f398f38ff1534303d4bb80bd6cece245f24016": "pro vytvoření Problému na GitHubu!",
|
||||
"e22f3a5351944f3a1a10cfc7da6f65dfbe0037fe": "Probíhá kontrola aktualizací...",
|
||||
"a16e92385b4fd9677bb830a4b796b8b79c113290": "Dostupná aktualizace",
|
||||
"189b28aaa19b3c51c6111ad039c4fd5e2a22e370": "Můžete aktualizovat pomocí menu nastavení.",
|
||||
"1372e61c5bd06100844bd43b98b016aabc468f62": "Vyberte verzi:",
|
||||
"1f6d14a780a37a97899dc611881e6bc971268285": "Povolit sdílení",
|
||||
"6580b6a950d952df847cb3d8e7176720a740adc8": "Použít časové razítko",
|
||||
"4f2ed9e71a7c981db3e50ae2fedb28aff2ec4e6c": "Vteřin",
|
||||
"3a6e5a6aa78ca864f6542410c5dafb6334538106": "Zkopírovat do schránky",
|
||||
"a249a5ae13e0835383885aaf697d2890cc3e53e9": "Sdílet playlist",
|
||||
"15da89490e04496ca9ea1e1b3d44fb5efd4a75d9": "Sdílet video",
|
||||
"1d540dcd271b316545d070f9d182c372d923aadd": "Sdílet audio",
|
||||
"a1ad8b1be9be43b5183bd2c3186d4e19496f2a0b": "ID Relace:",
|
||||
"b6c453e0e61faea184bbaf5c5b0a1e164f4de2a2": "Vymazat všechna stahování",
|
||||
"eb98135e35af26a9a326ee69bd8ff104d36dd8ec": "(momentální)",
|
||||
"7117fc42f860e86d983bfccfcf2654e5750f3406": "Nejsou dostupná žádná stahování!",
|
||||
"42ff677ec14f111e88bd6cdd30145378e994d1bf": "Váš Profil",
|
||||
"bb694b49d408265c91c62799c2b3a7e3151c824d": "Odhlásit se",
|
||||
"ac9d09de42edca1296371e4d801349c9096ac8de": "UID:",
|
||||
"a5ed099ffc9e96f6970df843289ade8a7d20ab9f": "Vytvořeno:",
|
||||
"fa96f2137af0a24e6d6d54c598c0af7d5d5ad344": "Nejste přihlášen.",
|
||||
"a1dbca87b9f36d2b06a5cbcffb5814c4ae9b798a": "Vytvořit administrátorský účet",
|
||||
"2d2adf3ca26a676bca2269295b7455a26fd26980": "Nebyl zjištěn žádný administrátorský účet. Tímto se vytvoří administrátorský účet 'admin' a bude mu vytvořeno heslo.",
|
||||
"70a67e04629f6d412db0a12d51820b480788d795": "Vytvořit",
|
||||
"4d92a0395dd66778a931460118626c5794a3fc7a": "Přidat Uživatele",
|
||||
"b0d7dd8a1b0349622d6e0c6e643e24a9ea0efa1d": "Upravit Roli",
|
||||
"746f64ddd9001ac456327cd9a3d5152203a4b93c": "Uživatelské jméno",
|
||||
"52c1447c1ec9570a2a3025c7e566557b8d19ed92": "Role",
|
||||
"59a8c38db3091a63ac1cb9590188dc3a972acfb3": "Akce",
|
||||
"2bd201aea09e43fbfd3cd15ec0499b6755302329": "Spravovat uživatele",
|
||||
"95b95a9c79e4fd9ed41f6855e37b3b06af25bcab": "Odstranit uživatele",
|
||||
"632e8b20c98e8eec4059a605a4b011bb476137af": "Upravit uživatele",
|
||||
"29c97c8e76763bb15b6d515648fa5bd1eb0f7510": "UID Uživatele:",
|
||||
"e70e209561583f360b1e9cefd2cbb1fe434b6229": "Nové heslo",
|
||||
"6498fa1b8f563988f769654a75411bb8060134b9": "Nastavit nové heslo",
|
||||
"544e09cdc99a8978f48521d45f62db0da6dcf742": "Použít výchozí nastavení role",
|
||||
"4f20f2d5a6882190892e58b85f6ccbedfa737952": "Ano",
|
||||
"3d3ae7deebc5949b0c1c78b9847886a94321d9fd": "Ne",
|
||||
"57c6c05d8ebf4ef1180c2705033c044f655bb2c4": "Spravovat roli",
|
||||
"5009630cdf32ab4f1c78737b9617b8773512c05a": "Řádky:",
|
||||
"8a0bda4c47f10b2423ff183acefbf70d4ab52ea2": "Vyčistit záznamy",
|
||||
"24dc3ecf7ec2c2144910c4f3d38343828be03a4c": "Vytvořeno automaticky",
|
||||
"ccf5ea825526ac490974336cb5c24352886abc07": "Otevřít soubor",
|
||||
"5656a06f17c24b2d7eae9c221567b209743829a9": "Otevřít soubor na nové kartě",
|
||||
"a0720c36ee1057e5c54a86591b722485c62d7b1a": "Jít do odběrů",
|
||||
"94e01842dcee90531caa52e4147f70679bac87fe": "Odstranit a stáhnout znovu",
|
||||
"2031adb51e07a41844e8ba7704b054e98345c9c1": "Odstranit navždy",
|
||||
"ddc31f2885b1b33a7651963254b0c197f2a64086": "Zobrazit více.",
|
||||
"56a2a773fbd5a6b9ac2e6b89d29d70a2ed0f3227": "Zobrazit méně.",
|
||||
"2054791b822475aeaea95c0119113de3200f5e1c": "Délka:"
|
||||
}
|
||||
2517
src/assets/i18n/messages.cs.xlf
Normal file
2517
src/assets/i18n/messages.cs.xlf
Normal file
File diff suppressed because it is too large
Load Diff
@@ -18,8 +18,8 @@
|
||||
"4be966a9dcfbc9b54dfcc604b831c0289f847fa4": "URL verwenden",
|
||||
"d3f02f845e62cebd75fde451ab8479d2a8ad784d": "Ansehen",
|
||||
"4a9889d36910edc8323d7bab60858ab3da6d91df": "Nur Audio",
|
||||
"96a01fafe135afc58b0f8071a4ab00234495ce18": "Multi-Download Modus",
|
||||
"6a21ba5fb0ac804a525bf9ab168038c3ee88e661": "Download",
|
||||
"96a01fafe135afc58b0f8071a4ab00234495ce18": "Multi-Herunteraden-Modus",
|
||||
"6a21ba5fb0ac804a525bf9ab168038c3ee88e661": "Herunterladen",
|
||||
"6a3777f913cf3f288664f0632b9f24794fdcc24e": "Abbrechen",
|
||||
"322ed150e02666fe2259c5b4614eac7066f4ffa0": "Erweitert",
|
||||
"b7ffe7c6586d6f3f18a9246806a7c7d5538ab43e": "Simulierter Befehl:",
|
||||
@@ -49,9 +49,9 @@
|
||||
"f4e529ae5ffd73001d1ff4bbdeeb0a72e342e5c8": "Schließen",
|
||||
"ca3dbbc7f3e011bffe32a10a3ea45cc84f30ecf1": "ID:",
|
||||
"e684046d73bcee88e82f7ff01e2852789a05fc32": "Anzahl:",
|
||||
"321e4419a943044e674beb55b8039f42a9761ca5": "Info",
|
||||
"321e4419a943044e674beb55b8039f42a9761ca5": "Infos",
|
||||
"826b25211922a1b46436589233cb6f1a163d89b7": "Löschen",
|
||||
"34504b488c24c27e68089be549f0eeae6ebaf30b": "Löschen und zur Blacklist hinzufügen",
|
||||
"34504b488c24c27e68089be549f0eeae6ebaf30b": "Löschen und zur schwarzen Liste hinzufügen",
|
||||
"121cc5391cd2a5115bc2b3160379ee5b36cd7716": "Einstellungen",
|
||||
"801b98c6f02fe3b32f6afa3ee854c99ed83474e6": "URL",
|
||||
"54c512cca1923ab72faf1a0bd98d3d172469629a": "URL, über die auf diese Applikation zugegriffen wird, ohne Port.",
|
||||
@@ -88,8 +88,8 @@
|
||||
"78d3531417c0d4ba4c90f0d4ae741edc261ec8df": "Dateimanager aktivieren",
|
||||
"a5a1be0a5df07de9eec57f5d2a86ed0204b2e75a": "Download-Manager aktivieren",
|
||||
"c33bd5392b39dbed36b8e5a1145163a15d45835f": "Qualitätsauswahl erlauben",
|
||||
"bda5508e24e0d77debb28bcd9194d8fefb1cfb92": "Nur Download Modus",
|
||||
"09d31c803a7252658694e1e3176b97f5655a3fe3": "Multi-Download Modus erlauben",
|
||||
"bda5508e24e0d77debb28bcd9194d8fefb1cfb92": "Nur-Herunterladen-Modus",
|
||||
"09d31c803a7252658694e1e3176b97f5655a3fe3": "Multi-Herunterladen-Modus erlauben",
|
||||
"d8b47221b5af9e9e4cd5cb434d76fc0c91611409": "Einstellungen durch PIN schützen",
|
||||
"f5ec7b2cdf87d41154f4fcbc86e856314409dcb9": "Neuen PIN festlegen",
|
||||
"1c4dbce56d96b8974aac24a02f7ab2ee81415014": "Öffentliche API aktivieren",
|
||||
@@ -120,7 +120,7 @@
|
||||
"199c17e5d6a419313af3c325f06dcbb9645ca618": "ist ein quelloffener YouTube-Downloader, der nach den Material-Design-Richtlinien von Google erstellt wurde. Sie können Ihre Lieblingsvideos reibungslos als Video- oder Audiodateien herunterladen und sogar Ihre Lieblingskanäle und Wiedergabelisten abonnieren, um auf dem Laufenden zu bleiben.",
|
||||
"bc0ad0ee6630acb7fcb7802ec79f5a0ee943c1a7": "beinhaltet viele tolle Funktionen! API, Docker und Lokalisierung werden unter anderem unterstützt. Informieren Sie sich über alle unterstützten Funktionen auf Github.",
|
||||
"a45e3b05f0529dc5246d70ef62304c94426d4c81": "Installierte Version:",
|
||||
"e22f3a5351944f3a1a10cfc7da6f65dfbe0037fe": "Suche nach Updates ...",
|
||||
"e22f3a5351944f3a1a10cfc7da6f65dfbe0037fe": "Suche nach Aktualisierungen …",
|
||||
"a16e92385b4fd9677bb830a4b796b8b79c113290": "Aktualisierung verfügbar",
|
||||
"189b28aaa19b3c51c6111ad039c4fd5e2a22e370": "Sie können über das Einstellungsmenü aktualisieren.",
|
||||
"b33536f59b94ec935a16bd6869d836895dc5300c": "Haben Sie einen Fehler gefunden oder einen Vorschlag?",
|
||||
@@ -130,7 +130,7 @@
|
||||
"a5ed099ffc9e96f6970df843289ade8a7d20ab9f": "Erstellt:",
|
||||
"fa96f2137af0a24e6d6d54c598c0af7d5d5ad344": "Sie sind nicht angemeldet.",
|
||||
"6765b4c916060f6bc42d9bb69e80377dbcb5e4e9": "Anmelden",
|
||||
"bb694b49d408265c91c62799c2b3a7e3151c824d": "Ausloggen",
|
||||
"bb694b49d408265c91c62799c2b3a7e3151c824d": "Abmelden",
|
||||
"a1dbca87b9f36d2b06a5cbcffb5814c4ae9b798a": "Admin-Konto erstellen",
|
||||
"2d2adf3ca26a676bca2269295b7455a26fd26980": "Es wurde kein Standard-Administratorkonto erkannt. Ein Administratorkonto mit dem Benutzernamen \"admin\" wird erstellt und ein Passwort wird festgelegt.",
|
||||
"70a67e04629f6d412db0a12d51820b480788d795": "Erstellen",
|
||||
@@ -149,16 +149,16 @@
|
||||
"5b3075e8dc3f3921ec316b0bd83b6d14a06c1a4f": "Änderungen speichern",
|
||||
"4f8b2bb476981727ab34ed40fde1218361f92c45": "Details",
|
||||
"e9aff8e6df2e2bf6299ea27bb2894c70bc48bd4d": "Ein Fehler ist aufgetreten:",
|
||||
"77b0c73840665945b25bd128709aa64c8f017e1c": "Download Start:",
|
||||
"08ff9375ec078065bcdd7637b7ea65fce2979266": "Download Ende:",
|
||||
"77b0c73840665945b25bd128709aa64c8f017e1c": "Start des Herunterladen:",
|
||||
"08ff9375ec078065bcdd7637b7ea65fce2979266": "Ende des Herunterladen:",
|
||||
"ad127117f9471612f47d01eae09709da444a36a4": "Dateipfad(e):",
|
||||
"a9806cf78ce00eb2613eeca11354a97e033377b8": "Abonnieren Sie eine Wiedergabeliste oder einen Kanal",
|
||||
"93efc99ae087fc116de708ecd3ace86ca237cf30": "URL der Wiedergabeliste oder des Kanales",
|
||||
"08f5d0ef937ae17feb1b04aff15ad88911e87baf": "Benutzerdefinierter Name",
|
||||
"f3f62aa84d59f3a8b900cc9a7eec3ef279a7b4e7": "Dies ist optional",
|
||||
"ea30873bd3f0d5e4fb2378eec3f0a1db77634a28": "Alle Uploads herunterladen",
|
||||
"ea30873bd3f0d5e4fb2378eec3f0a1db77634a28": "Alle hochgeladene Videos herunterladen",
|
||||
"28a678e9cabf86e44c32594c43fa0e890135c20f": "Videos herunterladen aus den letzten",
|
||||
"408ca4911457e84a348cecf214f02c69289aa8f1": "Nur Streaming Modus",
|
||||
"408ca4911457e84a348cecf214f02c69289aa8f1": "Nur-Streaming-Modus",
|
||||
"d0336848b0c375a1c25ba369b3481ee383217a4f": "Abonnieren",
|
||||
"e78c0d60ac39787f62c9159646fe0b3c1ed55a1d": "Typ:",
|
||||
"a44d86aa1e6c20ced07aca3a7c081d8db9ded1c6": "Archiv:",
|
||||
@@ -179,7 +179,7 @@
|
||||
"cfc2f436ec2beffb042e7511a73c89c372e86a6c": "Registrieren",
|
||||
"a1ad8b1be9be43b5183bd2c3186d4e19496f2a0b": "Sitzungs-ID:",
|
||||
"eb98135e35af26a9a326ee69bd8ff104d36dd8ec": "(aktuell)",
|
||||
"7117fc42f860e86d983bfccfcf2654e5750f3406": "Zurzeit sind keine Herunterladen-Ereignisse verfügbar!",
|
||||
"7117fc42f860e86d983bfccfcf2654e5750f3406": "Zurzeit sind keine Downloads verfügbar!",
|
||||
"b7ff2e2b909c53abe088fe60b9f4b6ac7757247f": "Nutzer registrieren",
|
||||
"024886ca34a6f309e3e51c2ed849320592c3faaa": "Benutzername",
|
||||
"2bd201aea09e43fbfd3cd15ec0499b6755302329": "Benutzer verwalten",
|
||||
@@ -199,7 +199,7 @@
|
||||
"fd59fb984749fcdb5e386ae85faec82f8e5ac098": "Logs erscheinen hier",
|
||||
"98b6ec9ec138186d663e64770267b67334353d63": "Benutzerdefinierte Dateiausgabe",
|
||||
"f432e1a8d6adb12e612127978ce2e0ced933959c": "Diese werden nach den Standardargumenten hinzugefügt.",
|
||||
"c76a955642714b8949ff3e4b4990864a2e2cac95": "Nur-Audio Modus",
|
||||
"c76a955642714b8949ff3e4b4990864a2e2cac95": "Nur-Audio-Modus",
|
||||
"eb3d5aefff38a814b76da74371cbf02c0789a1ef": "Protokolle",
|
||||
"80651a7ad1229ea6613557d3559f702cfa5aecf5": "Cookies setzen",
|
||||
"431e5f3a0dde88768d1074baedd65266412b3f02": "Cookies verwenden",
|
||||
@@ -221,5 +221,50 @@
|
||||
"511b600ae4cf037e4eb3b7a58410842cd5727490": "Weitere Inhalte hinzufügen",
|
||||
"f61c6867295f3b53d23557021f2f4e0aa1d0b8fc": "Typ",
|
||||
"2d1ea268a6a9f483dbc2cbfe19bf4256a57a6af4": "Video",
|
||||
"f0baeb8b69d120073b6d60d34785889b0c3232c8": "Audio"
|
||||
"f0baeb8b69d120073b6d60d34785889b0c3232c8": "Audio",
|
||||
"d02888c485d3aeab6de628508f4a00312a722894": "Meine Videos",
|
||||
"a0720c36ee1057e5c54a86591b722485c62d7b1a": "Zu den Abonnements gehen",
|
||||
"5656a06f17c24b2d7eae9c221567b209743829a9": "Datei in einer neuen Registerkarte öffnen",
|
||||
"b6c453e0e61faea184bbaf5c5b0a1e164f4de2a2": "Herunterladen-Liste leeren",
|
||||
"544e09cdc99a8978f48521d45f62db0da6dcf742": "Rollenstandard verwenden",
|
||||
"ccf5ea825526ac490974336cb5c24352886abc07": "Datei öffnen",
|
||||
"8a0bda4c47f10b2423ff183acefbf70d4ab52ea2": "Protokolldatei löschen",
|
||||
"95b95a9c79e4fd9ed41f6855e37b3b06af25bcab": "Benutzer löschen",
|
||||
"632e8b20c98e8eec4059a605a4b011bb476137af": "Benutzer bearbeiten",
|
||||
"fa548cee6ea11c160a416cac3e6bdec0363883dc": "Authentifizierungsmethode",
|
||||
"3697f8583ea42868aa269489ad366103d94aece7": "Bearbeiten",
|
||||
"348cc5d553b18e862eb1c1770e5636f6b05ba130": "Ein Fehler ist aufgetreten",
|
||||
"4d8a18b04a1f785ecd8021ac824e0dfd5881dbfc": "Der Download war erfolgreich",
|
||||
"cfa67d14d84fe0e9fadf251dc51ffc181173b662": "Search Base",
|
||||
"080cc6abcba236390fc22e79792d0d3443a3bd2a": "Bind Credentials",
|
||||
"f50fa6c09c8944aed504f6325f2913ee6c7a296a": "Bind DN",
|
||||
"1db9789b93069861019bd0ccaa5d4706b00afc61": "LDAP URL",
|
||||
"e3d7c5f019e79a3235a28ba24df24f11712c7627": "LDAP",
|
||||
"5fb1e0083c9b2a40ac8ae7dcb2618311c291b8b9": "Twitch-Chat automatisch herunterladen",
|
||||
"84ffcebac2709ca0785f4a1d5ba274433b5beabc": "Wird auch als Client-ID bezeichnet.",
|
||||
"8ae23bc4302a479f687f4b20a84c276182e2519c": "Twitch-API-Schlüssel",
|
||||
"d162f9fcd6a7187b391e004f072ab3da8377c47d": "Twitch-API verwenden",
|
||||
"04201f9d27abd7d6f58a4328ab98063ce1072006": "Kategorien",
|
||||
"3d1a47dc18b7bd8b5d9e1eb44b235ed9c4a2b513": "Neue Uploads erneut herunterladen",
|
||||
"792dc6a57f28a1066db283f2e736484f066005fd": "Twitch-Chat herunterladen",
|
||||
"e4eeb9106dbcbc91ca1ac3fb4068915998a70f37": "Neue Regel hinzufügen",
|
||||
"2489eefea00931942b91f4a1ae109514b591e2e1": "Regeln",
|
||||
"c3b0b86523f1d10e84a71f9b188d54913a11af3b": "Kategorie bearbeiten",
|
||||
"07db550ae114d9faad3a0cbb68bcc16ab6cd31fc": "Pausiert",
|
||||
"73423607944a694ce6f9e55cfee329681bb4d9f9": "Keine Videos gefunden.",
|
||||
"29376982b1205d9d6ea3d289e8e2f8e1ac2839b1": "Umgekehrte Reihenfolge",
|
||||
"33026f57ea65cd9c8a5d917a08083f71a718933a": "Normale Reihenfolge",
|
||||
"5caadefa4143cf6766a621b0f54f91f373a1f164": "Inhalt hinzufügen",
|
||||
"0cc1dec590ecd74bef71a865fb364779bc42a749": "Kategorie:",
|
||||
"303e45ffae995c9817e510e38cb969e6bb3adcbf": "(Pausiert)",
|
||||
"56a2a773fbd5a6b9ac2e6b89d29d70a2ed0f3227": "Weniger sehen.",
|
||||
"ddc31f2885b1b33a7651963254b0c197f2a64086": "Mehr sehen.",
|
||||
"24dc3ecf7ec2c2144910c4f3d38343828be03a4c": "Automatisch generiert",
|
||||
"c776eb4992b6c98f58cd89b20c1ea8ac37888521": "Wähle einen Download-Agenten",
|
||||
"ef418d4ece7c844f3a5e431da1aa59bedd88da7b": "Globale benutzerdefinierte Argumente",
|
||||
"1148fd45287ff09955b938756bc302042bcb29c7": "Der Pfad ist relativ zu den darüberliegenden Downloadpfaden. Erweiterung auslassen.",
|
||||
"cfe829634b1144bc44b6d38cf5584ea65db9804f": "Standard-Dateiausgabe",
|
||||
"13759b09a7f4074ceee8fa2f968f9815fdf63295": "Manchmal werden neue Videos heruntergeladen bevor diese von der Videoplattform vollständig verarbeitet wurden. Diese Einstellung bewirkt, dass am Folgetag neue Videos auf eine höhere Auflösung überprüft werden.",
|
||||
"dad95154dcef3509b8cc705046061fd24994bbb7": "Aufrufe",
|
||||
"d641b8fa5ac5e85114c733b1f7de6976bd091f70": "Maximale Qualität"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -241,5 +241,31 @@
|
||||
"3697f8583ea42868aa269489ad366103d94aece7": "Editando",
|
||||
"0c43af932e6a4ee85500e28f01b3538b4eb27bc4": "Nivel de registro",
|
||||
"a8b7b9c168fd936a75e500806a8c0d7755ef1198": "NOTA: La carga de cookies nuevas anulará las cookies anteriores y las cookies son para toda la instancia, no por usuario.",
|
||||
"511b600ae4cf037e4eb3b7a58410842cd5727490": "Agregar más contenido"
|
||||
"511b600ae4cf037e4eb3b7a58410842cd5727490": "Agregar más contenido",
|
||||
"56a2a773fbd5a6b9ac2e6b89d29d70a2ed0f3227": "Ver menos.",
|
||||
"ddc31f2885b1b33a7651963254b0c197f2a64086": "Ver más.",
|
||||
"c776eb4992b6c98f58cd89b20c1ea8ac37888521": "Seleccione un agente de descarga",
|
||||
"5fb1e0083c9b2a40ac8ae7dcb2618311c291b8b9": "Descarga automática de Twitch Chat",
|
||||
"84ffcebac2709ca0785f4a1d5ba274433b5beabc": "También conocido como ID de cliente.",
|
||||
"8ae23bc4302a479f687f4b20a84c276182e2519c": "Clave de API de Twitch",
|
||||
"d162f9fcd6a7187b391e004f072ab3da8377c47d": "Usar la API de Twitch",
|
||||
"04201f9d27abd7d6f58a4328ab98063ce1072006": "Categorías",
|
||||
"ef418d4ece7c844f3a5e431da1aa59bedd88da7b": "Args personalizados globales",
|
||||
"1148fd45287ff09955b938756bc302042bcb29c7": "La ruta es relativa a las rutas de descarga anteriores. No incluya la extensión.",
|
||||
"cfe829634b1144bc44b6d38cf5584ea65db9804f": "Salida de archivo predeterminada",
|
||||
"3d1a47dc18b7bd8b5d9e1eb44b235ed9c4a2b513": "Volver a descargar nuevas cargas",
|
||||
"13759b09a7f4074ceee8fa2f968f9815fdf63295": "A veces, los videos nuevos se descargan antes de procesarse por completo. Esta configuración significará que los nuevos videos se verificarán para una versión de mayor calidad al día siguiente.",
|
||||
"dad95154dcef3509b8cc705046061fd24994bbb7": "vistas",
|
||||
"792dc6a57f28a1066db283f2e736484f066005fd": "Descargar Twitch Chat",
|
||||
"e4eeb9106dbcbc91ca1ac3fb4068915998a70f37": "Añadir nueva regla",
|
||||
"2489eefea00931942b91f4a1ae109514b591e2e1": "Reglas",
|
||||
"c3b0b86523f1d10e84a71f9b188d54913a11af3b": "Editando la categoría",
|
||||
"07db550ae114d9faad3a0cbb68bcc16ab6cd31fc": "Pausado",
|
||||
"73423607944a694ce6f9e55cfee329681bb4d9f9": "No se encontraron vídeos.",
|
||||
"29376982b1205d9d6ea3d289e8e2f8e1ac2839b1": "Orden inverso",
|
||||
"33026f57ea65cd9c8a5d917a08083f71a718933a": "Orden normal",
|
||||
"5caadefa4143cf6766a621b0f54f91f373a1f164": "Añadir contenido",
|
||||
"0cc1dec590ecd74bef71a865fb364779bc42a749": "Categoría:",
|
||||
"303e45ffae995c9817e510e38cb969e6bb3adcbf": "(Pausado)",
|
||||
"d641b8fa5ac5e85114c733b1f7de6976bd091f70": "Calidad máxima"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -30,8 +30,8 @@
|
||||
"fcfd4675b4c90f08d18d3abede9a9a4dff4cfdc7": "Documentation",
|
||||
"19d1ae64d94d28a29b2c57ae8671aace906b5401": "Le chemin est relatif au chemin de téléchargement de la config. Ne pas inclure l'extension.",
|
||||
"8fad10737d3e3735a6699a4d89cbf6c20f6bb55f": "S'authentifier",
|
||||
"08c74dc9762957593b91f6eb5d65efdfc975bf48": "Identifiant",
|
||||
"c32ef07f8803a223a83ed17024b38e8d82292407": "Mot de Passe",
|
||||
"08c74dc9762957593b91f6eb5d65efdfc975bf48": "Nom d'utilisateur",
|
||||
"c32ef07f8803a223a83ed17024b38e8d82292407": "Mot de passe",
|
||||
"4a0dada6e841a425de3e5006e6a04df26c644fa5": "Audio",
|
||||
"9779715ac05308973d8f1c8658b29b986e92450f": "Vos fichiers audio sont ici",
|
||||
"47546e45bbb476baaaad38244db444c427ddc502": "Listes de lecture",
|
||||
@@ -41,7 +41,7 @@
|
||||
"0f59c46ca29e9725898093c9ea6b586730d0624e": "Aucune liste de lecture disponible. Créez-en une à l'aide du bouton \\\"+\\\" bleu de votre fichier vidéo.",
|
||||
"616e206cb4f25bd5885fc35925365e43cf5fb929": "Nom :",
|
||||
"c52db455cca9109ee47e1a612c3f4117c09eb71b": "URL :",
|
||||
"c6eb45d085384903e53ab001a3513d1de6a1dbac": "Uploader :",
|
||||
"c6eb45d085384903e53ab001a3513d1de6a1dbac": "Chaîne :",
|
||||
"109c6f4a5e46efb933612ededfaf52a13178b7e0": "Taille du fichier :",
|
||||
"bd630d8669b16e5f264ec4649d9b469fe03e5ff4": "Chemin :",
|
||||
"a67e7d843cef735c79d5ef1c8ba4af3e758912bb": "Mise en ligne :",
|
||||
@@ -49,7 +49,7 @@
|
||||
"4f389e41e4592f7f9bb76abdd8af4afdfb13f4f1": "Modifier la liste de lecture",
|
||||
"ca3dbbc7f3e011bffe32a10a3ea45cc84f30ecf1": "ID :",
|
||||
"e684046d73bcee88e82f7ff01e2852789a05fc32": "Compteur :",
|
||||
"28f86ffd419b869711aa13f5e5ff54be6d70731c": "Editer",
|
||||
"28f86ffd419b869711aa13f5e5ff54be6d70731c": "Modifier",
|
||||
"826b25211922a1b46436589233cb6f1a163d89b7": "Effacer",
|
||||
"321e4419a943044e674beb55b8039f42a9761ca5": "Informations",
|
||||
"34504b488c24c27e68089be549f0eeae6ebaf30b": "Supprimer et bannir",
|
||||
@@ -77,7 +77,7 @@
|
||||
"09006404cccc24b7a8f8d1ce0b39f2761ab841d8": "les vidéos téléchargées à partir de vos abonnements sont enregistrées dans un fichier texte dans le sous-répertoire du fichier d'abonnement.",
|
||||
"29ed79a98fc01e7f9537777598e31dbde3aa7981": "Cela vous permet de supprimer définitivement des vidéos de vos abonnements sans vous désabonner et vous permet d'enregistrer les vidéos que vous avez téléchargées en cas de perte de données.",
|
||||
"27a56aad79d8b61269ed303f11664cc78bcc2522": "Thème",
|
||||
"ff7cee38a2259526c519f878e71b964f41db4348": "Default",
|
||||
"ff7cee38a2259526c519f878e71b964f41db4348": "Par défaut",
|
||||
"adb4562d2dbd3584370e44496969d58c511ecb63": "Sombre",
|
||||
"7a6bacee4c31cb5c0ac2d24274fb4610d8858602": "Autoriser le changement du thème",
|
||||
"fe46ccaae902ce974e2441abe752399288298619": "Choix de la langue",
|
||||
@@ -117,8 +117,8 @@
|
||||
"ec71e08aee647ea4a71fd6b7510c54d84a797ca6": "Sélectionner une méthode de téléchargement",
|
||||
"00e274c496b094a019f0679c3fab3945793f3335": "Niveau des logs",
|
||||
"dc3d990391c944d1fbfc7cfb402f7b5e112fb3a8": "Autoriser le téléchargement avancé",
|
||||
"431e5f3a0dde88768d1074baedd65266412b3f02": "Utiliser les Cookies",
|
||||
"80651a7ad1229ea6613557d3559f702cfa5aecf5": "Gérer les Cookies",
|
||||
"431e5f3a0dde88768d1074baedd65266412b3f02": "Utiliser les cookies",
|
||||
"80651a7ad1229ea6613557d3559f702cfa5aecf5": "Gérer les cookies",
|
||||
"bc2e854e111ecf2bd7db170da5e3c2ed08181d88": "Avancé",
|
||||
"37224420db54d4bc7696f157b779a7225f03ca9d": "Autoriser l'enregistrement des utilisateurs",
|
||||
"4d13a9cd5ed3dcee0eab22cb25198d43886942be": "Utilisateurs",
|
||||
@@ -138,8 +138,8 @@
|
||||
"ac9d09de42edca1296371e4d801349c9096ac8de": "UID :",
|
||||
"a5ed099ffc9e96f6970df843289ade8a7d20ab9f": "Créé le :",
|
||||
"fa96f2137af0a24e6d6d54c598c0af7d5d5ad344": "Vous n'êtes pas identifié.",
|
||||
"6765b4c916060f6bc42d9bb69e80377dbcb5e4e9": "Identifiant",
|
||||
"bb694b49d408265c91c62799c2b3a7e3151c824d": "Déconnexion",
|
||||
"6765b4c916060f6bc42d9bb69e80377dbcb5e4e9": "Se connecter",
|
||||
"bb694b49d408265c91c62799c2b3a7e3151c824d": "Se déconnecter",
|
||||
"a1dbca87b9f36d2b06a5cbcffb5814c4ae9b798a": "Créer un compte administrateur",
|
||||
"2d2adf3ca26a676bca2269295b7455a26fd26980": "Aucun compte administrateur détecté. Veuillez définir le mot de passe du compte adminstrateur \\\"admin\\\".",
|
||||
"70a67e04629f6d412db0a12d51820b480788d795": "Créer",
|
||||
@@ -149,8 +149,8 @@
|
||||
"357064ca9d9ac859eb618e28e8126fa32be049e2": "Abonnements",
|
||||
"822fab38216f64e8166d368b59fe756ca39d301b": "Téléchargements",
|
||||
"a249a5ae13e0835383885aaf697d2890cc3e53e9": "Partager une liste de lecture",
|
||||
"15da89490e04496ca9ea1e1b3d44fb5efd4a75d9": "Partager vidéo",
|
||||
"1d540dcd271b316545d070f9d182c372d923aadd": "Partager audio",
|
||||
"15da89490e04496ca9ea1e1b3d44fb5efd4a75d9": "Partager une vidéo",
|
||||
"1d540dcd271b316545d070f9d182c372d923aadd": "Partager un audio",
|
||||
"1f6d14a780a37a97899dc611881e6bc971268285": "Activer le partage",
|
||||
"6580b6a950d952df847cb3d8e7176720a740adc8": "Utiliser l'horodatage",
|
||||
"4f2ed9e71a7c981db3e50ae2fedb28aff2ec4e6c": "Secondes",
|
||||
@@ -192,7 +192,7 @@
|
||||
"eb98135e35af26a9a326ee69bd8ff104d36dd8ec": "(actual)",
|
||||
"7117fc42f860e86d983bfccfcf2654e5750f3406": "Pas de téléchargements !",
|
||||
"b7ff2e2b909c53abe088fe60b9f4b6ac7757247f": "Ajouter un utilisateur",
|
||||
"024886ca34a6f309e3e51c2ed849320592c3faaa": "Identifiant",
|
||||
"024886ca34a6f309e3e51c2ed849320592c3faaa": "Nom d'utilisateur",
|
||||
"2bd201aea09e43fbfd3cd15ec0499b6755302329": "Gérer l'utilisateur",
|
||||
"29c97c8e76763bb15b6d515648fa5bd1eb0f7510": "Identifiant de l'utilisateur :",
|
||||
"e70e209561583f360b1e9cefd2cbb1fe434b6229": "Nouveau mot de passe",
|
||||
@@ -224,5 +224,46 @@
|
||||
"f61c6867295f3b53d23557021f2f4e0aa1d0b8fc": "Type",
|
||||
"2d1ea268a6a9f483dbc2cbfe19bf4256a57a6af4": "Vidéo",
|
||||
"f0baeb8b69d120073b6d60d34785889b0c3232c8": "Audio",
|
||||
"3697f8583ea42868aa269489ad366103d94aece7": "Édition"
|
||||
"3697f8583ea42868aa269489ad366103d94aece7": "Édition",
|
||||
"d02888c485d3aeab6de628508f4a00312a722894": "Mes vidéos",
|
||||
"a0720c36ee1057e5c54a86591b722485c62d7b1a": "Aller aux abonnements",
|
||||
"5656a06f17c24b2d7eae9c221567b209743829a9": "Ouvrir le fichier dans un nouvel onglet",
|
||||
"ccf5ea825526ac490974336cb5c24352886abc07": "Ouvrir le fichier",
|
||||
"8a0bda4c47f10b2423ff183acefbf70d4ab52ea2": "Effacer les journaux",
|
||||
"95b95a9c79e4fd9ed41f6855e37b3b06af25bcab": "Supprimer l'utilisateur",
|
||||
"544e09cdc99a8978f48521d45f62db0da6dcf742": "Utiliser le groupe par défaut",
|
||||
"632e8b20c98e8eec4059a605a4b011bb476137af": "Modifier l'utilisateur",
|
||||
"b6c453e0e61faea184bbaf5c5b0a1e164f4de2a2": "Effacer tous les téléchargements",
|
||||
"cfa67d14d84fe0e9fadf251dc51ffc181173b662": "Search Base",
|
||||
"080cc6abcba236390fc22e79792d0d3443a3bd2a": "Bind Credentials",
|
||||
"f50fa6c09c8944aed504f6325f2913ee6c7a296a": "Bind DN",
|
||||
"1db9789b93069861019bd0ccaa5d4706b00afc61": "LDAP URL",
|
||||
"e3d7c5f019e79a3235a28ba24df24f11712c7627": "LDAP",
|
||||
"56a2a773fbd5a6b9ac2e6b89d29d70a2ed0f3227": "Voir moins.",
|
||||
"ddc31f2885b1b33a7651963254b0c197f2a64086": "Voir plus.",
|
||||
"24dc3ecf7ec2c2144910c4f3d38343828be03a4c": "Généré automatiquement",
|
||||
"c776eb4992b6c98f58cd89b20c1ea8ac37888521": "Sélectionnez un agent de téléchargement",
|
||||
"5fb1e0083c9b2a40ac8ae7dcb2618311c291b8b9": "Télécharger automatiquement la discussion Twitch",
|
||||
"84ffcebac2709ca0785f4a1d5ba274433b5beabc": "Aussi connu sous le nom d'identifiant client.",
|
||||
"8ae23bc4302a479f687f4b20a84c276182e2519c": "Clé API Twitch",
|
||||
"d162f9fcd6a7187b391e004f072ab3da8377c47d": "Utiliser l'API de Twitch",
|
||||
"04201f9d27abd7d6f58a4328ab98063ce1072006": "Catégories",
|
||||
"ef418d4ece7c844f3a5e431da1aa59bedd88da7b": "Arguments personnalisés globaux",
|
||||
"1148fd45287ff09955b938756bc302042bcb29c7": "Le chemin est relatif aux chemins de téléchargement ci-dessus. Ne pas inclure l'extension.",
|
||||
"cfe829634b1144bc44b6d38cf5584ea65db9804f": "Sortie de fichier par défaut",
|
||||
"3d1a47dc18b7bd8b5d9e1eb44b235ed9c4a2b513": "Retélécharger les nouvelles mises en ligne",
|
||||
"13759b09a7f4074ceee8fa2f968f9815fdf63295": "Il arrive que de nouvelles vidéos soient téléchargées avant d'être entièrement traitées. Grâce à ce paramètre, les nouvelles vidéos seront vérifiées pour obtenir une version de meilleure qualité le jour suivant.",
|
||||
"dad95154dcef3509b8cc705046061fd24994bbb7": "vues",
|
||||
"792dc6a57f28a1066db283f2e736484f066005fd": "Télécharger la discussion Twitch",
|
||||
"e4eeb9106dbcbc91ca1ac3fb4068915998a70f37": "Ajouter une nouvelle règle",
|
||||
"2489eefea00931942b91f4a1ae109514b591e2e1": "Règles",
|
||||
"c3b0b86523f1d10e84a71f9b188d54913a11af3b": "Modification de la catégorie",
|
||||
"07db550ae114d9faad3a0cbb68bcc16ab6cd31fc": "En pause",
|
||||
"73423607944a694ce6f9e55cfee329681bb4d9f9": "Aucune vidéo trouvée.",
|
||||
"29376982b1205d9d6ea3d289e8e2f8e1ac2839b1": "Ordre inverse",
|
||||
"33026f57ea65cd9c8a5d917a08083f71a718933a": "Ordre normal",
|
||||
"5caadefa4143cf6766a621b0f54f91f373a1f164": "Ajouter du contenu",
|
||||
"0cc1dec590ecd74bef71a865fb364779bc42a749": "Catégorie :",
|
||||
"303e45ffae995c9817e510e38cb969e6bb3adcbf": "(En pause)",
|
||||
"d641b8fa5ac5e85114c733b1f7de6976bd091f70": "Qualité maximale"
|
||||
}
|
||||
2578
src/assets/i18n/messages.fr.xlf
Normal file
2578
src/assets/i18n/messages.fr.xlf
Normal file
File diff suppressed because it is too large
Load Diff
248
src/assets/i18n/messages.id.json
Normal file
248
src/assets/i18n/messages.id.json
Normal file
@@ -0,0 +1,248 @@
|
||||
{
|
||||
"004b222ff9ef9dd4771b777950ca1d0e4cd4348a": "Tentang",
|
||||
"994363f08f9fbfa3b3994ff7b35c6904fdff18d8": "Profil",
|
||||
"adb4562d2dbd3584370e44496969d58c511ecb63": "Gelap",
|
||||
"121cc5391cd2a5115bc2b3160379ee5b36cd7716": "Pengaturan",
|
||||
"92eee6be6de0b11c924e3ab27db30257159c0a7c": "Beranda",
|
||||
"6765b4c916060f6bc42d9bb69e80377dbcb5e4e9": "Masuk",
|
||||
"357064ca9d9ac859eb618e28e8126fa32be049e2": "Langganan",
|
||||
"822fab38216f64e8166d368b59fe756ca39d301b": "Unduhan",
|
||||
"4a9889d36910edc8323d7bab60858ab3da6d91df": "Hanya Audio",
|
||||
"6a21ba5fb0ac804a525bf9ab168038c3ee88e661": "Unduhan",
|
||||
"a38ae1082fec79ba1f379978337385a539a28e73": "Kualitas",
|
||||
"4be966a9dcfbc9b54dfcc604b831c0289f847fa4": "Gunakan Alamat URL",
|
||||
"d3f02f845e62cebd75fde451ab8479d2a8ad784d": "Tampilkan",
|
||||
"96a01fafe135afc58b0f8071a4ab00234495ce18": "Mode Multi-unduh",
|
||||
"6a3777f913cf3f288664f0632b9f24794fdcc24e": "Batal",
|
||||
"322ed150e02666fe2259c5b4614eac7066f4ffa0": "Khusus",
|
||||
"4e4c721129466be9c3862294dc40241b64045998": "Gunakan arg kustom",
|
||||
"ad2f8ac8b7de7945b80c8e424484da94e597125f": "Arg kustom",
|
||||
"a6911c2157f1b775284bbe9654ce5eb30cf45d7f": "Tidak perlu mencantumkan alamat URL, hanya bagian setelahnya saja. Arg dibatasi menggunakan dua koma seperti ini: ,,",
|
||||
"3a92a3443c65a52f37ca7efb8f453b35dbefbf29": "Gunakan output kustom",
|
||||
"d9c02face477f2f9cdaae318ccee5f89856851fb": "Output kustom",
|
||||
"fcfd4675b4c90f08d18d3abede9a9a4dff4cfdc7": "Dokumentasi",
|
||||
"19d1ae64d94d28a29b2c57ae8671aace906b5401": "Path adalah relatif kepada path unduh konfigurasi. Jangan mencantumkan ektensi.",
|
||||
"b7ffe7c6586d6f3f18a9246806a7c7d5538ab43e": "Perintah yang disimulasi:",
|
||||
"8fad10737d3e3735a6699a4d89cbf6c20f6bb55f": "Gunakan otentikasi",
|
||||
"08c74dc9762957593b91f6eb5d65efdfc975bf48": "Nama Pengguna",
|
||||
"c32ef07f8803a223a83ed17024b38e8d82292407": "Kata Sandi",
|
||||
"17f0ea5d2d7a262b0e875acc70475f102aee84e6": "Buat sebuah playlist",
|
||||
"cff1428d10d59d14e45edec3c735a27b5482db59": "Nama",
|
||||
"f61c6867295f3b53d23557021f2f4e0aa1d0b8fc": "Jenis",
|
||||
"f0baeb8b69d120073b6d60d34785889b0c3232c8": "Audio",
|
||||
"2d1ea268a6a9f483dbc2cbfe19bf4256a57a6af4": "Video",
|
||||
"f47e2d56dd8a145b2e9599da9730c049d52962a2": "Berkas audio",
|
||||
"a52dae09be10ca3a65da918533ced3d3f4992238": "Video",
|
||||
"a9806cf78ce00eb2613eeca11354a97e033377b8": "Langganan ke playlist atau saluran/channel",
|
||||
"801b98c6f02fe3b32f6afa3ee854c99ed83474e6": "Alamat URL",
|
||||
"93efc99ae087fc116de708ecd3ace86ca237cf30": "Playlist atau alamat URL saluran/channel",
|
||||
"08f5d0ef937ae17feb1b04aff15ad88911e87baf": "Nama kustom",
|
||||
"ea30873bd3f0d5e4fb2378eec3f0a1db77634a28": "Unduh semua unggahan",
|
||||
"d641b8fa5ac5e85114c733b1f7de6976bd091f70": "Kualitas maksimal",
|
||||
"c76a955642714b8949ff3e4b4990864a2e2cac95": "Mode hanya-audio",
|
||||
"408ca4911457e84a348cecf214f02c69289aa8f1": "Mode hanya-streaming",
|
||||
"f432e1a8d6adb12e612127978ce2e0ced933959c": "Ini ditambahkan setelah argumen standar.",
|
||||
"98b6ec9ec138186d663e64770267b67334353d63": "Output berkas kustom",
|
||||
"d7b35c384aecd25a516200d6921836374613dfe7": "Batal",
|
||||
"d0336848b0c375a1c25ba369b3481ee383217a4f": "Langganan",
|
||||
"28a678e9cabf86e44c32594c43fa0e890135c20f": "Unduh video yang diunggah terakhir",
|
||||
"e78c0d60ac39787f62c9159646fe0b3c1ed55a1d": "Jenis:",
|
||||
"c52db455cca9109ee47e1a612c3f4117c09eb71b": "Alamat URL:",
|
||||
"ca3dbbc7f3e011bffe32a10a3ea45cc84f30ecf1": "ID:",
|
||||
"f4e529ae5ffd73001d1ff4bbdeeb0a72e342e5c8": "Tutup",
|
||||
"8efc77bf327659c0fec1f518cf48a98cdcd9dddf": "Ekspor Arsip",
|
||||
"3042bd3ad8dffcfeca5fd1ae6159fd1047434e95": "Batal Langganan",
|
||||
"303e45ffae995c9817e510e38cb969e6bb3adcbf": "(Dijeda)",
|
||||
"a44d86aa1e6c20ced07aca3a7c081d8db9ded1c6": "Arsip:",
|
||||
"616e206cb4f25bd5885fc35925365e43cf5fb929": "Nama:",
|
||||
"c6eb45d085384903e53ab001a3513d1de6a1dbac": "Pengunggah:",
|
||||
"109c6f4a5e46efb933612ededfaf52a13178b7e0": "Ukuran berkas:",
|
||||
"bd630d8669b16e5f264ec4649d9b469fe03e5ff4": "Jalur/path:",
|
||||
"a67e7d843cef735c79d5ef1c8ba4af3e758912bb": "Tanggal Unggah:",
|
||||
"0cc1dec590ecd74bef71a865fb364779bc42a749": "Kategori:",
|
||||
"d9e83ac17026e70ef6e9c0f3240a3b2450367f40": "Modifikasi arg youtube-dl",
|
||||
"7fc1946abe2b40f60059c6cd19975d677095fd19": "Simulasikan arg baru",
|
||||
"0b71824ae71972f236039bed43f8d2323e8fd570": "Tambah sebuah arg",
|
||||
"c8b0e59eb491f2ac7505f0fbab747062e6b32b23": "Cari berdasarkan kategori",
|
||||
"9eeb91caef5a50256dd87e1c4b7b3e8216479377": "Gunakan nilai arg",
|
||||
"7de2451ed3fb8d8b847979bd3f0c740b970f167b": "Tambah arg",
|
||||
"b2623aee44b70c9a4ba1fce16c8a593b0a4c7974": "Modifikasi",
|
||||
"25d8ad5eba2ec24e68295a27d6a4bb9b49e3dacd": "Nilai arg",
|
||||
"91ecce65f1d23f9419d1c953cd6b7bc7f91c110e": "Pembaruan",
|
||||
"b7ff2e2b909c53abe088fe60b9f4b6ac7757247f": "Daftarkan pengguna",
|
||||
"024886ca34a6f309e3e51c2ed849320592c3faaa": "Nama pengguna",
|
||||
"cfc2f436ec2beffb042e7511a73c89c372e86a6c": "Daftar",
|
||||
"ebadf946ae90f13ecd0c70f09edbc0f983af8a0f": "Unggah cookie baru",
|
||||
"a8b7b9c168fd936a75e500806a8c0d7755ef1198": "CATATAN: Mengunggah cookie baru akan menimpa cookie Anda sebelumnya. Juga ingat bahwa cookie termasuk dalam pengguna-banyak, bukan per-pengguna.",
|
||||
"98a8a42e5efffe17ab786636ed0139b4c7032d0e": "Tarik dan Jatuhkan",
|
||||
"4f389e41e4592f7f9bb76abdd8af4afdfb13f4f1": "Modifikasi playlist",
|
||||
"5caadefa4143cf6766a621b0f54f91f373a1f164": "Tambah konten",
|
||||
"52c9a103b812f258bcddc3d90a6e3f46871d25fe": "Simpan",
|
||||
"33026f57ea65cd9c8a5d917a08083f71a718933a": "Urutan normal",
|
||||
"29376982b1205d9d6ea3d289e8e2f8e1ac2839b1": "Urutan terbalik",
|
||||
"d02888c485d3aeab6de628508f4a00312a722894": "Video saya",
|
||||
"7e892ba15f2c6c17e83510e273b3e10fc32ea016": "Cari",
|
||||
"73423607944a694ce6f9e55cfee329681bb4d9f9": "Tidak ada video ditemukan.",
|
||||
"3697f8583ea42868aa269489ad366103d94aece7": "Menyunting",
|
||||
"07db550ae114d9faad3a0cbb68bcc16ab6cd31fc": "Dijeda",
|
||||
"c3b0b86523f1d10e84a71f9b188d54913a11af3b": "Kategori suntingan",
|
||||
"2489eefea00931942b91f4a1ae109514b591e2e1": "Aturan",
|
||||
"e4eeb9106dbcbc91ca1ac3fb4068915998a70f37": "Tambah aturan baru",
|
||||
"792dc6a57f28a1066db283f2e736484f066005fd": "Unduh Twitch Chat",
|
||||
"28f86ffd419b869711aa13f5e5ff54be6d70731c": "Sunting",
|
||||
"826b25211922a1b46436589233cb6f1a163d89b7": "Hapus",
|
||||
"321e4419a943044e674beb55b8039f42a9761ca5": "Informasi",
|
||||
"e684046d73bcee88e82f7ff01e2852789a05fc32": "Hitung:",
|
||||
"34504b488c24c27e68089be549f0eeae6ebaf30b": "Hapus dan jadikan daftarhitam",
|
||||
"dad95154dcef3509b8cc705046061fd24994bbb7": "dilihat",
|
||||
"5b3075e8dc3f3921ec316b0bd83b6d14a06c1a4f": "Simpan perubahan",
|
||||
"4d8a18b04a1f785ecd8021ac824e0dfd5881dbfc": "Unduhan telah berhasil",
|
||||
"348cc5d553b18e862eb1c1770e5636f6b05ba130": "Sebuah kesalahan telah terjadi",
|
||||
"4f8b2bb476981727ab34ed40fde1218361f92c45": "Rincian",
|
||||
"e9aff8e6df2e2bf6299ea27bb2894c70bc48bd4d": "Sebuah kesalahan telah terjadi:",
|
||||
"77b0c73840665945b25bd128709aa64c8f017e1c": "Unduhan dimulai:",
|
||||
"08ff9375ec078065bcdd7637b7ea65fce2979266": "Unduhan berakhir:",
|
||||
"ad127117f9471612f47d01eae09709da444a36a4": "Jalur file:",
|
||||
"e2319dec5b4ccfb6ed9f55ccabd63650a8fdf547": "Langganan anda",
|
||||
"807cf11e6ac1cde912496f764c176bdfdd6b7e19": "Saluran",
|
||||
"47546e45bbb476baaaad38244db444c427ddc502": "Daftar putar",
|
||||
"29b89f751593e1b347eef103891b7a1ff36ec03f": "Nama tidak tersedia. Pengambilan saluran sedang berlangsung.",
|
||||
"4636cd4a1379c50d471e98786098c4d39e1e82ad": "Anda tidak memiliki saluran langganan.",
|
||||
"2e0a410652cb07d069f576b61eab32586a18320d": "Nama tidak tersedia. Pengambilan daftar-putar sedang berlangsung.",
|
||||
"587b57ced54965d8874c3fd0e9dfedb987e5df04": "Anda tidak memiliki langganan daftar putar.",
|
||||
"82421c3e46a0453a70c42900eab51d58d79e6599": "Utama",
|
||||
"0ba25ad86a240576c4f20a2fada4722ebba77b1e": "Pengunduh",
|
||||
"d5f69691f9f05711633128b5a3db696783266b58": "Ekstra",
|
||||
"bc2e854e111ecf2bd7db170da5e3c2ed08181d88": "Lanjutan",
|
||||
"4d13a9cd5ed3dcee0eab22cb25198d43886942be": "Pengguna",
|
||||
"eb3d5aefff38a814b76da74371cbf02c0789a1ef": "Log",
|
||||
"fe8fd36dbf5deee1d56564965787a782a66eba44": "{VAR_SELECT, select, true {Tutup} false {Batal} other {lainnya}}",
|
||||
"54c512cca1923ab72faf1a0bd98d3d172469629a": "URL aplikasi ini akan diakses dari, tanpa port.",
|
||||
"cb2741a46e3560f6bc6dfd99d385e86b08b26d72": "Port",
|
||||
"22e8f1d0423a3b784fe40fab187b92c06541b577": "Port yang diinginkan. Default-nya adalah 17442.",
|
||||
"d4477669a560750d2064051a510ef4d7679e2f3e": "Mode multi-pengguna",
|
||||
"2eb03565fcdce7a7a67abc277a936a32fcf51557": "Path basis pengguna",
|
||||
"a64505c41150663968e277ec9b3ddaa5f4838798": "Path basis untuk pengguna dan unduhan video mereka.",
|
||||
"4e3120311801c4acd18de7146add2ee4a4417773": "Izinkan langganan",
|
||||
"4bee2a4bef2d26d37c9b353c278e24e5cd309ce3": "Langganan Pangkros Base",
|
||||
"bc9892814ee2d119ae94378c905ea440a249b84a": "Jalur dasar untuk video dari saluran dan daftar putar Anda yang berlangganan. Itu relatif terhadap folder root ytdl-material.",
|
||||
"5bef4b25ba680da7fff06b86a91b1fc7e6a926e3": "Periksa interval",
|
||||
"0f56a7449b77630c114615395bbda4cab398efd8": "Unit adalah detik, hanya termasuk angka.",
|
||||
"13759b09a7f4074ceee8fa2f968f9815fdf63295": "Kadang-kadang video baru diunduh sebelum diproses sepenuhnya. Pengaturan ini akan berarti video baru akan diperiksa untuk versi berkualitas lebih tinggi pada hari berikutnya.",
|
||||
"3d1a47dc18b7bd8b5d9e1eb44b235ed9c4a2b513": "Redownload unggahan segar",
|
||||
"27a56aad79d8b61269ed303f11664cc78bcc2522": "Tema",
|
||||
"ff7cee38a2259526c519f878e71b964f41db4348": "Default",
|
||||
"7a6bacee4c31cb5c0ac2d24274fb4610d8858602": "Izinkan perubahan tema",
|
||||
"fe46ccaae902ce974e2441abe752399288298619": "Bahasa",
|
||||
"ab2756805742e84ad0cc0468f4be2d8aa9f855a5": "Jalur folder audio",
|
||||
"c2c89cdf45d46ea64d2ed2f9ac15dfa4d77e26ca": "Jalur untuk unduhan hanya audio. Ini relatif terhadap folder root ytdl-material.",
|
||||
"46826331da1949bd6fb74624447057099c9d20cd": "Jalur folder video",
|
||||
"17c92e6d47a213fa95b5aa344b3f258147123f93": "Jalur untuk unduhan video. Ini relatif terhadap folder root YTDL-material.",
|
||||
"cfe829634b1144bc44b6d38cf5584ea65db9804f": "Output file default",
|
||||
"1148fd45287ff09955b938756bc302042bcb29c7": "Jalur relatif terhadap jalur unduhan di atas. Jangan sertakan ekstensi.",
|
||||
"ef418d4ece7c844f3a5e431da1aa59bedd88da7b": "Args kustom global",
|
||||
"6b995e7130b4d667eaab6c5f61b362ace486d26d": "Args kustom global untuk unduhan di beranda. Args dibatasi menggunakan dua koma seperti itu: ,,",
|
||||
"04201f9d27abd7d6f58a4328ab98063ce1072006": "Kategori",
|
||||
"78e49b7339b4fa7184dd21bcaae107ce9b7076f6": "Gunakan YouTube-DL Archive",
|
||||
"ffc19f32b1cba0daefc0e5668f89346db1db83ad": "Sertakan thumbnail",
|
||||
"384de8f8f112c9e6092eb2698706d391553f3e8d": "Sertakan metadata",
|
||||
"fb35145bfb84521e21b6385363d59221f436a573": "Bunuh semua unduhan",
|
||||
"61f8fd90b5f8cb20c70371feb2ee5e1fac5a9095": "Top judul",
|
||||
"78d3531417c0d4ba4c90f0d4ae741edc261ec8df": "Manajer file diaktifkan",
|
||||
"a5a1be0a5df07de9eec57f5d2a86ed0204b2e75a": "Unduhan Manager diaktifkan",
|
||||
"c33bd5392b39dbed36b8e5a1145163a15d45835f": "Izinkan kualitas pilih",
|
||||
"bda5508e24e0d77debb28bcd9194d8fefb1cfb92": "Unduh Only Mode",
|
||||
"09d31c803a7252658694e1e3176b97f5655a3fe3": "Izinkan Mode Multi-Unduh",
|
||||
"1c4dbce56d96b8974aac24a02f7ab2ee81415014": "Aktifkan API publik",
|
||||
"23bd81dcc30b74d06279a26d7a42e8901c1b124e": "Kunci API Publik",
|
||||
"41016a73d8ad85e6cb26dffa0a8fab9fe8f60d8e": "Lihat dokumentasi",
|
||||
"00a94f58d9eb2e3aa561440eabea616d0c937fa2": "Ini akan menghapus kunci API lama Anda!",
|
||||
"1b258b258b4cc475ceb2871305b61756b0134f4a": "Menghasilkan",
|
||||
"d5d7c61349f3b0859336066e6d453fc35d334fe5": "Gunakan API YouTube",
|
||||
"ce10d31febb3d9d60c160750570310f303a22c22": "YouTube API Key",
|
||||
"8602e313cdfa7c4cc475ccbe86459fce3c3fd986": "Menghasilkan kunci itu mudah!",
|
||||
"d162f9fcd6a7187b391e004f072ab3da8377c47d": "Gunakan Twitch API",
|
||||
"8ae23bc4302a479f687f4b20a84c276182e2519c": "Kunci Twitch API",
|
||||
"84ffcebac2709ca0785f4a1d5ba274433b5beabc": "Juga dikenal sebagai ID Klien.",
|
||||
"5fb1e0083c9b2a40ac8ae7dcb2618311c291b8b9": "Obrolan Download Otomatis",
|
||||
"9b3cedfa83c6d7acb3210953289d1be4aab115c7": "Klik disini",
|
||||
"7f09776373995003161235c0c8d02b7f91dbc4df": "Untuk mengunduh ekstensi Chrome Bahan YouTubedl resmi secara manual.",
|
||||
"5b5296423906ab3371fdb2b5a5aaa83acaa2ee52": "Anda harus secara manual memuat ekstensi dan memodifikasi pengaturan ekstensi untuk mengatur URL frontend.",
|
||||
"9a2ec6da48771128384887525bdcac992632c863": "Untuk menginstal ekstensi Firefox Bahan YouTubedl resmi langsung dari halaman ekstensi Firefox.",
|
||||
"eb81be6b49e195e5307811d1d08a19259d411f37": "Instruksi pengaturan terperinci.",
|
||||
"cb17ff8fe3961cf90f44bee97c88a3f3347a7e55": "Tidak banyak yang diperlukan selain mengubah pengaturan ekstensi untuk mengatur URL frontend.",
|
||||
"61b81b11aad0b9d970ece2fce18405f07eac69c2": "Seret tautan di bawah ke bookmark Anda, dan Anda baik-baik saja! Cukup navigasikan ke video YouTube yang ingin Anda unduh, dan klik bookmark.",
|
||||
"c505d6c5de63cc700f0aaf8a4b31fae9e18024e5": "Buat 'audio hanya' Bookmarklet",
|
||||
"ec71e08aee647ea4a71fd6b7510c54d84a797ca6": "Pilih pengunduh",
|
||||
"5fab47f146b0a4b809dcebf3db9da94df6299ea1": "Gunakan agen pengunduhan default",
|
||||
"c776eb4992b6c98f58cd89b20c1ea8ac37888521": "Pilih agen unduhan",
|
||||
"0c43af932e6a4ee85500e28f01b3538b4eb27bc4": "Layar log",
|
||||
"db6c192032f4cab809aad35215f0aa4765761897": "Login kedaluwarsa",
|
||||
"dc3d990391c944d1fbfc7cfb402f7b5e112fb3a8": "Izinkan unduhan lanjutan",
|
||||
"431e5f3a0dde88768d1074baedd65266412b3f02": "Gunakan cookie",
|
||||
"80651a7ad1229ea6613557d3559f702cfa5aecf5": "Atur Cookie",
|
||||
"37224420db54d4bc7696f157b779a7225f03ca9d": "Izinkan pendaftaran pengguna",
|
||||
"fa548cee6ea11c160a416cac3e6bdec0363883dc": "Metode Auth",
|
||||
"4f56ced9d6b85aeb1d4346433361d47ea72dac1a": "Intern",
|
||||
"e3d7c5f019e79a3235a28ba24df24f11712c7627": "LDAP",
|
||||
"1db9789b93069861019bd0ccaa5d4706b00afc61": "URL LDAP",
|
||||
"f50fa6c09c8944aed504f6325f2913ee6c7a296a": "Gabung DN",
|
||||
"080cc6abcba236390fc22e79792d0d3443a3bd2a": "Mengikat kredensial",
|
||||
"cfa67d14d84fe0e9fadf251dc51ffc181173b662": "Basis pencarian",
|
||||
"e01d54ecc1a0fcf9525a3c100ed8b83d94e61c23": "Cari Filter",
|
||||
"cec82c0a545f37420d55a9b6c45c20546e82f94e": "Tentang YouTubedl-material",
|
||||
"199c17e5d6a419313af3c325f06dcbb9645ca618": "Adalah pengunduh YouTube open-source yang dibangun di bawah spesifikasi desain bahan Google. Anda dapat mengunduh video favorit Anda dengan mulus sebagai file video atau audio, dan bahkan berlangganan saluran dan daftar putar favorit Anda untuk terus diperbarui dengan video baru mereka.",
|
||||
"bc0ad0ee6630acb7fcb7802ec79f5a0ee943c1a7": "Memiliki beberapa fitur yang luar biasa termasuk! API yang luas, dukungan Docker, dan dukungan pelokalan (terjemahan). Baca pada semua fitur yang didukung dengan mengklik ikon GitHub di atas.",
|
||||
"a45e3b05f0529dc5246d70ef62304c94426d4c81": "Versi yang diinstal:",
|
||||
"b33536f59b94ec935a16bd6869d836895dc5300c": "Menemukan bug atau punya saran?",
|
||||
"e1f398f38ff1534303d4bb80bd6cece245f24016": "Untuk menciptakan masalah!",
|
||||
"e22f3a5351944f3a1a10cfc7da6f65dfbe0037fe": "Memeriksa pembaruan ...",
|
||||
"a16e92385b4fd9677bb830a4b796b8b79c113290": "Pembaruan tersedia",
|
||||
"189b28aaa19b3c51c6111ad039c4fd5e2a22e370": "Anda dapat memperbarui dari menu Pengaturan.",
|
||||
"1372e61c5bd06100844bd43b98b016aabc468f62": "Pilih versi:",
|
||||
"1f6d14a780a37a97899dc611881e6bc971268285": "Aktifkan berbagi",
|
||||
"6580b6a950d952df847cb3d8e7176720a740adc8": "Gunakan Timestamp",
|
||||
"4f2ed9e71a7c981db3e50ae2fedb28aff2ec4e6c": "Detik",
|
||||
"3a6e5a6aa78ca864f6542410c5dafb6334538106": "Menyalin ke clipboard",
|
||||
"a249a5ae13e0835383885aaf697d2890cc3e53e9": "Bagikan Daftar Putar",
|
||||
"15da89490e04496ca9ea1e1b3d44fb5efd4a75d9": "Berbagi video",
|
||||
"1d540dcd271b316545d070f9d182c372d923aadd": "Bagikan audio",
|
||||
"a1ad8b1be9be43b5183bd2c3186d4e19496f2a0b": "ID Sesi:",
|
||||
"b6c453e0e61faea184bbaf5c5b0a1e164f4de2a2": "Hapus semua unduhan",
|
||||
"eb98135e35af26a9a326ee69bd8ff104d36dd8ec": "(arus)",
|
||||
"7117fc42f860e86d983bfccfcf2654e5750f3406": "Tidak ada unduhan yang tersedia!",
|
||||
"42ff677ec14f111e88bd6cdd30145378e994d1bf": "Profil kamu",
|
||||
"bb694b49d408265c91c62799c2b3a7e3151c824d": "Keluar",
|
||||
"ac9d09de42edca1296371e4d801349c9096ac8de": "UID:",
|
||||
"a5ed099ffc9e96f6970df843289ade8a7d20ab9f": "Diciptakan:",
|
||||
"fa96f2137af0a24e6d6d54c598c0af7d5d5ad344": "Kamu tidak masuk.",
|
||||
"a1dbca87b9f36d2b06a5cbcffb5814c4ae9b798a": "Buat Akun Admin",
|
||||
"2d2adf3ca26a676bca2269295b7455a26fd26980": "Tidak ada akun admin default yang terdeteksi. Ini akan membuat dan mengatur kata sandi untuk akun admin dengan nama pengguna sebagai 'admin'.",
|
||||
"70a67e04629f6d412db0a12d51820b480788d795": "Buat",
|
||||
"4d92a0395dd66778a931460118626c5794a3fc7a": "Tambahkan pengguna",
|
||||
"b0d7dd8a1b0349622d6e0c6e643e24a9ea0efa1d": "Edit peran",
|
||||
"746f64ddd9001ac456327cd9a3d5152203a4b93c": "Nama pengguna",
|
||||
"52c1447c1ec9570a2a3025c7e566557b8d19ed92": "Peran",
|
||||
"59a8c38db3091a63ac1cb9590188dc3a972acfb3": "Tindakan",
|
||||
"2bd201aea09e43fbfd3cd15ec0499b6755302329": "Kelola pengguna",
|
||||
"95b95a9c79e4fd9ed41f6855e37b3b06af25bcab": "Hapus pengguna",
|
||||
"632e8b20c98e8eec4059a605a4b011bb476137af": "Edit Pengguna",
|
||||
"29c97c8e76763bb15b6d515648fa5bd1eb0f7510": "UID pengguna:",
|
||||
"e70e209561583f360b1e9cefd2cbb1fe434b6229": "Kata sandi baru",
|
||||
"6498fa1b8f563988f769654a75411bb8060134b9": "Atur kata sandi baru",
|
||||
"544e09cdc99a8978f48521d45f62db0da6dcf742": "Gunakan default peran",
|
||||
"4f20f2d5a6882190892e58b85f6ccbedfa737952": "Ya",
|
||||
"3d3ae7deebc5949b0c1c78b9847886a94321d9fd": "Tidak",
|
||||
"57c6c05d8ebf4ef1180c2705033c044f655bb2c4": "Kelola peran",
|
||||
"5009630cdf32ab4f1c78737b9617b8773512c05a": "Baris:",
|
||||
"8a0bda4c47f10b2423ff183acefbf70d4ab52ea2": "Hapus log",
|
||||
"24dc3ecf7ec2c2144910c4f3d38343828be03a4c": "Dihasilkan otomatis",
|
||||
"ccf5ea825526ac490974336cb5c24352886abc07": "Buka Berkas",
|
||||
"5656a06f17c24b2d7eae9c221567b209743829a9": "Buka berkas di tab baru",
|
||||
"a0720c36ee1057e5c54a86591b722485c62d7b1a": "Menuju ke langganan",
|
||||
"94e01842dcee90531caa52e4147f70679bac87fe": "Hapus dan unduh ulang",
|
||||
"2031adb51e07a41844e8ba7704b054e98345c9c1": "Hapus selamanya",
|
||||
"ddc31f2885b1b33a7651963254b0c197f2a64086": "Lihat lebih banyak.",
|
||||
"56a2a773fbd5a6b9ac2e6b89d29d70a2ed0f3227": "Lihat lebih sedikit.",
|
||||
"2054791b822475aeaea95c0119113de3200f5e1c": "Panjang:"
|
||||
}
|
||||
2517
src/assets/i18n/messages.id.xlf
Normal file
2517
src/assets/i18n/messages.id.xlf
Normal file
File diff suppressed because it is too large
Load Diff
249
src/assets/i18n/messages.it.json
Normal file
249
src/assets/i18n/messages.it.json
Normal file
@@ -0,0 +1,249 @@
|
||||
{
|
||||
"17f0ea5d2d7a262b0e875acc70475f102aee84e6": "Crea una scaletta",
|
||||
"cff1428d10d59d14e45edec3c735a27b5482db59": "Nome",
|
||||
"f0baeb8b69d120073b6d60d34785889b0c3232c8": "Audio",
|
||||
"2d1ea268a6a9f483dbc2cbfe19bf4256a57a6af4": "Video",
|
||||
"f61c6867295f3b53d23557021f2f4e0aa1d0b8fc": "Tipo",
|
||||
"f47e2d56dd8a145b2e9599da9730c049d52962a2": "File audio",
|
||||
"a52dae09be10ca3a65da918533ced3d3f4992238": "Video",
|
||||
"d9e83ac17026e70ef6e9c0f3240a3b2450367f40": "Modifica i parametri di youtube-dl",
|
||||
"7fc1946abe2b40f60059c6cd19975d677095fd19": "Simula i nuovi parametri",
|
||||
"0b71824ae71972f236039bed43f8d2323e8fd570": "Aggiungi un'impostazione",
|
||||
"c8b0e59eb491f2ac7505f0fbab747062e6b32b23": "Cerca per categoria",
|
||||
"9eeb91caef5a50256dd87e1c4b7b3e8216479377": "Usa valore impostato",
|
||||
"25d8ad5eba2ec24e68295a27d6a4bb9b49e3dacd": "Valore impostato",
|
||||
"7de2451ed3fb8d8b847979bd3f0c740b970f167b": "Aggiungi impostazione",
|
||||
"d7b35c384aecd25a516200d6921836374613dfe7": "Annulla",
|
||||
"b2623aee44b70c9a4ba1fce16c8a593b0a4c7974": "Modifica",
|
||||
"a38ae1082fec79ba1f379978337385a539a28e73": "Qualità",
|
||||
"4be966a9dcfbc9b54dfcc604b831c0289f847fa4": "Utilizza URL",
|
||||
"d3f02f845e62cebd75fde451ab8479d2a8ad784d": "Visualizza",
|
||||
"4a9889d36910edc8323d7bab60858ab3da6d91df": "Solo audio",
|
||||
"96a01fafe135afc58b0f8071a4ab00234495ce18": "Modalità download multiplo",
|
||||
"6a21ba5fb0ac804a525bf9ab168038c3ee88e661": "Scarica",
|
||||
"6a3777f913cf3f288664f0632b9f24794fdcc24e": "Annulla",
|
||||
"322ed150e02666fe2259c5b4614eac7066f4ffa0": "Avanzato",
|
||||
"b7ffe7c6586d6f3f18a9246806a7c7d5538ab43e": "Comando simulato:",
|
||||
"4e4c721129466be9c3862294dc40241b64045998": "Usa parametri personalizzati",
|
||||
"ad2f8ac8b7de7945b80c8e424484da94e597125f": "Parametri personalizzati",
|
||||
"a6911c2157f1b775284bbe9654ce5eb30cf45d7f": "Non è necessario includere l'URL, solo ciò che viene dopo. I parametri sono delimitati da due vigole: ,,",
|
||||
"3a92a3443c65a52f37ca7efb8f453b35dbefbf29": "Usa output personalizzato",
|
||||
"d9c02face477f2f9cdaae318ccee5f89856851fb": "Output personalizzata",
|
||||
"fcfd4675b4c90f08d18d3abede9a9a4dff4cfdc7": "Documentazione",
|
||||
"19d1ae64d94d28a29b2c57ae8671aace906b5401": "Il percorso è relativo al percorso di download configurato. Non includere l'estensione.",
|
||||
"8fad10737d3e3735a6699a4d89cbf6c20f6bb55f": "Utilizza l'autenticazione",
|
||||
"08c74dc9762957593b91f6eb5d65efdfc975bf48": "Nome utente",
|
||||
"c32ef07f8803a223a83ed17024b38e8d82292407": "Password",
|
||||
"616e206cb4f25bd5885fc35925365e43cf5fb929": "Nome:",
|
||||
"c52db455cca9109ee47e1a612c3f4117c09eb71b": "URL:",
|
||||
"c6eb45d085384903e53ab001a3513d1de6a1dbac": "Caricato da:",
|
||||
"109c6f4a5e46efb933612ededfaf52a13178b7e0": "Dimensioni file:",
|
||||
"bd630d8669b16e5f264ec4649d9b469fe03e5ff4": "Percorso:",
|
||||
"a67e7d843cef735c79d5ef1c8ba4af3e758912bb": "Data di caricamento:",
|
||||
"f4e529ae5ffd73001d1ff4bbdeeb0a72e342e5c8": "Chiudi",
|
||||
"4f389e41e4592f7f9bb76abdd8af4afdfb13f4f1": "Modifica playlist",
|
||||
"511b600ae4cf037e4eb3b7a58410842cd5727490": "Aggiungi più contenuto",
|
||||
"52c9a103b812f258bcddc3d90a6e3f46871d25fe": "Salva",
|
||||
"ca3dbbc7f3e011bffe32a10a3ea45cc84f30ecf1": "ID:",
|
||||
"e684046d73bcee88e82f7ff01e2852789a05fc32": "Conteggio:",
|
||||
"28f86ffd419b869711aa13f5e5ff54be6d70731c": "Modifica",
|
||||
"826b25211922a1b46436589233cb6f1a163d89b7": "Elimina",
|
||||
"321e4419a943044e674beb55b8039f42a9761ca5": "Informazioni",
|
||||
"34504b488c24c27e68089be549f0eeae6ebaf30b": "Elimina e aggiungi alla lista nera",
|
||||
"ebadf946ae90f13ecd0c70f09edbc0f983af8a0f": "Carica nuovi cookie",
|
||||
"98a8a42e5efffe17ab786636ed0139b4c7032d0e": "Trascina e rilascia",
|
||||
"a8b7b9c168fd936a75e500806a8c0d7755ef1198": "NOTA: il caricamento di nuovi cookie sovrascriverà i cookie precedenti. Inoltre tieni presente che i cookie sono a livello di processo, non per utente.",
|
||||
"121cc5391cd2a5115bc2b3160379ee5b36cd7716": "Impostazioni",
|
||||
"801b98c6f02fe3b32f6afa3ee854c99ed83474e6": "URL",
|
||||
"54c512cca1923ab72faf1a0bd98d3d172469629a": "URL con cui si accederà a questa applicazione, senza porta.",
|
||||
"cb2741a46e3560f6bc6dfd99d385e86b08b26d72": "Porta",
|
||||
"22e8f1d0423a3b784fe40fab187b92c06541b577": "Porta personalizzata. La predefinita è 17442.",
|
||||
"d4477669a560750d2064051a510ef4d7679e2f3e": "Modalità multiutente",
|
||||
"2eb03565fcdce7a7a67abc277a936a32fcf51557": "Percorso profili utente",
|
||||
"a64505c41150663968e277ec9b3ddaa5f4838798": "Percorso per profili utente e per video scaricati da ognuno.",
|
||||
"4e3120311801c4acd18de7146add2ee4a4417773": "Consenti iscrizioni",
|
||||
"4bee2a4bef2d26d37c9b353c278e24e5cd309ce3": "Percorso salvataggio playlist sottoscritte",
|
||||
"bc9892814ee2d119ae94378c905ea440a249b84a": "Percorso salvataggio per i video dei canali e delle playlist sottoscritte. È relativo alla cartella principale di YTDL-Material.",
|
||||
"5bef4b25ba680da7fff06b86a91b1fc7e6a926e3": "Intervallo di verifica",
|
||||
"0f56a7449b77630c114615395bbda4cab398efd8": "Unità in secondi, inserire solo numeri.",
|
||||
"27a56aad79d8b61269ed303f11664cc78bcc2522": "Tema",
|
||||
"ff7cee38a2259526c519f878e71b964f41db4348": "Predefinito",
|
||||
"adb4562d2dbd3584370e44496969d58c511ecb63": "Scuro",
|
||||
"7a6bacee4c31cb5c0ac2d24274fb4610d8858602": "Consenti variazione tema",
|
||||
"fe46ccaae902ce974e2441abe752399288298619": "Lingua",
|
||||
"82421c3e46a0453a70c42900eab51d58d79e6599": "Principale",
|
||||
"ab2756805742e84ad0cc0468f4be2d8aa9f855a5": "Percorso della cartella audio",
|
||||
"c2c89cdf45d46ea64d2ed2f9ac15dfa4d77e26ca": "Percorso per download solo audio. È relativo alla cartella principale di YTDL-Material.",
|
||||
"46826331da1949bd6fb74624447057099c9d20cd": "Percorso cartella Video",
|
||||
"17c92e6d47a213fa95b5aa344b3f258147123f93": "Percorso per il download di video. È relativo alla cartella principale di YTDL-Material.",
|
||||
"6b995e7130b4d667eaab6c5f61b362ace486d26d": "Parametri personalizzati generali per i download sulla home page. I parametri sono delimitati da due virgole: ,,",
|
||||
"78e49b7339b4fa7184dd21bcaae107ce9b7076f6": "Usa l'archivio youtube-dl",
|
||||
"ffc19f32b1cba0daefc0e5668f89346db1db83ad": "Includi anteprima",
|
||||
"384de8f8f112c9e6092eb2698706d391553f3e8d": "Includi metadati",
|
||||
"fb35145bfb84521e21b6385363d59221f436a573": "Interrompi tutti i download",
|
||||
"0ba25ad86a240576c4f20a2fada4722ebba77b1e": "Scaricato da",
|
||||
"61f8fd90b5f8cb20c70371feb2ee5e1fac5a9095": "Titolo della barra superiore",
|
||||
"78d3531417c0d4ba4c90f0d4ae741edc261ec8df": "Abilita il file manager",
|
||||
"a5a1be0a5df07de9eec57f5d2a86ed0204b2e75a": "Abilita il download manager",
|
||||
"c33bd5392b39dbed36b8e5a1145163a15d45835f": "Consenti la selezione della qualità",
|
||||
"bda5508e24e0d77debb28bcd9194d8fefb1cfb92": "Modalità solo download",
|
||||
"09d31c803a7252658694e1e3176b97f5655a3fe3": "Consenti la modalità di download multiplo",
|
||||
"1c4dbce56d96b8974aac24a02f7ab2ee81415014": "Abilita l'API Pubblica",
|
||||
"23bd81dcc30b74d06279a26d7a42e8901c1b124e": "Chiave API Pubblica",
|
||||
"41016a73d8ad85e6cb26dffa0a8fab9fe8f60d8e": "Visualizza la documentazione",
|
||||
"1b258b258b4cc475ceb2871305b61756b0134f4a": "Genera",
|
||||
"00a94f58d9eb2e3aa561440eabea616d0c937fa2": "Cancellerai la tua chiave API precedente!",
|
||||
"d5d7c61349f3b0859336066e6d453fc35d334fe5": "Usa l'API di YouTube",
|
||||
"ce10d31febb3d9d60c160750570310f303a22c22": "Chiave API YouTube",
|
||||
"8602e313cdfa7c4cc475ccbe86459fce3c3fd986": "Generare una chiave è facile!",
|
||||
"9b3cedfa83c6d7acb3210953289d1be4aab115c7": "Premi qui",
|
||||
"7f09776373995003161235c0c8d02b7f91dbc4df": "per scaricare manualmente l'estensione ufficiale per Chrome di YoutubeDL-Material.",
|
||||
"5b5296423906ab3371fdb2b5a5aaa83acaa2ee52": "È necessario installare manualmente l'estensione e modificarne le impostazioni inserendo l'URL della pagina principale.",
|
||||
"9a2ec6da48771128384887525bdcac992632c863": "per installare l'estensione ufficiale YoutubeDL-Material per Firefox direttamente dalla pagina delle estensioni di Firefox.",
|
||||
"eb81be6b49e195e5307811d1d08a19259d411f37": "Istruzioni dettagliate per la configurazione.",
|
||||
"cb17ff8fe3961cf90f44bee97c88a3f3347a7e55": "Viene richiesto semplicemente di modificare le impostazioni dell'estensione, inserendo l'URL della pagina principale.",
|
||||
"61b81b11aad0b9d970ece2fce18405f07eac69c2": "Trascina il link qui sotto tra tuoi preferiti e sei a posto! Vai al video YouTube che desideri scaricare e fai clic sul preferito.",
|
||||
"c505d6c5de63cc700f0aaf8a4b31fae9e18024e5": "Genera un preferito \"solo audio\"",
|
||||
"d5f69691f9f05711633128b5a3db696783266b58": "Extra",
|
||||
"5fab47f146b0a4b809dcebf3db9da94df6299ea1": "Usa agente di download predefinito",
|
||||
"ec71e08aee647ea4a71fd6b7510c54d84a797ca6": "Seleziona un metodo di download",
|
||||
"0c43af932e6a4ee85500e28f01b3538b4eb27bc4": "Livello di Log",
|
||||
"db6c192032f4cab809aad35215f0aa4765761897": "Scadenza accesso",
|
||||
"dc3d990391c944d1fbfc7cfb402f7b5e112fb3a8": "Consenti download avanzato",
|
||||
"431e5f3a0dde88768d1074baedd65266412b3f02": "Usa i cookie",
|
||||
"80651a7ad1229ea6613557d3559f702cfa5aecf5": "Imposta i cookie",
|
||||
"bc2e854e111ecf2bd7db170da5e3c2ed08181d88": "Avanzate",
|
||||
"37224420db54d4bc7696f157b779a7225f03ca9d": "Consenti registrazione utente",
|
||||
"4f56ced9d6b85aeb1d4346433361d47ea72dac1a": "Interno",
|
||||
"e3d7c5f019e79a3235a28ba24df24f11712c7627": "LDAP",
|
||||
"fa548cee6ea11c160a416cac3e6bdec0363883dc": "Metodo di autenticazione",
|
||||
"1db9789b93069861019bd0ccaa5d4706b00afc61": "URL LDAP",
|
||||
"f50fa6c09c8944aed504f6325f2913ee6c7a296a": "Associa DN",
|
||||
"080cc6abcba236390fc22e79792d0d3443a3bd2a": "Associa credenziali",
|
||||
"cfa67d14d84fe0e9fadf251dc51ffc181173b662": "Base di ricerca",
|
||||
"e01d54ecc1a0fcf9525a3c100ed8b83d94e61c23": "Filtro di ricerca",
|
||||
"4d13a9cd5ed3dcee0eab22cb25198d43886942be": "Utenti",
|
||||
"eb3d5aefff38a814b76da74371cbf02c0789a1ef": "Registri",
|
||||
"fe8fd36dbf5deee1d56564965787a782a66eba44": "{VAR_SELECT, seleziona, vero {Close} falso {Cancel} altro {otha}}",
|
||||
"cec82c0a545f37420d55a9b6c45c20546e82f94e": "Informazioni su YoutubeDL-Material",
|
||||
"199c17e5d6a419313af3c325f06dcbb9645ca618": "è un downloader di YouTube open source costruito secondo le specifiche Material Design di Google. Puoi scaricare agevolmente i tuoi video preferiti come file video o solo audio e persino iscriverti ai tuoi canali e playlist preferiti per tenerti aggiornato con i nuovi video pubblicati.",
|
||||
"bc0ad0ee6630acb7fcb7802ec79f5a0ee943c1a7": "ha alcune fantastiche funzionalità incluse! Una solida API, supporto Docker e supporto per la localizzazione (traduzione). Leggi tutte le funzionalità supportate cliccando sull'icona GitHub in alto.",
|
||||
"a45e3b05f0529dc5246d70ef62304c94426d4c81": "Versione installata:",
|
||||
"e22f3a5351944f3a1a10cfc7da6f65dfbe0037fe": "Verifica aggiornamenti in corso...",
|
||||
"a16e92385b4fd9677bb830a4b796b8b79c113290": "Aggiornamento disponibile",
|
||||
"189b28aaa19b3c51c6111ad039c4fd5e2a22e370": "È possibile eseguire l'aggiornamento dal menù impostazioni.",
|
||||
"b33536f59b94ec935a16bd6869d836895dc5300c": "Hai trovato un errore o hai un suggerimento?",
|
||||
"e1f398f38ff1534303d4bb80bd6cece245f24016": "per segnalare un problema!",
|
||||
"42ff677ec14f111e88bd6cdd30145378e994d1bf": "Il tuo profilo",
|
||||
"ac9d09de42edca1296371e4d801349c9096ac8de": "UID:",
|
||||
"a5ed099ffc9e96f6970df843289ade8a7d20ab9f": "Creato:",
|
||||
"fa96f2137af0a24e6d6d54c598c0af7d5d5ad344": "Non hai eseguito l'accesso.",
|
||||
"6765b4c916060f6bc42d9bb69e80377dbcb5e4e9": "Accedi",
|
||||
"bb694b49d408265c91c62799c2b3a7e3151c824d": "Esci",
|
||||
"a1dbca87b9f36d2b06a5cbcffb5814c4ae9b798a": "Crea un account amministratore",
|
||||
"2d2adf3ca26a676bca2269295b7455a26fd26980": "Nessun account amministratore predefinito rilevato. Verrà creato e impostata la password per un account amministratore con il nome utente \"admin\".",
|
||||
"70a67e04629f6d412db0a12d51820b480788d795": "Crea",
|
||||
"994363f08f9fbfa3b3994ff7b35c6904fdff18d8": "Profilo",
|
||||
"004b222ff9ef9dd4771b777950ca1d0e4cd4348a": "Informazioni",
|
||||
"92eee6be6de0b11c924e3ab27db30257159c0a7c": "Pagina principale",
|
||||
"357064ca9d9ac859eb618e28e8126fa32be049e2": "Iscrizioni",
|
||||
"822fab38216f64e8166d368b59fe756ca39d301b": "Download",
|
||||
"a249a5ae13e0835383885aaf697d2890cc3e53e9": "Condividi playlist",
|
||||
"15da89490e04496ca9ea1e1b3d44fb5efd4a75d9": "Condividi il video",
|
||||
"1d540dcd271b316545d070f9d182c372d923aadd": "Condividi l'audio",
|
||||
"1f6d14a780a37a97899dc611881e6bc971268285": "Abilita la condivisione",
|
||||
"6580b6a950d952df847cb3d8e7176720a740adc8": "Usa data e ora",
|
||||
"4f2ed9e71a7c981db3e50ae2fedb28aff2ec4e6c": "Secondi",
|
||||
"3a6e5a6aa78ca864f6542410c5dafb6334538106": "Copia negli appunti",
|
||||
"5b3075e8dc3f3921ec316b0bd83b6d14a06c1a4f": "Salva le modifiche",
|
||||
"4d8a18b04a1f785ecd8021ac824e0dfd5881dbfc": "Il download è riuscito",
|
||||
"348cc5d553b18e862eb1c1770e5636f6b05ba130": "Si è verificato un errore",
|
||||
"4f8b2bb476981727ab34ed40fde1218361f92c45": "Dettagli",
|
||||
"e9aff8e6df2e2bf6299ea27bb2894c70bc48bd4d": "Si è verificato un errore:",
|
||||
"77b0c73840665945b25bd128709aa64c8f017e1c": "Inizio download:",
|
||||
"08ff9375ec078065bcdd7637b7ea65fce2979266": "Termine download:",
|
||||
"ad127117f9471612f47d01eae09709da444a36a4": "Percorso(i) file:",
|
||||
"a9806cf78ce00eb2613eeca11354a97e033377b8": "Iscriviti alla playlist o al canale",
|
||||
"93efc99ae087fc116de708ecd3ace86ca237cf30": "URL della playlist o del canale",
|
||||
"08f5d0ef937ae17feb1b04aff15ad88911e87baf": "Nome personalizzato",
|
||||
"ea30873bd3f0d5e4fb2378eec3f0a1db77634a28": "Scarica tutti i file caricati",
|
||||
"28a678e9cabf86e44c32594c43fa0e890135c20f": "Scarica i video caricati negli ultimi",
|
||||
"c76a955642714b8949ff3e4b4990864a2e2cac95": "Modalità solo audio",
|
||||
"408ca4911457e84a348cecf214f02c69289aa8f1": "Modalità solo streaming",
|
||||
"f432e1a8d6adb12e612127978ce2e0ced933959c": "Questi vengono aggiunti dopo ai parametri standard.",
|
||||
"98b6ec9ec138186d663e64770267b67334353d63": "File di uscita personalizzato",
|
||||
"d0336848b0c375a1c25ba369b3481ee383217a4f": "Iscriviti",
|
||||
"e78c0d60ac39787f62c9159646fe0b3c1ed55a1d": "Tipo:",
|
||||
"a44d86aa1e6c20ced07aca3a7c081d8db9ded1c6": "Archivio:",
|
||||
"8efc77bf327659c0fec1f518cf48a98cdcd9dddf": "Esporta archivio",
|
||||
"3042bd3ad8dffcfeca5fd1ae6159fd1047434e95": "Annulla l'iscrizione",
|
||||
"e2319dec5b4ccfb6ed9f55ccabd63650a8fdf547": "I tuoi abbonamenti",
|
||||
"807cf11e6ac1cde912496f764c176bdfdd6b7e19": "Canali",
|
||||
"29b89f751593e1b347eef103891b7a1ff36ec03f": "Nome non disponibile. Recupero del canale in corso.",
|
||||
"4636cd4a1379c50d471e98786098c4d39e1e82ad": "Non sei iscritto a nessun canale.",
|
||||
"47546e45bbb476baaaad38244db444c427ddc502": "Scalette",
|
||||
"2e0a410652cb07d069f576b61eab32586a18320d": "Nome non disponibile. Recupero playlist in corso.",
|
||||
"587b57ced54965d8874c3fd0e9dfedb987e5df04": "Non sei iscritto a nessuna playlist.",
|
||||
"3697f8583ea42868aa269489ad366103d94aece7": "Modifica",
|
||||
"7e892ba15f2c6c17e83510e273b3e10fc32ea016": "Cerca",
|
||||
"2054791b822475aeaea95c0119113de3200f5e1c": "Durata:",
|
||||
"94e01842dcee90531caa52e4147f70679bac87fe": "Elimina e scarica di nuovo",
|
||||
"2031adb51e07a41844e8ba7704b054e98345c9c1": "Elimina definitivamente",
|
||||
"91ecce65f1d23f9419d1c953cd6b7bc7f91c110e": "Aggiornato da",
|
||||
"1372e61c5bd06100844bd43b98b016aabc468f62": "Seleziona una versione:",
|
||||
"cfc2f436ec2beffb042e7511a73c89c372e86a6c": "Registrati",
|
||||
"a1ad8b1be9be43b5183bd2c3186d4e19496f2a0b": "ID sessione:",
|
||||
"eb98135e35af26a9a326ee69bd8ff104d36dd8ec": "(attuale)",
|
||||
"b6c453e0e61faea184bbaf5c5b0a1e164f4de2a2": "Cancella tutti i download",
|
||||
"7117fc42f860e86d983bfccfcf2654e5750f3406": "Nessun download disponibile!",
|
||||
"b7ff2e2b909c53abe088fe60b9f4b6ac7757247f": "Registra un utente",
|
||||
"024886ca34a6f309e3e51c2ed849320592c3faaa": "Nome utente",
|
||||
"2bd201aea09e43fbfd3cd15ec0499b6755302329": "Gestisci utente",
|
||||
"29c97c8e76763bb15b6d515648fa5bd1eb0f7510": "UID utente:",
|
||||
"e70e209561583f360b1e9cefd2cbb1fe434b6229": "Nuova password",
|
||||
"6498fa1b8f563988f769654a75411bb8060134b9": "Imposta nuova password",
|
||||
"544e09cdc99a8978f48521d45f62db0da6dcf742": "Usa il ruolo predefinito",
|
||||
"4f20f2d5a6882190892e58b85f6ccbedfa737952": "Sì",
|
||||
"3d3ae7deebc5949b0c1c78b9847886a94321d9fd": "No",
|
||||
"57c6c05d8ebf4ef1180c2705033c044f655bb2c4": "Gestisci ruolo",
|
||||
"746f64ddd9001ac456327cd9a3d5152203a4b93c": "Nome utente",
|
||||
"52c1447c1ec9570a2a3025c7e566557b8d19ed92": "Ruolo",
|
||||
"59a8c38db3091a63ac1cb9590188dc3a972acfb3": "Azioni",
|
||||
"632e8b20c98e8eec4059a605a4b011bb476137af": "Modifica utente",
|
||||
"95b95a9c79e4fd9ed41f6855e37b3b06af25bcab": "Elimina utente",
|
||||
"4d92a0395dd66778a931460118626c5794a3fc7a": "Aggiungi utenti",
|
||||
"b0d7dd8a1b0349622d6e0c6e643e24a9ea0efa1d": "Modifica ruolo",
|
||||
"5009630cdf32ab4f1c78737b9617b8773512c05a": "Linee:",
|
||||
"8a0bda4c47f10b2423ff183acefbf70d4ab52ea2": "Cancella i registri",
|
||||
"ccf5ea825526ac490974336cb5c24352886abc07": "Apri il file",
|
||||
"5656a06f17c24b2d7eae9c221567b209743829a9": "Apri il file in una nuova scheda",
|
||||
"a0720c36ee1057e5c54a86591b722485c62d7b1a": "Vai alle iscrizioni",
|
||||
"d02888c485d3aeab6de628508f4a00312a722894": "I miei video",
|
||||
"ef418d4ece7c844f3a5e431da1aa59bedd88da7b": "Parametri personalizzati generali",
|
||||
"56a2a773fbd5a6b9ac2e6b89d29d70a2ed0f3227": "Nascondi.",
|
||||
"84ffcebac2709ca0785f4a1d5ba274433b5beabc": "Conosciuto anche come Client ID.",
|
||||
"cfe829634b1144bc44b6d38cf5584ea65db9804f": "File output predefinito",
|
||||
"3d1a47dc18b7bd8b5d9e1eb44b235ed9c4a2b513": "Riscarica i nuovi contenuti",
|
||||
"13759b09a7f4074ceee8fa2f968f9815fdf63295": "A volte i nuovi video vengono scaricati prima di essere completamente elaborati. Questa impostazione significa che per i nuovi video verrà effettuata una verifica il giorno successivo per la ricerca di versioni di qualità superiore.",
|
||||
"ddc31f2885b1b33a7651963254b0c197f2a64086": "Vedi altro.",
|
||||
"24dc3ecf7ec2c2144910c4f3d38343828be03a4c": "Generato automaticamente",
|
||||
"c776eb4992b6c98f58cd89b20c1ea8ac37888521": "Seleziona un agente di download",
|
||||
"5fb1e0083c9b2a40ac8ae7dcb2618311c291b8b9": "Scarica automaticamente le Chat Twitch",
|
||||
"8ae23bc4302a479f687f4b20a84c276182e2519c": "Chiave dell'API Twitch",
|
||||
"d162f9fcd6a7187b391e004f072ab3da8377c47d": "Usa l'API Twitch",
|
||||
"04201f9d27abd7d6f58a4328ab98063ce1072006": "Categorie",
|
||||
"1148fd45287ff09955b938756bc302042bcb29c7": "Il percorso è riferito ai percorsi di download sopra. Non includere l'estensione.",
|
||||
"dad95154dcef3509b8cc705046061fd24994bbb7": "visualizzazioni",
|
||||
"792dc6a57f28a1066db283f2e736484f066005fd": "Scarica Chat Twitch",
|
||||
"e4eeb9106dbcbc91ca1ac3fb4068915998a70f37": "Aggiungi nuova regola",
|
||||
"2489eefea00931942b91f4a1ae109514b591e2e1": "Regole",
|
||||
"c3b0b86523f1d10e84a71f9b188d54913a11af3b": "Categoria in modifica",
|
||||
"07db550ae114d9faad3a0cbb68bcc16ab6cd31fc": "In pausa",
|
||||
"73423607944a694ce6f9e55cfee329681bb4d9f9": "Nessun video trovato.",
|
||||
"29376982b1205d9d6ea3d289e8e2f8e1ac2839b1": "Ordine inverso",
|
||||
"33026f57ea65cd9c8a5d917a08083f71a718933a": "Ordine normale",
|
||||
"5caadefa4143cf6766a621b0f54f91f373a1f164": "Aggiungi contenuto",
|
||||
"0cc1dec590ecd74bef71a865fb364779bc42a749": "Categoria:",
|
||||
"303e45ffae995c9817e510e38cb969e6bb3adcbf": "(In pausa)",
|
||||
"d641b8fa5ac5e85114c733b1f7de6976bd091f70": "Qualità massima"
|
||||
}
|
||||
2482
src/assets/i18n/messages.it.xlf
Normal file
2482
src/assets/i18n/messages.it.xlf
Normal file
File diff suppressed because it is too large
Load Diff
248
src/assets/i18n/messages.nl.json
Normal file
248
src/assets/i18n/messages.nl.json
Normal file
@@ -0,0 +1,248 @@
|
||||
{
|
||||
"004b222ff9ef9dd4771b777950ca1d0e4cd4348a": "Over",
|
||||
"994363f08f9fbfa3b3994ff7b35c6904fdff18d8": "Profiel",
|
||||
"adb4562d2dbd3584370e44496969d58c511ecb63": "Donker",
|
||||
"121cc5391cd2a5115bc2b3160379ee5b36cd7716": "Instellingen",
|
||||
"92eee6be6de0b11c924e3ab27db30257159c0a7c": "Overzicht",
|
||||
"6765b4c916060f6bc42d9bb69e80377dbcb5e4e9": "Inloggen",
|
||||
"357064ca9d9ac859eb618e28e8126fa32be049e2": "Abonnementen",
|
||||
"822fab38216f64e8166d368b59fe756ca39d301b": "Downloads",
|
||||
"4a9889d36910edc8323d7bab60858ab3da6d91df": "Alleen audio",
|
||||
"6a21ba5fb0ac804a525bf9ab168038c3ee88e661": "Downloaden",
|
||||
"a38ae1082fec79ba1f379978337385a539a28e73": "Kwaliteit",
|
||||
"4be966a9dcfbc9b54dfcc604b831c0289f847fa4": "URL gebruiken",
|
||||
"d3f02f845e62cebd75fde451ab8479d2a8ad784d": "Bekijken",
|
||||
"96a01fafe135afc58b0f8071a4ab00234495ce18": "Meerdere video's downloaden",
|
||||
"6a3777f913cf3f288664f0632b9f24794fdcc24e": "Afbreken",
|
||||
"322ed150e02666fe2259c5b4614eac7066f4ffa0": "Geavanceerd",
|
||||
"4e4c721129466be9c3862294dc40241b64045998": "Aanvullende opties toekennen",
|
||||
"ad2f8ac8b7de7945b80c8e424484da94e597125f": "Aanvullende opties",
|
||||
"a6911c2157f1b775284bbe9654ce5eb30cf45d7f": "Je hoeft alleen de aanvullende opties op te geven, dus niet de url. Je kunt de opties scheiden met twee komma's: ,,",
|
||||
"3a92a3443c65a52f37ca7efb8f453b35dbefbf29": "Aangepaste uitvoer gebruiken",
|
||||
"d9c02face477f2f9cdaae318ccee5f89856851fb": "Aangepaste uitvoer",
|
||||
"fcfd4675b4c90f08d18d3abede9a9a4dff4cfdc7": "Documentatie",
|
||||
"19d1ae64d94d28a29b2c57ae8671aace906b5401": "Het pad is relatief aan het ingestelde downloadpad. Laat de extensie achterwege.",
|
||||
"b7ffe7c6586d6f3f18a9246806a7c7d5538ab43e": "Geteste opdracht:",
|
||||
"8fad10737d3e3735a6699a4d89cbf6c20f6bb55f": "Authenticatie gebruiken",
|
||||
"08c74dc9762957593b91f6eb5d65efdfc975bf48": "Gebruikersnaam",
|
||||
"c32ef07f8803a223a83ed17024b38e8d82292407": "Wachtwoord",
|
||||
"17f0ea5d2d7a262b0e875acc70475f102aee84e6": "Afspeellijst maken",
|
||||
"cff1428d10d59d14e45edec3c735a27b5482db59": "Naam",
|
||||
"f61c6867295f3b53d23557021f2f4e0aa1d0b8fc": "Soort",
|
||||
"f0baeb8b69d120073b6d60d34785889b0c3232c8": "Audio",
|
||||
"2d1ea268a6a9f483dbc2cbfe19bf4256a57a6af4": "Video",
|
||||
"f47e2d56dd8a145b2e9599da9730c049d52962a2": "Audiobestanden",
|
||||
"a52dae09be10ca3a65da918533ced3d3f4992238": "Video's",
|
||||
"a9806cf78ce00eb2613eeca11354a97e033377b8": "Abonneren op afspeellijst of kanaal",
|
||||
"801b98c6f02fe3b32f6afa3ee854c99ed83474e6": "URL",
|
||||
"93efc99ae087fc116de708ecd3ace86ca237cf30": "De url van de afspeellijst of het kanaal",
|
||||
"08f5d0ef937ae17feb1b04aff15ad88911e87baf": "Aangepaste naam",
|
||||
"ea30873bd3f0d5e4fb2378eec3f0a1db77634a28": "Alle uploads downloaden",
|
||||
"d641b8fa5ac5e85114c733b1f7de6976bd091f70": "Maximumkwaliteit",
|
||||
"c76a955642714b8949ff3e4b4990864a2e2cac95": "Audiomodus",
|
||||
"408ca4911457e84a348cecf214f02c69289aa8f1": "Streamingmodus",
|
||||
"f432e1a8d6adb12e612127978ce2e0ced933959c": "Deze worden toegevoegd ná de standaardopties.",
|
||||
"98b6ec9ec138186d663e64770267b67334353d63": "Aangepaste bestandsuitvoer",
|
||||
"d7b35c384aecd25a516200d6921836374613dfe7": "Annuleren",
|
||||
"d0336848b0c375a1c25ba369b3481ee383217a4f": "Abonneren",
|
||||
"28a678e9cabf86e44c32594c43fa0e890135c20f": "Video's downloaden die geüpload zijn in de afgelopen",
|
||||
"e78c0d60ac39787f62c9159646fe0b3c1ed55a1d": "Soort:",
|
||||
"c52db455cca9109ee47e1a612c3f4117c09eb71b": "URL:",
|
||||
"ca3dbbc7f3e011bffe32a10a3ea45cc84f30ecf1": "ID:",
|
||||
"f4e529ae5ffd73001d1ff4bbdeeb0a72e342e5c8": "Sluiten",
|
||||
"8efc77bf327659c0fec1f518cf48a98cdcd9dddf": "Archief exporteren",
|
||||
"3042bd3ad8dffcfeca5fd1ae6159fd1047434e95": "De-abonneren",
|
||||
"303e45ffae995c9817e510e38cb969e6bb3adcbf": "(onderbroken)",
|
||||
"a44d86aa1e6c20ced07aca3a7c081d8db9ded1c6": "Archief:",
|
||||
"616e206cb4f25bd5885fc35925365e43cf5fb929": "Naam:",
|
||||
"c6eb45d085384903e53ab001a3513d1de6a1dbac": "Uploader:",
|
||||
"109c6f4a5e46efb933612ededfaf52a13178b7e0": "Bestandsgrootte:",
|
||||
"bd630d8669b16e5f264ec4649d9b469fe03e5ff4": "Pad:",
|
||||
"a67e7d843cef735c79d5ef1c8ba4af3e758912bb": "Uploaddatum:",
|
||||
"0cc1dec590ecd74bef71a865fb364779bc42a749": "Categorie:",
|
||||
"d9e83ac17026e70ef6e9c0f3240a3b2450367f40": "youtube-dl-opties aanpassen",
|
||||
"7fc1946abe2b40f60059c6cd19975d677095fd19": "Geteste nieuwe aanvullende opties",
|
||||
"0b71824ae71972f236039bed43f8d2323e8fd570": "Optie toevoegen",
|
||||
"c8b0e59eb491f2ac7505f0fbab747062e6b32b23": "Zoeken op categorie",
|
||||
"9eeb91caef5a50256dd87e1c4b7b3e8216479377": "Optiewaarde gebruiken",
|
||||
"7de2451ed3fb8d8b847979bd3f0c740b970f167b": "Optie toevoegen",
|
||||
"b2623aee44b70c9a4ba1fce16c8a593b0a4c7974": "Aanpassen",
|
||||
"25d8ad5eba2ec24e68295a27d6a4bb9b49e3dacd": "Optiewaarde",
|
||||
"91ecce65f1d23f9419d1c953cd6b7bc7f91c110e": "Updater",
|
||||
"b7ff2e2b909c53abe088fe60b9f4b6ac7757247f": "Gebruikersregistratie",
|
||||
"024886ca34a6f309e3e51c2ed849320592c3faaa": "Gebruikersnaam",
|
||||
"cfc2f436ec2beffb042e7511a73c89c372e86a6c": "Registreren",
|
||||
"ebadf946ae90f13ecd0c70f09edbc0f983af8a0f": "Nieuwe cookies uploaden",
|
||||
"a8b7b9c168fd936a75e500806a8c0d7755ef1198": "Let op: de nieuwe cookies overschrijven de oude. Daarnaast zijn de cookies procesgebonden en niet gebruikersgebonden.",
|
||||
"98a8a42e5efffe17ab786636ed0139b4c7032d0e": "Slepen-en-neerzetten",
|
||||
"4f389e41e4592f7f9bb76abdd8af4afdfb13f4f1": "Afspeellijst aanpassen",
|
||||
"5caadefa4143cf6766a621b0f54f91f373a1f164": "Inhoud toevoegen",
|
||||
"52c9a103b812f258bcddc3d90a6e3f46871d25fe": "Opslaan",
|
||||
"33026f57ea65cd9c8a5d917a08083f71a718933a": "Normale volgorde",
|
||||
"29376982b1205d9d6ea3d289e8e2f8e1ac2839b1": "Omgekeerde volgorde",
|
||||
"d02888c485d3aeab6de628508f4a00312a722894": "Mijn video's",
|
||||
"7e892ba15f2c6c17e83510e273b3e10fc32ea016": "Zoeken",
|
||||
"73423607944a694ce6f9e55cfee329681bb4d9f9": "Geen video's gevonden.",
|
||||
"3697f8583ea42868aa269489ad366103d94aece7": "Bewerken",
|
||||
"07db550ae114d9faad3a0cbb68bcc16ab6cd31fc": "Onderbroken",
|
||||
"c3b0b86523f1d10e84a71f9b188d54913a11af3b": "Categorie bewerken",
|
||||
"2489eefea00931942b91f4a1ae109514b591e2e1": "Regels",
|
||||
"e4eeb9106dbcbc91ca1ac3fb4068915998a70f37": "Regel toevoegen",
|
||||
"792dc6a57f28a1066db283f2e736484f066005fd": "Twitch-chatgesprek downloaden",
|
||||
"28f86ffd419b869711aa13f5e5ff54be6d70731c": "Aanpassen",
|
||||
"826b25211922a1b46436589233cb6f1a163d89b7": "Verwijderen",
|
||||
"321e4419a943044e674beb55b8039f42a9761ca5": "Informatie",
|
||||
"e684046d73bcee88e82f7ff01e2852789a05fc32": "Aantal:",
|
||||
"34504b488c24c27e68089be549f0eeae6ebaf30b": "Verwijderen en op zwarte lijst plaatsen",
|
||||
"dad95154dcef3509b8cc705046061fd24994bbb7": "weergaven",
|
||||
"5b3075e8dc3f3921ec316b0bd83b6d14a06c1a4f": "Aanpassingen opslaan",
|
||||
"4d8a18b04a1f785ecd8021ac824e0dfd5881dbfc": "Het downloaden is voltooid",
|
||||
"348cc5d553b18e862eb1c1770e5636f6b05ba130": "Er is een fout opgetreden",
|
||||
"4f8b2bb476981727ab34ed40fde1218361f92c45": "Details",
|
||||
"e9aff8e6df2e2bf6299ea27bb2894c70bc48bd4d": "Er is een fout opgetreden:",
|
||||
"77b0c73840665945b25bd128709aa64c8f017e1c": "Gestart om:",
|
||||
"08ff9375ec078065bcdd7637b7ea65fce2979266": "Afgerond om:",
|
||||
"ad127117f9471612f47d01eae09709da444a36a4": "Bestandspad(en):",
|
||||
"e2319dec5b4ccfb6ed9f55ccabd63650a8fdf547": "Mijn abonnementen",
|
||||
"807cf11e6ac1cde912496f764c176bdfdd6b7e19": "Kanalen",
|
||||
"47546e45bbb476baaaad38244db444c427ddc502": "Afspeellijsten",
|
||||
"29b89f751593e1b347eef103891b7a1ff36ec03f": "De naam is niet beschikbaar omdat het kanaal nog wordt opgehaald.",
|
||||
"4636cd4a1379c50d471e98786098c4d39e1e82ad": "Je hebt geen abonnementen.",
|
||||
"2e0a410652cb07d069f576b61eab32586a18320d": "De naam is niet beschikbaar omdat de afspeellijst nog wordt opgehaald.",
|
||||
"587b57ced54965d8874c3fd0e9dfedb987e5df04": "Je hebt geen abonnementen.",
|
||||
"82421c3e46a0453a70c42900eab51d58d79e6599": "Algemeen",
|
||||
"0ba25ad86a240576c4f20a2fada4722ebba77b1e": "Downloader",
|
||||
"d5f69691f9f05711633128b5a3db696783266b58": "Diversen",
|
||||
"bc2e854e111ecf2bd7db170da5e3c2ed08181d88": "Geavanceerd",
|
||||
"4d13a9cd5ed3dcee0eab22cb25198d43886942be": "Gebruikers",
|
||||
"eb3d5aefff38a814b76da74371cbf02c0789a1ef": "Logboeken",
|
||||
"fe8fd36dbf5deee1d56564965787a782a66eba44": "{VAR_SELECT, select, true {Close} false {Cancel} other {otha}}",
|
||||
"54c512cca1923ab72faf1a0bd98d3d172469629a": "De url waarvan deze app wordt geladen, zonder het poortnummer.",
|
||||
"cb2741a46e3560f6bc6dfd99d385e86b08b26d72": "Poort",
|
||||
"22e8f1d0423a3b784fe40fab187b92c06541b577": "Het gewenste poortnummer (standaard: 17442).",
|
||||
"d4477669a560750d2064051a510ef4d7679e2f3e": "Meerdere gebruikers",
|
||||
"2eb03565fcdce7a7a67abc277a936a32fcf51557": "Gebruikersbasispad",
|
||||
"a64505c41150663968e277ec9b3ddaa5f4838798": "Het basispad voor gebruikers en hun gedownloade video's.",
|
||||
"4e3120311801c4acd18de7146add2ee4a4417773": "Abonnementen toestaan",
|
||||
"4bee2a4bef2d26d37c9b353c278e24e5cd309ce3": "Abonnementenbasispad",
|
||||
"bc9892814ee2d119ae94378c905ea440a249b84a": "Het basispad voor video's van afspeellijsten en kanalen uit je abonnementen. Dit is relatief aan YTDL-Material's hoofdmap.",
|
||||
"5bef4b25ba680da7fff06b86a91b1fc7e6a926e3": "Controletussenpoos",
|
||||
"0f56a7449b77630c114615395bbda4cab398efd8": "In seconden (alleen cijfers).",
|
||||
"13759b09a7f4074ceee8fa2f968f9815fdf63295": "Soms worden nieuwe video's gedownload voordat ze volledig verwerkt zijn. Met deze instelling wordt de volgende dag gecontroleerd of er een hogere kwaliteit beschikbaar is.",
|
||||
"3d1a47dc18b7bd8b5d9e1eb44b235ed9c4a2b513": "Nieuwe uploads opnieuw downloaden",
|
||||
"27a56aad79d8b61269ed303f11664cc78bcc2522": "Thema",
|
||||
"ff7cee38a2259526c519f878e71b964f41db4348": "Standaard",
|
||||
"7a6bacee4c31cb5c0ac2d24274fb4610d8858602": "Themawijziging toestaan",
|
||||
"fe46ccaae902ce974e2441abe752399288298619": "Taal",
|
||||
"ab2756805742e84ad0cc0468f4be2d8aa9f855a5": "Audiopad",
|
||||
"c2c89cdf45d46ea64d2ed2f9ac15dfa4d77e26ca": "Het pad voor audiodownloads. Dit is relatief aan YTDL-Material's hoofdmap.",
|
||||
"46826331da1949bd6fb74624447057099c9d20cd": "Videomap",
|
||||
"17c92e6d47a213fa95b5aa344b3f258147123f93": "Het pad voor videodownloads. Dit is relatief aan YTDL-Material's hoofdmap.",
|
||||
"cfe829634b1144bc44b6d38cf5584ea65db9804f": "Standaard bestandsuitvoer",
|
||||
"1148fd45287ff09955b938756bc302042bcb29c7": "Dit pad is relatief aan bovenstaande downloadpaden. Laat de extensie achterwege.",
|
||||
"ef418d4ece7c844f3a5e431da1aa59bedd88da7b": "Algemene aanvullende opties",
|
||||
"6b995e7130b4d667eaab6c5f61b362ace486d26d": "Algemene aanvullende opties voor downloads op de overzichtspagina. Scheidt deze met komma's: ,,",
|
||||
"04201f9d27abd7d6f58a4328ab98063ce1072006": "Categorieën",
|
||||
"78e49b7339b4fa7184dd21bcaae107ce9b7076f6": "youtube-dl-archief gebruiken",
|
||||
"ffc19f32b1cba0daefc0e5668f89346db1db83ad": "Miniatuurvoorbeeld opslaan",
|
||||
"384de8f8f112c9e6092eb2698706d391553f3e8d": "Metagegevens opslaan",
|
||||
"fb35145bfb84521e21b6385363d59221f436a573": "Alle downloads afbreken",
|
||||
"61f8fd90b5f8cb20c70371feb2ee5e1fac5a9095": "Boventitel",
|
||||
"78d3531417c0d4ba4c90f0d4ae741edc261ec8df": "Bestandsbeheer ingeschakeld",
|
||||
"a5a1be0a5df07de9eec57f5d2a86ed0204b2e75a": "Downloadbeheer ingeschakeld",
|
||||
"c33bd5392b39dbed36b8e5a1145163a15d45835f": "Kwaliteitskeuze toestaan",
|
||||
"bda5508e24e0d77debb28bcd9194d8fefb1cfb92": "Downloadmodus",
|
||||
"09d31c803a7252658694e1e3176b97f5655a3fe3": "Meerdere downloads toestaan",
|
||||
"1c4dbce56d96b8974aac24a02f7ab2ee81415014": "Openbare api gebruiken",
|
||||
"23bd81dcc30b74d06279a26d7a42e8901c1b124e": "Openbare api-sleutel",
|
||||
"41016a73d8ad85e6cb26dffa0a8fab9fe8f60d8e": "Documentatie bekijken",
|
||||
"00a94f58d9eb2e3aa561440eabea616d0c937fa2": "Let op: hiermee verwijder je je oude api-sleutel!",
|
||||
"1b258b258b4cc475ceb2871305b61756b0134f4a": "Genereren",
|
||||
"d5d7c61349f3b0859336066e6d453fc35d334fe5": "YouTube-api gebruiken",
|
||||
"ce10d31febb3d9d60c160750570310f303a22c22": "YouTube-api-sleutel",
|
||||
"8602e313cdfa7c4cc475ccbe86459fce3c3fd986": "Het genereren van een sleutel is eenvoudig.",
|
||||
"d162f9fcd6a7187b391e004f072ab3da8377c47d": "Twitch-api gebruiken",
|
||||
"8ae23bc4302a479f687f4b20a84c276182e2519c": "Twitch-api-sleutel",
|
||||
"84ffcebac2709ca0785f4a1d5ba274433b5beabc": "Ook wel de client-id.",
|
||||
"5fb1e0083c9b2a40ac8ae7dcb2618311c291b8b9": "Twitch-chatgesprekken automatisch downloaden",
|
||||
"9b3cedfa83c6d7acb3210953289d1be4aab115c7": "Klik hier",
|
||||
"7f09776373995003161235c0c8d02b7f91dbc4df": "om de officiële Chrome-extensie van YouTubeDL-Material te downloaden.",
|
||||
"5b5296423906ab3371fdb2b5a5aaa83acaa2ee52": "Hiervoor dien je de extensie handmatig te laden en de frontend-url op te geven in de instellingen.",
|
||||
"9a2ec6da48771128384887525bdcac992632c863": "om de officiële Firefox-extensie van YouTubeDL-Material te installeren.",
|
||||
"eb81be6b49e195e5307811d1d08a19259d411f37": "Uitgebreide installatiehandleiding.",
|
||||
"cb17ff8fe3961cf90f44bee97c88a3f3347a7e55": "Je hoeft alleen de frontend-url op te geven in de instellingen.",
|
||||
"61b81b11aad0b9d970ece2fce18405f07eac69c2": "Sleep de link naar je bladwijzers en klaar is Kees! Ga vervolgens naar een YouTube-video en klik op de bladwijzer.",
|
||||
"c505d6c5de63cc700f0aaf8a4b31fae9e18024e5": "Audio-bookmarklet genereren",
|
||||
"ec71e08aee647ea4a71fd6b7510c54d84a797ca6": "Kies een downloader",
|
||||
"5fab47f146b0a4b809dcebf3db9da94df6299ea1": "Standaard downloadagent gebruiken",
|
||||
"c776eb4992b6c98f58cd89b20c1ea8ac37888521": "Kies een downloadagent",
|
||||
"0c43af932e6a4ee85500e28f01b3538b4eb27bc4": "Logniveau",
|
||||
"db6c192032f4cab809aad35215f0aa4765761897": "Inlogverloopdatum",
|
||||
"dc3d990391c944d1fbfc7cfb402f7b5e112fb3a8": "Geavanceerd downloaden toestaan",
|
||||
"431e5f3a0dde88768d1074baedd65266412b3f02": "Cookies gebruiken",
|
||||
"80651a7ad1229ea6613557d3559f702cfa5aecf5": "Cookies instellen",
|
||||
"37224420db54d4bc7696f157b779a7225f03ca9d": "Gebruikersregistratie toestaan",
|
||||
"fa548cee6ea11c160a416cac3e6bdec0363883dc": "Authenticatiemethode",
|
||||
"4f56ced9d6b85aeb1d4346433361d47ea72dac1a": "Intern",
|
||||
"e3d7c5f019e79a3235a28ba24df24f11712c7627": "LDAP",
|
||||
"1db9789b93069861019bd0ccaa5d4706b00afc61": "LDAP-url",
|
||||
"f50fa6c09c8944aed504f6325f2913ee6c7a296a": "Bind DN",
|
||||
"080cc6abcba236390fc22e79792d0d3443a3bd2a": "Bind-inloggegevens",
|
||||
"cfa67d14d84fe0e9fadf251dc51ffc181173b662": "Zoekdatabank",
|
||||
"e01d54ecc1a0fcf9525a3c100ed8b83d94e61c23": "Zoekfilter",
|
||||
"cec82c0a545f37420d55a9b6c45c20546e82f94e": "Over YouTubeDL-Material",
|
||||
"199c17e5d6a419313af3c325f06dcbb9645ca618": "is een opensource YouTube-downloader, gebouwd volgens Google's Material Design-specificaties. Je kunt naadloos je favoriete video's downloaden als audio- of videobestanden of abonneren op je favoriete kanalen of afspeellijsten om altijd de nieuwste video's binnen te halen.",
|
||||
"bc0ad0ee6630acb7fcb7802ec79f5a0ee943c1a7": "bevat een aantal handige functies, zoals een uitgebreide api, Docker-ondersteuning en is volledig vertaalbaar. Meer functies zijn te vinden op onze GitHub-pagina (klik op het GitHub-pictogram).",
|
||||
"a45e3b05f0529dc5246d70ef62304c94426d4c81": "Geïnstalleerde versie:",
|
||||
"b33536f59b94ec935a16bd6869d836895dc5300c": "Heb je een bug aangetroffen of een idee?",
|
||||
"e1f398f38ff1534303d4bb80bd6cece245f24016": "om een 'issue' te openen!",
|
||||
"e22f3a5351944f3a1a10cfc7da6f65dfbe0037fe": "Bezig met controleren op updates...",
|
||||
"a16e92385b4fd9677bb830a4b796b8b79c113290": "Update beschikbaar",
|
||||
"189b28aaa19b3c51c6111ad039c4fd5e2a22e370": "Je kunt de update installeren via het instellingenmenu.",
|
||||
"1372e61c5bd06100844bd43b98b016aabc468f62": "Kies een versie:",
|
||||
"1f6d14a780a37a97899dc611881e6bc971268285": "Delen toestaan",
|
||||
"6580b6a950d952df847cb3d8e7176720a740adc8": "Tijdstempel gebruiken",
|
||||
"4f2ed9e71a7c981db3e50ae2fedb28aff2ec4e6c": "seconden",
|
||||
"3a6e5a6aa78ca864f6542410c5dafb6334538106": "Kopiëren naar klembord",
|
||||
"a249a5ae13e0835383885aaf697d2890cc3e53e9": "Afspeellijst delen",
|
||||
"15da89490e04496ca9ea1e1b3d44fb5efd4a75d9": "Video delen",
|
||||
"1d540dcd271b316545d070f9d182c372d923aadd": "Audio delen",
|
||||
"a1ad8b1be9be43b5183bd2c3186d4e19496f2a0b": "Sessie-id:",
|
||||
"b6c453e0e61faea184bbaf5c5b0a1e164f4de2a2": "Alle downloads wissen",
|
||||
"eb98135e35af26a9a326ee69bd8ff104d36dd8ec": "(huidig)",
|
||||
"7117fc42f860e86d983bfccfcf2654e5750f3406": "Geen downloads beschikbaar!",
|
||||
"42ff677ec14f111e88bd6cdd30145378e994d1bf": "Mijn profiel",
|
||||
"bb694b49d408265c91c62799c2b3a7e3151c824d": "Uitloggen",
|
||||
"ac9d09de42edca1296371e4d801349c9096ac8de": "UID:",
|
||||
"a5ed099ffc9e96f6970df843289ade8a7d20ab9f": "Aangemaakt:",
|
||||
"fa96f2137af0a24e6d6d54c598c0af7d5d5ad344": "Je bent niet ingelogd.",
|
||||
"a1dbca87b9f36d2b06a5cbcffb5814c4ae9b798a": "Beheerdersaccount aanmaken",
|
||||
"2d2adf3ca26a676bca2269295b7455a26fd26980": "Er zijn geen beheerdersaccounts aangetroffen. Hiermee maak je een beheerdersaccount met wachtwoord aan - de gebruikersnaam is 'admin'.",
|
||||
"70a67e04629f6d412db0a12d51820b480788d795": "Aanmaken",
|
||||
"4d92a0395dd66778a931460118626c5794a3fc7a": "Gebruikers toevoegen",
|
||||
"b0d7dd8a1b0349622d6e0c6e643e24a9ea0efa1d": "Rol aanpassen",
|
||||
"746f64ddd9001ac456327cd9a3d5152203a4b93c": "Gebruikersnaam",
|
||||
"52c1447c1ec9570a2a3025c7e566557b8d19ed92": "Rol",
|
||||
"59a8c38db3091a63ac1cb9590188dc3a972acfb3": "Acties",
|
||||
"2bd201aea09e43fbfd3cd15ec0499b6755302329": "Gebruiker beheren",
|
||||
"95b95a9c79e4fd9ed41f6855e37b3b06af25bcab": "Gebruiker verwijderen",
|
||||
"632e8b20c98e8eec4059a605a4b011bb476137af": "Gebruiker bewerken",
|
||||
"29c97c8e76763bb15b6d515648fa5bd1eb0f7510": "Gebruikers-uid:",
|
||||
"e70e209561583f360b1e9cefd2cbb1fe434b6229": "Nieuw wachtwoord",
|
||||
"6498fa1b8f563988f769654a75411bb8060134b9": "Nieuw wachtwoord instellen",
|
||||
"544e09cdc99a8978f48521d45f62db0da6dcf742": "Standaardrol gebruiken",
|
||||
"4f20f2d5a6882190892e58b85f6ccbedfa737952": "Ja",
|
||||
"3d3ae7deebc5949b0c1c78b9847886a94321d9fd": "Nee",
|
||||
"57c6c05d8ebf4ef1180c2705033c044f655bb2c4": "Rol beheren",
|
||||
"5009630cdf32ab4f1c78737b9617b8773512c05a": "Aantal regels:",
|
||||
"8a0bda4c47f10b2423ff183acefbf70d4ab52ea2": "Logboeken wissen",
|
||||
"24dc3ecf7ec2c2144910c4f3d38343828be03a4c": "Automatisch gegenereerd",
|
||||
"ccf5ea825526ac490974336cb5c24352886abc07": "Bestand openen",
|
||||
"5656a06f17c24b2d7eae9c221567b209743829a9": "Bestand openen op nieuw tabblad",
|
||||
"a0720c36ee1057e5c54a86591b722485c62d7b1a": "Ga naar abonnement",
|
||||
"94e01842dcee90531caa52e4147f70679bac87fe": "Verwijderen en opnieuw downloaden",
|
||||
"2031adb51e07a41844e8ba7704b054e98345c9c1": "Permanent verwijderen",
|
||||
"ddc31f2885b1b33a7651963254b0c197f2a64086": "Meer tonen.",
|
||||
"56a2a773fbd5a6b9ac2e6b89d29d70a2ed0f3227": "Minder tonen.",
|
||||
"2054791b822475aeaea95c0119113de3200f5e1c": "Duur:"
|
||||
}
|
||||
2517
src/assets/i18n/messages.nl.xlf
Normal file
2517
src/assets/i18n/messages.nl.xlf
Normal file
File diff suppressed because it is too large
Load Diff
248
src/assets/i18n/messages.pt.json
Normal file
248
src/assets/i18n/messages.pt.json
Normal file
@@ -0,0 +1,248 @@
|
||||
{
|
||||
"004b222ff9ef9dd4771b777950ca1d0e4cd4348a": "Sobre",
|
||||
"994363f08f9fbfa3b3994ff7b35c6904fdff18d8": "Perfil",
|
||||
"adb4562d2dbd3584370e44496969d58c511ecb63": "Escuro",
|
||||
"121cc5391cd2a5115bc2b3160379ee5b36cd7716": "Configurações",
|
||||
"92eee6be6de0b11c924e3ab27db30257159c0a7c": "Início",
|
||||
"6765b4c916060f6bc42d9bb69e80377dbcb5e4e9": "Entrar",
|
||||
"357064ca9d9ac859eb618e28e8126fa32be049e2": "Inscrições",
|
||||
"822fab38216f64e8166d368b59fe756ca39d301b": "Baixados",
|
||||
"4a9889d36910edc8323d7bab60858ab3da6d91df": "Apenas áudio",
|
||||
"6a21ba5fb0ac804a525bf9ab168038c3ee88e661": "Baixar",
|
||||
"a38ae1082fec79ba1f379978337385a539a28e73": "Qualidade",
|
||||
"4be966a9dcfbc9b54dfcc604b831c0289f847fa4": "Usar URL",
|
||||
"d3f02f845e62cebd75fde451ab8479d2a8ad784d": "Ver",
|
||||
"96a01fafe135afc58b0f8071a4ab00234495ce18": "Modo baixar-múltiplos",
|
||||
"6a3777f913cf3f288664f0632b9f24794fdcc24e": "Cancelar",
|
||||
"322ed150e02666fe2259c5b4614eac7066f4ffa0": "Avançado",
|
||||
"4e4c721129466be9c3862294dc40241b64045998": "Usar argumentos personalizados",
|
||||
"ad2f8ac8b7de7945b80c8e424484da94e597125f": "Argumentos personalizados",
|
||||
"a6911c2157f1b775284bbe9654ce5eb30cf45d7f": "Não é necessário incluir URL, apenas tudo depois. Argumentos são delimitados usando duas vírgulas assim: ,,",
|
||||
"3a92a3443c65a52f37ca7efb8f453b35dbefbf29": "Usar saída personalizada",
|
||||
"d9c02face477f2f9cdaae318ccee5f89856851fb": "Saída personalizada",
|
||||
"fcfd4675b4c90f08d18d3abede9a9a4dff4cfdc7": "Documentação",
|
||||
"19d1ae64d94d28a29b2c57ae8671aace906b5401": "Caminho é relativo a configuração do caminho de download. Não inclua a extensão.",
|
||||
"b7ffe7c6586d6f3f18a9246806a7c7d5538ab43e": "Comando simulado:",
|
||||
"8fad10737d3e3735a6699a4d89cbf6c20f6bb55f": "Usar autenticação",
|
||||
"08c74dc9762957593b91f6eb5d65efdfc975bf48": "Nome de usuário",
|
||||
"c32ef07f8803a223a83ed17024b38e8d82292407": "Senha",
|
||||
"17f0ea5d2d7a262b0e875acc70475f102aee84e6": "Criar lista de reprodução",
|
||||
"cff1428d10d59d14e45edec3c735a27b5482db59": "Nome",
|
||||
"f61c6867295f3b53d23557021f2f4e0aa1d0b8fc": "Tipo",
|
||||
"f0baeb8b69d120073b6d60d34785889b0c3232c8": "Áudio",
|
||||
"2d1ea268a6a9f483dbc2cbfe19bf4256a57a6af4": "Vídeo",
|
||||
"f47e2d56dd8a145b2e9599da9730c049d52962a2": "Arquivos de áudio",
|
||||
"a52dae09be10ca3a65da918533ced3d3f4992238": "Vídeos",
|
||||
"a9806cf78ce00eb2613eeca11354a97e033377b8": "Inscreva-se numa lista de reprodução ou canal",
|
||||
"801b98c6f02fe3b32f6afa3ee854c99ed83474e6": "URL",
|
||||
"93efc99ae087fc116de708ecd3ace86ca237cf30": "A URL da lista de reprodução ou canal",
|
||||
"08f5d0ef937ae17feb1b04aff15ad88911e87baf": "Nome personalizado",
|
||||
"ea30873bd3f0d5e4fb2378eec3f0a1db77634a28": "Baixar todos uploads",
|
||||
"d641b8fa5ac5e85114c733b1f7de6976bd091f70": "Qualidade máxima",
|
||||
"c76a955642714b8949ff3e4b4990864a2e2cac95": "Modo apenas-áudio",
|
||||
"408ca4911457e84a348cecf214f02c69289aa8f1": "Modo somente streaming",
|
||||
"f432e1a8d6adb12e612127978ce2e0ced933959c": "Eles são adicionados após os argumentos padrão.",
|
||||
"98b6ec9ec138186d663e64770267b67334353d63": "Saída de arquivo personalizado",
|
||||
"d7b35c384aecd25a516200d6921836374613dfe7": "Cancelar",
|
||||
"d0336848b0c375a1c25ba369b3481ee383217a4f": "Inscreva-se",
|
||||
"28a678e9cabf86e44c32594c43fa0e890135c20f": "Baixe vídeos enviados no último",
|
||||
"e78c0d60ac39787f62c9159646fe0b3c1ed55a1d": "Tipo:",
|
||||
"c52db455cca9109ee47e1a612c3f4117c09eb71b": "URL:",
|
||||
"ca3dbbc7f3e011bffe32a10a3ea45cc84f30ecf1": "ID:",
|
||||
"f4e529ae5ffd73001d1ff4bbdeeb0a72e342e5c8": "Fechar",
|
||||
"8efc77bf327659c0fec1f518cf48a98cdcd9dddf": "Exportar Arquivos",
|
||||
"3042bd3ad8dffcfeca5fd1ae6159fd1047434e95": "Cancelar inscrição",
|
||||
"303e45ffae995c9817e510e38cb969e6bb3adcbf": "(Pausado)",
|
||||
"a44d86aa1e6c20ced07aca3a7c081d8db9ded1c6": "Arquivos:",
|
||||
"616e206cb4f25bd5885fc35925365e43cf5fb929": "Nome:",
|
||||
"c6eb45d085384903e53ab001a3513d1de6a1dbac": "Quem subiu:",
|
||||
"109c6f4a5e46efb933612ededfaf52a13178b7e0": "Tamanho do arquivo:",
|
||||
"bd630d8669b16e5f264ec4649d9b469fe03e5ff4": "Caminho:",
|
||||
"a67e7d843cef735c79d5ef1c8ba4af3e758912bb": "Data de upload:",
|
||||
"0cc1dec590ecd74bef71a865fb364779bc42a749": "Categoria:",
|
||||
"d9e83ac17026e70ef6e9c0f3240a3b2450367f40": "Modificar argumentos do youtube-dl",
|
||||
"7fc1946abe2b40f60059c6cd19975d677095fd19": "Novos argumentos simulados",
|
||||
"0b71824ae71972f236039bed43f8d2323e8fd570": "Adicionar um argumento",
|
||||
"c8b0e59eb491f2ac7505f0fbab747062e6b32b23": "Buscar por categoria",
|
||||
"9eeb91caef5a50256dd87e1c4b7b3e8216479377": "Usar valor do argumento",
|
||||
"7de2451ed3fb8d8b847979bd3f0c740b970f167b": "Adicionar argumento",
|
||||
"b2623aee44b70c9a4ba1fce16c8a593b0a4c7974": "Modificar",
|
||||
"25d8ad5eba2ec24e68295a27d6a4bb9b49e3dacd": "Valor do argumento",
|
||||
"91ecce65f1d23f9419d1c953cd6b7bc7f91c110e": "Atualizador",
|
||||
"b7ff2e2b909c53abe088fe60b9f4b6ac7757247f": "Registrar usuário",
|
||||
"024886ca34a6f309e3e51c2ed849320592c3faaa": "Nome de usuário",
|
||||
"cfc2f436ec2beffb042e7511a73c89c372e86a6c": "Registrar",
|
||||
"ebadf946ae90f13ecd0c70f09edbc0f983af8a0f": "Fazer upload de novos cookies",
|
||||
"a8b7b9c168fd936a75e500806a8c0d7755ef1198": "NOTA: O upload de novos cookies substituirá os cookies anteriores. Observe também que os cookies abrangem toda a instância, não por usuário.",
|
||||
"98a8a42e5efffe17ab786636ed0139b4c7032d0e": "Arrastar e soltar",
|
||||
"4f389e41e4592f7f9bb76abdd8af4afdfb13f4f1": "Modificar lista de reprodução",
|
||||
"5caadefa4143cf6766a621b0f54f91f373a1f164": "Adicionar conteúdo",
|
||||
"52c9a103b812f258bcddc3d90a6e3f46871d25fe": "Salvar",
|
||||
"33026f57ea65cd9c8a5d917a08083f71a718933a": "Ordem normal",
|
||||
"29376982b1205d9d6ea3d289e8e2f8e1ac2839b1": "Ordem reversa",
|
||||
"d02888c485d3aeab6de628508f4a00312a722894": "Meus vídeos",
|
||||
"7e892ba15f2c6c17e83510e273b3e10fc32ea016": "Buscar",
|
||||
"73423607944a694ce6f9e55cfee329681bb4d9f9": "Nenhum vídeo encontrado.",
|
||||
"3697f8583ea42868aa269489ad366103d94aece7": "Editando",
|
||||
"07db550ae114d9faad3a0cbb68bcc16ab6cd31fc": "Em pausa",
|
||||
"c3b0b86523f1d10e84a71f9b188d54913a11af3b": "Editando categoria",
|
||||
"2489eefea00931942b91f4a1ae109514b591e2e1": "Regras",
|
||||
"e4eeb9106dbcbc91ca1ac3fb4068915998a70f37": "Adicionar nova regra",
|
||||
"792dc6a57f28a1066db283f2e736484f066005fd": "Baixar Twitch Chat",
|
||||
"28f86ffd419b869711aa13f5e5ff54be6d70731c": "Editar",
|
||||
"826b25211922a1b46436589233cb6f1a163d89b7": "Excluir",
|
||||
"321e4419a943044e674beb55b8039f42a9761ca5": "Detalhes",
|
||||
"e684046d73bcee88e82f7ff01e2852789a05fc32": "Total:",
|
||||
"34504b488c24c27e68089be549f0eeae6ebaf30b": "Excluir e bloquear",
|
||||
"dad95154dcef3509b8cc705046061fd24994bbb7": "visualizações",
|
||||
"5b3075e8dc3f3921ec316b0bd83b6d14a06c1a4f": "Salvar alterações",
|
||||
"4d8a18b04a1f785ecd8021ac824e0dfd5881dbfc": "Download finalizado com sucesso",
|
||||
"348cc5d553b18e862eb1c1770e5636f6b05ba130": "Ocorreu um erro",
|
||||
"4f8b2bb476981727ab34ed40fde1218361f92c45": "Detalhes",
|
||||
"e9aff8e6df2e2bf6299ea27bb2894c70bc48bd4d": "Ocorreu um erro:",
|
||||
"77b0c73840665945b25bd128709aa64c8f017e1c": "Download iniciado:",
|
||||
"08ff9375ec078065bcdd7637b7ea65fce2979266": "Download finalizado:",
|
||||
"ad127117f9471612f47d01eae09709da444a36a4": "Destino do arquivo:",
|
||||
"e2319dec5b4ccfb6ed9f55ccabd63650a8fdf547": "Suas inscrições",
|
||||
"807cf11e6ac1cde912496f764c176bdfdd6b7e19": "Canais",
|
||||
"47546e45bbb476baaaad38244db444c427ddc502": "Playlists",
|
||||
"29b89f751593e1b347eef103891b7a1ff36ec03f": "Nome não disponível. Carregamento do canal em andamento.",
|
||||
"4636cd4a1379c50d471e98786098c4d39e1e82ad": "Você não está inscrito em um canal.",
|
||||
"2e0a410652cb07d069f576b61eab32586a18320d": "Nome não disponível. Carregamento da playlist em andamento.",
|
||||
"587b57ced54965d8874c3fd0e9dfedb987e5df04": "Você não está inscrito em uma playlist.",
|
||||
"82421c3e46a0453a70c42900eab51d58d79e6599": "Geral",
|
||||
"0ba25ad86a240576c4f20a2fada4722ebba77b1e": "Downloader",
|
||||
"d5f69691f9f05711633128b5a3db696783266b58": "Extra",
|
||||
"bc2e854e111ecf2bd7db170da5e3c2ed08181d88": "Avançado",
|
||||
"4d13a9cd5ed3dcee0eab22cb25198d43886942be": "Usuários",
|
||||
"eb3d5aefff38a814b76da74371cbf02c0789a1ef": "Logs",
|
||||
"fe8fd36dbf5deee1d56564965787a782a66eba44": "{VAR_SELECT, select, true {Fechar} false {Cancelar} other {Outro}}",
|
||||
"54c512cca1923ab72faf1a0bd98d3d172469629a": "URL para acesso ao app, sem porta.",
|
||||
"cb2741a46e3560f6bc6dfd99d385e86b08b26d72": "Porta",
|
||||
"22e8f1d0423a3b784fe40fab187b92c06541b577": "Porta desejada. (Padrão 17442).",
|
||||
"d4477669a560750d2064051a510ef4d7679e2f3e": "Modo multi-usuário",
|
||||
"2eb03565fcdce7a7a67abc277a936a32fcf51557": "Diretório base de usuários",
|
||||
"a64505c41150663968e277ec9b3ddaa5f4838798": "Diretório base para usuários e seus vídeos baixados.",
|
||||
"4e3120311801c4acd18de7146add2ee4a4417773": "Permitir inscrições",
|
||||
"4bee2a4bef2d26d37c9b353c278e24e5cd309ce3": "Diretório base para Inscrições",
|
||||
"bc9892814ee2d119ae94378c905ea440a249b84a": "Diretório base para vídeos das inscrições em canais e playlists. Relativo ao diretório raiz do YTDL-Material.",
|
||||
"5bef4b25ba680da7fff06b86a91b1fc7e6a926e3": "Intervalo de checagem",
|
||||
"0f56a7449b77630c114615395bbda4cab398efd8": "Unidade em segundos, inclua apenas números.",
|
||||
"13759b09a7f4074ceee8fa2f968f9815fdf63295": "Algumas vezes vídeos são baixados antes de serem totalmente processados. Esta configuração faz com que vídeos novos sejam verificados por uma qualidade mais alta no dia seguinte.",
|
||||
"3d1a47dc18b7bd8b5d9e1eb44b235ed9c4a2b513": "Baixar novamente uploads recentes",
|
||||
"27a56aad79d8b61269ed303f11664cc78bcc2522": "Aparência",
|
||||
"ff7cee38a2259526c519f878e71b964f41db4348": "Padrão",
|
||||
"7a6bacee4c31cb5c0ac2d24274fb4610d8858602": "Permitir alteração da aparência",
|
||||
"fe46ccaae902ce974e2441abe752399288298619": "Idioma",
|
||||
"ab2756805742e84ad0cc0468f4be2d8aa9f855a5": "Diretório para áudio",
|
||||
"c2c89cdf45d46ea64d2ed2f9ac15dfa4d77e26ca": "Diretório para downloads de 'áudio apenas'. Relativo ao diretório raiz do YTDL-Material.",
|
||||
"46826331da1949bd6fb74624447057099c9d20cd": "Diretório de Vídeos",
|
||||
"17c92e6d47a213fa95b5aa344b3f258147123f93": "Diretório para download de vídeos. Relativo ao diretório raiz do YTDL-Material.",
|
||||
"cfe829634b1144bc44b6d38cf5584ea65db9804f": "Arquivo de destino padrão",
|
||||
"1148fd45287ff09955b938756bc302042bcb29c7": "O caminho é relativo ao diretório de download acima. Não inclua extensão.",
|
||||
"ef418d4ece7c844f3a5e431da1aa59bedd88da7b": "Parâmetro global personalizado",
|
||||
"6b995e7130b4d667eaab6c5f61b362ace486d26d": "Parâmetro global personalizado para downloads na home page. Parâmetros são delimitados utilizando duas vírgulas. Ex: ,,",
|
||||
"04201f9d27abd7d6f58a4328ab98063ce1072006": "Categorias",
|
||||
"78e49b7339b4fa7184dd21bcaae107ce9b7076f6": "Armazenar youtube-dl",
|
||||
"ffc19f32b1cba0daefc0e5668f89346db1db83ad": "Incluir miniatura",
|
||||
"384de8f8f112c9e6092eb2698706d391553f3e8d": "Incluir metadados",
|
||||
"fb35145bfb84521e21b6385363d59221f436a573": "Parar todos os downloads",
|
||||
"61f8fd90b5f8cb20c70371feb2ee5e1fac5a9095": "Título",
|
||||
"78d3531417c0d4ba4c90f0d4ae741edc261ec8df": "Habilitar gerenciador de arquivos",
|
||||
"a5a1be0a5df07de9eec57f5d2a86ed0204b2e75a": "Habilitar gerenciador de downloads",
|
||||
"c33bd5392b39dbed36b8e5a1145163a15d45835f": "Permitir seleção de qualidade",
|
||||
"bda5508e24e0d77debb28bcd9194d8fefb1cfb92": "Modo Apenas Download",
|
||||
"09d31c803a7252658694e1e3176b97f5655a3fe3": "Permitir modo múltiplos downloads",
|
||||
"1c4dbce56d96b8974aac24a02f7ab2ee81415014": "Habilitar API pública",
|
||||
"23bd81dcc30b74d06279a26d7a42e8901c1b124e": "Chave da API Pública",
|
||||
"41016a73d8ad85e6cb26dffa0a8fab9fe8f60d8e": "Ver documentação",
|
||||
"00a94f58d9eb2e3aa561440eabea616d0c937fa2": "Isso irá excluir sua chave API anterior!",
|
||||
"1b258b258b4cc475ceb2871305b61756b0134f4a": "Gerar",
|
||||
"d5d7c61349f3b0859336066e6d453fc35d334fe5": "Usar API do YouTube",
|
||||
"ce10d31febb3d9d60c160750570310f303a22c22": "Chave da API do YouTube",
|
||||
"8602e313cdfa7c4cc475ccbe86459fce3c3fd986": "Gerar uma chave é fácil!",
|
||||
"d162f9fcd6a7187b391e004f072ab3da8377c47d": "Usar API do Twitch",
|
||||
"8ae23bc4302a479f687f4b20a84c276182e2519c": "Chave da API do Twitch",
|
||||
"84ffcebac2709ca0785f4a1d5ba274433b5beabc": "Também chamado de ID do Cliente (Client ID).",
|
||||
"5fb1e0083c9b2a40ac8ae7dcb2618311c291b8b9": "Baixar Twitch Chat automaticamente",
|
||||
"9b3cedfa83c6d7acb3210953289d1be4aab115c7": "Clique aqui",
|
||||
"7f09776373995003161235c0c8d02b7f91dbc4df": "para baixar a extensão do YoutubeDL-Material para o Chrome manualmente.",
|
||||
"5b5296423906ab3371fdb2b5a5aaa83acaa2ee52": "Você deve carregar a extensão manualmente e alterar as configurações para a URL inicial.",
|
||||
"9a2ec6da48771128384887525bdcac992632c863": "para instalar a extensão oficial do YoutubeDL-Material para o Firefox diretamente da página de extensões.",
|
||||
"eb81be6b49e195e5307811d1d08a19259d411f37": "Instruções de instalação detalhadas.",
|
||||
"cb17ff8fe3961cf90f44bee97c88a3f3347a7e55": "Nada além de alterar a configuração da extensão para a URL inicial é necessário.",
|
||||
"61b81b11aad0b9d970ece2fce18405f07eac69c2": "Arraste o link abaixo para seus Favoritos, e pronto! Navegue para o vídeo do Youtube que deseja baixar e clique no link favoritado.",
|
||||
"c505d6c5de63cc700f0aaf8a4b31fae9e18024e5": "Gerar favorito 'apenas áudio' interativo",
|
||||
"ec71e08aee647ea4a71fd6b7510c54d84a797ca6": "Selecione um downloader",
|
||||
"5fab47f146b0a4b809dcebf3db9da94df6299ea1": "Usar agente de download padrão",
|
||||
"c776eb4992b6c98f58cd89b20c1ea8ac37888521": "Selecionar um agente de download",
|
||||
"0c43af932e6a4ee85500e28f01b3538b4eb27bc4": "Nível do Log",
|
||||
"db6c192032f4cab809aad35215f0aa4765761897": "Expiração do login",
|
||||
"dc3d990391c944d1fbfc7cfb402f7b5e112fb3a8": "Permitir Download avançado",
|
||||
"431e5f3a0dde88768d1074baedd65266412b3f02": "Usar Cookies",
|
||||
"80651a7ad1229ea6613557d3559f702cfa5aecf5": "Configurar Cookies",
|
||||
"37224420db54d4bc7696f157b779a7225f03ca9d": "Permitir cadastro de usuário",
|
||||
"fa548cee6ea11c160a416cac3e6bdec0363883dc": "Método de autenticação",
|
||||
"4f56ced9d6b85aeb1d4346433361d47ea72dac1a": "Interno",
|
||||
"e3d7c5f019e79a3235a28ba24df24f11712c7627": "LDAP",
|
||||
"1db9789b93069861019bd0ccaa5d4706b00afc61": "URL do LDAP",
|
||||
"f50fa6c09c8944aed504f6325f2913ee6c7a296a": "Bind DN",
|
||||
"080cc6abcba236390fc22e79792d0d3443a3bd2a": "Bind Credentials",
|
||||
"cfa67d14d84fe0e9fadf251dc51ffc181173b662": "Search Base",
|
||||
"e01d54ecc1a0fcf9525a3c100ed8b83d94e61c23": "Search Filter",
|
||||
"cec82c0a545f37420d55a9b6c45c20546e82f94e": "Sobre o YoutubeDL-Material",
|
||||
"199c17e5d6a419313af3c325f06dcbb9645ca618": "é um downloader open-source para o Youtube feito nas especificações do Mateerial Design da Google. Você pode baixar seus vídeos favoritos como arquivos de vídeo ou áudio, e até se inscrever nos seus canais ou playlists favoritos para ficar atualizado com os novos vídeos publicados.",
|
||||
"bc0ad0ee6630acb7fcb7802ec79f5a0ee943c1a7": "tem algumas funcionalidades incríveis inclusas! Uma API extensa, suporte ao Docker, suporte à tradução. Leia sobre todas as funcionalidades suportadas clicando no ícone do GitHub acima.",
|
||||
"a45e3b05f0529dc5246d70ef62304c94426d4c81": "Versão instalada:",
|
||||
"b33536f59b94ec935a16bd6869d836895dc5300c": "Achou m bug ou tem uma sugestão?",
|
||||
"e1f398f38ff1534303d4bb80bd6cece245f24016": "para criar uma ocorrência!",
|
||||
"e22f3a5351944f3a1a10cfc7da6f65dfbe0037fe": "Buscando por atualizações...",
|
||||
"a16e92385b4fd9677bb830a4b796b8b79c113290": "Atualização disponível",
|
||||
"189b28aaa19b3c51c6111ad039c4fd5e2a22e370": "Você pode atualizar pelo Menu de Configuração.",
|
||||
"1372e61c5bd06100844bd43b98b016aabc468f62": "Selecionar uma versão:",
|
||||
"1f6d14a780a37a97899dc611881e6bc971268285": "Habilitar compartilhamento",
|
||||
"6580b6a950d952df847cb3d8e7176720a740adc8": "Usar timestamp",
|
||||
"4f2ed9e71a7c981db3e50ae2fedb28aff2ec4e6c": "Segundos",
|
||||
"3a6e5a6aa78ca864f6542410c5dafb6334538106": "Copiar para a Área de transferência",
|
||||
"a249a5ae13e0835383885aaf697d2890cc3e53e9": "Compartilhar playlist",
|
||||
"15da89490e04496ca9ea1e1b3d44fb5efd4a75d9": "Compartilhar Vídeo",
|
||||
"1d540dcd271b316545d070f9d182c372d923aadd": "Compartilhar Áudio",
|
||||
"a1ad8b1be9be43b5183bd2c3186d4e19496f2a0b": "ID da sessão:",
|
||||
"b6c453e0e61faea184bbaf5c5b0a1e164f4de2a2": "Limpar todos os downloads",
|
||||
"eb98135e35af26a9a326ee69bd8ff104d36dd8ec": "(atual)",
|
||||
"7117fc42f860e86d983bfccfcf2654e5750f3406": "Não há downloads disponíveis!",
|
||||
"42ff677ec14f111e88bd6cdd30145378e994d1bf": "Seu Perfil",
|
||||
"bb694b49d408265c91c62799c2b3a7e3151c824d": "Sair",
|
||||
"ac9d09de42edca1296371e4d801349c9096ac8de": "UID:",
|
||||
"a5ed099ffc9e96f6970df843289ade8a7d20ab9f": "Criado:",
|
||||
"fa96f2137af0a24e6d6d54c598c0af7d5d5ad344": "Você não está logado.",
|
||||
"a1dbca87b9f36d2b06a5cbcffb5814c4ae9b798a": "Criar conta de Administrador",
|
||||
"2d2adf3ca26a676bca2269295b7455a26fd26980": "Conta padrão de Administrador não detectada. Isto irá criar e configurar uma senha para a conta de administrador com o usuário 'admin'.",
|
||||
"70a67e04629f6d412db0a12d51820b480788d795": "Criar",
|
||||
"4d92a0395dd66778a931460118626c5794a3fc7a": "Adicionar Usuários",
|
||||
"b0d7dd8a1b0349622d6e0c6e643e24a9ea0efa1d": "Editar função",
|
||||
"746f64ddd9001ac456327cd9a3d5152203a4b93c": "Nome de usuário",
|
||||
"52c1447c1ec9570a2a3025c7e566557b8d19ed92": "Função",
|
||||
"59a8c38db3091a63ac1cb9590188dc3a972acfb3": "Ações",
|
||||
"2bd201aea09e43fbfd3cd15ec0499b6755302329": "Gerenciar usuário",
|
||||
"95b95a9c79e4fd9ed41f6855e37b3b06af25bcab": "Excluir usuário",
|
||||
"632e8b20c98e8eec4059a605a4b011bb476137af": "Editar usuário",
|
||||
"29c97c8e76763bb15b6d515648fa5bd1eb0f7510": "UID do usuário:",
|
||||
"e70e209561583f360b1e9cefd2cbb1fe434b6229": "Nova senha",
|
||||
"6498fa1b8f563988f769654a75411bb8060134b9": "Aplicar nova senha",
|
||||
"544e09cdc99a8978f48521d45f62db0da6dcf742": "Usar função padrão",
|
||||
"4f20f2d5a6882190892e58b85f6ccbedfa737952": "Sim",
|
||||
"3d3ae7deebc5949b0c1c78b9847886a94321d9fd": "Não",
|
||||
"57c6c05d8ebf4ef1180c2705033c044f655bb2c4": "Gerenciar função",
|
||||
"5009630cdf32ab4f1c78737b9617b8773512c05a": "Linhas:",
|
||||
"8a0bda4c47f10b2423ff183acefbf70d4ab52ea2": "Limpar logs",
|
||||
"24dc3ecf7ec2c2144910c4f3d38343828be03a4c": "Auto-gerado",
|
||||
"ccf5ea825526ac490974336cb5c24352886abc07": "Abrir arquivo",
|
||||
"5656a06f17c24b2d7eae9c221567b209743829a9": "Abrir arquivo em uma nova aba",
|
||||
"a0720c36ee1057e5c54a86591b722485c62d7b1a": "Ir para Inscrições",
|
||||
"94e01842dcee90531caa52e4147f70679bac87fe": "Excluir e baixar novamente",
|
||||
"2031adb51e07a41844e8ba7704b054e98345c9c1": "Excluir para sempre",
|
||||
"ddc31f2885b1b33a7651963254b0c197f2a64086": "Ver mais.",
|
||||
"56a2a773fbd5a6b9ac2e6b89d29d70a2ed0f3227": "Ver menos.",
|
||||
"2054791b822475aeaea95c0119113de3200f5e1c": "Duração:"
|
||||
}
|
||||
2517
src/assets/i18n/messages.pt.xlf
Normal file
2517
src/assets/i18n/messages.pt.xlf
Normal file
File diff suppressed because it is too large
Load Diff
268
src/assets/i18n/messages.ru.json
Normal file
268
src/assets/i18n/messages.ru.json
Normal file
@@ -0,0 +1,268 @@
|
||||
{
|
||||
"17f0ea5d2d7a262b0e875acc70475f102aee84e6": "Создайте список воспроизведения",
|
||||
"cff1428d10d59d14e45edec3c735a27b5482db59": "Имя",
|
||||
"f47e2d56dd8a145b2e9599da9730c049d52962a2": "Аудиофайлы",
|
||||
"a52dae09be10ca3a65da918533ced3d3f4992238": "Видео",
|
||||
"d9e83ac17026e70ef6e9c0f3240a3b2450367f40": "Изменение аргументов youtube-dl",
|
||||
"7fc1946abe2b40f60059c6cd19975d677095fd19": "Моделирование новых аргументов",
|
||||
"0b71824ae71972f236039bed43f8d2323e8fd570": "Добавить аргумент",
|
||||
"c8b0e59eb491f2ac7505f0fbab747062e6b32b23": "Поиск по категориям",
|
||||
"9eeb91caef5a50256dd87e1c4b7b3e8216479377": "Использовать значение аргумента",
|
||||
"25d8ad5eba2ec24e68295a27d6a4bb9b49e3dacd": "Значение аргумента",
|
||||
"7de2451ed3fb8d8b847979bd3f0c740b970f167b": "Добавить аргумент",
|
||||
"d7b35c384aecd25a516200d6921836374613dfe7": "Отмена",
|
||||
"b2623aee44b70c9a4ba1fce16c8a593b0a4c7974": "Модифицировать",
|
||||
"038ebcb2a89155d90c24fa1c17bfe83dbadc3c20": "",
|
||||
"a38ae1082fec79ba1f379978337385a539a28e73": "Качество",
|
||||
"4be966a9dcfbc9b54dfcc604b831c0289f847fa4": "Использовать URL",
|
||||
"d3f02f845e62cebd75fde451ab8479d2a8ad784d": "Вид",
|
||||
"4a9889d36910edc8323d7bab60858ab3da6d91df": "Только аудио",
|
||||
"96a01fafe135afc58b0f8071a4ab00234495ce18": "Режим мультизагрузки",
|
||||
"6a21ba5fb0ac804a525bf9ab168038c3ee88e661": "Загрузка",
|
||||
"6a3777f913cf3f288664f0632b9f24794fdcc24e": "Отмена",
|
||||
"322ed150e02666fe2259c5b4614eac7066f4ffa0": "Расширенный",
|
||||
"b7ffe7c6586d6f3f18a9246806a7c7d5538ab43e": "Имитация команды:",
|
||||
"4e4c721129466be9c3862294dc40241b64045998": "Используйте пользовательские аргументы",
|
||||
"ad2f8ac8b7de7945b80c8e424484da94e597125f": "Пользовательские аргументы",
|
||||
"a6911c2157f1b775284bbe9654ce5eb30cf45d7f": "Нет необходимости включать URL, просто все, что после. Аргументы разграничиваются двумя запятыми следующим образом: ,,",
|
||||
"3a92a3443c65a52f37ca7efb8f453b35dbefbf29": "Использование пользовательского вывода",
|
||||
"d9c02face477f2f9cdaae318ccee5f89856851fb": "Пользовательский вывод",
|
||||
"fcfd4675b4c90f08d18d3abede9a9a4dff4cfdc7": "Документация",
|
||||
"19d1ae64d94d28a29b2c57ae8671aace906b5401": "Путь является относительным к пути загрузки конфигурации. Не включайте расширение.",
|
||||
"8fad10737d3e3735a6699a4d89cbf6c20f6bb55f": "Используйте аутентификацию",
|
||||
"08c74dc9762957593b91f6eb5d65efdfc975bf48": "Имя пользователя",
|
||||
"c32ef07f8803a223a83ed17024b38e8d82292407": "Пароль",
|
||||
"4a0dada6e841a425de3e5006e6a04df26c644fa5": "",
|
||||
"9779715ac05308973d8f1c8658b29b986e92450f": "",
|
||||
"47546e45bbb476baaaad38244db444c427ddc502": "Плейлисты",
|
||||
"78bd81adb4609b68cfa4c589222bdc233ba1faaa": "",
|
||||
"9d2b62bb0b91e2e17fb4177a7e3d6756a2e6ee33": "",
|
||||
"960582a8b9d7942716866ecfb7718309728f2916": "",
|
||||
"0f59c46ca29e9725898093c9ea6b586730d0624e": "",
|
||||
"616e206cb4f25bd5885fc35925365e43cf5fb929": "Имя:",
|
||||
"c52db455cca9109ee47e1a612c3f4117c09eb71b": "URL-адрес:",
|
||||
"c6eb45d085384903e53ab001a3513d1de6a1dbac": "Загрузчик:",
|
||||
"109c6f4a5e46efb933612ededfaf52a13178b7e0": "Размер файла:",
|
||||
"bd630d8669b16e5f264ec4649d9b469fe03e5ff4": "Путь:",
|
||||
"a67e7d843cef735c79d5ef1c8ba4af3e758912bb": "Дата загрузки:",
|
||||
"f4e529ae5ffd73001d1ff4bbdeeb0a72e342e5c8": "Закрыть",
|
||||
"4f389e41e4592f7f9bb76abdd8af4afdfb13f4f1": "Изменить список воспроизведения",
|
||||
"ca3dbbc7f3e011bffe32a10a3ea45cc84f30ecf1": "ID:",
|
||||
"e684046d73bcee88e82f7ff01e2852789a05fc32": "Подсчет:",
|
||||
"28f86ffd419b869711aa13f5e5ff54be6d70731c": "Редактировать",
|
||||
"826b25211922a1b46436589233cb6f1a163d89b7": "Удалить",
|
||||
"321e4419a943044e674beb55b8039f42a9761ca5": "Информация",
|
||||
"34504b488c24c27e68089be549f0eeae6ebaf30b": "Удаление и черный список",
|
||||
"ebadf946ae90f13ecd0c70f09edbc0f983af8a0f": "Загрузка новых файлов cookie",
|
||||
"98a8a42e5efffe17ab786636ed0139b4c7032d0e": "Перетаскивание",
|
||||
"85e0725c870b28458fd3bbba905397d890f00a69": "",
|
||||
"121cc5391cd2a5115bc2b3160379ee5b36cd7716": "Настройки",
|
||||
"801b98c6f02fe3b32f6afa3ee854c99ed83474e6": "URL-адрес",
|
||||
"54c512cca1923ab72faf1a0bd98d3d172469629a": "URL, с которого будет осуществляться доступ к этому приложению, без указания порта.",
|
||||
"cb2741a46e3560f6bc6dfd99d385e86b08b26d72": "Порт",
|
||||
"22e8f1d0423a3b784fe40fab187b92c06541b577": "Желаемый порт. По умолчанию - 17442.",
|
||||
"d4477669a560750d2064051a510ef4d7679e2f3e": "Многопользовательский режим",
|
||||
"2eb03565fcdce7a7a67abc277a936a32fcf51557": "Базовый путь пользователей",
|
||||
"a64505c41150663968e277ec9b3ddaa5f4838798": "Базовый путь для пользователей и их загруженных видео.",
|
||||
"cbe16a57be414e84b6a68309d08fad894df797d6": "",
|
||||
"0c1875a79b7ecc792cc1bebca3e063e40b5764f9": "",
|
||||
"736551b93461d2de64b118cf4043eee1d1c2cb2c": "",
|
||||
"4e3120311801c4acd18de7146add2ee4a4417773": "Разрешить подписки",
|
||||
"4bee2a4bef2d26d37c9b353c278e24e5cd309ce3": "Базовый путь подписки",
|
||||
"bc9892814ee2d119ae94378c905ea440a249b84a": "Базовый путь для видео из подписанных вами каналов и плейлистов. Он является относительным к корневой папке YTDL-Material.",
|
||||
"5bef4b25ba680da7fff06b86a91b1fc7e6a926e3": "Контрольный интервал",
|
||||
"0f56a7449b77630c114615395bbda4cab398efd8": "Единица измерения - секунды, включайте только цифры.",
|
||||
"78e49b7339b4fa7184dd21bcaae107ce9b7076f6": "Используйте архив youtube-dl",
|
||||
"fa9fe4255231dd1cc6b29d3d254a25cb7c764f0f": "",
|
||||
"09006404cccc24b7a8f8d1ce0b39f2761ab841d8": "",
|
||||
"29ed79a98fc01e7f9537777598e31dbde3aa7981": "",
|
||||
"27a56aad79d8b61269ed303f11664cc78bcc2522": "Тема",
|
||||
"ff7cee38a2259526c519f878e71b964f41db4348": "По умолчанию",
|
||||
"adb4562d2dbd3584370e44496969d58c511ecb63": "Тёмный",
|
||||
"7a6bacee4c31cb5c0ac2d24274fb4610d8858602": "Разрешить смену темы",
|
||||
"fe46ccaae902ce974e2441abe752399288298619": "Язык",
|
||||
"82421c3e46a0453a70c42900eab51d58d79e6599": "Главная",
|
||||
"ab2756805742e84ad0cc0468f4be2d8aa9f855a5": "Путь к папке аудио",
|
||||
"c2c89cdf45d46ea64d2ed2f9ac15dfa4d77e26ca": "Путь для загрузки только аудиофайлов. Он является относительным к корневой папке YTDL-Material.",
|
||||
"46826331da1949bd6fb74624447057099c9d20cd": "Путь к папке с видео",
|
||||
"17c92e6d47a213fa95b5aa344b3f258147123f93": "Путь для загрузки видео. Относится к корневой папке YTDL-Material.",
|
||||
"6b995e7130b4d667eaab6c5f61b362ace486d26d": "Глобальные пользовательские аргументы для загрузок на главной странице. Аргументы разграничиваются с помощью двух запятых следующим образом: ,,",
|
||||
"d01715b75228878a773ae6d059acc639d4898a03": "",
|
||||
"0ba25ad86a240576c4f20a2fada4722ebba77b1e": "Загрузчик",
|
||||
"61f8fd90b5f8cb20c70371feb2ee5e1fac5a9095": "Верхний заголовок",
|
||||
"78d3531417c0d4ba4c90f0d4ae741edc261ec8df": "Включен файловый менеджер",
|
||||
"a5a1be0a5df07de9eec57f5d2a86ed0204b2e75a": "Включен менеджер загрузок",
|
||||
"c33bd5392b39dbed36b8e5a1145163a15d45835f": "Разрешить выбор качества",
|
||||
"bda5508e24e0d77debb28bcd9194d8fefb1cfb92": "Режим только загрузки",
|
||||
"09d31c803a7252658694e1e3176b97f5655a3fe3": "Разрешить режим мультизагрузки",
|
||||
"d8b47221b5af9e9e4cd5cb434d76fc0c91611409": "",
|
||||
"f5ec7b2cdf87d41154f4fcbc86e856314409dcb9": "",
|
||||
"1c4dbce56d96b8974aac24a02f7ab2ee81415014": "Включить публичный API",
|
||||
"23bd81dcc30b74d06279a26d7a42e8901c1b124e": "Открытый ключ API",
|
||||
"41016a73d8ad85e6cb26dffa0a8fab9fe8f60d8e": "Просмотр документации",
|
||||
"1b258b258b4cc475ceb2871305b61756b0134f4a": "Создать",
|
||||
"d5d7c61349f3b0859336066e6d453fc35d334fe5": "Используйте API YouTube",
|
||||
"ce10d31febb3d9d60c160750570310f303a22c22": "Ключ API Youtube",
|
||||
"8602e313cdfa7c4cc475ccbe86459fce3c3fd986": "Сгенерировать ключ очень просто!",
|
||||
"9b3cedfa83c6d7acb3210953289d1be4aab115c7": "Нажмите здесь",
|
||||
"7f09776373995003161235c0c8d02b7f91dbc4df": "чтобы загрузить официальное расширение YoutubeDL-Material Chrome вручную.",
|
||||
"5b5296423906ab3371fdb2b5a5aaa83acaa2ee52": "Вы должны вручную загрузить расширение и изменить его настройки, чтобы установить URL-адрес фронтенда.",
|
||||
"9a2ec6da48771128384887525bdcac992632c863": "чтобы установить официальное расширение YoutubeDL-Material Firefox прямо со страницы расширений Firefox.",
|
||||
"eb81be6b49e195e5307811d1d08a19259d411f37": "Подробные инструкции по настройке.",
|
||||
"cb17ff8fe3961cf90f44bee97c88a3f3347a7e55": "Не требуется ничего особенного, кроме изменения настроек расширения для установки внешнего URL.",
|
||||
"61b81b11aad0b9d970ece2fce18405f07eac69c2": "Перетащите приведенную ниже ссылку в закладки, и все готово! Просто перейдите к видео YouTube, которое вы хотите загрузить, и нажмите на закладку.",
|
||||
"c505d6c5de63cc700f0aaf8a4b31fae9e18024e5": "Создать букмарклет \"только аудио\"",
|
||||
"d5f69691f9f05711633128b5a3db696783266b58": "Дополнительно",
|
||||
"5fab47f146b0a4b809dcebf3db9da94df6299ea1": "Использовать агент загрузки по умолчанию",
|
||||
"ec71e08aee647ea4a71fd6b7510c54d84a797ca6": "Выберите программу загрузки",
|
||||
"00e274c496b094a019f0679c3fab3945793f3335": "",
|
||||
"dc3d990391c944d1fbfc7cfb402f7b5e112fb3a8": "Разрешить расширенную загрузку",
|
||||
"431e5f3a0dde88768d1074baedd65266412b3f02": "Использование файлов cookie",
|
||||
"80651a7ad1229ea6613557d3559f702cfa5aecf5": "Установить Cookies",
|
||||
"bc2e854e111ecf2bd7db170da5e3c2ed08181d88": "Продвинутый",
|
||||
"37224420db54d4bc7696f157b779a7225f03ca9d": "Разрешить регистрацию пользователей",
|
||||
"4d13a9cd5ed3dcee0eab22cb25198d43886942be": "Пользователи",
|
||||
"eb3d5aefff38a814b76da74371cbf02c0789a1ef": "Журналы",
|
||||
"52c9a103b812f258bcddc3d90a6e3f46871d25fe": "Сохранить",
|
||||
"fe8fd36dbf5deee1d56564965787a782a66eba44": "{VAR_SELECT, select, true {Close} false {Cancel} other {otha}}",
|
||||
"cec82c0a545f37420d55a9b6c45c20546e82f94e": "О YoutubeDL-Material",
|
||||
"199c17e5d6a419313af3c325f06dcbb9645ca618": "это загрузчик YouTube с открытым исходным кодом, созданный в соответствии со спецификациями Material Design от Google. Вы можете легко загружать любимые видеоролики в виде видео- или аудиофайлов и даже подписываться на любимые каналы и плейлисты, чтобы быть в курсе их новых видео.",
|
||||
"bc0ad0ee6630acb7fcb7802ec79f5a0ee943c1a7": "включает в себя несколько потрясающих функций! Обширный API, поддержка Docker и поддержка локализации (перевода). Ознакомьтесь со всеми поддерживаемыми функциями, нажав на значок GitHub выше.",
|
||||
"a45e3b05f0529dc5246d70ef62304c94426d4c81": "Установленная версия:",
|
||||
"e22f3a5351944f3a1a10cfc7da6f65dfbe0037fe": "Проверяем обновления...",
|
||||
"a16e92385b4fd9677bb830a4b796b8b79c113290": "Доступно обновление",
|
||||
"189b28aaa19b3c51c6111ad039c4fd5e2a22e370": "Обновление можно выполнить в меню настроек.",
|
||||
"b33536f59b94ec935a16bd6869d836895dc5300c": "Нашли ошибку или у вас есть предложение?",
|
||||
"e1f398f38ff1534303d4bb80bd6cece245f24016": "чтобы создать проблему!",
|
||||
"42ff677ec14f111e88bd6cdd30145378e994d1bf": "Ваш профиль",
|
||||
"ac9d09de42edca1296371e4d801349c9096ac8de": "UID:",
|
||||
"a5ed099ffc9e96f6970df843289ade8a7d20ab9f": "Создан:",
|
||||
"fa96f2137af0a24e6d6d54c598c0af7d5d5ad344": "Вы не вошли в систему.",
|
||||
"6765b4c916060f6bc42d9bb69e80377dbcb5e4e9": "Логин",
|
||||
"bb694b49d408265c91c62799c2b3a7e3151c824d": "Выход из системы",
|
||||
"a1dbca87b9f36d2b06a5cbcffb5814c4ae9b798a": "Создайте учетную запись администратора",
|
||||
"2d2adf3ca26a676bca2269295b7455a26fd26980": "Учетная запись администратора по умолчанию не обнаружена. Это создаст и установит пароль для учетной записи администратора с именем пользователя 'admin'.",
|
||||
"70a67e04629f6d412db0a12d51820b480788d795": "Создать",
|
||||
"994363f08f9fbfa3b3994ff7b35c6904fdff18d8": "Профиль",
|
||||
"004b222ff9ef9dd4771b777950ca1d0e4cd4348a": "О",
|
||||
"92eee6be6de0b11c924e3ab27db30257159c0a7c": "Дом",
|
||||
"357064ca9d9ac859eb618e28e8126fa32be049e2": "Подписки",
|
||||
"822fab38216f64e8166d368b59fe756ca39d301b": "Загрузки",
|
||||
"a249a5ae13e0835383885aaf697d2890cc3e53e9": "Поделиться плейлистом",
|
||||
"15da89490e04496ca9ea1e1b3d44fb5efd4a75d9": "Поделиться видео",
|
||||
"1d540dcd271b316545d070f9d182c372d923aadd": "Поделиться аудиозаписью",
|
||||
"1f6d14a780a37a97899dc611881e6bc971268285": "Включить совместное использование",
|
||||
"6580b6a950d952df847cb3d8e7176720a740adc8": "Используйте метку времени",
|
||||
"4f2ed9e71a7c981db3e50ae2fedb28aff2ec4e6c": "Секунды",
|
||||
"3a6e5a6aa78ca864f6542410c5dafb6334538106": "Копировать в буфер обмена",
|
||||
"5b3075e8dc3f3921ec316b0bd83b6d14a06c1a4f": "Сохранить изменения",
|
||||
"4f8b2bb476981727ab34ed40fde1218361f92c45": "Подробности",
|
||||
"e9aff8e6df2e2bf6299ea27bb2894c70bc48bd4d": "Произошла ошибка:",
|
||||
"77b0c73840665945b25bd128709aa64c8f017e1c": "Начало загрузки:",
|
||||
"08ff9375ec078065bcdd7637b7ea65fce2979266": "Конец загрузки:",
|
||||
"ad127117f9471612f47d01eae09709da444a36a4": "Путь(и) к файлам:",
|
||||
"a9806cf78ce00eb2613eeca11354a97e033377b8": "Подписаться на плейлист или канал",
|
||||
"93efc99ae087fc116de708ecd3ace86ca237cf30": "URL-адрес списка воспроизведения или канала",
|
||||
"08f5d0ef937ae17feb1b04aff15ad88911e87baf": "Пользовательское имя",
|
||||
"ea30873bd3f0d5e4fb2378eec3f0a1db77634a28": "Загрузить все загруженные файлы",
|
||||
"28a678e9cabf86e44c32594c43fa0e890135c20f": "Скачать видео, загруженное за последние время",
|
||||
"c76a955642714b8949ff3e4b4990864a2e2cac95": "Только аудио режим",
|
||||
"408ca4911457e84a348cecf214f02c69289aa8f1": "Режим только для потокового вещания",
|
||||
"f432e1a8d6adb12e612127978ce2e0ced933959c": "Они добавляются после стандартных аргументов.",
|
||||
"98b6ec9ec138186d663e64770267b67334353d63": "Пользовательский вывод файлов",
|
||||
"d0336848b0c375a1c25ba369b3481ee383217a4f": "Подписаться",
|
||||
"e78c0d60ac39787f62c9159646fe0b3c1ed55a1d": "Тип:",
|
||||
"a44d86aa1e6c20ced07aca3a7c081d8db9ded1c6": "Архив:",
|
||||
"8efc77bf327659c0fec1f518cf48a98cdcd9dddf": "Экспорт архива",
|
||||
"3042bd3ad8dffcfeca5fd1ae6159fd1047434e95": "Отписаться от рассылки",
|
||||
"e2319dec5b4ccfb6ed9f55ccabd63650a8fdf547": "Ваши подписки",
|
||||
"807cf11e6ac1cde912496f764c176bdfdd6b7e19": "Каналы",
|
||||
"29b89f751593e1b347eef103891b7a1ff36ec03f": "Имя недоступно. Идет поиск канала.",
|
||||
"4636cd4a1379c50d471e98786098c4d39e1e82ad": "У вас нет подписок на каналы.",
|
||||
"2e0a410652cb07d069f576b61eab32586a18320d": "Имя недоступно. Идет поиск списка воспроизведения.",
|
||||
"587b57ced54965d8874c3fd0e9dfedb987e5df04": "У вас нет подписок на плейлисты.",
|
||||
"7e892ba15f2c6c17e83510e273b3e10fc32ea016": "Поиск",
|
||||
"2054791b822475aeaea95c0119113de3200f5e1c": "Длина:",
|
||||
"94e01842dcee90531caa52e4147f70679bac87fe": "Удаление и повторная загрузка",
|
||||
"2031adb51e07a41844e8ba7704b054e98345c9c1": "Удалить навсегда",
|
||||
"91ecce65f1d23f9419d1c953cd6b7bc7f91c110e": "Обновление",
|
||||
"1372e61c5bd06100844bd43b98b016aabc468f62": "Выберите версию:",
|
||||
"cfc2f436ec2beffb042e7511a73c89c372e86a6c": "Зарегистрироваться",
|
||||
"a1ad8b1be9be43b5183bd2c3186d4e19496f2a0b": "Идентификатор сессии:",
|
||||
"eb98135e35af26a9a326ee69bd8ff104d36dd8ec": "(текущий)",
|
||||
"7117fc42f860e86d983bfccfcf2654e5750f3406": "Загрузка недоступна!",
|
||||
"b7ff2e2b909c53abe088fe60b9f4b6ac7757247f": "Зарегистрировать пользователя",
|
||||
"024886ca34a6f309e3e51c2ed849320592c3faaa": "Имя пользователя",
|
||||
"2bd201aea09e43fbfd3cd15ec0499b6755302329": "Управление пользователями",
|
||||
"29c97c8e76763bb15b6d515648fa5bd1eb0f7510": "Идентификатор пользователя:",
|
||||
"e70e209561583f360b1e9cefd2cbb1fe434b6229": "Новый пароль",
|
||||
"6498fa1b8f563988f769654a75411bb8060134b9": "Установите новый пароль",
|
||||
"40da072004086c9ec00d125165da91eaade7f541": "",
|
||||
"4f20f2d5a6882190892e58b85f6ccbedfa737952": "Да",
|
||||
"3d3ae7deebc5949b0c1c78b9847886a94321d9fd": "Нет",
|
||||
"57c6c05d8ebf4ef1180c2705033c044f655bb2c4": "Управлять ролью",
|
||||
"746f64ddd9001ac456327cd9a3d5152203a4b93c": "Имя пользователя",
|
||||
"52c1447c1ec9570a2a3025c7e566557b8d19ed92": "Роль",
|
||||
"59a8c38db3091a63ac1cb9590188dc3a972acfb3": "Действия",
|
||||
"4d92a0395dd66778a931460118626c5794a3fc7a": "Добавить пользователей",
|
||||
"b0d7dd8a1b0349622d6e0c6e643e24a9ea0efa1d": "Редактировать роль",
|
||||
"fd59fb984749fcdb5e386ae85faec82f8e5ac098": "",
|
||||
"5009630cdf32ab4f1c78737b9617b8773512c05a": "Линии:",
|
||||
"56a2a773fbd5a6b9ac2e6b89d29d70a2ed0f3227": "Смотреть меньше.",
|
||||
"ddc31f2885b1b33a7651963254b0c197f2a64086": "См. подробнее.",
|
||||
"a0720c36ee1057e5c54a86591b722485c62d7b1a": "Перейти к подписке",
|
||||
"5656a06f17c24b2d7eae9c221567b209743829a9": "Открыть файл в новой вкладке",
|
||||
"ccf5ea825526ac490974336cb5c24352886abc07": "Открыть файл",
|
||||
"24dc3ecf7ec2c2144910c4f3d38343828be03a4c": "Автогенерируемый",
|
||||
"8a0bda4c47f10b2423ff183acefbf70d4ab52ea2": "Очистить журналы",
|
||||
"544e09cdc99a8978f48521d45f62db0da6dcf742": "Использовать роль по умолчанию",
|
||||
"632e8b20c98e8eec4059a605a4b011bb476137af": "Редактировать пользователя",
|
||||
"95b95a9c79e4fd9ed41f6855e37b3b06af25bcab": "Удалить пользователя",
|
||||
"b6c453e0e61faea184bbaf5c5b0a1e164f4de2a2": "Очистить все загрузки",
|
||||
"e01d54ecc1a0fcf9525a3c100ed8b83d94e61c23": "Фильтр поиска",
|
||||
"cfa67d14d84fe0e9fadf251dc51ffc181173b662": "База поиска",
|
||||
"080cc6abcba236390fc22e79792d0d3443a3bd2a": "Связать учетные данные",
|
||||
"f50fa6c09c8944aed504f6325f2913ee6c7a296a": "Привязать DN",
|
||||
"1db9789b93069861019bd0ccaa5d4706b00afc61": "URL-адрес LDAP",
|
||||
"e3d7c5f019e79a3235a28ba24df24f11712c7627": "LDAP",
|
||||
"4f56ced9d6b85aeb1d4346433361d47ea72dac1a": "Внутренний",
|
||||
"fa548cee6ea11c160a416cac3e6bdec0363883dc": "Метод авторизации",
|
||||
"db6c192032f4cab809aad35215f0aa4765761897": "Истечение срока действия логина",
|
||||
"0c43af932e6a4ee85500e28f01b3538b4eb27bc4": "Уровень журнала",
|
||||
"c776eb4992b6c98f58cd89b20c1ea8ac37888521": "Выберите агент загрузки",
|
||||
"5fb1e0083c9b2a40ac8ae7dcb2618311c291b8b9": "Автоматическая загрузка чата Twitch",
|
||||
"84ffcebac2709ca0785f4a1d5ba274433b5beabc": "Также известен как идентификатор клиента.",
|
||||
"8ae23bc4302a479f687f4b20a84c276182e2519c": "Ключ API Twitch",
|
||||
"d162f9fcd6a7187b391e004f072ab3da8377c47d": "Используйте API Twitch",
|
||||
"00a94f58d9eb2e3aa561440eabea616d0c937fa2": "Это удалит ваш старый ключ API!",
|
||||
"fb35145bfb84521e21b6385363d59221f436a573": "Убить все загрузки",
|
||||
"384de8f8f112c9e6092eb2698706d391553f3e8d": "Включить метаданные",
|
||||
"ffc19f32b1cba0daefc0e5668f89346db1db83ad": "Включить уменьшенное изображение",
|
||||
"04201f9d27abd7d6f58a4328ab98063ce1072006": "Категории",
|
||||
"ef418d4ece7c844f3a5e431da1aa59bedd88da7b": "Глобальные пользовательские аргументы",
|
||||
"1148fd45287ff09955b938756bc302042bcb29c7": "Путь является относительным по отношению к вышеуказанным путям загрузки. Не включайте расширение.",
|
||||
"cfe829634b1144bc44b6d38cf5584ea65db9804f": "Вывод файлов по умолчанию",
|
||||
"3d1a47dc18b7bd8b5d9e1eb44b235ed9c4a2b513": "Повторная загрузка свежих загрузок",
|
||||
"13759b09a7f4074ceee8fa2f968f9815fdf63295": "Иногда новые видео загружаются до полной обработки. Эта настройка означает, что новые видео будут проверяться на наличие версии более высокого качества на следующий день.",
|
||||
"348cc5d553b18e862eb1c1770e5636f6b05ba130": "Произошла ошибка",
|
||||
"4d8a18b04a1f785ecd8021ac824e0dfd5881dbfc": "Загрузка прошла успешно",
|
||||
"dad95154dcef3509b8cc705046061fd24994bbb7": "просмотры",
|
||||
"792dc6a57f28a1066db283f2e736484f066005fd": "Скачать Чат Twitch",
|
||||
"e4eeb9106dbcbc91ca1ac3fb4068915998a70f37": "Добавить новое правило",
|
||||
"2489eefea00931942b91f4a1ae109514b591e2e1": "Правила",
|
||||
"c3b0b86523f1d10e84a71f9b188d54913a11af3b": "Категория редактирования",
|
||||
"07db550ae114d9faad3a0cbb68bcc16ab6cd31fc": "Приостановлено",
|
||||
"3697f8583ea42868aa269489ad366103d94aece7": "Редактирование",
|
||||
"73423607944a694ce6f9e55cfee329681bb4d9f9": "Видео не найдено.",
|
||||
"d02888c485d3aeab6de628508f4a00312a722894": "Мои видео",
|
||||
"29376982b1205d9d6ea3d289e8e2f8e1ac2839b1": "Обратный порядок",
|
||||
"33026f57ea65cd9c8a5d917a08083f71a718933a": "Обычный порядок",
|
||||
"5caadefa4143cf6766a621b0f54f91f373a1f164": "Добавить содержание",
|
||||
"a8b7b9c168fd936a75e500806a8c0d7755ef1198": "ПРИМЕЧАНИЕ: Загрузка новых файлов cookie отменяет предыдущие файлы cookie. Также обратите внимание, что файлы cookie используются в масштабах всего экземпляра, а не каждого пользователя.",
|
||||
"0cc1dec590ecd74bef71a865fb364779bc42a749": "Категория:",
|
||||
"303e45ffae995c9817e510e38cb969e6bb3adcbf": "(Пауза)",
|
||||
"d641b8fa5ac5e85114c733b1f7de6976bd091f70": "Максимальное качество",
|
||||
"2d1ea268a6a9f483dbc2cbfe19bf4256a57a6af4": "Видео",
|
||||
"f0baeb8b69d120073b6d60d34785889b0c3232c8": "Аудио",
|
||||
"f61c6867295f3b53d23557021f2f4e0aa1d0b8fc": "Тип"
|
||||
}
|
||||
2589
src/assets/i18n/messages.ru.xlf
Normal file
2589
src/assets/i18n/messages.ru.xlf
Normal file
File diff suppressed because it is too large
Load Diff
@@ -113,7 +113,7 @@
|
||||
"61b81b11aad0b9d970ece2fce18405f07eac69c2": "只需将下面的链接拖放到书签栏中。在YouTube页面上您只需单击书签即可下载视频。",
|
||||
"c505d6c5de63cc700f0aaf8a4b31fae9e18024e5": "生成“仅音频”书签",
|
||||
"d5f69691f9f05711633128b5a3db696783266b58": "额外",
|
||||
"5fab47f146b0a4b809dcebf3db9da94df6299ea1": "使用默认下载代理",
|
||||
"5fab47f146b0a4b809dcebf3db9da94df6299ea1": "使用默认下载程序",
|
||||
"ec71e08aee647ea4a71fd6b7510c54d84a797ca6": "选择下载器",
|
||||
"00e274c496b094a019f0679c3fab3945793f3335": "选择日志级别",
|
||||
"dc3d990391c944d1fbfc7cfb402f7b5e112fb3a8": "开启高级下载选项",
|
||||
@@ -124,7 +124,7 @@
|
||||
"4d13a9cd5ed3dcee0eab22cb25198d43886942be": "用户",
|
||||
"eb3d5aefff38a814b76da74371cbf02c0789a1ef": "日志",
|
||||
"52c9a103b812f258bcddc3d90a6e3f46871d25fe": "保存",
|
||||
"fe8fd36dbf5deee1d56564965787a782a66eba44": "{VAR_SELECT, select, true {关} false {取消} }",
|
||||
"fe8fd36dbf5deee1d56564965787a782a66eba44": "{VAR_SELECT, select, true {关} false {取消} other {其他} }",
|
||||
"cec82c0a545f37420d55a9b6c45c20546e82f94e": "关于 YoutubeDL-Material",
|
||||
"199c17e5d6a419313af3c325f06dcbb9645ca618": "是根据Google的Material Design规范构建的开源YouTube下载器。您可以将喜欢的视频下载为视频或音频文件,并且可以订阅喜欢的频道和播放列表,以便及时下载他们的新视频。",
|
||||
"bc0ad0ee6630acb7fcb7802ec79f5a0ee943c1a7": "包含很多很棒的功能!支持API,Docker和本地化。在Github上查找所有受支持的功能。",
|
||||
@@ -238,5 +238,32 @@
|
||||
"cfa67d14d84fe0e9fadf251dc51ffc181173b662": "搜索起点",
|
||||
"544e09cdc99a8978f48521d45f62db0da6dcf742": "使用角色预设",
|
||||
"3697f8583ea42868aa269489ad366103d94aece7": "编辑中",
|
||||
"fb35145bfb84521e21b6385363d59221f436a573": "取消所有下载"
|
||||
"fb35145bfb84521e21b6385363d59221f436a573": "取消所有下载",
|
||||
"56a2a773fbd5a6b9ac2e6b89d29d70a2ed0f3227": "查看更少",
|
||||
"c776eb4992b6c98f58cd89b20c1ea8ac37888521": "选择一个下载程序",
|
||||
"d641b8fa5ac5e85114c733b1f7de6976bd091f70": "最高画质",
|
||||
"ddc31f2885b1b33a7651963254b0c197f2a64086": "查看更多...",
|
||||
"5fb1e0083c9b2a40ac8ae7dcb2618311c291b8b9": "自动下载Twitch弹幕",
|
||||
"84ffcebac2709ca0785f4a1d5ba274433b5beabc": "也称为客户ID",
|
||||
"8ae23bc4302a479f687f4b20a84c276182e2519c": "Twitch API 密钥",
|
||||
"d162f9fcd6a7187b391e004f072ab3da8377c47d": "使用Twitch API",
|
||||
"04201f9d27abd7d6f58a4328ab98063ce1072006": "分类",
|
||||
"ef418d4ece7c844f3a5e431da1aa59bedd88da7b": "全局自定义变量",
|
||||
"1148fd45287ff09955b938756bc302042bcb29c7": "路径相对于上述下载路径,不包括扩展名。",
|
||||
"cfe829634b1144bc44b6d38cf5584ea65db9804f": "默认输出文件夹",
|
||||
"3d1a47dc18b7bd8b5d9e1eb44b235ed9c4a2b513": "重新下载新上传的内容",
|
||||
"13759b09a7f4074ceee8fa2f968f9815fdf63295": "有时新视频会在完全处理前下载。这项设置指新视频会在第二天检查视频是否有更高画质。",
|
||||
"dad95154dcef3509b8cc705046061fd24994bbb7": "浏览",
|
||||
"792dc6a57f28a1066db283f2e736484f066005fd": "下载Twitch弹幕",
|
||||
"e4eeb9106dbcbc91ca1ac3fb4068915998a70f37": "添加新规则",
|
||||
"2489eefea00931942b91f4a1ae109514b591e2e1": "规则",
|
||||
"c3b0b86523f1d10e84a71f9b188d54913a11af3b": "编辑类别",
|
||||
"07db550ae114d9faad3a0cbb68bcc16ab6cd31fc": "暂停",
|
||||
"73423607944a694ce6f9e55cfee329681bb4d9f9": "找不到视频",
|
||||
"29376982b1205d9d6ea3d289e8e2f8e1ac2839b1": "倒序",
|
||||
"33026f57ea65cd9c8a5d917a08083f71a718933a": "正序",
|
||||
"5caadefa4143cf6766a621b0f54f91f373a1f164": "添加内容",
|
||||
"0cc1dec590ecd74bef71a865fb364779bc42a749": "类别:",
|
||||
"303e45ffae995c9817e510e38cb969e6bb3adcbf": "(暂停)",
|
||||
"24dc3ecf7ec2c2144910c4f3d38343828be03a4c": "自动生成的"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user