diff --git a/common/config/private/IPrivateConfig.ts b/common/config/private/IPrivateConfig.ts index e8a6b40c..ff33dc2f 100644 --- a/common/config/private/IPrivateConfig.ts +++ b/common/config/private/IPrivateConfig.ts @@ -77,13 +77,17 @@ export interface TaskConfig { scheduled: TaskScheduleDTO[]; } +export type codecType = 'libvpx-vp9' | 'libx264' | 'libvpx' | 'libx265'; +export type resolutionType = 240 | 360 | 480 | 720 | 1080 | 1440 | 2160 | 4320; +export type formatType = 'mp4' | 'webm'; + export interface VideoConfig { transcoding: { bitRate: number, - resolution: 240 | 360 | 480 | 720 | 1080 | 1440 | 2160 | 4320, + resolution: resolutionType, fps: number, - codec: 'libvpx-vp9' | 'libx264' | 'libvpx', - format: 'mp4' | 'webm' + codec: codecType, + format: formatType }; } diff --git a/frontend/app/ui/settings/_abstract/abstract.settings.component.css b/frontend/app/ui/settings/_abstract/abstract.settings.component.css index 1fdf46fb..891d3705 100644 --- a/frontend/app/ui/settings/_abstract/abstract.settings.component.css +++ b/frontend/app/ui/settings/_abstract/abstract.settings.component.css @@ -6,10 +6,6 @@ margin-left: 10px; } -.form-control { - margin: 5px 0; -} - .switch-wrapper { display: inline-block; diff --git a/frontend/app/ui/settings/indexing/indexing.settings.component.html b/frontend/app/ui/settings/indexing/indexing.settings.component.html index 9137ca02..33307b3b 100644 --- a/frontend/app/ui/settings/indexing/indexing.settings.component.html +++ b/frontend/app/ui/settings/indexing/indexing.settings.component.html @@ -102,8 +102,8 @@
indexing: {{Progress.comment}}
- elapsed: {{TimeElapsed | duration}}
- left: {{TimeLeft | duration}} + elapsed: {{tasksService.calcTimeElapsed(Progress) | duration}}
+ left: {{tasksService.calcTimeLeft(Progress) | duration}}
+

Misc:

+

Navigation bar:

diff --git a/frontend/app/ui/settings/scheduled-tasks.service.ts b/frontend/app/ui/settings/scheduled-tasks.service.ts index 07f4d2ea..a36d065a 100644 --- a/frontend/app/ui/settings/scheduled-tasks.service.ts +++ b/frontend/app/ui/settings/scheduled-tasks.service.ts @@ -7,70 +7,81 @@ import {NetworkService} from '../../model/network/network.service'; export class ScheduledTasksService { - public progress: BehaviorSubject<{ [key: string]: TaskProgressDTO }>; - public onTaskFinish: EventEmitter = new EventEmitter(); - timer: number = null; - private subscribers = 0; + public progress: BehaviorSubject<{ [key: string]: TaskProgressDTO }>; + public onTaskFinish: EventEmitter = new EventEmitter(); + timer: number = null; + private subscribers = 0; - constructor(private _networkService: NetworkService) { - this.progress = new BehaviorSubject({}); - } - - - subscribeToProgress(): void { - this.incSubscribers(); - } - - unsubscribeFromProgress(): void { - this.decSubscribers(); - } - - public forceUpdate() { - return this.getProgress(); - } - - public async start(id: string, config?: any): Promise { - await this._networkService.postJson('/admin/tasks/scheduled/' + id + '/start', {config: config}); - this.forceUpdate(); - } - - public async stop(id: string): Promise { - await this._networkService.postJson('/admin/tasks/scheduled/' + id + '/stop'); - this.forceUpdate(); - } - - protected async getProgress() { - const prevPrg = this.progress.value; - this.progress.next(await this._networkService.getJson<{ [key: string]: TaskProgressDTO }>('/admin/tasks/scheduled/progress')); - for (const prg in prevPrg) { - if (!this.progress.value.hasOwnProperty(prg)) { - this.onTaskFinish.emit(prg); - } + constructor(private _networkService: NetworkService) { + this.progress = new BehaviorSubject({}); } - } - protected getProgressPeriodically() { - if (this.timer != null || this.subscribers === 0) { - return; + public calcTimeElapsed(progress: TaskProgressDTO) { + if (progress) { + return (progress.time.current - progress.time.start); + } } - let repeatTime = 5000; - if (Object.values(this.progress.value).length === 0) { - repeatTime = 10000; + + public calcTimeLeft(progress: TaskProgressDTO) { + if (progress) { + return (progress.time.current - progress.time.start) / progress.progress * progress.left; + } } - this.timer = window.setTimeout(async () => { - await this.getProgress(); - this.timer = null; - this.getProgressPeriodically(); - }, repeatTime); - } - private incSubscribers() { - this.subscribers++; - this.getProgressPeriodically(); - } + subscribeToProgress(): void { + this.incSubscribers(); + } - private decSubscribers() { - this.subscribers--; - } + unsubscribeFromProgress(): void { + this.decSubscribers(); + } + + public forceUpdate() { + return this.getProgress(); + } + + public async start(id: string, config?: any): Promise { + await this._networkService.postJson('/admin/tasks/scheduled/' + id + '/start', {config: config}); + this.forceUpdate(); + } + + public async stop(id: string): Promise { + await this._networkService.postJson('/admin/tasks/scheduled/' + id + '/stop'); + this.forceUpdate(); + } + + protected async getProgress() { + const prevPrg = this.progress.value; + this.progress.next(await this._networkService.getJson<{ [key: string]: TaskProgressDTO }>('/admin/tasks/scheduled/progress')); + for (const prg in prevPrg) { + if (!this.progress.value.hasOwnProperty(prg)) { + this.onTaskFinish.emit(prg); + } + } + } + + protected getProgressPeriodically() { + if (this.timer != null || this.subscribers === 0) { + return; + } + let repeatTime = 5000; + if (Object.values(this.progress.value).length === 0) { + repeatTime = 10000; + } + this.timer = window.setTimeout(async () => { + await this.getProgress(); + this.timer = null; + this.getProgressPeriodically(); + }, repeatTime); + } + + private incSubscribers() { + this.subscribers++; + this.getProgressPeriodically(); + } + + private decSubscribers() { + this.subscribers--; + } } diff --git a/frontend/app/ui/settings/video/video.settings.component.css b/frontend/app/ui/settings/video/video.settings.component.css index e69de29b..29ed858a 100644 --- a/frontend/app/ui/settings/video/video.settings.component.css +++ b/frontend/app/ui/settings/video/video.settings.component.css @@ -0,0 +1,4 @@ +.buttons-row { + margin-top: 10px; + margin-bottom: 20px; +} diff --git a/frontend/app/ui/settings/video/video.settings.component.html b/frontend/app/ui/settings/video/video.settings.component.html index 01070f7b..30c78891 100644 --- a/frontend/app/ui/settings/video/video.settings.component.html +++ b/frontend/app/ui/settings/video/video.settings.component.html @@ -1,7 +1,8 @@
- Video settings* + Video settings + *
+ [(ngModel)]="settings.client.enabled">
- Video support uses ffmpeg. ffmpeg and ffprobe binaries need to be available in the PATH or the @ffmpeg-installer/ffmpeg and @ffprobe-installer/ffprobe optional node packages need to be installed.  + Video support uses ffmpeg. ffmpeg and ffprobe binaries need to be available in the PATH or the + @ffmpeg-installer/ffmpeg and @ffprobe-installer/ffprobe optional node packages need to be installed. + + +
+ +

Video transcoding:

+
+ +
+ + Select the format to encode your videos to. +
+
+ +
+ +
+ + This codec will be used for the transcoding. +
+
+ +
+ +
+ + Resolution of the output video. +
+
+ +
+ +
+ + Target frame per second (fps) of the output video. +
+
+ +
+ +
+ +
+ mbps +
+ Target bit rate for the output video. This should be less than the + upload rate of your home server. +
+
+ +
+
+ + To ensure smooth video playback, video transcoding is recommended to a lower bit rate than the + server's upload rate. +   + The transcoded videos will be save to the thumbnail folder.  + You can trigger the transcoding manually, but you can also create an automatic encoding task in + advanced settings mode. +   + +
+ + + + + + + +
+
+ indexing: {{Progress.comment}}
+ elapsed: {{tasksService.calcTimeElapsed(Progress) | duration}}
+ left: {{tasksService.calcTimeLeft(Progress) | duration}} +
+
+ {{Progress.progress}} + /{{Progress.progress + Progress.left}} +
+
+
+ +
diff --git a/frontend/app/ui/settings/video/video.settings.component.ts b/frontend/app/ui/settings/video/video.settings.component.ts index 428ff11e..8db230f3 100644 --- a/frontend/app/ui/settings/video/video.settings.component.ts +++ b/frontend/app/ui/settings/video/video.settings.component.ts @@ -6,6 +6,10 @@ import {NavigationService} from '../../../model/navigation.service'; import {NotificationService} from '../../../model/notification.service'; import {ClientConfig} from '../../../../../common/config/public/ConfigClass'; import {I18n} from '@ngx-translate/i18n-polyfill'; +import {codecType, formatType, resolutionType, VideoConfig} from '../../../../../common/config/private/IPrivateConfig'; +import {ScheduledTasksService} from '../scheduled-tasks.service'; +import {DefaultsTasks} from '../../../../../common/entities/task/TaskDTO'; +import {ErrorDTO} from '../../../../../common/entities/Error'; @Component({ @@ -15,17 +19,108 @@ import {I18n} from '@ngx-translate/i18n-polyfill'; './../_abstract/abstract.settings.component.css'], providers: [VideoSettingsService], }) -export class VideoSettingsComponent extends SettingsComponent { +export class VideoSettingsComponent extends SettingsComponent<{ server: VideoConfig, client: ClientConfig.VideoConfig }> { + + resolutions: resolutionType[] = [360, 480, 720, 1080, 1440, 2160, 4320]; + codecs: { [key: string]: codecType[] } = {webm: ['libvpx', 'libvpx-vp9'], mp4: ['libx264', 'libx265']}; + formats: formatType[] = ['mp4', 'webm']; + fps = [24, 25, 30, 48, 50, 60]; constructor(_authService: AuthenticationService, _navigation: NavigationService, _settingsService: VideoSettingsService, + public tasksService: ScheduledTasksService, notification: NotificationService, i18n: I18n) { - super(i18n('Video'), _authService, _navigation, _settingsService, notification, i18n, s => s.Client.Video); + super(i18n('Video'), _authService, _navigation, _settingsService, notification, i18n, s => ({ + client: s.Client.Video, + server: s.Server.Video + })); } + get Progress() { + return this.tasksService.progress.value[DefaultsTasks[DefaultsTasks['Video Converting']]]; + } + + get bitRate(): number { + return this.settings.server.transcoding.bitRate / 1024 / 1024; + } + + set bitRate(value: number) { + this.settings.server.transcoding.bitRate = Math.round(value * 1024 * 1024); + } + + getRecommendedBitRate(resolution: number, fps: number) { + let bitRate = 1024 * 1024; + if (resolution <= 360) { + bitRate = 1024 * 1024; + } else if (resolution <= 480) { + bitRate = 2.5 * 1024 * 1024; + } else if (resolution <= 720) { + bitRate = 5 * 1024 * 1024; + } else if (resolution <= 1080) { + bitRate = 8 * 1024 * 1024; + } else if (resolution <= 1440) { + bitRate = 16 * 1024 * 1024; + } else if (resolution <= 2016) { + bitRate = 40 * 1024 * 1024; + } + + if (fps > 30) { + bitRate *= 1.5; + } + + return bitRate; + } + + updateBitRate() { + this.settings.server.transcoding.bitRate = this.getRecommendedBitRate(this.settings.server.transcoding.resolution, + this.settings.server.transcoding.fps); + } + + formatChanged(format: formatType) { + this.settings.server.transcoding.codec = this.codecs[format][0]; + } + + + async transcode() { + this.inProgress = true; + this.error = ''; + try { + await this.tasksService.start(DefaultsTasks[DefaultsTasks['Video Converting']]); + this.notification.info(this.i18n('Video transcoding started')); + this.inProgress = false; + return true; + } catch (err) { + console.log(err); + if (err.message) { + this.error = (err).message; + } + } + + this.inProgress = false; + return false; + } + + async cancelTranscoding() { + this.inProgress = true; + this.error = ''; + try { + await this.tasksService.stop(DefaultsTasks[DefaultsTasks['Video Converting']]); + this.notification.info(this.i18n('Video transcoding interrupted')); + this.inProgress = false; + return true; + } catch (err) { + console.log(err); + if (err.message) { + this.error = (err).message; + } + } + + this.inProgress = false; + return false; + } }