diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f43c8722..fe0d4c340 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## v6.3.2 + +### Bug fixes + + * Fix 403 error when downloading private/internal video + * Don't crash video federation and live replay generation on missing thumbnail/preview + * Fix advanced search input with multiple automatic search tokens + * Fix player "Copy URL" when the video is fullscreen + * Fix account videos search + * Add missing max transcoding fps config in admin + * Don't add mobile buttons if the player controls are disabled + + ## v6.3.1 ### IMPORTANT NOTES diff --git a/client/package.json b/client/package.json index 73c868f7e..2a7c9da72 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "peertube-client", - "version": "6.3.1", + "version": "6.3.2", "private": true, "license": "AGPL-3.0", "author": { diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts index 98ca692a7..ebc669801 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts @@ -22,6 +22,7 @@ import { SERVICES_TWITTER_USERNAME_VALIDATOR, SIGNUP_LIMIT_VALIDATOR, SIGNUP_MINIMUM_AGE_VALIDATOR, + TRANSCODING_MAX_FPS_VALIDATOR, TRANSCODING_THREADS_VALIDATOR } from '@app/shared/form-validators/custom-config-validators' import { USER_VIDEO_QUOTA_DAILY_VALIDATOR, USER_VIDEO_QUOTA_VALIDATOR } from '@app/shared/form-validators/user-validators' @@ -30,6 +31,7 @@ import { FormReactiveService } from '@app/shared/shared-forms/form-reactive.serv import { CustomPageService } from '@app/shared/shared-main/custom-page/custom-page.service' import { NgbNav, NgbNavContent, NgbNavItem, NgbNavLink, NgbNavLinkBase, NgbNavOutlet } from '@ng-bootstrap/ng-bootstrap' import { CustomConfig, CustomPage, HTMLServerConfig } from '@peertube/peertube-models' +import merge from 'lodash-es/merge' import omit from 'lodash-es/omit' import { forkJoin } from 'rxjs' import { SelectOptionsItem } from 'src/types/select-options-item.model' @@ -40,7 +42,6 @@ import { EditHomepageComponent } from './edit-homepage.component' import { EditInstanceInformationComponent } from './edit-instance-information.component' import { EditLiveConfigurationComponent } from './edit-live-configuration.component' import { EditVODTranscodingComponent } from './edit-vod-transcoding.component' -import merge from 'lodash-es/merge' type ComponentCustomConfig = CustomConfig & { instanceCustomHomepage: CustomPage @@ -239,6 +240,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { }, remoteRunners: { enabled: null + }, + fps: { + max: TRANSCODING_MAX_FPS_VALIDATOR } }, live: { @@ -260,6 +264,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { alwaysTranscodeOriginalResolution: null, remoteRunners: { enabled: null + }, + fps: { + max: TRANSCODING_MAX_FPS_VALIDATOR } } }, diff --git a/client/src/app/+admin/config/edit-custom-config/edit-live-configuration.component.html b/client/src/app/+admin/config/edit-custom-config/edit-live-configuration.component.html index d6edd95f2..f693eeab4 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-live-configuration.component.html +++ b/client/src/app/+admin/config/edit-custom-config/edit-live-configuration.component.html @@ -48,7 +48,7 @@
- (-1 for "unlimited") + (-1 for "unlimited")
@@ -60,7 +60,7 @@
- (-1 for "unlimited") + (-1 for "unlimited")
@@ -116,6 +116,19 @@
+
+ + + Cap transcoded live FPS. Max resolution stream still keeps the original FPS. + +
+ + FPS +
+ + +
+

Live resolutions to generate

