Merge branch 'Chocobozzz:develop' into feature/Remember-user-table-pagination-in-admin
This commit is contained in:
commit
6cd05dc963
|
@ -1,7 +1,10 @@
|
||||||
<ng-template #modal>
|
<ng-template #modal>
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h1 i18n class="modal-title">Contact the administrator(s)<p class="modal-subtitle">{{ instanceName }}</p></h1>
|
<h1 i18n class="modal-title">Contact the administrator(s)<p class="modal-subtitle">{{ instanceName }}</p></h1>
|
||||||
<my-global-icon iconName="cross" aria-label="Close" tabindex="0" role="button" (click)="hide()" (keydown.enter)="hide()"></my-global-icon>
|
|
||||||
|
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||||
|
<my-global-icon iconName="cross"></my-global-icon>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 i18n class="modal-title">Follow</h4>
|
<h4 i18n class="modal-title">Follow</h4>
|
||||||
|
|
||||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||||
|
<my-global-icon iconName="cross"></my-global-icon>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|
|
@ -5,7 +5,9 @@
|
||||||
<ng-container *ngIf="isReject()">Reject {{ registration.username }} registration</ng-container>
|
<ng-container *ngIf="isReject()">Reject {{ registration.username }} registration</ng-container>
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||||
|
<my-global-icon iconName="cross"></my-global-icon>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form novalidate [formGroup]="form" (ngSubmit)="processRegistration()">
|
<form novalidate [formGroup]="form" (ngSubmit)="processRegistration()">
|
||||||
|
|
|
@ -112,11 +112,13 @@
|
||||||
<li *ngFor="let file of video.files">
|
<li *ngFor="let file of video.files">
|
||||||
<a target="_blank" rel="noopener noreferrer" [href]="file.fileUrl">{{ file.resolution.label }}</a>: {{ file.size | bytes: 1 }}
|
<a target="_blank" rel="noopener noreferrer" [href]="file.fileUrl">{{ file.resolution.label }}</a>: {{ file.size | bytes: 1 }}
|
||||||
|
|
||||||
<my-global-icon
|
<button
|
||||||
*ngIf="canRemoveOneFile(video)"
|
*ngIf="canRemoveOneFile(video)" class="border-0 p-0"
|
||||||
i18n-ngbTooltip ngbTooltip="Delete this file" iconName="delete" role="button"
|
i18n-title title="Delete this file"
|
||||||
(click)="removeVideoFile(video, file, 'web-videos')"
|
(click)="removeVideoFile(video, file, 'web-videos')"
|
||||||
></my-global-icon>
|
>
|
||||||
|
<my-global-icon iconName="delete"></my-global-icon>
|
||||||
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -128,11 +130,13 @@
|
||||||
<li *ngFor="let file of video.streamingPlaylists[0].files">
|
<li *ngFor="let file of video.streamingPlaylists[0].files">
|
||||||
<a target="_blank" rel="noopener noreferrer" [href]="file.fileUrl">{{ file.resolution.label }}</a>: {{ file.size | bytes: 1 }}
|
<a target="_blank" rel="noopener noreferrer" [href]="file.fileUrl">{{ file.resolution.label }}</a>: {{ file.size | bytes: 1 }}
|
||||||
|
|
||||||
<my-global-icon
|
<button
|
||||||
*ngIf="canRemoveOneFile(video)"
|
*ngIf="canRemoveOneFile(video)" class="border-0 p-0"
|
||||||
i18n-ngbTooltip ngbTooltip="Delete this file" iconName="delete" role="button"
|
i18n-title title="Delete this file"
|
||||||
(click)="removeVideoFile(video, file, 'hls')"
|
(click)="removeVideoFile(video, file, 'hls')"
|
||||||
></my-global-icon>
|
>
|
||||||
|
<my-global-icon iconName="delete"></my-global-icon>
|
||||||
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<h1 i18n class="title-page-v2">
|
<h1 class="title-page-v2">
|
||||||
<strong class="underline-orange">{{ instanceName }}</strong>
|
<strong class="underline-orange">{{ instanceName }}</strong>
|
||||||
>
|
|
||||||
Login
|
<ng-container i18n>Login</ng-container>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div class="margin-content">
|
<div class="margin-content">
|
||||||
|
@ -120,7 +120,9 @@
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 i18n class="modal-title">Forgot your password</h4>
|
<h4 i18n class="modal-title">Forgot your password</h4>
|
||||||
|
|
||||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hideForgotPasswordModal()"></my-global-icon>
|
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hideForgotPasswordModal()">
|
||||||
|
<my-global-icon iconName="cross"></my-global-icon>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body text-start">
|
<div class="modal-body text-start">
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h1 i18n class="modal-title">Accept ownership</h1>
|
<h1 i18n class="modal-title">Accept ownership</h1>
|
||||||
|
|
||||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="dismiss()"></my-global-icon>
|
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="dismiss()">
|
||||||
|
<my-global-icon iconName="cross"></my-global-icon>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body" [formGroup]="form">
|
<div class="modal-body" [formGroup]="form">
|
||||||
|
|
|
@ -2,14 +2,20 @@
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 i18n class="modal-title">Change ownership</h4>
|
<h4 i18n class="modal-title">Change ownership</h4>
|
||||||
|
|
||||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="dismiss()"></my-global-icon>
|
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="dismiss()">
|
||||||
|
<my-global-icon iconName="cross"></my-global-icon>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body" [formGroup]="form">
|
<div class="modal-body" [formGroup]="form">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label i18n for="next-ownership-username">Select the next owner</label>
|
<label i18n for="next-ownership-username">Select the next owner</label>
|
||||||
<p-autoComplete formControlName="username" [suggestions]="usernamePropositions"
|
|
||||||
(completeMethod)="search($event)" id="next-ownership-username"></p-autoComplete>
|
<p-autoComplete
|
||||||
|
formControlName="username" [suggestions]="usernamePropositions"
|
||||||
|
(completeMethod)="search($event)" id="next-ownership-username"
|
||||||
|
></p-autoComplete>
|
||||||
|
|
||||||
<div *ngIf="formErrors.username" class="form-error" role="alert">
|
<div *ngIf="formErrors.username" class="form-error" role="alert">
|
||||||
{{ formErrors.username }}
|
{{ formErrors.username }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,7 +3,9 @@
|
||||||
|
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 i18n class="modal-title">Add caption</h4>
|
<h4 i18n class="modal-title">Add caption</h4>
|
||||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||||
|
<my-global-icon iconName="cross"></my-global-icon>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
<ng-container [formGroup]="form">
|
<ng-container [formGroup]="form">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 i18n class="modal-title">Edit caption</h4>
|
<h4 i18n class="modal-title">Edit caption</h4>
|
||||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||||
|
<my-global-icon iconName="cross"></my-global-icon>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|
|
@ -3,11 +3,13 @@
|
||||||
<my-actor-avatar [actor]="user?.account" [actorType]="getAvatarActorType()" size="25"></my-actor-avatar>
|
<my-actor-avatar [actor]="user?.account" [actorType]="getAvatarActorType()" size="25"></my-actor-avatar>
|
||||||
|
|
||||||
<div class="textarea-wrapper">
|
<div class="textarea-wrapper">
|
||||||
<textarea i18n-placeholder placeholder="Add comment..." myAutoResize
|
<textarea
|
||||||
[readonly]="(user === null) ? true : false"
|
i18n-placeholder placeholder="Add comment..." myAutoResize
|
||||||
(click)="openVisitorModal($event)"
|
[readonly]="(user === null) ? true : false"
|
||||||
formControlName="text" [ngClass]="{ 'input-error': formErrors['text'] }"
|
(click)="openVisitorModal($event)"
|
||||||
(keyup.control.enter)="onValidKey()" (keyup.meta.enter)="onValidKey()" #textarea>
|
formControlName="text" [ngClass]="{ 'input-error': formErrors['text'] }"
|
||||||
|
(keyup.control.enter)="onValidKey()" (keyup.meta.enter)="onValidKey()" #textarea
|
||||||
|
>
|
||||||
</textarea>
|
</textarea>
|
||||||
|
|
||||||
<my-help
|
<my-help
|
||||||
|
@ -57,7 +59,10 @@
|
||||||
<ng-template #visitorModal let-modal>
|
<ng-template #visitorModal let-modal>
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 class="modal-title" id="modal-basic-title" i18n>You are one step away from commenting</h4>
|
<h4 class="modal-title" id="modal-basic-title" i18n>You are one step away from commenting</h4>
|
||||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hideModals()"></my-global-icon>
|
|
||||||
|
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hideModals()">
|
||||||
|
<my-global-icon iconName="cross"></my-global-icon>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
@ -81,8 +86,12 @@
|
||||||
<ng-template #emojiModal>
|
<ng-template #emojiModal>
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 class="modal-title" id="modal-basic-title" i18n>Markdown Emoji List</h4>
|
<h4 class="modal-title" id="modal-basic-title" i18n>Markdown Emoji List</h4>
|
||||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hideModals()"></my-global-icon>
|
|
||||||
|
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hideModals()">
|
||||||
|
<my-global-icon iconName="cross"></my-global-icon>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="emoji-flex">
|
<div class="emoji-flex">
|
||||||
<div class="emoji-flex-item" *ngFor="let emojiMarkup of getEmojiMarkupList()">
|
<div class="emoji-flex-item" *ngFor="let emojiMarkup of getEmojiMarkupList()">
|
||||||
|
|
|
@ -19,23 +19,21 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="playlist-controls">
|
<div class="playlist-controls">
|
||||||
<my-global-icon
|
<button
|
||||||
iconName="videos"
|
class="border-0 p-0 me-2" [ngClass]="{ active: autoPlayNextVideoPlaylist }" (click)="switchAutoPlayNextVideoPlaylist()"
|
||||||
[class.active]="autoPlayNextVideoPlaylist"
|
[ngbTooltip]="autoPlayNextVideoPlaylistSwitchText" [ariaLabel]="autoPlayNextVideoPlaylistSwitchText"
|
||||||
(click)="switchAutoPlayNextVideoPlaylist()"
|
placement="bottom auto" container="body"
|
||||||
[ngbTooltip]="autoPlayNextVideoPlaylistSwitchText"
|
>
|
||||||
placement="bottom auto"
|
<my-global-icon iconName="videos"></my-global-icon>
|
||||||
container="body"
|
</button>
|
||||||
></my-global-icon>
|
|
||||||
|
|
||||||
<my-global-icon
|
<button
|
||||||
iconName="repeat"
|
class="border-0 p-0" [ngClass]="{ active: loopPlaylist }" (click)="switchLoopPlaylist()"
|
||||||
[class.active]="loopPlaylist"
|
[ngbTooltip]="loopPlaylistSwitchText" [ariaLabel]="loopPlaylistSwitchText"
|
||||||
(click)="switchLoopPlaylist()"
|
placement="bottom auto" container="body"
|
||||||
[ngbTooltip]="loopPlaylistSwitchText"
|
>
|
||||||
placement="bottom auto"
|
<my-global-icon iconName="repeat"></my-global-icon>
|
||||||
container="body"
|
</button>
|
||||||
></my-global-icon>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -46,18 +46,8 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
|
|
||||||
my-global-icon:not(:last-child) {
|
button:not(.active) {
|
||||||
@include margin-right(.5rem);
|
opacity: .5;
|
||||||
}
|
|
||||||
|
|
||||||
my-global-icon {
|
|
||||||
&:not(.active) {
|
|
||||||
opacity: .5;
|
|
||||||
}
|
|
||||||
|
|
||||||
::ng-deep {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,9 @@
|
||||||
<div [innerHTML]="broadcastMessage.message"></div>
|
<div [innerHTML]="broadcastMessage.message"></div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
*ngIf="broadcastMessage.dismissable" (click)="hideBroadcastMessage()" class="border-0" title="Close this message" i18n-title>
|
*ngIf="broadcastMessage.dismissable" (click)="hideBroadcastMessage()"
|
||||||
|
class="border-0" title="Close this message" i18n-title
|
||||||
|
>
|
||||||
<my-global-icon iconName="cross"></my-global-icon>
|
<my-global-icon iconName="cross"></my-global-icon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
<ng-template #modal let-hide="close">
|
<ng-template #modal let-hide="close">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 i18n class="modal-title">Change the language</h4>
|
<h4 i18n class="modal-title">Change the language</h4>
|
||||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||||
|
<my-global-icon iconName="cross"></my-global-icon>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
<ng-template #modal let-hide="close">
|
<ng-template #modal let-hide="close">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 i18n class="modal-title">Welcome to {{ instanceName }}, dear user!</h4>
|
<h4 i18n class="modal-title">Welcome to {{ instanceName }}, dear user!</h4>
|
||||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||||
|
<my-global-icon iconName="cross"></my-global-icon>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
<ng-template #modal let-hide="close">
|
<ng-template #modal let-hide="close">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 i18n class="modal-title">Welcome to PeerTube, dear administrator!</h4>
|
<h4 i18n class="modal-title">Welcome to PeerTube, dear administrator!</h4>
|
||||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||||
|
<my-global-icon iconName="cross"></my-global-icon>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|
|
@ -3,7 +3,9 @@
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 class="modal-title">{{ title }}</h4>
|
<h4 class="modal-title">{{ title }}</h4>
|
||||||
|
|
||||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="dismiss()"></my-global-icon>
|
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="dismiss()">
|
||||||
|
<my-global-icon iconName="cross"></my-global-icon>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body" >
|
<div class="modal-body" >
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
<ng-template #modal let-hide="close">
|
<ng-template #modal let-hide="close">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 class="modal-title">{{title}}</h4>
|
<h4 class="modal-title">{{title}}</h4>
|
||||||
<my-global-icon *ngIf="close" iconName="cross" aria-label="Close" role="button" (click)="onCloseClick()"></my-global-icon>
|
|
||||||
|
<button *ngIf="close" class="border-0 p-0" title="Close this modal" i18n-title (click)="onCloseClick()">
|
||||||
|
<my-global-icon iconName="cross"></my-global-icon>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body" [innerHTML]="content"></div>
|
<div class="modal-body" [innerHTML]="content"></div>
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
<ng-template #modal let-hide="close">
|
<ng-template #modal let-hide="close">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 i18n class="modal-title">Configuration warning!</h4>
|
<h4 i18n class="modal-title">Configuration warning!</h4>
|
||||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||||
|
<my-global-icon iconName="cross"></my-global-icon>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
<ng-template #modal let-hide="close">
|
<ng-template #modal let-hide="close">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 i18n class="modal-title">My settings</h4>
|
<h4 i18n class="modal-title">My settings</h4>
|
||||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||||
|
<my-global-icon iconName="cross"></my-global-icon>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|
|
@ -5,7 +5,9 @@
|
||||||
<ng-container i18n *ngIf="!isAdminView">Messages with the moderation team</ng-container>
|
<ng-container i18n *ngIf="!isAdminView">Messages with the moderation team</ng-container>
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||||
|
<my-global-icon iconName="cross"></my-global-icon>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 i18n class="modal-title">Moderation comment</h4>
|
<h4 i18n class="modal-title">Moderation comment</h4>
|
||||||
|
|
||||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||||
|
<my-global-icon iconName="cross"></my-global-icon>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
<button class="feed border-0 p-0" *ngIf="syndicationItems && syndicationItems.length !== 0">
|
<button
|
||||||
<my-global-icon
|
*ngIf="syndicationItems && syndicationItems.length !== 0"
|
||||||
role="button" aria-label="Open syndication dropdown" i18n-aria-label
|
[ngbPopover]="feedsList" [autoClose]="true" placement="bottom left auto"
|
||||||
*ngIf="syndicationItems.length !== 0" [ngbPopover]="feedsList" [autoClose]="true" placement="bottom left auto"
|
class="feed border-0 p-0"
|
||||||
class="icon-syndication" iconName="syndication"
|
title="Open syndication dropdown" i18n-title
|
||||||
>
|
>
|
||||||
</my-global-icon>
|
<my-global-icon iconName="syndication"></my-global-icon>
|
||||||
|
|
||||||
<ng-template #feedsList>
|
<ng-template #feedsList>
|
||||||
<a *ngFor="let item of syndicationItems" [href]="item.url" target="_blank" rel="noopener noreferrer">{{ item.label }}</a>
|
<a *ngFor="let item of syndicationItems" class="feed-link" [href]="item.url" target="_blank" rel="noopener noreferrer">{{ item.label }}</a>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -4,15 +4,15 @@
|
||||||
.feed {
|
.feed {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
.feed-link {
|
||||||
color: pvar(--mainForegroundColor);
|
color: pvar(--mainForegroundColor);
|
||||||
display: block;
|
display: block;
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 class="modal-title">{{ action }}</h4>
|
<h4 class="modal-title">{{ action }}</h4>
|
||||||
|
|
||||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||||
|
<my-global-icon iconName="cross"></my-global-icon>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
<ng-template #modal>
|
<ng-template #modal>
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 class="modal-title">{{ modalTitle }}</h4>
|
<h4 class="modal-title">{{ modalTitle }}</h4>
|
||||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||||
|
<my-global-icon iconName="cross"></my-global-icon>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
<ng-template #modal>
|
<ng-template #modal>
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 i18n class="modal-title">Report video "{{ video.name }}"</h4>
|
<h4 i18n class="modal-title">Report video "{{ video.name }}"</h4>
|
||||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||||
|
<my-global-icon iconName="cross"></my-global-icon>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 i18n class="modal-title">{{ getModalTitle() }}</h4>
|
<h4 i18n class="modal-title">{{ getModalTitle() }}</h4>
|
||||||
|
|
||||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||||
|
<my-global-icon iconName="cross"></my-global-icon>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|
|
@ -9,7 +9,9 @@
|
||||||
<h4 i18n class="modal-title" *ngIf="getSingleVideo().isLive">Block live "{{ getSingleVideo().name }}"</h4>
|
<h4 i18n class="modal-title" *ngIf="getSingleVideo().isLive">Block live "{{ getSingleVideo().name }}"</h4>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||||
|
<my-global-icon iconName="cross"></my-global-icon>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
<ng-template #modal let-hide="close">
|
<ng-template #modal let-hide="close">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 i18n class="modal-title">Support {{ displayName }}</h4>
|
<h4 i18n class="modal-title">Support {{ displayName }}</h4>
|
||||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||||
|
<my-global-icon iconName="cross"></my-global-icon>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body" [innerHTML]="htmlSupport"></div>
|
<div class="modal-body" [innerHTML]="htmlSupport"></div>
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h4 i18n class="modal-title">Live information</h4>
|
<h4 i18n class="modal-title">Live information</h4>
|
||||||
|
|
||||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="dismiss()"></my-global-icon>
|
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="dismiss()">
|
||||||
|
<my-global-icon iconName="cross"></my-global-icon>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body" *ngIf="live">
|
<div class="modal-body" *ngIf="live">
|
||||||
|
|
|
@ -11,7 +11,9 @@
|
||||||
</div>
|
</div>
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
<button class="border-0 p-0" title="Close this modal" i18n-title (click)="hide()">
|
||||||
|
<my-global-icon iconName="cross"></my-global-icon>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<div class="margin-content">
|
<div class="margin-content">
|
||||||
<div class="videos-header">
|
<div class="videos-header pt-4 mb-4">
|
||||||
<h1 *ngIf="displayTitle" class="title" placement="bottom" [ngbTooltip]="titleTooltip" container="body">
|
<h1 *ngIf="displayTitle" class="title mb-1" placement="bottom" [ngbTooltip]="titleTooltip" container="body">
|
||||||
{{ title }}
|
{{ title }}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@
|
||||||
<my-feed [syndicationItems]="syndicationItems"></my-feed>
|
<my-feed [syndicationItems]="syndicationItems"></my-feed>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="action-block">
|
<div *ngIf="headerActions.length !== 0" class="action-block mt-3">
|
||||||
<ng-container *ngFor="let action of headerActions">
|
<ng-container *ngFor="let action of headerActions">
|
||||||
<a *ngIf="action.routerLink" class="ms-2" [routerLink]="action.routerLink" routerLinkActive="active">
|
<a *ngIf="action.routerLink" class="ms-2" [routerLink]="action.routerLink" routerLinkActive="active">
|
||||||
<ng-container *ngTemplateOutlet="actionContent; context:{ $implicit: action }"></ng-container>
|
<ng-container *ngTemplateOutlet="actionContent; context:{ $implicit: action }"></ng-container>
|
||||||
|
|
|
@ -4,12 +4,11 @@
|
||||||
@use '_miniature' as *;
|
@use '_miniature' as *;
|
||||||
|
|
||||||
// Cannot set margin top to videos-header because of the main header fixed position
|
// Cannot set margin top to videos-header because of the main header fixed position
|
||||||
$margin-top: 30px;
|
$margin-top: 2rem;
|
||||||
|
|
||||||
.videos-header {
|
.videos-header {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 1fr auto;
|
grid-template-columns: auto 1fr auto;
|
||||||
margin-bottom: 30px;
|
|
||||||
|
|
||||||
.title,
|
.title,
|
||||||
.title-subscription {
|
.title-subscription {
|
||||||
|
@ -21,9 +20,6 @@ $margin-top: 30px;
|
||||||
color: pvar(--mainForegroundColor);
|
color: pvar(--mainForegroundColor);
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-weight: $font-semibold;
|
font-weight: $font-semibold;
|
||||||
|
|
||||||
margin-top: $margin-top;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-subscription {
|
.title-subscription {
|
||||||
|
@ -39,7 +35,6 @@ $margin-top: 30px;
|
||||||
.action-block {
|
.action-block {
|
||||||
grid-column: 3;
|
grid-column: 3;
|
||||||
grid-row: 1/3;
|
grid-row: 1/3;
|
||||||
margin-top: $margin-top;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
my-feed {
|
my-feed {
|
||||||
|
@ -77,15 +72,15 @@ $margin-top: 30px;
|
||||||
@include margin-right(pvar(--horizontalMarginContent));
|
@include margin-right(pvar(--horizontalMarginContent));
|
||||||
|
|
||||||
.video-wrapper {
|
.video-wrapper {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: $mobile-view) {
|
@media screen and (max-width: $mobile-view) {
|
||||||
.videos-header,
|
.videos-header,
|
||||||
my-video-filters-header {
|
my-video-filters-header {
|
||||||
@include margin-left(15px);
|
@include margin-left(1rem);
|
||||||
@include margin-right(15px);
|
@include margin-right(1rem);
|
||||||
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
@ -95,9 +90,8 @@ $margin-top: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.videos-header {
|
.videos-header {
|
||||||
flex-direction: column;
|
text-align: center;
|
||||||
align-items: center;
|
width: 100%;
|
||||||
height: auto;
|
margin-bottom: 1rem;
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,10 +53,13 @@
|
||||||
|
|
||||||
<my-edit-button *ngIf="owned && touchScreenEditButton" [ptRouterLink]="[ '/my-library', 'video-playlists', playlist.uuid ]"></my-edit-button>
|
<my-edit-button *ngIf="owned && touchScreenEditButton" [ptRouterLink]="[ '/my-library', 'video-playlists', playlist.uuid ]"></my-edit-button>
|
||||||
|
|
||||||
<div *ngIf="owned" class="more dropdown-root" ngbDropdown #moreDropdown="ngbDropdown" placement="left auto"
|
<div
|
||||||
(openChange)="onDropdownOpenChange()" autoClose="outside" container="body"
|
*ngIf="owned" class="more dropdown-root" ngbDropdown #moreDropdown="ngbDropdown" placement="left auto"
|
||||||
|
(openChange)="onDropdownOpenChange()" autoClose="outside" container="body"
|
||||||
>
|
>
|
||||||
<my-global-icon iconName="more-vertical" ngbDropdownToggle role="button" class="icon-more" (click)="$event.preventDefault()"></my-global-icon>
|
<button class="border-0 p-0 more-button" (click)="$event.preventDefault()" ngbDropdownToggle>
|
||||||
|
<my-global-icon iconName="more-vertical"></my-global-icon>
|
||||||
|
</button>
|
||||||
|
|
||||||
<div ngbDropdownMenu>
|
<div ngbDropdownMenu>
|
||||||
<ng-container *ngIf="playlistElement.video">
|
<ng-container *ngIf="playlistElement.video">
|
||||||
|
|
|
@ -29,24 +29,6 @@ my-video-thumbnail,
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-bottom: 1px solid $separator-border-color;
|
border-bottom: 1px solid $separator-border-color;
|
||||||
|
|
||||||
.more {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: rgba(0, 0, 0, 0.05);
|
|
||||||
|
|
||||||
.more {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media not all and (hover: hover) and (pointer: fine) {
|
|
||||||
.more {
|
|
||||||
opacity: 1 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.playing {
|
&.playing {
|
||||||
background-color: rgba(0, 0, 0, 0.02);
|
background-color: rgba(0, 0, 0, 0.02);
|
||||||
}
|
}
|
||||||
|
@ -87,20 +69,38 @@ my-video-thumbnail,
|
||||||
}
|
}
|
||||||
|
|
||||||
.more {
|
.more {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-button {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|
||||||
&.show {
|
&::after {
|
||||||
opacity: 1;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-more {
|
my-global-icon {
|
||||||
@include apply-svg-color(pvar(--greyForegroundColor));
|
@include apply-svg-color(pvar(--greyForegroundColor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
display: flex;
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
&::after {
|
&:hover,
|
||||||
border: 0;
|
&:focus-within,
|
||||||
}
|
.show {
|
||||||
|
.more-button {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media not all and (hover: hover) and (pointer: fine) {
|
||||||
|
.more-button {
|
||||||
|
opacity: 1 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -183,7 +183,7 @@ my-video-thumbnail,
|
||||||
my-edit-button {
|
my-edit-button {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
||||||
+ .more {
|
+ .more-button {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -204,7 +204,7 @@ my-video-thumbnail,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
my-edit-button + .more {
|
my-edit-button + .more-button {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,16 +17,14 @@ class ChaptersPlugin extends Plugin {
|
||||||
this.player.ready(() => {
|
this.player.ready(() => {
|
||||||
player.addClass('vjs-chapters')
|
player.addClass('vjs-chapters')
|
||||||
|
|
||||||
this.player.one('durationchange', () => {
|
for (const chapter of this.chapters) {
|
||||||
for (const chapter of this.chapters) {
|
if (chapter.timecode === 0) continue
|
||||||
if (chapter.timecode === 0) continue
|
|
||||||
|
|
||||||
const marker = new ProgressBarMarkerComponent(player, { timecode: chapter.timecode })
|
const marker = new ProgressBarMarkerComponent(player, { timecode: chapter.timecode })
|
||||||
|
|
||||||
this.markers.push(marker)
|
this.markers.push(marker)
|
||||||
this.getSeekBar().addChild(marker)
|
this.getSeekBar().addChild(marker)
|
||||||
}
|
}
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +32,8 @@ class ChaptersPlugin extends Plugin {
|
||||||
for (const marker of this.markers) {
|
for (const marker of this.markers) {
|
||||||
this.getSeekBar().removeChild(marker)
|
this.getSeekBar().removeChild(marker)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
super.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
getChapter (timecode: number) {
|
getChapter (timecode: number) {
|
||||||
|
|
|
@ -9,16 +9,25 @@ export class ProgressBarMarkerComponent extends Component {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
||||||
constructor (player: videojs.Player, options?: ProgressBarMarkerComponentOptions & videojs.ComponentOptions) {
|
constructor (player: videojs.Player, options?: ProgressBarMarkerComponentOptions & videojs.ComponentOptions) {
|
||||||
super(player, options)
|
super(player, options)
|
||||||
|
|
||||||
|
const updateMarker = () => {
|
||||||
|
(this.el() as HTMLElement).style.setProperty('left', this.buildLeftStyle())
|
||||||
|
}
|
||||||
|
this.player().on('durationchange', updateMarker)
|
||||||
|
|
||||||
|
this.one('dispose', () => this.player().off('durationchange', updateMarker))
|
||||||
}
|
}
|
||||||
|
|
||||||
createEl () {
|
createEl () {
|
||||||
const left = (this.options_.timecode / this.player().duration()) * 100
|
|
||||||
|
|
||||||
return videojs.dom.createEl('span', {
|
return videojs.dom.createEl('span', {
|
||||||
className: 'vjs-marker',
|
className: 'vjs-marker',
|
||||||
style: `left: ${left}%`
|
style: `left: ${this.buildLeftStyle()}`
|
||||||
}) as HTMLButtonElement
|
}) as HTMLButtonElement
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private buildLeftStyle () {
|
||||||
|
return `${(this.options_.timecode / this.player().duration()) * 100}%`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
videojs.registerComponent('ProgressBarMarkerComponent', ProgressBarMarkerComponent)
|
videojs.registerComponent('ProgressBarMarkerComponent', ProgressBarMarkerComponent)
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -115,7 +115,7 @@
|
||||||
"bencode": "^4.0.0",
|
"bencode": "^4.0.0",
|
||||||
"bittorrent-tracker": "^10.0.12",
|
"bittorrent-tracker": "^10.0.12",
|
||||||
"bluebird": "^3.5.0",
|
"bluebird": "^3.5.0",
|
||||||
"bullmq": "^3.6.6",
|
"bullmq": "^4.12.3",
|
||||||
"bytes": "^3.0.0",
|
"bytes": "^3.0.0",
|
||||||
"chokidar": "^3.4.2",
|
"chokidar": "^3.4.2",
|
||||||
"commander": "^11.0.0",
|
"commander": "^11.0.0",
|
||||||
|
|
|
@ -172,15 +172,21 @@ async function getVideoStream (path: string, existingProbe?: FfprobeData) {
|
||||||
// Chapters
|
// Chapters
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
async function getChaptersFromContainer (path: string, existingProbe?: FfprobeData) {
|
async function getChaptersFromContainer (options: {
|
||||||
const metadata = existingProbe || await ffprobePromise(path)
|
path: string
|
||||||
|
maxTitleLength: number
|
||||||
|
ffprobe?: FfprobeData
|
||||||
|
}) {
|
||||||
|
const { path, maxTitleLength, ffprobe } = options
|
||||||
|
|
||||||
|
const metadata = ffprobe || await ffprobePromise(path)
|
||||||
|
|
||||||
if (!Array.isArray(metadata?.chapters)) return []
|
if (!Array.isArray(metadata?.chapters)) return []
|
||||||
|
|
||||||
return metadata.chapters
|
return metadata.chapters
|
||||||
.map(c => ({
|
.map(c => ({
|
||||||
timecode: c.start_time,
|
timecode: Math.round(c.start_time),
|
||||||
title: c['TAG:title']
|
title: (c['TAG:title'] || '').slice(0, maxTitleLength)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { VideoStateType } from '../videos/index.js'
|
||||||
import { VideoStudioTaskCut } from '../videos/studio/index.js'
|
import { VideoStudioTaskCut } from '../videos/studio/index.js'
|
||||||
import { SendEmailOptions } from './emailer.model.js'
|
import { SendEmailOptions } from './emailer.model.js'
|
||||||
|
|
||||||
export type JobState = 'active' | 'completed' | 'failed' | 'waiting' | 'delayed' | 'paused' | 'waiting-children'
|
export type JobState = 'active' | 'completed' | 'failed' | 'waiting' | 'delayed' | 'paused' | 'waiting-children' | 'prioritized'
|
||||||
|
|
||||||
export type JobType =
|
export type JobType =
|
||||||
| 'activitypub-cleaner'
|
| 'activitypub-cleaner'
|
||||||
|
|
|
@ -19,7 +19,7 @@ describe('Test videos chapters API validator', function () {
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
|
|
||||||
before(async function () {
|
before(async function () {
|
||||||
this.timeout(30000)
|
this.timeout(60000)
|
||||||
|
|
||||||
server = await createSingleServer(1)
|
server = await createSingleServer(1)
|
||||||
|
|
||||||
|
|
|
@ -199,7 +199,7 @@ describe('Test video studio API validator', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should fail with a video that is already waiting for edition', async function () {
|
it('Should fail with a video that is already waiting for edition', async function () {
|
||||||
this.timeout(120000)
|
this.timeout(360000)
|
||||||
|
|
||||||
await command.createEditionTasks({
|
await command.createEditionTasks({
|
||||||
videoId: videoUUID,
|
videoId: videoUUID,
|
||||||
|
@ -257,7 +257,7 @@ describe('Test video studio API validator', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should succeed with the correct params', async function () {
|
it('Should succeed with the correct params', async function () {
|
||||||
this.timeout(120000)
|
this.timeout(360000)
|
||||||
|
|
||||||
await cut(0, 2, HttpStatusCode.NO_CONTENT_204)
|
await cut(0, 2, HttpStatusCode.NO_CONTENT_204)
|
||||||
|
|
||||||
|
@ -291,7 +291,7 @@ describe('Test video studio API validator', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should succeed with the correct params', async function () {
|
it('Should succeed with the correct params', async function () {
|
||||||
this.timeout(120000)
|
this.timeout(360000)
|
||||||
|
|
||||||
await addWatermark('custom-thumbnail.jpg', HttpStatusCode.NO_CONTENT_204)
|
await addWatermark('custom-thumbnail.jpg', HttpStatusCode.NO_CONTENT_204)
|
||||||
|
|
||||||
|
@ -337,7 +337,7 @@ describe('Test video studio API validator', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should succeed with the correct params', async function () {
|
it('Should succeed with the correct params', async function () {
|
||||||
this.timeout(120000)
|
this.timeout(360000)
|
||||||
|
|
||||||
await addIntroOutro('add-intro', 'video_very_short_240p.mp4', HttpStatusCode.NO_CONTENT_204)
|
await addIntroOutro('add-intro', 'video_very_short_240p.mp4', HttpStatusCode.NO_CONTENT_204)
|
||||||
await waitJobs([ server ])
|
await waitJobs([ server ])
|
||||||
|
@ -347,7 +347,7 @@ describe('Test video studio API validator', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should check total quota when creating the task', async function () {
|
it('Should check total quota when creating the task', async function () {
|
||||||
this.timeout(120000)
|
this.timeout(360000)
|
||||||
|
|
||||||
const user = await server.users.create({ username: 'user_quota_1' })
|
const user = await server.users.create({ username: 'user_quota_1' })
|
||||||
const token = await server.login.getAccessToken('user_quota_1')
|
const token = await server.login.getAccessToken('user_quota_1')
|
||||||
|
|
|
@ -66,7 +66,7 @@ describe('Test admin notifications', function () {
|
||||||
|
|
||||||
joinPeerTubeServer.setLatestVersion('1.4.2')
|
joinPeerTubeServer.setLatestVersion('1.4.2')
|
||||||
|
|
||||||
await wait(3000)
|
await wait(4500)
|
||||||
await checkNewPeerTubeVersion({ ...baseParams, latestVersion: '1.4.2', checkType: 'absence' })
|
await checkNewPeerTubeVersion({ ...baseParams, latestVersion: '1.4.2', checkType: 'absence' })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -75,14 +75,14 @@ describe('Test admin notifications', function () {
|
||||||
|
|
||||||
joinPeerTubeServer.setLatestVersion('15.4.2')
|
joinPeerTubeServer.setLatestVersion('15.4.2')
|
||||||
|
|
||||||
await wait(3000)
|
await wait(4500)
|
||||||
await checkNewPeerTubeVersion({ ...baseParams, latestVersion: '15.4.2', checkType: 'presence' })
|
await checkNewPeerTubeVersion({ ...baseParams, latestVersion: '15.4.2', checkType: 'presence' })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should not send the same notification to admins', async function () {
|
it('Should not send the same notification to admins', async function () {
|
||||||
this.timeout(30000)
|
this.timeout(30000)
|
||||||
|
|
||||||
await wait(3000)
|
await wait(4500)
|
||||||
expect(adminNotifications.filter(n => n.type === UserNotificationType.NEW_PEERTUBE_VERSION)).to.have.lengthOf(1)
|
expect(adminNotifications.filter(n => n.type === UserNotificationType.NEW_PEERTUBE_VERSION)).to.have.lengthOf(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ describe('Test admin notifications', function () {
|
||||||
|
|
||||||
joinPeerTubeServer.setLatestVersion('15.4.3')
|
joinPeerTubeServer.setLatestVersion('15.4.3')
|
||||||
|
|
||||||
await wait(3000)
|
await wait(4500)
|
||||||
await checkNewPeerTubeVersion({ ...baseParams, latestVersion: '15.4.3', checkType: 'presence' })
|
await checkNewPeerTubeVersion({ ...baseParams, latestVersion: '15.4.3', checkType: 'presence' })
|
||||||
expect(adminNotifications.filter(n => n.type === UserNotificationType.NEW_PEERTUBE_VERSION)).to.have.lengthOf(2)
|
expect(adminNotifications.filter(n => n.type === UserNotificationType.NEW_PEERTUBE_VERSION)).to.have.lengthOf(2)
|
||||||
})
|
})
|
||||||
|
|
|
@ -39,7 +39,7 @@ describe('Test comments notifications', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should not send a new comment notification after a comment on another video', async function () {
|
it('Should not send a new comment notification after a comment on another video', async function () {
|
||||||
this.timeout(30000)
|
this.timeout(60000)
|
||||||
|
|
||||||
const { uuid, shortUUID } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
|
const { uuid, shortUUID } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ describe('Test comments notifications', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should not send a new comment notification if I comment my own video', async function () {
|
it('Should not send a new comment notification if I comment my own video', async function () {
|
||||||
this.timeout(30000)
|
this.timeout(60000)
|
||||||
|
|
||||||
const { uuid, shortUUID } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
|
const { uuid, shortUUID } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ describe('Test comments notifications', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should not send a new comment notification if the account is muted', async function () {
|
it('Should not send a new comment notification if the account is muted', async function () {
|
||||||
this.timeout(30000)
|
this.timeout(60000)
|
||||||
|
|
||||||
await servers[0].blocklist.addToMyBlocklist({ token: userToken, account: 'root' })
|
await servers[0].blocklist.addToMyBlocklist({ token: userToken, account: 'root' })
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ describe('Test comments notifications', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should send a new comment notification after a local comment on my video', async function () {
|
it('Should send a new comment notification after a local comment on my video', async function () {
|
||||||
this.timeout(30000)
|
this.timeout(60000)
|
||||||
|
|
||||||
const { uuid, shortUUID } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
|
const { uuid, shortUUID } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ describe('Test comments notifications', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should send a new comment notification after a remote comment on my video', async function () {
|
it('Should send a new comment notification after a remote comment on my video', async function () {
|
||||||
this.timeout(30000)
|
this.timeout(60000)
|
||||||
|
|
||||||
const { uuid, shortUUID } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
|
const { uuid, shortUUID } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@ describe('Test comments notifications', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should send a new comment notification after a local reply on my video', async function () {
|
it('Should send a new comment notification after a local reply on my video', async function () {
|
||||||
this.timeout(30000)
|
this.timeout(60000)
|
||||||
|
|
||||||
const { uuid, shortUUID } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
|
const { uuid, shortUUID } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ describe('Test comments notifications', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should send a new comment notification after a remote reply on my video', async function () {
|
it('Should send a new comment notification after a remote reply on my video', async function () {
|
||||||
this.timeout(30000)
|
this.timeout(60000)
|
||||||
|
|
||||||
const { uuid, shortUUID } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
|
const { uuid, shortUUID } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
|
||||||
await waitJobs(servers)
|
await waitJobs(servers)
|
||||||
|
@ -148,7 +148,7 @@ describe('Test comments notifications', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should convert markdown in comment to html', async function () {
|
it('Should convert markdown in comment to html', async function () {
|
||||||
this.timeout(30000)
|
this.timeout(60000)
|
||||||
|
|
||||||
const { uuid } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'cool video' } })
|
const { uuid } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'cool video' } })
|
||||||
|
|
||||||
|
@ -178,7 +178,7 @@ describe('Test comments notifications', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should not send a new mention comment notification if I mention the video owner', async function () {
|
it('Should not send a new mention comment notification if I mention the video owner', async function () {
|
||||||
this.timeout(30000)
|
this.timeout(60000)
|
||||||
|
|
||||||
const { uuid, shortUUID } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
|
const { uuid, shortUUID } = await servers[0].videos.upload({ token: userToken, attributes: { name: 'super video' } })
|
||||||
|
|
||||||
|
@ -189,7 +189,7 @@ describe('Test comments notifications', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should not send a new mention comment notification if I mention myself', async function () {
|
it('Should not send a new mention comment notification if I mention myself', async function () {
|
||||||
this.timeout(30000)
|
this.timeout(60000)
|
||||||
|
|
||||||
const { uuid, shortUUID } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
|
const { uuid, shortUUID } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
|
||||||
|
|
||||||
|
@ -200,7 +200,7 @@ describe('Test comments notifications', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should not send a new mention notification if the account is muted', async function () {
|
it('Should not send a new mention notification if the account is muted', async function () {
|
||||||
this.timeout(30000)
|
this.timeout(60000)
|
||||||
|
|
||||||
await servers[0].blocklist.addToMyBlocklist({ token: userToken, account: 'root' })
|
await servers[0].blocklist.addToMyBlocklist({ token: userToken, account: 'root' })
|
||||||
|
|
||||||
|
@ -215,7 +215,7 @@ describe('Test comments notifications', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should not send a new mention notification if the remote account mention a local account', async function () {
|
it('Should not send a new mention notification if the remote account mention a local account', async function () {
|
||||||
this.timeout(30000)
|
this.timeout(60000)
|
||||||
|
|
||||||
const { uuid, shortUUID } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
|
const { uuid, shortUUID } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
|
||||||
|
|
||||||
|
@ -229,7 +229,7 @@ describe('Test comments notifications', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should send a new mention notification after local comments', async function () {
|
it('Should send a new mention notification after local comments', async function () {
|
||||||
this.timeout(30000)
|
this.timeout(60000)
|
||||||
|
|
||||||
const { uuid, shortUUID } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
|
const { uuid, shortUUID } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
|
||||||
|
|
||||||
|
@ -245,7 +245,7 @@ describe('Test comments notifications', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should send a new mention notification after remote comments', async function () {
|
it('Should send a new mention notification after remote comments', async function () {
|
||||||
this.timeout(30000)
|
this.timeout(60000)
|
||||||
|
|
||||||
const { uuid, shortUUID } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
|
const { uuid, shortUUID } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
|
||||||
|
|
||||||
|
@ -277,7 +277,7 @@ describe('Test comments notifications', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should convert markdown in comment to html', async function () {
|
it('Should convert markdown in comment to html', async function () {
|
||||||
this.timeout(30000)
|
this.timeout(60000)
|
||||||
|
|
||||||
const { uuid } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
|
const { uuid } = await servers[0].videos.upload({ attributes: { name: 'super video' } })
|
||||||
|
|
||||||
|
|
|
@ -233,7 +233,7 @@ describe('Test user notifications', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should not send a notification if the wait transcoding is false', async function () {
|
it('Should not send a notification if the wait transcoding is false', async function () {
|
||||||
this.timeout(100_000)
|
this.timeout(240000)
|
||||||
|
|
||||||
await uploadRandomVideoOnServers(servers, 2, { waitTranscoding: false })
|
await uploadRandomVideoOnServers(servers, 2, { waitTranscoding: false })
|
||||||
await waitJobs(servers)
|
await waitJobs(servers)
|
||||||
|
@ -245,7 +245,7 @@ describe('Test user notifications', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should send a notification even if the video is not transcoded in other resolutions', async function () {
|
it('Should send a notification even if the video is not transcoded in other resolutions', async function () {
|
||||||
this.timeout(100_000)
|
this.timeout(240000)
|
||||||
|
|
||||||
const { name, shortUUID } = await uploadRandomVideoOnServers(servers, 2, { waitTranscoding: true, fixture: 'video_short_240p.mp4' })
|
const { name, shortUUID } = await uploadRandomVideoOnServers(servers, 2, { waitTranscoding: true, fixture: 'video_short_240p.mp4' })
|
||||||
await waitJobs(servers)
|
await waitJobs(servers)
|
||||||
|
@ -254,7 +254,7 @@ describe('Test user notifications', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should send a notification with a transcoded video', async function () {
|
it('Should send a notification with a transcoded video', async function () {
|
||||||
this.timeout(100_000)
|
this.timeout(240000)
|
||||||
|
|
||||||
const { name, shortUUID } = await uploadRandomVideoOnServers(servers, 2, { waitTranscoding: true })
|
const { name, shortUUID } = await uploadRandomVideoOnServers(servers, 2, { waitTranscoding: true })
|
||||||
await waitJobs(servers)
|
await waitJobs(servers)
|
||||||
|
@ -263,7 +263,7 @@ describe('Test user notifications', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should send a notification when an imported video is transcoded', async function () {
|
it('Should send a notification when an imported video is transcoded', async function () {
|
||||||
this.timeout(120000)
|
this.timeout(240000)
|
||||||
|
|
||||||
const name = 'video import ' + buildUUID()
|
const name = 'video import ' + buildUUID()
|
||||||
|
|
||||||
|
|
|
@ -232,7 +232,7 @@ describe('Test stats (excluding redundancy)', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should have the correct AP stats', async function () {
|
it('Should have the correct AP stats', async function () {
|
||||||
this.timeout(120000)
|
this.timeout(240000)
|
||||||
|
|
||||||
await servers[0].config.disableTranscoding()
|
await servers[0].config.disableTranscoding()
|
||||||
|
|
||||||
|
|
|
@ -489,7 +489,7 @@ describe('Test video transcoding', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should downscale to the closest divisor standard framerate', async function () {
|
it('Should downscale to the closest divisor standard framerate', async function () {
|
||||||
this.timeout(200_000)
|
this.timeout(360_000)
|
||||||
|
|
||||||
let tempFixturePath: string
|
let tempFixturePath: string
|
||||||
|
|
||||||
|
|
|
@ -74,7 +74,7 @@ describe('Test video playlists', function () {
|
||||||
let commands: PlaylistsCommand[]
|
let commands: PlaylistsCommand[]
|
||||||
|
|
||||||
before(async function () {
|
before(async function () {
|
||||||
this.timeout(240000)
|
this.timeout(360000)
|
||||||
|
|
||||||
servers = await createMultipleServers(3)
|
servers = await createMultipleServers(3)
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { HttpStatusCode, VideoCreate, VideoPrivacy, VideoState } from '@peertube
|
||||||
import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger.js'
|
import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger.js'
|
||||||
import { createReqFiles } from '../../../helpers/express-utils.js'
|
import { createReqFiles } from '../../../helpers/express-utils.js'
|
||||||
import { logger, loggerTagsFactory } from '../../../helpers/logger.js'
|
import { logger, loggerTagsFactory } from '../../../helpers/logger.js'
|
||||||
import { MIMETYPES } from '../../../initializers/constants.js'
|
import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../../initializers/constants.js'
|
||||||
import { sequelizeTypescript } from '../../../initializers/database.js'
|
import { sequelizeTypescript } from '../../../initializers/database.js'
|
||||||
import { Hooks } from '../../../lib/plugins/hooks.js'
|
import { Hooks } from '../../../lib/plugins/hooks.js'
|
||||||
import { generateLocalVideoMiniature } from '../../../lib/thumbnail.js'
|
import { generateLocalVideoMiniature } from '../../../lib/thumbnail.js'
|
||||||
|
@ -145,7 +145,10 @@ async function addVideo (options: {
|
||||||
const videoFile = await buildNewFile({ path: videoPhysicalFile.path, mode: 'web-video' })
|
const videoFile = await buildNewFile({ path: videoPhysicalFile.path, mode: 'web-video' })
|
||||||
const originalFilename = videoPhysicalFile.originalname
|
const originalFilename = videoPhysicalFile.originalname
|
||||||
|
|
||||||
const containerChapters = await getChaptersFromContainer(videoPhysicalFile.path)
|
const containerChapters = await getChaptersFromContainer({
|
||||||
|
path: videoPhysicalFile.path,
|
||||||
|
maxTitleLength: CONSTRAINTS_FIELDS.VIDEO_CHAPTERS.TITLE.max
|
||||||
|
})
|
||||||
logger.debug(`Got ${containerChapters.length} chapters from video "${video.name}" container`, { containerChapters, ...lTags(video.uuid) })
|
logger.debug(`Got ${containerChapters.length} chapters from video "${video.name}" container`, { containerChapters, ...lTags(video.uuid) })
|
||||||
|
|
||||||
// Move physical file
|
// Move physical file
|
||||||
|
|
|
@ -2,10 +2,10 @@ import { JobState } from '@peertube/peertube-models'
|
||||||
import { jobTypes } from '@server/lib/job-queue/job-queue.js'
|
import { jobTypes } from '@server/lib/job-queue/job-queue.js'
|
||||||
import { exists } from './misc.js'
|
import { exists } from './misc.js'
|
||||||
|
|
||||||
const jobStates: JobState[] = [ 'active', 'completed', 'failed', 'waiting', 'delayed', 'paused', 'waiting-children' ]
|
const jobStates = new Set<JobState>([ 'active', 'completed', 'failed', 'waiting', 'delayed', 'paused', 'waiting-children', 'prioritized' ])
|
||||||
|
|
||||||
function isValidJobState (value: JobState) {
|
function isValidJobState (value: JobState) {
|
||||||
return exists(value) && jobStates.includes(value)
|
return exists(value) && jobStates.has(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
function isValidJobType (value: any) {
|
function isValidJobType (value: any) {
|
||||||
|
|
|
@ -41,7 +41,7 @@ import {
|
||||||
import { logger } from '../../../helpers/logger.js'
|
import { logger } from '../../../helpers/logger.js'
|
||||||
import { getSecureTorrentName } from '../../../helpers/utils.js'
|
import { getSecureTorrentName } from '../../../helpers/utils.js'
|
||||||
import { createTorrentAndSetInfoHash, downloadWebTorrentVideo } from '../../../helpers/webtorrent.js'
|
import { createTorrentAndSetInfoHash, downloadWebTorrentVideo } from '../../../helpers/webtorrent.js'
|
||||||
import { JOB_TTL } from '../../../initializers/constants.js'
|
import { CONSTRAINTS_FIELDS, JOB_TTL } from '../../../initializers/constants.js'
|
||||||
import { sequelizeTypescript } from '../../../initializers/database.js'
|
import { sequelizeTypescript } from '../../../initializers/database.js'
|
||||||
import { VideoFileModel } from '../../../models/video/video-file.js'
|
import { VideoFileModel } from '../../../models/video/video-file.js'
|
||||||
import { VideoImportModel } from '../../../models/video/video-import.js'
|
import { VideoImportModel } from '../../../models/video/video-import.js'
|
||||||
|
@ -143,16 +143,20 @@ async function processFile (downloader: () => Promise<string>, videoImport: MVid
|
||||||
throw new Error('The user video quota is exceeded with this video to import.')
|
throw new Error('The user video quota is exceeded with this video to import.')
|
||||||
}
|
}
|
||||||
|
|
||||||
const probe = await ffprobePromise(tempVideoPath)
|
const ffprobe = await ffprobePromise(tempVideoPath)
|
||||||
|
|
||||||
const { resolution } = await isAudioFile(tempVideoPath, probe)
|
const { resolution } = await isAudioFile(tempVideoPath, ffprobe)
|
||||||
? { resolution: VideoResolution.H_NOVIDEO }
|
? { resolution: VideoResolution.H_NOVIDEO }
|
||||||
: await getVideoStreamDimensionsInfo(tempVideoPath, probe)
|
: await getVideoStreamDimensionsInfo(tempVideoPath, ffprobe)
|
||||||
|
|
||||||
const fps = await getVideoStreamFPS(tempVideoPath, probe)
|
const fps = await getVideoStreamFPS(tempVideoPath, ffprobe)
|
||||||
const duration = await getVideoStreamDuration(tempVideoPath, probe)
|
const duration = await getVideoStreamDuration(tempVideoPath, ffprobe)
|
||||||
|
|
||||||
const containerChapters = await getChaptersFromContainer(tempVideoPath, probe)
|
const containerChapters = await getChaptersFromContainer({
|
||||||
|
path: tempVideoPath,
|
||||||
|
maxTitleLength: CONSTRAINTS_FIELDS.VIDEO_CHAPTERS.TITLE.max,
|
||||||
|
ffprobe
|
||||||
|
})
|
||||||
|
|
||||||
// Prepare video file object for creation in database
|
// Prepare video file object for creation in database
|
||||||
const fileExt = getLowercaseExtension(tempVideoPath)
|
const fileExt = getLowercaseExtension(tempVideoPath)
|
||||||
|
|
|
@ -257,6 +257,9 @@ class JobQueue {
|
||||||
queue.on('error', err => { logger.error('Error in job queue %s.', handlerName, { err }) })
|
queue.on('error', err => { logger.error('Error in job queue %s.', handlerName, { err }) })
|
||||||
|
|
||||||
this.queues[handlerName] = queue
|
this.queues[handlerName] = queue
|
||||||
|
|
||||||
|
queue.removeDeprecatedPriorityKey()
|
||||||
|
.catch(err => logger.error('Cannot remove bullmq deprecated priority keys of ' + handlerName, { err }))
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildQueueEvent (handlerName: JobType) {
|
private buildQueueEvent (handlerName: JobType) {
|
||||||
|
@ -455,12 +458,15 @@ class JobQueue {
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildStateFilter (state?: JobState) {
|
private buildStateFilter (state?: JobState) {
|
||||||
if (!state) return jobStates
|
if (!state) return Array.from(jobStates)
|
||||||
|
|
||||||
const states = [ state ]
|
const states = [ state ]
|
||||||
|
|
||||||
// Include parent if filtering on waiting
|
// Include parent and prioritized if filtering on waiting
|
||||||
if (state === 'waiting') states.push('waiting-children')
|
if (state === 'waiting') {
|
||||||
|
states.push('waiting-children')
|
||||||
|
states.push('prioritized')
|
||||||
|
}
|
||||||
|
|
||||||
return states
|
return states
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { buildVideoEmbedPath, buildVideoWatchPath, pick } from '@peertube/peertube-core-utils'
|
import { buildVideoEmbedPath, buildVideoWatchPath, pick, wait } from '@peertube/peertube-core-utils'
|
||||||
import { ffprobePromise, getAudioStream, getVideoStreamDimensionsInfo, getVideoStreamFPS, hasAudioStream } from '@peertube/peertube-ffmpeg'
|
import { ffprobePromise, getAudioStream, getVideoStreamDimensionsInfo, getVideoStreamFPS, hasAudioStream } from '@peertube/peertube-ffmpeg'
|
||||||
import {
|
import {
|
||||||
ResultList,
|
ResultList,
|
||||||
|
@ -1925,7 +1925,20 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
|
||||||
? getHLSRedundancyDirectory(this)
|
? getHLSRedundancyDirectory(this)
|
||||||
: getHLSDirectory(this)
|
: getHLSDirectory(this)
|
||||||
|
|
||||||
await remove(directoryPath)
|
try {
|
||||||
|
await remove(directoryPath)
|
||||||
|
} catch (err) {
|
||||||
|
// If it's a live, ffmpeg may have added another file while fs-extra is removing the directory
|
||||||
|
// So wait a little bit and retry
|
||||||
|
if (err.code === 'ENOTEMPTY') {
|
||||||
|
await wait(1000)
|
||||||
|
await remove(directoryPath)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
|
||||||
if (isRedundancy !== true) {
|
if (isRedundancy !== true) {
|
||||||
const streamingPlaylistWithFiles = streamingPlaylist as MStreamingPlaylistFilesVideo
|
const streamingPlaylistWithFiles = streamingPlaylist as MStreamingPlaylistFilesVideo
|
||||||
|
|
18
yarn.lock
18
yarn.lock
|
@ -3572,17 +3572,18 @@ builtins@^5.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
semver "^7.0.0"
|
semver "^7.0.0"
|
||||||
|
|
||||||
bullmq@^3.6.6:
|
bullmq@^4.12.3:
|
||||||
version "3.15.8"
|
version "4.12.3"
|
||||||
resolved "https://registry.yarnpkg.com/bullmq/-/bullmq-3.15.8.tgz#e8ec5b46b0b7d7ce57e509280d03745109411e05"
|
resolved "https://registry.yarnpkg.com/bullmq/-/bullmq-4.12.3.tgz#0c649b9a5e48227519c526ee9edd96b982eee22d"
|
||||||
integrity sha512-k3uimHGhl5svqD7SEak+iI6c5DxeLOaOXzCufI9Ic0ST3nJr69v71TGR4cXCTXdgCff3tLec5HgoBnfyWjgn5A==
|
integrity sha512-4uPp4NQTALFF+eFK7g8VJM+rt0aiduQdzBomgiEO1OK4OE+TdgC6cjGXooKI/asuB8iDhSZ+pSnGYy5Xyr6qRA==
|
||||||
dependencies:
|
dependencies:
|
||||||
cron-parser "^4.6.0"
|
cron-parser "^4.6.0"
|
||||||
glob "^8.0.3"
|
glob "^8.0.3"
|
||||||
ioredis "^5.3.2"
|
ioredis "^5.3.2"
|
||||||
lodash "^4.17.21"
|
lodash "^4.17.21"
|
||||||
msgpackr "^1.6.2"
|
msgpackr "^1.6.2"
|
||||||
semver "^7.3.7"
|
node-abort-controller "^3.1.1"
|
||||||
|
semver "^7.5.4"
|
||||||
tslib "^2.0.0"
|
tslib "^2.0.0"
|
||||||
uuid "^9.0.0"
|
uuid "^9.0.0"
|
||||||
|
|
||||||
|
@ -7458,6 +7459,11 @@ nice-try@^1.0.4:
|
||||||
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
|
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
|
||||||
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
|
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
|
||||||
|
|
||||||
|
node-abort-controller@^3.1.1:
|
||||||
|
version "3.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548"
|
||||||
|
integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==
|
||||||
|
|
||||||
node-addon-api@^3.0.0:
|
node-addon-api@^3.0.0:
|
||||||
version "3.2.1"
|
version "3.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161"
|
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161"
|
||||||
|
@ -8911,7 +8917,7 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.3.1:
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
|
||||||
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
|
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
|
||||||
|
|
||||||
semver@^7.0.0, semver@^7.3.2, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.1, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4:
|
semver@^7.0.0, semver@^7.3.2, semver@^7.3.5, semver@^7.3.8, semver@^7.5.1, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4:
|
||||||
version "7.5.4"
|
version "7.5.4"
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
|
||||||
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
|
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
|
||||||
|
|
Loading…
Reference in New Issue
Block a user