Compare commits

..

35 Commits

Author SHA1 Message Date
Isaac Abadi
1c6b7815fe Login tabs now fill the entire component
Expanded widths of inputs on login page to 100%
2022-07-06 23:31:41 -04:00
Isaac Abadi
24475386f9 Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material 2022-07-01 00:55:03 -04:00
Isaac Abadi
55268301f6 Removed config env vars set message if no items were set 2022-07-01 00:54:38 -04:00
Isaac Abadi
faa76abbbd Fixed issue where setting resolution in a sub would instead require that resolution to exist (#678 and #330) 2022-07-01 00:51:30 -04:00
Glassed Silver
b827f8f0cc Merge pull request #687 from Tzahi12345/add-permissions-for-tasks-manager
Add permissions for tasks manager
2022-06-30 16:34:08 +02:00
Glassed Silver
b6b61c42d4 Merge pull request #677 from weblate/weblate-youtubedl-material-ytdl-material
Translations update from Hosted Weblate
2022-06-30 16:22:45 +02:00
Glassed Silver
6af1ce4092 Merge pull request #683 from adripo/patch-1
fix: #682 install tzdata
2022-06-30 16:20:40 +02:00
Glassed Silver
303d0015c6 Merge pull request #688 from adripo/patch-2
fix: remove exposed ports for mongo
2022-06-30 16:04:45 +02:00
adripo
56db43da79 fix: remove exposed ports for mongo
exposed ports between services in the same stack is not needed
2022-06-30 13:11:21 +02:00
Isaac Abadi
64b1a9e5c0 Updated mangled translations
Improved automatic translations command
2022-06-30 01:34:52 -04:00
Isaac Abadi
48f0a700ab Paginator is now always visible to avoid case where file type filter permanently disappears 2022-06-30 01:30:06 -04:00
Isaac Abadi
768798c6b3 Fixed issue where one-off playlist downlaods would only include the first video 2022-06-30 01:29:18 -04:00
Isaac Abadi
9d1f93acfb Added translations for manage-role component 2022-06-30 00:44:11 -04:00
Isaac Abadi
077a0d8fdb Added migration to add tasks manager permission for admin role
All routes are now properly protected against logged in users w/o permissions
2022-06-30 00:43:57 -04:00
Tzahi12345
c9359f172e Merge pull request #681 from adripo/node-config-fix
fix: node-config fix environment variable
2022-06-29 23:46:45 -04:00
Tzahi12345
d6dc4756a7 Merge pull request #680 from adripo/remove-container-config-env
fix: remove write_ytdl_config
2022-06-29 23:44:50 -04:00
adripo
9bc9b17294 fix: #682 install tzdata 2022-06-29 23:52:25 +02:00
adripo
80d3580447 fix: node-config fix environment variable 2022-06-29 20:20:27 +02:00
adripo
3f15f3bcaf fix: remove env variable check 2022-06-29 19:44:13 +02:00
Tzahi12345
703848e4e5 Merge pull request #675 from FoxxMD/unknownFormatsFix
Fixed parsing expected file size for videos with no known formats
2022-06-29 11:31:32 -04:00
Sebastian Danielsson
934965720e Translated using Weblate (Swedish)
Currently translated at 31.4% (120 of 381 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/sv/
2022-06-29 17:17:36 +02:00
AbsurdUsername
bb4a882d19 Translated using Weblate (Italian)
Currently translated at 100.0% (381 of 381 strings)

Translation: YoutubeDL-Material/ytdl-material
Translate-URL: https://hosted.weblate.org/projects/youtubedl-material/ytdl-material/it/
2022-06-29 17:17:36 +02:00
FoxxMD
74315b8c76 Fixed parsing expected file size for videos with no known formats 2022-06-29 09:37:26 -04:00
Glassed Silver
a9e95c5bb8 Merge pull request #672 from weblate/weblate-youtubedl-material-ytdl-material
Translations update from Hosted Weblate
2022-06-28 07:20:04 +02:00
Sebastian
fe45a889c9 Added translation using Weblate (Swedish) 2022-06-27 22:14:59 +02:00
Isaac Abadi
e726e991cc Partially reverted fecefde3ad 2022-06-27 13:12:53 -04:00
Isaac Abadi
940267651d Hotfix for bug where a sub with missing videos would cause a crash during migration #670 2022-06-27 03:52:05 -04:00
Isaac Abadi
2dc68139f7 Streaming-only subs are now actually paused
DB transfers in any direction now generate backups and associated logs are set to info
2022-06-27 00:08:41 -04:00
Isaac Abadi
301451d021 Fixed issue where tasks would not initialize properly after migration until server restart
streaming-only subscriptions now autopause due to deprecated
2022-06-26 23:47:03 -04:00
Isaac Abadi
a7f8795e7e Fixed missing latest tag on docker-release 2022-06-26 23:10:47 -04:00
Isaac Abadi
162094a9b9 File type filter now only appears with the paginator 2022-06-26 23:06:15 -04:00
Isaac Abadi
e843b4c97f Fixed crash during docker-release action 2022-06-26 22:07:21 -04:00
Isaac Abadi
c784091ad6 Fixed issue where docker-release action couldn't accept inputs (2) 2022-06-26 22:04:42 -04:00
Isaac Abadi
fb404d3cee Fixed issue where docker-release action couldn't accept inputs 2022-06-26 22:02:23 -04:00
Tzahi12345
68c2ee26ff Merge pull request #657 from Tzahi12345/4.3-prep
4.3 Prep
2022-06-26 21:13:51 -04:00
22 changed files with 4098 additions and 81 deletions

View File

@@ -36,7 +36,7 @@ jobs:
- name: Set image tag
id: tags
run: |
if [ ${{ github.event.action }} == "workflow_dispatch" ]; then
if [ "${{ github.event.inputs.tags }}" != "" ]; then
echo "::set-output name=tags::${{ github.event.inputs.tags }}"
elif [ ${{ github.event.action }} == "release" ]; then
echo "::set-output name=tags::${{ github.event.release.tag_name }}"
@@ -53,8 +53,8 @@ jobs:
${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }}
ghcr.io/${{ github.repository_owner }}/${{ secrets.DOCKERHUB_REPO }}
tags: |
raw=${{ steps.tags.outputs.tags }}
raw=latest
type=raw,value=${{ steps.tags.outputs.tags }}
type=raw,value=latest
- name: setup platform emulator
uses: docker/setup-qemu-action@v1

View File

@@ -9,15 +9,16 @@ RUN sh ./ffmpeg-fetch.sh
# Create our Ubuntu 22.04 with node 16
# Go to 20.04
FROM ubuntu:20.04 AS base
ENV DEBIAN_FRONTEND=noninteractive
ARG DEBIAN_FRONTEND=noninteractive
ENV UID=1000
ENV GID=1000
ENV USER=youtube
ENV NO_UPDATE_NOTIFIER=true
ENV PM2_HOME=/app/pm2
ENV ALLOW_CONFIG_MUTATIONS=true
RUN groupadd -g $GID $USER && useradd --system -m -g $USER --uid $UID $USER && \
apt update && \
apt install -y --no-install-recommends curl ca-certificates && \
apt install -y --no-install-recommends curl ca-certificates tzdata && \
curl -fsSL https://deb.nodesource.com/setup_16.x | bash - && \
apt install -y --no-install-recommends nodejs && \
npm -g install npm && \
@@ -47,7 +48,8 @@ RUN npm config set strict-ssl false && \
# Final image
FROM base
RUN apt update && \
RUN npm install -g pm2 && \
apt update && \
apt install -y --no-install-recommends gosu python3-minimal python-is-python3 python3-pip atomicparsley && \
apt clean && \
rm -rf /var/lib/apt/lists/*

View File

@@ -68,7 +68,8 @@ db.defaults(
configWriteFlag: false,
downloads: {},
subscriptions: [],
files_to_db_migration_complete: false
files_to_db_migration_complete: false,
tasks_manager_role_migration_complete: false
}).write();
users_db.defaults(
@@ -147,16 +148,11 @@ if (fs.existsSync('version.json')) {
// don't overwrite config if it already happened.. NOT
// let alreadyWritten = db.get('configWriteFlag').value();
let writeConfigMode = process.env.write_ytdl_config;
// checks if config exists, if not, a config is auto generated
config_api.configExistsCheck();
if (writeConfigMode) {
setAndLoadConfig();
} else {
loadConfig();
}
setAndLoadConfig();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
@@ -187,13 +183,22 @@ async function checkMigrations() {
if (!new_db_system_migration_complete) {
logger.info('Beginning migration: 4.2->4.3+')
let success = await db_api.importJSONToDB(db.value(), users_db.value());
await tasks_api.setupTasks(); // necessary as tasks were not properly initialized at first
// sets migration to complete
db.set('new_db_system_migration_complete', true).write();
if (success) { logger.info('4.2->4.3+ migration complete!'); }
else { logger.error('Migration failed: 4.2->4.3+'); }
}
const tasks_manager_role_migration_complete = db.get('tasks_manager_role_migration_complete').value();
if (!tasks_manager_role_migration_complete) {
logger.info('Checking if tasks manager role permissions exist for admin user...');
const success = await auth_api.changeRolePermissions('admin', 'tasks_manager', 'yes');
if (success) logger.info('Task manager permissions check complete!');
else logger.error('Failed to auto add tasks manager permissions to admin role!');
db.set('tasks_manager_role_migration_complete', true).write();
}
return true;
}
@@ -483,8 +488,9 @@ async function setAndLoadConfig() {
}
async function setConfigFromEnv() {
let config_items = getEnvConfigItems();
let success = config_api.setConfigItems(config_items);
const config_items = getEnvConfigItems();
if (!config_items || config_items.length === 0) return true;
const success = config_api.setConfigItems(config_items);
if (success) {
logger.info('Config items set using ENV variables.');
await utils.wait(100);
@@ -575,7 +581,11 @@ async function watchSubscriptions() {
if (!subscriptions) return;
const valid_subscriptions = subscriptions.filter(sub => !sub.paused);
// auto pause deprecated streamingOnly mode
const streaming_only_subs = subscriptions.filter(sub => sub.streamingOnly);
subscriptions_api.updateSubscriptionPropertyMultiple(streaming_only_subs, {paused: true});
const valid_subscriptions = subscriptions.filter(sub => !sub.paused && !sub.streamingOnly);
let subscriptions_amount = valid_subscriptions.length;
let delay_interval = calculateSubcriptionRetrievalDelay(subscriptions_amount);

View File

@@ -361,7 +361,6 @@ exports.userHasPermission = async function(user_uid, permission) {
logger.error('Invalid role ' + role);
return false;
}
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);
@@ -376,7 +375,8 @@ exports.userHasPermission = async function(user_uid, permission) {
}
// no overrides, let's check if the role has the permission
if (role_permissions.includes(permission)) {
const role_has_permission = await exports.roleHasPermissions(role, permission);
if (role_has_permission) {
return true;
} else {
logger.verbose(`User ${user_uid} failed to get permission ${permission}`);
@@ -384,6 +384,16 @@ exports.userHasPermission = async function(user_uid, permission) {
}
}
exports.roleHasPermissions = async function(role, permission) {
const role_obj = await db_api.getRecord('roles', {key: role})
if (!role) {
logger.error(`Role ${role} does not exist!`);
}
const role_permissions = role_obj['permissions'];
if (role_permissions && role_permissions.includes(permission)) return true;
else return false;
}
exports.userPermissions = async function(user_uid) {
let user_permissions = [];
const user_obj = await db_api.getRecord('users', ({uid: user_uid}));

View File

@@ -221,7 +221,8 @@ exports.AVAILABLE_PERMISSIONS = [
'subscriptions',
'sharing',
'advanced_download',
'downloads_manager'
'downloads_manager',
'tasks_manager'
];
exports.DETAILS_BIN_PATH = 'node_modules/youtube-dl/bin/details'

View File

@@ -198,7 +198,7 @@ async function registerFileDBManual(file_object) {
path_object = path.parse(file_object['path']);
file_object['path'] = path.format(path_object);
exports.insertRecordIntoTable('files', file_object, {path: file_object['path']})
await exports.insertRecordIntoTable('files', file_object, {path: file_object['path']})
return file_object;
}
@@ -925,6 +925,7 @@ exports.importJSONToDB = async (db_json, users_json) => {
const createFilesRecords = (files, subscriptions) => {
for (let i = 0; i < subscriptions.length; i++) {
const subscription = subscriptions[i];
if (!subscription['videos']) continue;
subscription['videos'] = subscription['videos'].map(file => ({ ...file, sub_id: subscription['id'], user_uid: subscription['user_uid'] ? subscription['user_uid'] : undefined}));
files = files.concat(subscriptions[i]['videos']);
}
@@ -985,7 +986,7 @@ exports.backupDB = async () => {
const backup_file_name = `${using_local_db ? 'local' : 'remote'}_db.json.${Date.now()/1000}.bak`;
const path_to_backups = path.join(backup_dir, backup_file_name);
logger.verbose(`Backing up ${using_local_db ? 'local' : 'remote'} DB to ${path_to_backups}`);
logger.info(`Backing up ${using_local_db ? 'local' : 'remote'} DB to ${path_to_backups}`);
const table_to_records = {};
for (let i = 0; i < tables_list.length; i++) {
@@ -1032,10 +1033,11 @@ exports.transferDB = async (local_to_remote) => {
table_to_records[table] = await exports.getRecords(table);
}
logger.info('Backup up DB...');
await exports.backupDB(); // should backup always
using_local_db = !local_to_remote;
if (local_to_remote) {
logger.debug('Backup up DB...');
await exports.backupDB();
const db_connected = await exports.connectToDB(5, true);
if (!db_connected) {
logger.error('Failed to transfer database - could not connect to MongoDB. Verify that your connection URL is valid.');

View File

@@ -203,6 +203,7 @@ async function collectInfo(download_uid) {
options.customOutput = category['custom_output'];
options.noRelativePath = true;
args = await exports.generateArgs(url, type, options, download['user_uid']);
args = utils.filterArgs(args, ['--no-simulate']);
info = await exports.getVideoInfoByURL(url, args, download_uid);
}
@@ -356,7 +357,7 @@ async function downloadQueuedFile(download_uid) {
if (file_objs.length > 1) {
// create playlist
const playlist_name = file_objs.map(file_obj => file_obj.title).join(', ');
container = await db_api.createPlaylist(playlist_name, file_objs.map(file_obj => file_obj.uid), type, download['user_uid']);
container = await db_api.createPlaylist(playlist_name, file_objs.map(file_obj => file_obj.uid), download['user_uid']);
} else if (file_objs.length === 1) {
container = file_objs[0];
} else {
@@ -402,6 +403,8 @@ exports.generateArgs = async (url, type, options, user_uid = null, simulated = f
// video-specific args
const selectedHeight = options.selectedHeight;
const maxHeight = options.maxHeight;
const heightParam = selectedHeight || maxHeight;
// audio-specific args
const maxBitrate = options.maxBitrate;
@@ -422,8 +425,8 @@ exports.generateArgs = async (url, type, options, user_uid = null, simulated = f
} else {
if (customQualityConfiguration) {
qualityPath = ['-f', customQualityConfiguration, '--merge-output-format', 'mp4'];
} else if (selectedHeight && selectedHeight !== '' && !is_audio) {
qualityPath = ['-f', `'(mp4)[height=${selectedHeight}]`];
} else if (heightParam && heightParam !== '' && !is_audio) {
qualityPath = ['-f', `'(mp4)[height${maxHeight ? '<' : ''}=${heightParam}]`];
} else if (is_audio) {
qualityPath = ['--audio-quality', maxBitrate ? maxBitrate : '0']
}
@@ -506,7 +509,10 @@ exports.generateArgs = async (url, type, options, user_uid = null, simulated = f
}
if (default_downloader === 'yt-dlp') {
downloadConfig.push('--no-clean-infojson');
downloadConfig = utils.filterArgs(downloadConfig, ['--print-json']);
// in yt-dlp -j --no-simulate is preferable
downloadConfig.push('--no-clean-info-json', '-j', '--no-simulate');
}
}

View File

@@ -45,7 +45,6 @@
"passport-jwt": "^4.0.0",
"passport-ldapauth": "^3.0.1",
"passport-local": "^1.0.0",
"pm2": "^5.2.0",
"progress": "^2.0.3",
"ps-node": "^0.1.6",
"read-last-lines": "^1.7.2",

View File

@@ -313,7 +313,7 @@ function generateOptionsForSubscriptionDownload(sub, user_uid) {
let default_output = config_api.getConfigItem('ytdl_default_file_output') ? config_api.getConfigItem('ytdl_default_file_output') : '%(title)s';
const base_download_options = {
selectedHeight: sub.maxQuality && sub.maxQuality !== 'best' ? sub.maxQuality : null,
maxHeight: sub.maxQuality && sub.maxQuality !== 'best' ? sub.maxQuality : null,
customFileFolderPath: getAppendedBasePath(sub, basePath),
customOutput: sub.custom_output ? `${sub.custom_output}` : `${default_output}`,
customArchivePath: path.join(basePath, 'archives', sub.name),
@@ -407,7 +407,7 @@ async function generateArgsForSubscription(sub, user_uid, redownload = false, de
const default_downloader = utils.getCurrentDownloader() || config_api.getConfigItem('ytdl_default_downloader');
if (default_downloader === 'yt-dlp') {
downloadConfig.push('--no-clean-infojson');
downloadConfig.push('--no-clean-info-json');
}
downloadConfig = utils.filterArgs(downloadConfig, ['--write-comments']);
@@ -458,7 +458,7 @@ async function updateSubscription(sub) {
async function updateSubscriptionPropertyMultiple(subs, assignment_obj) {
subs.forEach(async sub => {
await updateSubscriptionProperty(sub, assignment_obj, sub.user_uid);
await updateSubscriptionProperty(sub, assignment_obj);
});
}

View File

@@ -172,11 +172,13 @@ function getExpectedFileSize(input_info_jsons) {
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 || available_format.filesize_approx)) {
if (info_json.formats !== undefined) {
info_json.formats.forEach(available_format => {
if (available_format.format_id === format_id && (available_format.filesize || available_format.filesize_approx)) {
individual_expected_filesize += (available_format.filesize ? available_format.filesize : available_format.filesize_approx);
}
});
}
});
}
});
expected_filesize += individual_expected_filesize;
});
@@ -210,7 +212,7 @@ function deleteJSONFile(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';
@@ -331,9 +333,9 @@ function createEdgeNGrams(str) {
if (str && str.length > 3) {
const minGram = 3
const maxGram = str.length
return str.split(" ").reduce((ngrams, token) => {
if (token.length > minGram) {
if (token.length > minGram) {
for (let i = minGram; i <= maxGram && i <= token.length; ++i) {
ngrams = [...ngrams, token.substr(0, i)]
}
@@ -343,7 +345,7 @@ function createEdgeNGrams(str) {
return ngrams
}, []).join(" ")
}
return str
}
@@ -459,7 +461,7 @@ function injectArgs(original_args, new_args) {
for (let i = 0; i < new_args.length; i++) {
const new_arg = new_args[i];
if (!new_arg.startsWith('-') && !new_arg.startsWith('--') && i > 0 && original_args.includes(new_args[i - 1])) continue;
if (CONSTS.YTDL_ARGS_WITH_VALUES.has(new_arg)) {
if (original_args.includes(new_arg)) {
const original_index = original_args.indexOf(new_arg);

View File

@@ -2,7 +2,6 @@ version: "2"
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'
@@ -20,11 +19,9 @@ services:
image: tzahi12345/youtubedl-material:latest
ytdl-mongo-db:
image: mongo
ports:
- "27017:27017"
logging:
driver: "none"
container_name: mongo-db
restart: always
volumes:
- ./db/:/data/db
- ./db/:/data/db

View File

@@ -13,7 +13,7 @@
"e2e": "ng e2e",
"electron": "ng build --base-href ./ && electron .",
"generate": "openapi --input ./\"Public API v1.yaml\" --output ./src/api-types --exportCore false --exportServices false --exportModels true",
"i18n-source": "ng extract-i18n --output-path=src/assets/i18n"
"i18n-source": "ng extract-i18n --output-path=src/assets/i18n --out-file=messages.en.xlf"
},
"engines": {
"node": "12.3.1",

View File

@@ -44,7 +44,7 @@
<a *ngIf="postsService.config && postsService.config.Advanced.multi_user_mode && !postsService.isLoggedIn" mat-list-item (click)="sidenav.close()" routerLink='/login'><ng-container i18n="Navigation menu Login Page title">Login</ng-container></a>
<a *ngIf="postsService.config && allowSubscriptions && postsService.hasPermission('subscriptions')" mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" routerLink='/subscriptions'><ng-container i18n="Navigation menu Subscriptions Page title">Subscriptions</ng-container></a>
<a *ngIf="postsService.config && enableDownloadsManager && postsService.hasPermission('downloads_manager')" mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" routerLink='/downloads'><ng-container i18n="Navigation menu Downloads Page title">Downloads</ng-container></a>
<a *ngIf="postsService.config && enableDownloadsManager && postsService.hasPermission('downloads_manager')" mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" routerLink='/tasks'><ng-container i18n="Navigation menu Tasks Page title">Tasks</ng-container></a>
<a *ngIf="postsService.config && postsService.hasPermission('tasks_manager')" mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" routerLink='/tasks'><ng-container i18n="Navigation menu Tasks Page title">Tasks</ng-container></a>
<ng-container *ngIf="postsService.config && postsService.hasPermission('settings')">
<mat-divider></mat-divider>
<a mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" routerLink='/settings'><ng-container i18n="Settings menu label">Settings</ng-container></a>

View File

@@ -53,9 +53,9 @@ export class CustomPlaylistsComponent implements OnInit {
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.getAllPlaylists();
this.postsService.openSnackBar($localize`Successfully created playlist!', '`);
this.postsService.openSnackBar($localize`Successfully created playlist!`);
} else if (result === false) {
this.postsService.openSnackBar($localize`ERROR: failed to create playlist!', '`);
this.postsService.openSnackBar($localize`ERROR: failed to create playlist!`);
}
});
}
@@ -75,6 +75,7 @@ export class CustomPlaylistsComponent implements OnInit {
}
} else {
// playlist not found
// TODO: Make translatable
console.error(`Playlist with ID ${playlistID} not found!`);
}
}
@@ -96,7 +97,7 @@ export class CustomPlaylistsComponent implements OnInit {
this.postsService.removePlaylist(playlistID).subscribe(res => {
if (res['success']) {
this.playlists.splice(index, 1);
this.postsService.openSnackBar($localize`Playlist successfully removed.', '`);
this.postsService.openSnackBar($localize`Playlist successfully removed.`);
}
this.getAllPlaylists();
});

View File

@@ -1,30 +1,30 @@
<mat-card class="login-card">
<mat-tab-group style="margin-bottom: 20px" [(selectedIndex)]="selectedTabIndex">
<mat-tab-group mat-stretch-tabs style="margin-bottom: 20px" [(selectedIndex)]="selectedTabIndex">
<mat-tab label="Login" i18n-label="Login">
<div style="margin-top: 10px;">
<mat-form-field>
<mat-form-field style="width: 100%">
<input [(ngModel)]="loginUsernameInput" matInput placeholder="User name" i18n-placeholder="User name">
</mat-form-field>
</div>
<div>
<mat-form-field>
<mat-form-field style="width: 100%">
<input [(ngModel)]="loginPasswordInput" (keyup.enter)="login()" type="password" matInput placeholder="Password" i18n-placeholder="Password">
</mat-form-field>
</div>
</mat-tab>
<mat-tab *ngIf="registrationEnabled" label="Register" i18n-label="Register">
<div style="margin-top: 10px;">
<mat-form-field>
<mat-form-field style="width: 100%">
<input [(ngModel)]="registrationUsernameInput" matInput placeholder="User name" i18n-placeholder="User name">
</mat-form-field>
</div>
<div>
<mat-form-field>
<mat-form-field style="width: 100%">
<input [(ngModel)]="registrationPasswordInput" (click)="register()" type="password" matInput placeholder="Password" i18n-placeholder="Password">
</mat-form-field>
</div>
<div>
<mat-form-field>
<mat-form-field style="width: 100%">
<input [(ngModel)]="registrationPasswordConfirmationInput" (click)="register()" type="password" matInput placeholder="Confirm Password" i18n-placeholder="Confirm Password">
</mat-form-field>
</div>

View File

@@ -14,12 +14,13 @@ export class ManageRoleComponent implements OnInit {
permissions = null;
permissionToLabel = {
'filemanager': 'File manager',
'settings': 'Settings access',
'subscriptions': 'Subscriptions',
'sharing': 'Share files',
'advanced_download': 'Use advanced download mode',
'downloads_manager': 'Use downloads manager'
'filemanager': $localize`File manager`,
'settings': $localize`Settings access`,
'subscriptions': $localize`Subscriptions`,
'sharing': $localize`Share files`,
'advanced_download': $localize`Use advanced download mode`,
'downloads_manager': $localize`Use downloads manager`,
'tasks_manager': $localize`Use tasks manager`,
}
constructor(public postsService: PostsService, private dialogRef: MatDialogRef<ManageRoleComponent>,

View File

@@ -106,7 +106,7 @@
</mat-select>
</mat-form-field>
</div>
<mat-paginator class="paginator" #paginator *ngIf="paged_data && paged_data.length > 0" (page)="pageChangeEvent($event)" [length]="file_count"
<mat-paginator class="paginator" #paginator (page)="pageChangeEvent($event)" [length]="file_count"
[pageSize]="pageSize"
[pageSizeOptions]="[5, 10, 25, 100, this.paged_data && this.paged_data.length > 100 ? this.paged_data.length : 250]">
</mat-paginator>

View File

@@ -306,7 +306,7 @@ export class MainComponent implements OnInit {
this.downloadingfile = false;
if (!this.autoplay && !this.downloadOnlyMode && !navigate_mode) {
// do nothing
this.reloadRecentVideos();
this.reloadRecentVideos(is_playlist);
} else {
// if download only mode, just download the file. no redirect
if (force_view === false && this.downloadOnlyMode && !this.iOS) {
@@ -315,7 +315,7 @@ export class MainComponent implements OnInit {
} else {
this.downloadFileFromServer(container as DatabaseFile, type);
}
this.reloadRecentVideos();
this.reloadRecentVideos(is_playlist);
} else {
localStorage.setItem('player_navigator', this.router.url.split(';')[0]);
if (is_playlist) {
@@ -780,8 +780,9 @@ export class MainComponent implements OnInit {
});
}
reloadRecentVideos(): void {
reloadRecentVideos(is_playlist = false): void {
this.postsService.files_changed.next(true);
if (is_playlist) this.postsService.playlists_changed.next(true);
}
getURLArray(url_str: string): Array<string> {

View File

@@ -4,7 +4,7 @@ import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
import { THEMES_CONFIG } from '../themes';
import { Router, CanActivate } from '@angular/router';
import { Router, CanActivate, ActivatedRouteSnapshot } from '@angular/router';
import { DOCUMENT } from '@angular/common';
import { BehaviorSubject } from 'rxjs';
import { MatSnackBar } from '@angular/material/snack-bar';
@@ -229,12 +229,15 @@ export class PostsService implements CanActivate {
}
}
canActivate(route, state): Promise<boolean> {
return new Promise(resolve => {
resolve(true);
})
console.log(route);
throw new Error('Method not implemented.');
canActivate(route: ActivatedRouteSnapshot, state): Promise<boolean> {
const PATH_TO_REQUIRED_PERM = {
settings: 'settings',
subscriptions: 'subscriptions',
downloads: 'downloads_manager',
tasks: 'tasks_manager'
}
const required_perm = PATH_TO_REQUIRED_PERM[route.routeConfig.path];
return required_perm ? this.hasPermission(required_perm) : true;
}
setTheme(theme) {

View File

@@ -94,25 +94,25 @@
</context-group>
<note priority="1" from="description">Settings menu label</note>
</trans-unit>
<trans-unit id="65918861159071115" datatype="html">
<source>Successfully created playlist!&apos;, &apos;</source>
<trans-unit id="39921032161993566" datatype="html">
<source>Successfully created playlist!</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/custom-playlists/custom-playlists.component.ts</context>
<context context-type="linenumber">56</context>
</context-group>
</trans-unit>
<trans-unit id="7241816854520039909" datatype="html">
<source>ERROR: failed to create playlist!&apos;, &apos;</source>
<trans-unit id="2070856663109337061" datatype="html">
<source>ERROR: failed to create playlist!</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/custom-playlists/custom-playlists.component.ts</context>
<context context-type="linenumber">58</context>
</context-group>
</trans-unit>
<trans-unit id="5070854963159885174" datatype="html">
<source>Playlist successfully removed.&apos;, &apos;</source>
<trans-unit id="820184305380634591" datatype="html">
<source>Playlist successfully removed.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/custom-playlists/custom-playlists.component.ts</context>
<context context-type="linenumber">99</context>
<context context-type="linenumber">100</context>
</context-group>
</trans-unit>
<trans-unit id="2f933b826a570836cab04f683970a2d22068458c" datatype="html">
@@ -645,6 +645,55 @@
</context-group>
<note priority="1" from="description">Close</note>
</trans-unit>
<trans-unit id="4250042184551786923" datatype="html">
<source>File manager</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage-role/manage-role.component.ts</context>
<context context-type="linenumber">17</context>
</context-group>
</trans-unit>
<trans-unit id="8525406024045742391" datatype="html">
<source>Settings access</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage-role/manage-role.component.ts</context>
<context context-type="linenumber">18</context>
</context-group>
</trans-unit>
<trans-unit id="1812379335568847528" datatype="html">
<source>Subscriptions</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage-role/manage-role.component.ts</context>
<context context-type="linenumber">19</context>
</context-group>
</trans-unit>
<trans-unit id="4744991787069301975" datatype="html">
<source>Share files</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage-role/manage-role.component.ts</context>
<context context-type="linenumber">20</context>
</context-group>
</trans-unit>
<trans-unit id="5029464708330583545" datatype="html">
<source>Use advanced download mode</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage-role/manage-role.component.ts</context>
<context context-type="linenumber">21</context>
</context-group>
</trans-unit>
<trans-unit id="4278268519633335280" datatype="html">
<source>Use downloads manager</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage-role/manage-role.component.ts</context>
<context context-type="linenumber">22</context>
</context-group>
</trans-unit>
<trans-unit id="3426988753455920032" datatype="html">
<source>Use tasks manager</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage-role/manage-role.component.ts</context>
<context context-type="linenumber">23</context>
</context-group>
</trans-unit>
<trans-unit id="2bd201aea09e43fbfd3cd15ec0499b6755302329" datatype="html">
<source>Manage user</source>
<context-group purpose="location">

View File

@@ -3332,7 +3332,7 @@
</trans-unit>
<trans-unit id="b1c08387975e6feada407c9b5f5f564261b8192b" datatype="html">
<source>Database information could not be retrieved. Check the server logs for more information.</source>
<target state="translated">Impossibile recuperare le informazioni del database. Controllare i registri del server per ulteriori informazioni.</target>
<target state="translated">Impossibile recuperare le informazioni del database. Controllare il registro del server per ulteriori informazioni.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">333</context>
@@ -3347,6 +3347,698 @@
<context context-type="linenumber">48</context>
</context-group>
</trans-unit>
<trans-unit id="5070854963159885174" datatype="html">
<source>Playlist successfully removed.', '</source>
<target state="translated">Playlist rimossa con successo.', '</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/custom-playlists/custom-playlists.component.ts</context>
<context context-type="linenumber">99</context>
</context-group>
</trans-unit>
<trans-unit id="5215119607776782829" datatype="html">
<source>Select downloads to clear</source>
<target state="translated">Seleziona i download da cancellare</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">132</context>
</context-group>
</trans-unit>
<trans-unit id="2723988842145709249" datatype="html">
<source>Errored downloads</source>
<target state="translated">Download errati</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">146</context>
</context-group>
</trans-unit>
<trans-unit id="3961621815065792326" datatype="html">
<source>Failed to clear finished downloads!</source>
<target state="translated">Cancellazione dei download finiti non riuscita!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">157</context>
</context-group>
</trans-unit>
<trans-unit id="5823550543348347814" datatype="html">
<source>Cleared downloads!</source>
<target state="translated">Download cancellati!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">159</context>
</context-group>
</trans-unit>
<trans-unit id="2293081271355999967" datatype="html">
<source>Logs successfully cleared!</source>
<target state="translated">Registro cancellato con successo!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/logs-viewer/logs-viewer.component.ts</context>
<context context-type="linenumber">75</context>
</context-group>
</trans-unit>
<trans-unit id="4516710756538206828" datatype="html">
<source>Failed to clear logs!</source>
<target state="translated">Impossibile cancellare il registro!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/logs-viewer/logs-viewer.component.ts</context>
<context context-type="linenumber">77</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/logs-viewer/logs-viewer.component.ts</context>
<context context-type="linenumber">80</context>
</context-group>
</trans-unit>
<trans-unit id="52e0fa8ada52c3f29774a4508582fd98250b9f93" datatype="html">
<source>My files</source>
<target state="translated">I miei file</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/recent-videos/recent-videos.component.html</context>
<context context-type="linenumber">20</context>
</context-group>
<note priority="1" from="description">My files title</note>
</trans-unit>
<trans-unit id="6827066f436adfc56a142d5816a8be6113d73b01" datatype="html">
<source>No files found.</source>
<target state="translated">Nessun file trovato.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/recent-videos/recent-videos.component.html</context>
<context context-type="linenumber">40</context>
</context-group>
<note priority="1" from="description">No files found</note>
</trans-unit>
<trans-unit id="5704ec2049d007c5f5fb495a5d8b607e68d58081" datatype="html">
<source>Order</source>
<target state="translated">Ordina</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/recent-videos/recent-videos.component.html</context>
<context context-type="linenumber">53</context>
</context-group>
<note priority="1" from="description">Order</note>
</trans-unit>
<trans-unit id="ae9a5141f5c6bd62cee4ce837598ea8b0904e5cf" datatype="html">
<source>Select files</source>
<target state="translated">Seleziona file</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/recent-videos/recent-videos.component.html</context>
<context context-type="linenumber">71</context>
</context-group>
<note priority="1" from="description">Select files</note>
</trans-unit>
<trans-unit id="2159130950882492111" datatype="html">
<source>Cancel</source>
<target state="translated">Annulla</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/confirm-dialog/confirm-dialog.component.ts</context>
<context context-type="linenumber">15</context>
</context-group>
</trans-unit>
<trans-unit id="1709839462010459086" datatype="html">
<source>Cookies successfully uploaded!</source>
<target state="translated">Cookie caricati con successo!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component.ts</context>
<context context-type="linenumber">42</context>
</context-group>
</trans-unit>
<trans-unit id="3480433876298276350" datatype="html">
<source>Database successfully restored!</source>
<target state="translated">Database ripristinato con successo!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/restore-db-dialog/restore-db-dialog.component.ts</context>
<context context-type="linenumber">39</context>
</context-group>
</trans-unit>
<trans-unit id="1946323844380374711" datatype="html">
<source>Failed to restore database! See browser console for more info.</source>
<target state="translated">Ripristino del database non riuscito! Guarda la console del browser per più informazioni.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/restore-db-dialog/restore-db-dialog.component.ts</context>
<context context-type="linenumber">46</context>
</context-group>
</trans-unit>
<trans-unit id="4960870191807928282" datatype="html">
<source>Sharing enabled.</source>
<target state="translated">Condivisione abilitata.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/share-media-dialog/share-media-dialog.component.ts</context>
<context context-type="linenumber">68</context>
</context-group>
</trans-unit>
<trans-unit id="2720327817780634026" datatype="html">
<source>Failed to enable sharing.</source>
<target state="translated">Abilitazione della condivisione non riuscita.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/share-media-dialog/share-media-dialog.component.ts</context>
<context context-type="linenumber">71</context>
</context-group>
</trans-unit>
<trans-unit id="5397815846940616259" datatype="html">
<source>You must specify an amount of time</source>
<target state="translated">Devi specificare un periodo di tempo</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/subscribe-dialog/subscribe-dialog.component.ts</context>
<context context-type="linenumber">79</context>
</context-group>
</trans-unit>
<trans-unit id="3544790314111256717" datatype="html">
<source>ERROR:</source>
<target state="translated">ERRORE:</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/subscribe-dialog/subscribe-dialog.component.ts</context>
<context context-type="linenumber">95</context>
</context-group>
</trans-unit>
<trans-unit id="f4003b626fcbf3a871778d4dba166e109d02f87c" datatype="html">
<source>Thumbnail URL</source>
<target state="translated">URL della miniatura</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/video-info-dialog/video-info-dialog.component.html</context>
<context context-type="linenumber">29</context>
</context-group>
<note priority="1" from="description">Thumbnail URL</note>
</trans-unit>
<trans-unit id="607de17c2a755f65775881c19e276e7c933bcf94" datatype="html">
<source>Category</source>
<target state="translated">Categoria</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/video-info-dialog/video-info-dialog.component.html</context>
<context context-type="linenumber">32</context>
</context-group>
<note priority="1" from="description">Category</note>
</trans-unit>
<trans-unit id="3f741a2c015bb728088b630296ca401e823c6af8" datatype="html">
<source>View count</source>
<target state="translated">Numero di visualizzazioni</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/video-info-dialog/video-info-dialog.component.html</context>
<context context-type="linenumber">42</context>
</context-group>
<note priority="1" from="description">View count</note>
</trans-unit>
<trans-unit id="989f5aa799ee9672675d68109bff29d1d88ebd49" datatype="html">
<source>Local view count</source>
<target state="translated">Numero di visualizzazioni locale</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/video-info-dialog/video-info-dialog.component.html</context>
<context context-type="linenumber">45</context>
</context-group>
<note priority="1" from="description">Local view count</note>
</trans-unit>
<trans-unit id="9fa37704969eeebd496a172c5077370f569df3ae" datatype="html">
<source>Resolution:</source>
<target state="translated">Risoluzione:</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/video-info-dialog/video-info-dialog.component.html</context>
<context context-type="linenumber">51</context>
</context-group>
<note priority="1" from="description">Video resolution property</note>
</trans-unit>
<trans-unit id="9fc54db2830fbbd332b1adebe28e9283069107ef" datatype="html">
<source>Audio bitrate:</source>
<target state="translated">Bitrate audio:</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/video-info-dialog/video-info-dialog.component.html</context>
<context context-type="linenumber">55</context>
</context-group>
<note priority="1" from="description">Video audio bitrate property</note>
</trans-unit>
<trans-unit id="2734512985872312443" datatype="html">
<source>Failed to get file information from the server.</source>
<target state="translated">Impossibile ottenere le informazioni del file dal server.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/player/player.component.ts</context>
<context context-type="linenumber">149</context>
</context-group>
</trans-unit>
<trans-unit id="5d78fe9ba69a8710613d3f7c35b22e9c8226e4dc" datatype="html">
<source>Twitch Client ID</source>
<target state="translated">ID Client Twitch</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">266</context>
</context-group>
<note priority="1" from="description">Twitch Client ID setting placeholder</note>
</trans-unit>
<trans-unit id="9208873922277364009" datatype="html">
<source>Failed to update categories!</source>
<target state="translated">Aggiornamento delle categorie non riuscito!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
<context context-type="linenumber">134</context>
</context-group>
</trans-unit>
<trans-unit id="7180231139026789468" datatype="html">
<source>Language successfully changed! Reload to update the page.</source>
<target state="translated">Lingua cambiata con successo! Ricarica per aggiornare la pagina.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
<context context-type="linenumber">209</context>
</context-group>
</trans-unit>
<trans-unit id="6123898845299902958" datatype="html">
<source>Successfully transfered DB! Reloading info...</source>
<target state="translated">DB trasferito con successo! Ricaricando informazioni...</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
<context context-type="linenumber">340</context>
</context-group>
</trans-unit>
<trans-unit id="5681417617361245213" datatype="html">
<source>Failed to transfer DB -- transfer was aborted. Error:</source>
<target state="translated">Trasferimento del DB non riuscito -- transferimento interrotto. Errore:</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
<context context-type="linenumber">343</context>
</context-group>
</trans-unit>
<trans-unit id="6519219215739537829" datatype="html">
<source>Connection failed! Error: Server error. See logs for more info.</source>
<target state="translated">Connessione non riuscita! Errore: Errore server. Guarda il registro per più informazioni.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
<context context-type="linenumber">363</context>
</context-group>
</trans-unit>
<trans-unit id="65918861159071115" datatype="html">
<source>Successfully created playlist!', '</source>
<target state="translated">Playlist creata con successo!', '</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/custom-playlists/custom-playlists.component.ts</context>
<context context-type="linenumber">56</context>
</context-group>
</trans-unit>
<trans-unit id="7114033980971410157" datatype="html">
<source>Failed to pause download! See server logs for more info.</source>
<target state="translated">Interruzione del download non riuscita! Guarda il registro del server per più informazioni.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">170</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">218</context>
</context-group>
</trans-unit>
<trans-unit id="8348223454028662277" datatype="html">
<source>OK.</source>
<target state="translated">OK.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/recent-videos/recent-videos.component.ts</context>
<context context-type="linenumber">270</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/recent-videos/recent-videos.component.ts</context>
<context context-type="linenumber">273</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/recent-videos/recent-videos.component.ts</context>
<context context-type="linenumber">276</context>
</context-group>
</trans-unit>
<trans-unit id="22bac71dbdc1ac62607135994f81cca8094cb251" datatype="html">
<source>Upload date</source>
<target state="translated">Data caricamento</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/video-info-dialog/video-info-dialog.component.html</context>
<context context-type="linenumber">20</context>
</context-group>
<note priority="1" from="description">Upload date</note>
</trans-unit>
<trans-unit id="1019978815798793544" datatype="html">
<source>Failed to retrieve logs!</source>
<target state="translated">Recupero dei registro non riuscito!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/logs-viewer/logs-viewer.component.ts</context>
<context context-type="linenumber">46</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/logs-viewer/logs-viewer.component.ts</context>
<context context-type="linenumber">51</context>
</context-group>
</trans-unit>
<trans-unit id="3481862581074838726" datatype="html">
<source>VOD url for this video is not supported. VOD ID must be after "twitch.tv/videos/"</source>
<target state="translated">L'url del VOD di questo video non è supportato. L'ID del VOD deve essere dopo "twitch.tv/videos/"</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/twitch-chat/twitch-chat.component.ts</context>
<context context-type="linenumber">99</context>
</context-group>
</trans-unit>
<trans-unit id="7241816854520039909" datatype="html">
<source>ERROR: failed to create playlist!', '</source>
<target state="translated">ERRORE: creazione della playlist non riuscita!', '</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/custom-playlists/custom-playlists.component.ts</context>
<context context-type="linenumber">58</context>
</context-group>
</trans-unit>
<trans-unit id="a4a4a5f03d7d0831ccf6774094e66a9507a42b58" datatype="html">
<source>Clear downloads</source>
<target state="translated">Cancella i download</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.html</context>
<context context-type="linenumber">85</context>
</context-group>
<note priority="1" from="description">Clear downloads</note>
</trans-unit>
<trans-unit id="3299455901271096793" datatype="html">
<source>Clear downloads</source>
<target state="translated">Cancella i download</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">131</context>
</context-group>
</trans-unit>
<trans-unit id="4050356167294261426" datatype="html">
<source>Delete success!</source>
<target state="translated">Cancellazione avvenuta con successo!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/recent-videos/recent-videos.component.ts</context>
<context context-type="linenumber">270</context>
</context-group>
</trans-unit>
<trans-unit id="7405156667148936748" datatype="html">
<source>Delete failed!</source>
<target state="translated">Cancellazione non riuscita!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/recent-videos/recent-videos.component.ts</context>
<context context-type="linenumber">273</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/recent-videos/recent-videos.component.ts</context>
<context context-type="linenumber">276</context>
</context-group>
</trans-unit>
<trans-unit id="8485375438204712002" datatype="html">
<source>Finished downloads</source>
<target state="translated">Download terminati</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">138</context>
</context-group>
</trans-unit>
<trans-unit id="5223827577229167333" datatype="html">
<source>Failed to pause all downloads! See server logs for more info.</source>
<target state="translated">Interruzione di tutti i download non riuscita! Guarda il registro del server per più informazioni.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">178</context>
</context-group>
</trans-unit>
<trans-unit id="5801924165267871854" datatype="html">
<source>Paused downloads</source>
<target state="translated">Download interrotti</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">142</context>
</context-group>
</trans-unit>
<trans-unit id="7157191502004604261" datatype="html">
<source>Chat could not be downloaded.</source>
<target state="translated">Impossibile scaricare la chat.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/twitch-chat/twitch-chat.component.ts</context>
<context context-type="linenumber">110</context>
</context-group>
</trans-unit>
<trans-unit id="87406377200084623" datatype="html">
<source>Logs copied to clipboard!</source>
<target state="translated">Registro copiato negli appunti!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/logs-viewer/logs-viewer.component.ts</context>
<context context-type="linenumber">56</context>
</context-group>
</trans-unit>
<trans-unit id="5456775416888155476" datatype="html">
<source>Failed to resume download! See server logs for more info.</source>
<target state="translated">Ripresa del download non riuscita! Guarda il registro del server per più informazioni.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">186</context>
</context-group>
</trans-unit>
<trans-unit id="6268791413935580107" datatype="html">
<source>Failed to resume all downloads! See server logs for more info.</source>
<target state="translated">Ripresa di tutti i download non riuscita! Guarda il registro del server per più informazioni.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">194</context>
</context-group>
</trans-unit>
<trans-unit id="571023367671104036" datatype="html">
<source>Failed to restart download! See server logs for more info.</source>
<target state="translated">Riavvio del download non riuscito! Guarda il registro del server per più informazioni.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">202</context>
</context-group>
</trans-unit>
<trans-unit id="4529487534884306633" datatype="html">
<source>Failed to cancel download! See server logs for more info.</source>
<target state="translated">Annullamento del download non riuscito! Guarda il registro del server per più informazioni.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/downloads/downloads.component.ts</context>
<context context-type="linenumber">210</context>
</context-group>
</trans-unit>
<trans-unit id="c1b7e6d75ff4285c7636c67e5ef259629b81725b" datatype="html">
<source>Confirm Password</source>
<target state="translated">Conferma password</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/login/login.component.html</context>
<context context-type="linenumber">28</context>
</context-group>
<note priority="1" from="description">Confirm Password</note>
</trans-unit>
<trans-unit id="8937901770314883418" datatype="html">
<source>Successfully deleted file:</source>
<target state="translated">File eliminati con successo:</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/recent-videos/recent-videos.component.ts</context>
<context context-type="linenumber">291</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/recent-videos/recent-videos.component.ts</context>
<context context-type="linenumber">299</context>
</context-group>
</trans-unit>
<trans-unit id="e58f5716d6c08b6a841eb003c9f9774b5c5d34a9" datatype="html">
<source>Delete and don't download again</source>
<target state="translated">Elimina e non scaricare di nuovo</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/unified-file-card/unified-file-card.component.html</context>
<context context-type="linenumber">37</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/unified-file-card/unified-file-card.component.html</context>
<context context-type="linenumber">40</context>
</context-group>
<note priority="1" from="description">Delete forever subscription video button</note>
</trans-unit>
<trans-unit id="9203653061903371757" datatype="html">
<source>Playlist updated successfully.</source>
<target state="translated">Playlist aggiornata con successo.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/create-playlist/create-playlist.component.ts</context>
<context context-type="linenumber">69</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/create-playlist/create-playlist.component.ts</context>
<context context-type="linenumber">75</context>
</context-group>
</trans-unit>
<trans-unit id="880407735794041263" datatype="html">
<source>Download failed.</source>
<target state="translated">Download non riuscito.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/twitch-chat/twitch-chat.component.ts</context>
<context context-type="linenumber">106</context>
</context-group>
</trans-unit>
<trans-unit id="643438049907907768" datatype="html">
<source>Failed to restore database! See logs for more info.</source>
<target state="translated">Ripristino del database non riuscito! Guarda il registro del server per più informazioni.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/restore-db-dialog/restore-db-dialog.component.ts</context>
<context context-type="linenumber">42</context>
</context-group>
</trans-unit>
<trans-unit id="2859348955905483094" datatype="html">
<source>Failed to enable sharing - server error.</source>
<target state="translated">Abilitazione della condivisione non riuscita - errore server.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/share-media-dialog/share-media-dialog.component.ts</context>
<context context-type="linenumber">74</context>
</context-group>
</trans-unit>
<trans-unit id="3561468911579213356" datatype="html">
<source>Sharing disabled.</source>
<target state="translated">Condivisione disabilitata.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/share-media-dialog/share-media-dialog.component.ts</context>
<context context-type="linenumber">79</context>
</context-group>
</trans-unit>
<trans-unit id="8692976466689769553" datatype="html">
<source>Failed to disable sharing - server error.</source>
<target state="translated">Disabilitazione della condivisione non riuscita - errore server.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/share-media-dialog/share-media-dialog.component.ts</context>
<context context-type="linenumber">85</context>
</context-group>
</trans-unit>
<trans-unit id="2876893175497409225" datatype="html">
<source>Update failed. Check logs for more details.</source>
<target state="translated">Aggiornamento non riuscito. Guarda il registro per più dettagli.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/update-progress-dialog/update-progress-dialog.component.ts</context>
<context context-type="linenumber">30</context>
</context-group>
</trans-unit>
<trans-unit id="3317b8688eb2cfabc4021cd7b2926b32f3864ad2" datatype="html">
<source>Choose a date</source>
<target state="translated">Seleziona una data</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/update-task-schedule-dialog/update-task-schedule-dialog.component.html</context>
<context context-type="linenumber">22</context>
</context-group>
<note priority="1" from="description">Choose a date</note>
</trans-unit>
<trans-unit id="7840375760456214518" datatype="html">
<source>Failed to disable sharing.</source>
<target state="translated">Disabilitazione della condivisione non riuscita.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/share-media-dialog/share-media-dialog.component.ts</context>
<context context-type="linenumber">82</context>
</context-group>
</trans-unit>
<trans-unit id="f8c7be184fefd6750e4e5d0c7a90e74721c58f8a" datatype="html">
<source>Uploader</source>
<target state="translated">Caricato da</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/video-info-dialog/video-info-dialog.component.html</context>
<context context-type="linenumber">17</context>
</context-group>
<note priority="1" from="description">Uploader</note>
</trans-unit>
<trans-unit id="d49d5d6786b69d140e20cfddfe29690a19641a88" datatype="html">
<source>Thumbnail path</source>
<target state="translated">Percorso della miniatura</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/dialogs/video-info-dialog/video-info-dialog.component.html</context>
<context context-type="linenumber">26</context>
</context-group>
<note priority="1" from="description">Thumbnail path</note>
</trans-unit>
<trans-unit id="8314249599019746316" datatype="html">
<source>Download failed!</source>
<target state="translated">Download non riuscito!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/main/main.component.ts</context>
<context context-type="linenumber">387</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/main/main.component.ts</context>
<context context-type="linenumber">775</context>
</context-group>
</trans-unit>
<trans-unit id="6789263921624845085" datatype="html">
<source>Failed to load playlist!</source>
<target state="translated">Caricamento della playlist non riuscito!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/player/player.component.ts</context>
<context context-type="linenumber">186</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/player/player.component.ts</context>
<context context-type="linenumber">189</context>
</context-group>
</trans-unit>
<trans-unit id="4c9a15ab7fb3dce1002ea7aea4ecada3c1ee12e9" datatype="html">
<source>Generating an ID/secret is easy!</source>
<target state="translated">Generare un ID/secret è facile!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">267</context>
</context-group>
<note priority="1" from="description">Twitch Client ID setting hint</note>
</trans-unit>
<trans-unit id="8506540da14d205ea092b4c856e242ed7f500643" datatype="html">
<source>Twitch Client Secret</source>
<target state="translated">Secret Client Twitch</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.html</context>
<context context-type="linenumber">272</context>
</context-group>
<note priority="1" from="description">Twitch Client Secret setting placeholder</note>
</trans-unit>
<trans-unit id="4604336107574138791" datatype="html">
<source>Chrome users must drag the 'Alternate URL' link to your bookmarks.</source>
<target state="translated">Gli utenti di Chrome devono trascinare il link del 'Alternate URL' nei preferiti.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
<context context-type="linenumber">237</context>
</context-group>
</trans-unit>
<trans-unit id="4257962986336738751" datatype="html">
<source>Successfully killed all downloads!</source>
<target state="translated">Uccisi con successo tutti i download!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
<context context-type="linenumber">292</context>
</context-group>
</trans-unit>
<trans-unit id="1942965859829798388" datatype="html">
<source>Restarting!</source>
<target state="translated">Riavviando!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
<context context-type="linenumber">307</context>
</context-group>
</trans-unit>
<trans-unit id="6224607866493148072" datatype="html">
<source>Failed to restart the server.</source>
<target state="translated">Riavvio del server non riuscito.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
<context context-type="linenumber">309</context>
</context-group>
</trans-unit>
<trans-unit id="2600933489084742998" datatype="html">
<source>Failed to kill all downloads! Check logs for details.</source>
<target state="translated">Impossibile uccidere tutti i download! Guarda il registro per più informazioni.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
<context context-type="linenumber">295</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
<context context-type="linenumber">299</context>
</context-group>
</trans-unit>
<trans-unit id="1716030487077666916" datatype="html">
<source>Failed to transfer DB -- API call failed. See browser logs for details.</source>
<target state="translated">Trasferimento del DB non riuscito -- Errore chiamata API. Guarda il registro del browser per più informazioni.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
<context context-type="linenumber">347</context>
</context-group>
</trans-unit>
<trans-unit id="6018050954136387828" datatype="html">
<source>Connection successful!</source>
<target state="translated">Connessione avvenuta con successo!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
<context context-type="linenumber">357</context>
</context-group>
</trans-unit>
<trans-unit id="4021495815084152271" datatype="html">
<source>Connection failed! Error:</source>
<target state="translated">Connessione non riuscita! Errore:</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/settings/settings.component.ts</context>
<context context-type="linenumber">359</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>

File diff suppressed because it is too large Load Diff