1
0
Fork 0
-----BEGIN PGP SIGNATURE-----
 
 iQEzBAABCAAdFiEExEqtY4NnkSypPt1XWDphLYkBWb4FAmcEviwACgkQWDphLYkB
 Wb6V9QgAgtGTp9zRJREaA769WZP6P09ytaIxuI3SnbVQ2rCLsdpdALFWgZ6ZvteH
 XQ2cEuykr9BZ+cioVmt5PCrU3nStzYG5P6WziubtEIdJemh0bv6E3En0u5ddau1r
 UvVedZPkVx5E7tTsl++CvlLBwnWt1OMPRYCK0/6sX5LgGFrHd/tKfrXUbf0kwH5E
 +Tha+eVINf3Ks4OEU66igqotQA5P7CHI6ls9fCkZM5hRm7bdA0RmCbRHviuOyXlW
 xAUboQhndo8BHtVoBIMvXJYhbaJ8UCwAK4E8YXSvO65h+pbrkMDG7BjLMX11GcKN
 BQmp2wA0VupPHvSTZlT9/eQc6w+d7Q==
 =+ma2
 -----END PGP SIGNATURE-----
gpgsig -----BEGIN PGP SIGNATURE-----
 
 iQJLBAABCAA1FiEEGCBYi9NGimfngx9dVTwOu+tdXwgFAmcNTDcXHGdpdEBrb3Rv
 dmFsZXhhcmlhbi5jb20ACgkQVTwOu+tdXwgevA/+My9McwmndIHdmLMjKTush7lT
 LlDIAZtSMX+Om+i0KzRvyE9VXI5kN8QzBolUH8vgBgk4eVKoLUaTPJKFIp4SzQrX
 UzUxXTA1iSPA9Tjnj/akcrLFpIBtjQUOix0h5itMo1+g/KO2JAgeU0ATrjfK+hH0
 nDWDVWjYd4qzLzPSRqUJfUxfwU6FL+KehwW8M2URLP65xjh+CK8gKnxbuJESfaIC
 8AJj0rpWm1QvMNKwVxyR2K3eTJ5o7kSiM+DE6GQ30Q5ph+LnZz1J0PxH4yc7c5RU
 1DAOxrFmnVcimZ6dZceE0tozs6SMfqXeNm49qoS2aAvUBWgJs0tafB4pmwv2/p0x
 N39XduMfeeziRv7Ak5LTckqmSUSrF+BkZaI9+GyQxgO6plzRDfyiI3B1NXbqylUk
 o8ZR3rsxzeGFWfpHYdcoN7a3PT99vZ3E04eOPEYrX6J5/y4+EYp+/eZOr/IYuH8v
 pYudS5KrYZoPwpGKB4Ndy0+E3Q49Iz7HaMXB9FkqxLSWBwdar4Tzm/gdt37pGUj8
 2vCtO/dRxFdsmIm2LRsVMprn4MYaa7FQwB9y3zq3NIj7yV4BnNAdsYszf1XszZql
 U0dbBrkxE/9epdBIhPuBEo4KAUX284JClbX1iKecdqPNi9Xl4yVIgffztDqBb0r2
 i7MuxFhAJbRM/wDqL+U=
 =t78K
 -----END PGP SIGNATURE-----

Merge tag 'v6.3.2' into changes

v6.3.2
This commit is contained in:
Alex Kotov 2024-10-14 20:52:06 +04:00
commit d36c84e880
17 changed files with 110 additions and 30 deletions

View file

@ -1,5 +1,18 @@
# Changelog # 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 ## v6.3.1
### IMPORTANT NOTES ### IMPORTANT NOTES

View file