diff --git a/client/src/app/+admin/config/edit-custom-config/edit-vod-transcoding.component.html b/client/src/app/+admin/config/edit-custom-config/edit-vod-transcoding.component.html index 0038a2d54..e13ee37d8 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-vod-transcoding.component.html +++ b/client/src/app/+admin/config/edit-custom-config/edit-vod-transcoding.component.html @@ -141,6 +141,19 @@
+
+ + + Cap transcoded video FPS. Max resolution file still keeps the original FPS. + +
+ + FPS +
+ + +
+
Resolutions to generate
diff --git a/client/src/app/shared/form-validators/custom-config-validators.ts b/client/src/app/shared/form-validators/custom-config-validators.ts index 8579cfaa4..519a1480a 100644 --- a/client/src/app/shared/form-validators/custom-config-validators.ts +++ b/client/src/app/shared/form-validators/custom-config-validators.ts @@ -65,6 +65,14 @@ export const TRANSCODING_THREADS_VALIDATOR: BuildFormValidator = { } } +export const TRANSCODING_MAX_FPS_VALIDATOR: BuildFormValidator = { + VALIDATORS: [ Validators.required, Validators.min(1) ], + MESSAGES: { + required: $localize`Transcoding max FPS is required.`, + min: $localize`Transcoding max FPS must be greater or equal to 1.` + } +} + export const MAX_LIVE_DURATION_VALIDATOR: BuildFormValidator = { VALIDATORS: [ Validators.required, Validators.min(-1) ], MESSAGES: { diff --git a/client/src/app/shared/shared-forms/advanced-input-filter.component.ts b/client/src/app/shared/shared-forms/advanced-input-filter.component.ts index 2648529de..00085f317 100644 --- a/client/src/app/shared/shared-forms/advanced-input-filter.component.ts +++ b/client/src/app/shared/shared-forms/advanced-input-filter.component.ts @@ -155,25 +155,30 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit { } private addFilterToSearch (search: string, newFilter: AdvancedInputFilterChild) { - const prefix = newFilter.value.split(':').shift() + const filterTokens = this.restService.tokenizeString(newFilter.value) + let searchTokens = this.restService.tokenizeString(search) - // Tokenize search and remove a potential existing filter - const tokens = this.restService.tokenizeString(search) - .filter(t => !t.startsWith(prefix)) + for (const filterToken of filterTokens) { + const prefix = filterToken.split(':').shift() - tokens.push(newFilter.value) + // Tokenize search and remove a potential existing filter + searchTokens = searchTokens.filter(t => !t.startsWith(prefix)) + searchTokens.push(filterToken) + } - return tokens.join(' ') + return searchTokens.join(' ') } private parseFilters (search: string) { - const tokens = this.restService.tokenizeString(search) + const searchTokens = this.restService.tokenizeString(search) this.enabledFilters = new Set() for (const group of this.filters) { for (const filter of group.children) { - if (tokens.includes(filter.value)) { + const filterTokens = this.restService.tokenizeString(filter.value) + + if (filterTokens.every(filterToken => searchTokens.includes(filterToken))) { this.enabledFilters.add(filter.value) } } diff --git a/client/src/app/shared/shared-main/video/video.service.ts b/client/src/app/shared/shared-main/video/video.service.ts index 69a30e29d..cbc8bbc42 100644 --- a/client/src/app/shared/shared-main/video/video.service.ts +++ b/client/src/app/shared/shared-main/video/video.service.ts @@ -384,14 +384,17 @@ export class VideoService { generateDownloadUrl (options: { video: Video files: VideoFile[] + videoFileToken?: string }) { - const { video, files } = options + const { video, files, videoFileToken } = options if (files.length === 0) throw new Error('Cannot generate download URL without files') let url = `${VideoService.BASE_VIDEO_DOWNLOAD_URL}/${video.uuid}?` url += files.map(f => 'videoFileIds=' + f.id).join('&') + if (videoFileToken) url += `&videoFileToken=${videoFileToken}` + return url } diff --git a/client/src/app/shared/shared-user-settings/user-video-settings.component.ts b/client/src/app/shared/shared-user-settings/user-video-settings.component.ts index ad2dddf59..bf474ecec 100644 --- a/client/src/app/shared/shared-user-settings/user-video-settings.component.ts +++ b/client/src/app/shared/shared-user-settings/user-video-settings.component.ts @@ -136,7 +136,7 @@ export class UserVideoSettingsComponent extends FormReactive implements OnInit, next: () => { this.authService.refreshUserInformation() - if (this.notifyOnUpdate) this.notifier.success($localize`Video settings updated.`, 'toto', 15000) + if (this.notifyOnUpdate) this.notifier.success($localize`Video settings updated.`) }, error: err => this.notifier.error(err.message) diff --git a/client/src/app/shared/shared-video-miniature/download/video-generate-download.component.ts b/client/src/app/shared/shared-video-miniature/download/video-generate-download.component.ts index 52bb04552..6cbe96cc4 100644 --- a/client/src/app/shared/shared-video-miniature/download/video-generate-download.component.ts +++ b/client/src/app/shared/shared-video-miniature/download/video-generate-download.component.ts @@ -104,7 +104,7 @@ export class VideoGenerateDownloadComponent implements OnInit { files.push(this.findAudioFileOnly()) } - return this.videoService.generateDownloadUrl({ video: this.video, files }) + return this.videoService.generateDownloadUrl({ video: this.video, videoFileToken: this.videoFileToken, files }) } // --------------------------------------------------------------------------- diff --git a/client/src/app/shared/shared-video-miniature/video-filters.model.ts b/client/src/app/shared/shared-video-miniature/video-filters.model.ts index a958f3aac..04cf6df45 100644 --- a/client/src/app/shared/shared-video-miniature/video-filters.model.ts +++ b/client/src/app/shared/shared-video-miniature/video-filters.model.ts @@ -45,7 +45,8 @@ export class VideoFilters { [ 'categoryOneOf', undefined ], [ 'scope', 'federated' ], [ 'allVideos', false ], - [ 'live', 'both' ] + [ 'live', 'both' ], + [ 'search', '' ] ]) private activeFilters: VideoFilterActive[] = [] diff --git a/client/src/assets/player/peertube-player.ts b/client/src/assets/player/peertube-player.ts index d4d4d4a7b..12c8b5e84 100644 --- a/client/src/assets/player/peertube-player.ts +++ b/client/src/assets/player/peertube-player.ts @@ -502,7 +502,7 @@ export class PeerTubePlayer { { label: player.localize('Copy the video URL'), listener: function () { - copyToClipboard(buildVideoLink({ shortUUID })) + copyToClipboard(buildVideoLink({ shortUUID }), player.el() as HTMLElement) } }, { @@ -510,17 +510,17 @@ export class PeerTubePlayer { listener: function () { const url = buildVideoLink({ shortUUID }) - copyToClipboard(decorateVideoLink({ url, startTime: player.currentTime() })) + copyToClipboard(decorateVideoLink({ url, startTime: player.currentTime() }), player.el() as HTMLElement) } }, { icon: 'code', label: player.localize('Copy embed code'), listener: () => { - copyToClipboard(buildVideoOrPlaylistEmbed({ - embedUrl: self.currentLoadOptions.embedUrl, - embedTitle: self.currentLoadOptions.embedTitle - })) + copyToClipboard( + buildVideoOrPlaylistEmbed({ embedUrl: self.currentLoadOptions.embedUrl, embedTitle: self.currentLoadOptions.embedTitle }), + player.el() as HTMLElement + ) } } ] diff --git a/client/src/assets/player/shared/mobile/peertube-mobile-plugin.ts b/client/src/assets/player/shared/mobile/peertube-mobile-plugin.ts index 247e6190a..008eab112 100644 --- a/client/src/assets/player/shared/mobile/peertube-mobile-plugin.ts +++ b/client/src/assets/player/shared/mobile/peertube-mobile-plugin.ts @@ -36,12 +36,15 @@ class PeerTubeMobilePlugin extends Plugin { this.seekAmount = 0 - this.peerTubeMobileButtons = player.addChild('PeerTubeMobileButtons', { reportTouchActivity: false }) as PeerTubeMobileButtons - if (videojs.browser.IS_ANDROID && screen.orientation) { this.handleFullscreenRotation() } + // Don't add buttons if the player doesn't have controls + if (!player.controls()) return + + this.peerTubeMobileButtons = player.addChild('PeerTubeMobileButtons', { reportTouchActivity: false }) as PeerTubeMobileButtons + if (!this.player.options_.userActions) this.player.options_.userActions = {}; // FIXME: typings diff --git a/client/src/root-helpers/utils.ts b/client/src/root-helpers/utils.ts index af94ed6ca..7addfadc4 100644 --- a/client/src/root-helpers/utils.ts +++ b/client/src/root-helpers/utils.ts @@ -1,13 +1,15 @@ -function copyToClipboard (text: string) { +function copyToClipboard (text: string, container?: HTMLElement) { + if (!container) container = document.body + const el = document.createElement('textarea') el.value = text el.setAttribute('readonly', '') el.style.position = 'absolute' el.style.left = '-9999px' - document.body.appendChild(el) + container.appendChild(el) el.select() document.execCommand('copy') - document.body.removeChild(el) + container.removeChild(el) } function wait (ms: number) { diff --git a/package.json b/package.json index b791ae6b9..b03c487b9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "peertube", "description": "PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.", - "version": "6.3.1", + "version": "6.3.2", "private": true, "licence": "AGPL-3.0", "engines": { diff --git a/server/core/lib/job-queue/handlers/video-live-ending.ts b/server/core/lib/job-queue/handlers/video-live-ending.ts index dba971649..ad862799f 100644 --- a/server/core/lib/job-queue/handlers/video-live-ending.ts +++ b/server/core/lib/job-queue/handlers/video-live-ending.ts @@ -178,7 +178,14 @@ async function saveReplayToExternalVideo (options: { inputFileMutexReleaser() } - await copyOrRegenerateThumbnails({ liveVideo, replayVideo }) + try { + await copyOrRegenerateThumbnails({ liveVideo, replayVideo }) + } catch (err) { + logger.error( + `Cannot copy/regenerate thumbnails of ended live ${liveVideo.uuid} to external video ${replayVideo.uuid}`, + lTags(liveVideo.uuid, replayVideo.uuid) + ) + } await createStoryboardJob(replayVideo) await createTranscriptionTaskIfNeeded(replayVideo) @@ -280,7 +287,11 @@ async function replaceLiveByReplay (options: { } // Regenerate the thumbnail & preview? - await regenerateMiniaturesIfNeeded(videoWithFiles, undefined) + try { + await regenerateMiniaturesIfNeeded(videoWithFiles, undefined) + } catch (err) { + logger.error(`Cannot regenerate thumbnails of ended live ${videoWithFiles.uuid}`, lTags(liveVideo.uuid)) + } // We consider this is a new video await moveToNextState({ video: videoWithFiles, isNewVideo: true }) diff --git a/server/core/models/video/formatter/video-activity-pub-format.ts b/server/core/models/video/formatter/video-activity-pub-format.ts index 0446b708f..efdbf0526 100644 --- a/server/core/models/video/formatter/video-activity-pub-format.ts +++ b/server/core/models/video/formatter/video-activity-pub-format.ts @@ -284,6 +284,7 @@ function buildTags (video: MVideoAP) { function buildIcon (video: MVideoAP): ActivityIconObject[] { return [ video.getMiniature(), video.getPreview() ] + .filter(i => !!i) .map(i => i.toActivityPubObject(video)) }