@ -1,6 +1,6 @@
{ {
"name": "peertube-client", "name": "peertube-client",
"version": "6.3.1", "version": "6.3.2",
"private": true, "private": true,
"license": "AGPL-3.0", "license": "AGPL-3.0",
"author": { "author": {

View file

@ -22,6 +22,7 @@ import {
SERVICES_TWITTER_USERNAME_VALIDATOR, SERVICES_TWITTER_USERNAME_VALIDATOR,
SIGNUP_LIMIT_VALIDATOR, SIGNUP_LIMIT_VALIDATOR,
SIGNUP_MINIMUM_AGE_VALIDATOR, SIGNUP_MINIMUM_AGE_VALIDATOR,
TRANSCODING_MAX_FPS_VALIDATOR,
TRANSCODING_THREADS_VALIDATOR TRANSCODING_THREADS_VALIDATOR
} from '@app/shared/form-validators/custom-config-validators' } 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' 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 { CustomPageService } from '@app/shared/shared-main/custom-page/custom-page.service'
import { NgbNav, NgbNavContent, NgbNavItem, NgbNavLink, NgbNavLinkBase, NgbNavOutlet } from '@ng-bootstrap/ng-bootstrap' import { NgbNav, NgbNavContent, NgbNavItem, NgbNavLink, NgbNavLinkBase, NgbNavOutlet } from '@ng-bootstrap/ng-bootstrap'
import { CustomConfig, CustomPage, HTMLServerConfig } from '@peertube/peertube-models' import { CustomConfig, CustomPage, HTMLServerConfig } from '@peertube/peertube-models'
import merge from 'lodash-es/merge'
import omit from 'lodash-es/omit' import omit from 'lodash-es/omit'
import { forkJoin } from 'rxjs' import { forkJoin } from 'rxjs'
import { SelectOptionsItem } from 'src/types/select-options-item.model' 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 { EditInstanceInformationComponent } from './edit-instance-information.component'
import { EditLiveConfigurationComponent } from './edit-live-configuration.component' import { EditLiveConfigurationComponent } from './edit-live-configuration.component'
import { EditVODTranscodingComponent } from './edit-vod-transcoding.component' import { EditVODTranscodingComponent } from './edit-vod-transcoding.component'
import merge from 'lodash-es/merge'
type ComponentCustomConfig = CustomConfig & { type ComponentCustomConfig = CustomConfig & {
instanceCustomHomepage: CustomPage instanceCustomHomepage: CustomPage
@ -239,6 +240,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
}, },
remoteRunners: { remoteRunners: {
enabled: null enabled: null
},
fps: {
max: TRANSCODING_MAX_FPS_VALIDATOR
} }
}, },
live: { live: {
@ -260,6 +264,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
alwaysTranscodeOriginalResolution: null, alwaysTranscodeOriginalResolution: null,
remoteRunners: { remoteRunners: {
enabled: null enabled: null
},
fps: {
max: TRANSCODING_MAX_FPS_VALIDATOR
} }
} }
}, },

View file

@ -48,7 +48,7 @@
<div class="form-group" [ngClass]="getDisabledLiveClass()"> <div class="form-group" [ngClass]="getDisabledLiveClass()">
<label i18n for="liveMaxInstanceLives">Max simultaneous lives created on your instance</label> <label i18n for="liveMaxInstanceLives">Max simultaneous lives created on your instance</label>
<span class="ms-2 small muted">(-1 for "unlimited")</span> <span i18n class="ms-2 small muted">(-1 for "unlimited")</span>
<div class="number-with-unit"> <div class="number-with-unit">
<input type="number" name="liveMaxInstanceLives" formControlName="maxInstanceLives" /> <input type="number" name="liveMaxInstanceLives" formControlName="maxInstanceLives" />
@ -60,7 +60,7 @@
<div class="form-group" [ngClass]="getDisabledLiveClass()"> <div class="form-group" [ngClass]="getDisabledLiveClass()">
<label i18n for="liveMaxUserLives">Max simultaneous lives created per user</label> <label i18n for="liveMaxUserLives">Max simultaneous lives created per user</label>
<span class="ms-2 small muted">(-1 for "unlimited")</span> <span i18n class="ms-2 small muted">(-1 for "unlimited")</span>
<div class="number-with-unit"> <div class="number-with-unit">
<input type="number" name="liveMaxUserLives" formControlName="maxUserLives" /> <input type="number" name="liveMaxUserLives" formControlName="maxUserLives" />
@ -116,6 +116,19 @@
<div [ngClass]="getDisabledLiveTranscodingClass()"> <div [ngClass]="getDisabledLiveTranscodingClass()">
<div class="form-group" formGroupName="fps">
<label i18n for="liveTranscodingFPSMax">Max live FPS</label>
<span i18n class="ms-2 small muted">Cap transcoded live FPS. Max resolution stream still keeps the original FPS.</span>
<div class="number-with-unit">
<input type="number" name="liveTranscodingFPSMax" formControlName="max" />
<span>FPS</span>
</div>
<div *ngIf="formErrors.live.transcoding.fps.max" class="form-error" role="alert">{{ formErrors.live.transcoding.fps.max }}</div>
</div>
<div class="ms-2 mt-3"> <div class="ms-2 mt-3">
<h4 i18n>Live resolutions to generate</h4> <h4 i18n>Live resolutions to generate</h4>

View file

@ -141,6 +141,19 @@
</div> </div>
</ng-container> </ng-container>
<div class="form-group" formGroupName="fps" [ngClass]="getTranscodingDisabledClass()">
<label i18n for="transcodingFPSMax">Max video FPS</label>
<span i18n class="ms-2 small muted">Cap transcoded video FPS. Max resolution file still keeps the original FPS.</span>
<div class="number-with-unit">
<input type="number" name="transcodingFPSMax" formControlName="max" />
<span>FPS</span>
</div>
<div *ngIf="formErrors.transcoding.fps.max" class="form-error" role="alert">{{ formErrors.transcoding.fps.max }}</div>
</div>
<div class="form-group" [ngClass]="getTranscodingDisabledClass()"> <div class="form-group" [ngClass]="getTranscodingDisabledClass()">
<div class="mb-2 fw-bold" i18n>Resolutions to generate</div> <div class="mb-2 fw-bold" i18n>Resolutions to generate</div>

View file

@ -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 = { export const MAX_LIVE_DURATION_VALIDATOR: BuildFormValidator = {
VALIDATORS: [ Validators.required, Validators.min(-1) ], VALIDATORS: [ Validators.required, Validators.min(-1) ],
MESSAGES: { MESSAGES: {

View file

@ -155,25 +155,30 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit {
} }
private addFilterToSearch (search: string, newFilter: AdvancedInputFilterChild) { 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)
for (const filterToken of filterTokens) {
const prefix = filterToken.split(':').shift()
// Tokenize search and remove a potential existing filter // Tokenize search and remove a potential existing filter
const tokens = this.restService.tokenizeString(search) searchTokens = searchTokens.filter(t => !t.startsWith(prefix))
.filter(t => !t.startsWith(prefix)) searchTokens.push(filterToken)
}
tokens.push(newFilter.value) return searchTokens.join(' ')
return tokens.join(' ')
} }
private parseFilters (search: string) { private parseFilters (search: string) {
const tokens = this.restService.tokenizeString(search) const searchTokens = this.restService.tokenizeString(search)
this.enabledFilters = new Set() this.enabledFilters = new Set()
for (const group of this.filters) { for (const group of this.filters) {
for (const filter of group.children) { 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) this.enabledFilters.add(filter.value)
} }
} }

View file

@ -384,14 +384,17 @@ export class VideoService {
generateDownloadUrl (options: { generateDownloadUrl (options: {
video: Video video: Video
files: VideoFile[] 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') if (files.length === 0) throw new Error('Cannot generate download URL without files')
let url = `${VideoService.BASE_VIDEO_DOWNLOAD_URL}/${video.uuid}?` let url = `${VideoService.BASE_VIDEO_DOWNLOAD_URL}/${video.uuid}?`
url += files.map(f => 'videoFileIds=' + f.id).join('&') url += files.map(f => 'videoFileIds=' + f.id).join('&')
if (videoFileToken) url += `&videoFileToken=${videoFileToken}`
return url return url
} }

View file

@ -136,7 +136,7 @@ export class UserVideoSettingsComponent extends FormReactive implements OnInit,
next: () => { next: () => {
this.authService.refreshUserInformation() 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) error: err => this.notifier.error(err.message)

View file

@ -104,7 +104,7 @@ export class VideoGenerateDownloadComponent implements OnInit {
files.push(this.findAudioFileOnly()) files.push(this.findAudioFileOnly())
} }
return this.videoService.generateDownloadUrl({ video: this.video, files }) return this.videoService.generateDownloadUrl({ video: this.video, videoFileToken: this.videoFileToken, files })
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View file

@ -45,7 +45,8 @@ export class VideoFilters {
[ 'categoryOneOf', undefined ], [ 'categoryOneOf', undefined ],
[ 'scope', 'federated' ], [ 'scope', 'federated' ],
[ 'allVideos', false ], [ 'allVideos', false ],
[ 'live', 'both' ] [ 'live', 'both' ],
[ 'search', '' ]
]) ])
private activeFilters: VideoFilterActive[] = [] private activeFilters: VideoFilterActive[] = []

View file

@ -502,7 +502,7 @@ export class PeerTubePlayer {
{ {
label: player.localize('Copy the video URL'), label: player.localize('Copy the video URL'),
listener: function () { listener: function () {
copyToClipboard(buildVideoLink({ shortUUID })) copyToClipboard(buildVideoLink({ shortUUID }), player.el() as HTMLElement)
} }
}, },
{ {
@ -510,17 +510,17 @@ export class PeerTubePlayer {
listener: function () { listener: function () {
const url = buildVideoLink({ shortUUID }) const url = buildVideoLink({ shortUUID })
copyToClipboard(decorateVideoLink({ url, startTime: player.currentTime() })) copyToClipboard(decorateVideoLink({ url, startTime: player.currentTime() }), player.el() as HTMLElement)
} }
}, },
{ {
icon: 'code', icon: 'code',
label: player.localize('Copy embed code'), label: player.localize('Copy embed code'),
listener: () => { listener: () => {
copyToClipboard(buildVideoOrPlaylistEmbed({ copyToClipboard(
embedUrl: self.currentLoadOptions.embedUrl, buildVideoOrPlaylistEmbed({ embedUrl: self.currentLoadOptions.embedUrl, embedTitle: self.currentLoadOptions.embedTitle }),
embedTitle: self.currentLoadOptions.embedTitle player.el() as HTMLElement
})) )
} }
} }
] ]

View file

@ -36,12 +36,15 @@ class PeerTubeMobilePlugin extends Plugin {
this.seekAmount = 0 this.seekAmount = 0
this.peerTubeMobileButtons = player.addChild('PeerTubeMobileButtons', { reportTouchActivity: false }) as PeerTubeMobileButtons
if (videojs.browser.IS_ANDROID && screen.orientation) { if (videojs.browser.IS_ANDROID && screen.orientation) {
this.handleFullscreenRotation() 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 = {}; if (!this.player.options_.userActions) this.player.options_.userActions = {};
// FIXME: typings // FIXME: typings

View file

@ -1,13 +1,15 @@
function copyToClipboard (text: string) { function copyToClipboard (text: string, container?: HTMLElement) {
if (!container) container = document.body
const el = document.createElement('textarea') const el = document.createElement('textarea')
el.value = text el.value = text
el.setAttribute('readonly', '') el.setAttribute('readonly', '')
el.style.position = 'absolute' el.style.position = 'absolute'
el.style.left = '-9999px' el.style.left = '-9999px'
document.body.appendChild(el) container.appendChild(el)
el.select() el.select()
document.execCommand('copy') document.execCommand('copy')
document.body.removeChild(el) container.removeChild(el)
} }
function wait (ms: number) { function wait (ms: number) {

View file

@ -1,7 +1,7 @@
{ {
"name": "peertube", "name": "peertube",
"description": "PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.", "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, "private": true,
"licence": "AGPL-3.0", "licence": "AGPL-3.0",
"engines": { "engines": {

View file

@ -178,7 +178,14 @@ async function saveReplayToExternalVideo (options: {
inputFileMutexReleaser() inputFileMutexReleaser()
} }
try {
await copyOrRegenerateThumbnails({ liveVideo, replayVideo }) 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 createStoryboardJob(replayVideo)
await createTranscriptionTaskIfNeeded(replayVideo) await createTranscriptionTaskIfNeeded(replayVideo)
@ -280,7 +287,11 @@ async function replaceLiveByReplay (options: {
} }
// Regenerate the thumbnail & preview? // Regenerate the thumbnail & preview?
try {
await regenerateMiniaturesIfNeeded(videoWithFiles, undefined) 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 // We consider this is a new video
await moveToNextState({ video: videoWithFiles, isNewVideo: true }) await moveToNextState({ video: videoWithFiles, isNewVideo: true })

View file

@ -284,6 +284,7 @@ function buildTags (video: MVideoAP) {
function buildIcon (video: MVideoAP): ActivityIconObject[] { function buildIcon (video: MVideoAP): ActivityIconObject[] {
return [ video.getMiniature(), video.getPreview() ] return [ video.getMiniature(), video.getPreview() ]
.filter(i => !!i)
.map(i => i.toActivityPubObject(video)) .map(i => i.toActivityPubObject(video))
} }