1
0
Fork 0
-----BEGIN PGP SIGNATURE-----
 
 iQEzBAABCAAdFiEExEqtY4NnkSypPt1XWDphLYkBWb4FAmV2w9QACgkQWDphLYkB
 Wb4fbQf/QSMa7zHYkGzgu9mxmyWjCbXzTLQQAWIe+11w9uWURoRKSAAbQ09a+8zy
 TC2F9zhJhbo9zGbgo+nBbn6hxWSPgtTL9CDJvRR+ObMyGe8TXImDaZoqUI+UFUEy
 jv3eeeA27piBir8NV0cKEcTReIzSY/y6sQYOGg++Cx29nWPb4ce/6HP/WB4BR80c
 8u9hQc9nF/fkNnmaVCxpqCkEfzCoCjXfQSctjEhTrgAJ38aBYuJ4Vc4+i3oYVnuU
 gO8BjFTuONUFQF04aXmClSQj8dGSugtwNJOnvpUNu9isWJSZLQ22erhcnBsJkgEM
 o2k0msMr1Jxc6ngPoqPIy27WqXiXKw==
 =XVQf
 -----END PGP SIGNATURE-----
gpgsig -----BEGIN PGP SIGNATURE-----
 
 iQJNBAABCAA3FiEEGCBYi9NGimfngx9dVTwOu+tdXwgFAmV6S0oZHGtvdG92YWxl
 eGFyaWFuQGdtYWlsLmNvbQAKCRBVPA67611fCHweEADP4ElWrWMrH4kjukiMUn83
 rtuDCv50tCGDrgfq73fPyMusMD54reEwBtSfmIfiPhSqanuoDrX4gtrJCfMZ2MLe
 YAPyS1E4Ma1rbd8qSljeW8pWCdOmJ35rhj+jv8WCUpo41UsMxtApyhNa218kuYZU
 dfywVfdJY+RgU+FtlVnR2KuMc3ASCTFrHqPxe8Kvy+g9Mip5o9sP+FKL2zXUMizz
 Wln7urCIkF52j49qPDv8NPLobWBxYmyidSoZDVl1Ydl6djCVKNDi5U5T04ojp/7V
 a2wFK+GhinNRotVPdVX1HkdkVSBXMFFG14wj6n/n6mvOsWX51Aj1yoFdkmvHdGdK
 62/F6kThQf7XdKzlehr0UUHvgA/HyKSS+V5V2wCIjPhUVf7Kp5IXEO8cibEcEpVh
 FGkrwd+MdycHpUIQPWPrOP6mrugJdDw7Ir9u5nxGTlB6Y0PXua0xPnxfP5+uLVjE
 6DdHUUFbd0rPPiVcqqNuRNSwuazA/UT4APq1CHA74ME3qP/eI09ePaUvyXVXUgJo
 nQTsp8g7BhlptUhMmeuW1XGkLfaTYVMAbMpiGz4S/wxnqIi2R75CE70BV06ZBFOF
 DWAEJR+i1138h7rqppQPW2cqpDTC5RWnkfkC9mJEJOIKS+pzZ/iU7fpjWAL67WrM
 11DvAKOcMQRTbw5ROLkDIA==
 =THDy
 -----END PGP SIGNATURE-----

Merge tag 'v6.0.2' into changes

v6.0.2
This commit is contained in:
Alex Kotov 2023-12-14 04:24:40 +04:00
commit 3438b07487
30 changed files with 187 additions and 60 deletions

View File

@ -1,5 +1,26 @@
# Changelog
## v6.0.2
### IMPORTANT NOTES
* If you upgrade from PeerTube **< v6.0.0**, please follow v6.0.0 IMPORTANT NOTES
* If you upgrade from PeerTube **v6.0.0**, please follow v6.0.1 IMPORTANT NOTES
### Bug fixes
* Fix upgrade.sh when Peertube is installed outside the standard path [#6064](https://github.com/Chocobozzz/PeerTube/pull/6064)
* Fix importing videos with too long chapter name
* Don't create chapters from description if there is only one
* Ensure user is owned by the auth plugin before updating its attributes
* Improve channels and accounts SEO by fixing structured JSON-LD data and canonical URLs
* Originally published and reupload date format consistency in watch page
* Fix cpu count when cpu info not available
* Fix embed when waiting for a live
* Fix updating already started live if live attributes don't change
* Fix displaying many countries in video stats
## v6.0.1
### IMPORTANT NOTES
@ -8,7 +29,7 @@
* We've made some modifications in v6.0.0 IMPORTANT NOTES, so if you upgrade from PeerTube v6.0.0:
* Ensure `location = /api/v1/videos/upload-resumable {` has been replaced by `location ~ ^/api/v1/videos/(upload-resumable|([^/]+/source/replace-resumable))$ {` in your nginx configuration
* Ensure you updated `storage.web_videos` configuration value to use `web-videos/` directory name
* Ensure your directory name on filesystem is the same as `storage.web_videos` configuration
* Ensure your directory name on filesystem is the same as `storage.web_videos` configuration value: directory on filesystem must be renamed from `videos/` to `web-videos/` to represent the value of `storage.web_videos`
### Bug fixes
@ -43,7 +64,7 @@ We have many important notes in this release. We know it's a pain for sysadmin,
* Directory on filesystem must be **renamed** from `videos/` to `web-videos/` to represent the value of `storage.web_videos`
* Classic installation: `sudo -u peertube mv '/var/www/peertube/storage/videos/' '/var/www/peertube/storage/web-videos/'`
* Docker installation: `mv '/path-to-docker-installation/docker-volume/data/videos/' '/path-to-docker-installation/docker-volume/data/web-videos/'`
* `transcoding.webtorrent` must be **renamed** to `transcoding.web_videos`: https://github.com/Chocobozzz/PeerTube/blob/develop/config/production.yaml.example#L522
* `transcoding.webtorrent` must be **renamed** to `transcoding.web_videos`: https://github.com/Chocobozzz/PeerTube/blob/develop/config/production.yaml.example#L532
* `object_storage.videos` must be **renamed** to `object_storage.web_videos`. The value of `object_storage.web_videos.bucket_name` doesn't need to be changed: https://github.com/Chocobozzz/PeerTube/blob/develop/config/production.yaml.example#L223
* `storage.storyboards` must be **added**: https://github.com/Chocobozzz/PeerTube/blob/develop/config/production.yaml.example#L157
@ -61,7 +82,7 @@ We have many important notes in this release. We know it's a pain for sysadmin,
* `location ~ ^(/static/(webseed|streaming-playlists)/private/)|^/download {` must be updated to `location ~ ^(/static/(webseed|web-videos|streaming-playlists)/private/)|^/download {`
* `location ~ ^/static/(webseed|redundancy|streaming-playlists)/ {` must be updated to `location ~ ^/static/(webseed|web-videos|redundancy|streaming-playlists)/ {`
* Tracing requires `--experimental-loader=@opentelemetry/instrumentation/hook.mjs` node option: https://github.com/Chocobozzz/PeerTube/blob/develop/config/production.yaml.example#L263
* Tracing requires `--experimental-loader=@opentelemetry/instrumentation/hook.mjs` node option: https://github.com/Chocobozzz/PeerTube/blob/develop/config/production.yaml.example#L264
#### Developers important notes

View File

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

View File

@ -45,7 +45,7 @@
</a>
<ng-template ngbNavContent>
<div class="chart-container" [ngStyle]="{ 'min-height': chartHeight }">
<div class="chart-container">
<p-chart
*ngIf="chartOptions[availableChart.id]"
[height]="chartHeight" [width]="chartWidth"

View File

@ -1,4 +1,4 @@
import { ChartConfiguration, ChartData, PluginOptionsByType, Scale, TooltipItem } from 'chart.js'
import { ChartConfiguration, ChartData, ChartOptions, PluginOptionsByType, Scale, TooltipItem } from 'chart.js'
import zoomPlugin from 'chartjs-plugin-zoom'
import { Observable, of } from 'rxjs'
import { SelectOptionsItem } from 'src/types'
@ -25,6 +25,9 @@ type CountryData = { name: string, viewers: number }[]
type ChartIngestData = VideoStatsTimeserie | VideoStatsRetention | CountryData
type ChartBuilderResult = {
type: 'line' | 'bar'
options?: ChartOptions<'bar'>
plugins: Partial<PluginOptionsByType<'line' | 'bar'>>
data: ChartData<'line' | 'bar'>
displayLegend: boolean
@ -136,6 +139,12 @@ export class VideoStatsComponent implements OnInit {
onChartChange (newActive: ActiveGraphId) {
this.activeGraphId = newActive
if (newActive === 'countries') {
this.chartHeight = `${Math.max(this.countries.length * 20, 300)}px`
} else {
this.chartHeight = '300px'
}
this.loadChart()
}
@ -333,7 +342,7 @@ export class VideoStatsComponent implements OnInit {
countries: (rawData: CountryData) => this.buildCountryChartOptions(rawData)
}
const { type, data, displayLegend, plugins } = dataBuilders[graphId](this.chartIngestData[graphId])
const { type, data, displayLegend, plugins, options } = dataBuilders[graphId](this.chartIngestData[graphId])
const self = this
@ -342,6 +351,8 @@ export class VideoStatsComponent implements OnInit {
data,
options: {
...options,
responsive: true,
scales: {
@ -366,7 +377,9 @@ export class VideoStatsComponent implements OnInit {
: undefined,
ticks: {
callback: value => this.formatYTick({ graphId, value })
callback: function (value) {
return self.formatYTick({ graphId, value, scale: this })
}
}
}
},
@ -489,6 +502,10 @@ export class VideoStatsComponent implements OnInit {
return {
type: 'bar' as 'bar',
options: {
indexAxis: 'y'
},
displayLegend: true,
plugins: {
@ -547,11 +564,13 @@ export class VideoStatsComponent implements OnInit {
private formatYTick (options: {
graphId: ActiveGraphId
value: number | string
scale?: Scale
}) {
const { graphId, value } = options
const { graphId, value, scale } = options
if (graphId === 'retention') return value + ' %'
if (graphId === 'aggregateWatchTime') return secondsToTime(+value)
if (graphId === 'countries' && scale) return scale.getLabelForValue(value as number)
return value.toLocaleString(this.localeId)
}

View File

@ -21,7 +21,7 @@ import {
} from '@app/shared/shared-main'
import { LiveVideoService } from '@app/shared/shared-video-live'
import { LoadingBarService } from '@ngx-loading-bar/core'
import { pick, simpleObjectsDeepEqual } from '@peertube/peertube-core-utils'
import { simpleObjectsDeepEqual } from '@peertube/peertube-core-utils'
import { HttpStatusCode, LiveVideo, LiveVideoUpdate, VideoPrivacy, VideoSource, VideoState } from '@peertube/peertube-models'
import { hydrateFormFromVideo } from './shared/video-edit-utils'
import { VideoUploadService } from './shared/video-upload.service'
@ -221,7 +221,12 @@ export class VideoUpdateComponent extends FormReactive implements OnInit, OnDest
}
// Don't update live attributes if they did not change
const baseVideo = pick(this.liveVideo, Object.keys(liveVideoUpdate) as (keyof LiveVideoUpdate)[])
const baseVideo = {
saveReplay: this.liveVideo.saveReplay,
replaySettings: this.liveVideo.replaySettings,
permanentLive: this.liveVideo.permanentLive,
latencyMode: this.liveVideo.latencyMode
}
const liveChanged = !simpleObjectsDeepEqual(baseVideo, liveVideoUpdate)
if (!liveChanged) return of(undefined)

View File

@ -35,7 +35,7 @@
<div *ngIf="!!video.originallyPublishedAt" class="attribute attribute-originally-published-at">
<span i18n class="attribute-label">Originally published</span>
<span class="attribute-value">{{ video.originallyPublishedAt | date: 'dd MMMM yyyy' }}</span>
<span class="attribute-value">{{ video.originallyPublishedAt | date: 'shortDate' }}</span>
</div>
<div class="attribute attribute-category">

View File

@ -14,7 +14,7 @@ export class WebVideoOptionsBuilder {
videoFiles: this.options.webVideo.videoFiles.length !== 0
? this.options.webVideo.videoFiles
: this.options?.hls.videoFiles || []
: this.options.hls?.videoFiles || []
}
}
}

View File

@ -27,7 +27,8 @@ class WebVideoPlugin extends Plugin {
this.videoFiles = options.videoFiles
this.videoFileToken = options.videoFileToken
this.updateVideoFile({ videoFile: this.pickAverageVideoFile(), isUserResolutionChange: false })
const videoFile = this.pickAverageVideoFile()
if (videoFile) this.updateVideoFile({ videoFile, isUserResolutionChange: false })
this.onLoadedMetadata = () => {
player.trigger('video-ratio-changed', { ratio: this.player.videoWidth() / this.player.videoHeight() })
@ -36,19 +37,19 @@ class WebVideoPlugin extends Plugin {
player.on('loadedmetadata', this.onLoadedMetadata)
player.ready(() => {
this.buildQualities()
this.setupNetworkInfoInterval()
if (this.videoFiles.length === 0) {
this.player.addClass('disabled')
return
}
this.buildQualities()
this.setupNetworkInfoInterval()
})
}
dispose () {
clearInterval(this.networkInfoInterval)
if (this.networkInfoInterval) clearInterval(this.networkInfoInterval)
if (this.onLoadedMetadata) this.player.off('loadedmetadata', this.onLoadedMetadata)
if (this.onErrorHandler) this.player.off('error', this.onErrorHandler)
@ -58,7 +59,7 @@ class WebVideoPlugin extends Plugin {
}
getCurrentResolutionId () {
return this.currentVideoFile.resolution.id
return this.currentVideoFile?.resolution.id
}
updateVideoFile (options: {
@ -123,7 +124,7 @@ class WebVideoPlugin extends Plugin {
private adaptPosterForAudioOnly () {
// Audio-only (resolutionId === 0) gets special treatment
if (this.currentVideoFile.resolution.id === 0) {
if (this.currentVideoFile?.resolution.id === 0) {
this.player.audioPosterMode(true)
} else {
this.player.audioPosterMode(false)
@ -154,6 +155,7 @@ class WebVideoPlugin extends Plugin {
}
private pickAverageVideoFile () {
if (!this.videoFiles || this.videoFiles.length === 0) return undefined
if (this.videoFiles.length === 1) return this.videoFiles[0]
const files = this.videoFiles.filter(f => f.resolution.id !== 0)
@ -165,7 +167,7 @@ class WebVideoPlugin extends Plugin {
id: videoFile.resolution.id,
label: this.buildQualityLabel(videoFile),
height: videoFile.resolution.id,
selected: videoFile.id === this.currentVideoFile.id,
selected: videoFile.id === this.currentVideoFile?.id,
selectCallback: () => this.updateVideoFile({ videoFile, isUserResolutionChange: true })
}))
@ -187,7 +189,7 @@ class WebVideoPlugin extends Plugin {
return this.player.trigger('network-info', {
source: 'web-video',
http: {
downloaded: this.player.bufferedPercent() * this.currentVideoFile.size
downloaded: this.player.bufferedPercent() * this.currentVideoFile?.size
}
} as PlayerNetworkInfo)
}, 1000)

View File

@ -1,7 +1,7 @@
{
"name": "peertube",
"description": "PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.",
"version": "6.0.1",
"version": "6.0.2",
"private": true,
"licence": "AGPL-3.0",
"engines": {

View File

@ -2,7 +2,7 @@ import { timeToInt, timecodeRegexString } from '../common/date.js'
const timecodeRegex = new RegExp(`^(${timecodeRegexString})\\s`)
export function parseChapters (text: string) {
export function parseChapters (text: string, maxTitleLength: number) {
if (!text) return []
const lines = text.split(/\r?\n|\r|\n/g)
@ -25,8 +25,11 @@ export function parseChapters (text: string) {
const timecode = timeToInt(timecodeText)
const title = line.replace(matched[0], '')
chapters.push({ timecode, title })
chapters.push({ timecode, title: title.slice(0, maxTitleLength) })
}
return chapters
// Only consider chapters if there are more than one
if (chapters.length > 1) return chapters
return []
}

View File

@ -178,13 +178,13 @@ describe('Test video chapters', function () {
checkChapters(chapters)
}
await servers[0].videos.update({ id: video.uuid, attributes: { description: '00:01 chapter 1' } })
await servers[0].videos.update({ id: video.uuid, attributes: { description: '00:01 chapter 1\n00:03 chapter 2' } })
await waitJobs(servers)
for (const server of servers) {
const { chapters } = await server.chapters.list({ videoId: video.uuid })
expect(chapters).to.deep.equal([ { timecode: 1, title: 'chapter 1' } ])
expect(chapters).to.deep.equal([ { timecode: 1, title: 'chapter 1' }, { timecode: 3, title: 'chapter 2' } ])
}
await servers[0].videos.update({ id: video.uuid, attributes: { description: 'null description' } })

View File

@ -103,7 +103,7 @@ describe('Test index HTML generation', function () {
it('Should use the original account URL for the canonical tag', async function () {
const accountURLtest = res => {
expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/a/root" />`)
expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/a/root/video-channels" />`)
}
accountURLtest(await makeHTMLRequest(servers[0].url, '/accounts/root@' + servers[0].host))
@ -113,7 +113,7 @@ describe('Test index HTML generation', function () {
it('Should use the original channel URL for the canonical tag', async function () {
const channelURLtests = res => {
expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/c/root_channel" />`)
expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/c/root_channel/videos" />`)
}
channelURLtests(await makeHTMLRequest(servers[0].url, '/video-channels/root_channel@' + servers[0].host))

View File

@ -48,7 +48,7 @@ describe('Test Open Graph and Twitter cards HTML tags', function () {
expect(text).to.contain(`<meta property="og:title" content="${account.displayName}" />`)
expect(text).to.contain(`<meta property="og:description" content="${account.description}" />`)
expect(text).to.contain('<meta property="og:type" content="website" />')
expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/a/${servers[0].store.user.username}" />`)
expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/a/${servers[0].store.user.username}/video-channels" />`)
}
async function channelPageTest (path: string) {
@ -58,7 +58,7 @@ describe('Test Open Graph and Twitter cards HTML tags', function () {
expect(text).to.contain(`<meta property="og:title" content="${servers[0].store.channel.displayName}" />`)
expect(text).to.contain(`<meta property="og:description" content="${channelDescription}" />`)
expect(text).to.contain('<meta property="og:type" content="website" />')
expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/c/${servers[0].store.channel.name}" />`)
expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/c/${servers[0].store.channel.name}/videos" />`)
}
async function watchVideoPageTest (path: string) {

View File

@ -212,11 +212,11 @@ describe('Test misc endpoints', function () {
expect(res.text).to.contain('<video:title>video 2</video:title>')
expect(res.text).to.not.contain('<video:title>video 3</video:title>')
expect(res.text).to.contain('<url><loc>' + server.url + '/c/channel1</loc></url>')
expect(res.text).to.contain('<url><loc>' + server.url + '/c/channel2</loc></url>')
expect(res.text).to.contain('<url><loc>' + server.url + '/c/channel1/videos</loc></url>')
expect(res.text).to.contain('<url><loc>' + server.url + '/c/channel2/videos</loc></url>')
expect(res.text).to.contain('<url><loc>' + server.url + '/a/user1</loc></url>')
expect(res.text).to.contain('<url><loc>' + server.url + '/a/user2</loc></url>')
expect(res.text).to.contain('<url><loc>' + server.url + '/a/user1/video-channels</loc></url>')
expect(res.text).to.contain('<url><loc>' + server.url + '/a/user2/video-channels</loc></url>')
})
it('Should not fail with big title/description videos', async function () {

View File

@ -242,6 +242,29 @@ describe('Test id and pass auth plugins', function () {
expect(laguna.pluginAuth).to.equal('peertube-plugin-test-id-pass-auth-two')
})
it('Should not update a user if not owned by the plugin auth', async function () {
{
await server.users.update({ userId: lagunaId, videoQuota: 43000, password: 'coucou', pluginAuth: null })
const body = await server.users.get({ userId: lagunaId })
expect(body.videoQuota).to.equal(43000)
expect(body.pluginAuth).to.be.null
}
{
await server.login.login({
user: { username: 'laguna', password: 'laguna password' },
expectedStatus: HttpStatusCode.BAD_REQUEST_400
})
}
{
const body = await server.users.get({ userId: lagunaId })
expect(body.videoQuota).to.equal(43000)
expect(body.pluginAuth).to.be.null
}
})
after(async function () {
await cleanupTests([ server ])
})

View File

@ -221,25 +221,38 @@ describe('Parse semantic version string', function () {
describe('Extract chapters', function () {
it('Should not extract chapters', function () {
expect(parseChapters('my super description\nno?')).to.deep.equal([])
expect(parseChapters('m00:00 super description\nno?')).to.deep.equal([])
expect(parseChapters('00:00super description\nno?')).to.deep.equal([])
expect(parseChapters('my super description\n'.repeat(10) + ' * list1\n * list 2\n * list 3')).to.deep.equal([])
expect(parseChapters('my super description\nno?', 100)).to.deep.equal([])
expect(parseChapters('m00:00 super description\nno?', 100)).to.deep.equal([])
expect(parseChapters('00:00super description\nno?', 100)).to.deep.equal([])
expect(parseChapters('my super description\n'.repeat(10) + ' * list1\n * list 2\n * list 3', 100)).to.deep.equal([])
expect(parseChapters('3 Hello coucou', 100)).to.deep.equal([])
expect(parseChapters('00:00 coucou', 100)).to.deep.equal([])
})
it('Should extract chapters', function () {
expect(parseChapters('00:00 coucou')).to.deep.equal([ { timecode: 0, title: 'coucou' } ])
expect(parseChapters('my super description\n\n00:01:30 chapter 1\n00:01:35 chapter 2')).to.deep.equal([
expect(parseChapters('00:00 coucou\n00:05 hello', 100)).to.deep.equal([
{ timecode: 0, title: 'coucou' },
{ timecode: 5, title: 'hello' }
])
expect(parseChapters('my super description\n\n00:01:30 chapter 1\n00:01:35 chapter 2', 100)).to.deep.equal([
{ timecode: 90, title: 'chapter 1' },
{ timecode: 95, title: 'chapter 2' }
])
expect(parseChapters('hi\n\n00:01:30 chapter 1\n00:01:35 chapter 2\nhi')).to.deep.equal([
expect(parseChapters('hi\n\n00:01:30 chapter 1\n00:01:35 chapter 2\nhi', 100)).to.deep.equal([
{ timecode: 90, title: 'chapter 1' },
{ timecode: 95, title: 'chapter 2' }
])
expect(parseChapters('hi\n\n00:01:30 chapter 1\n00:01:35 chapter 2\nhi\n00:01:40 chapter 3')).to.deep.equal([
expect(parseChapters('hi\n\n00:01:30 chapter 1\n00:01:35 chapter 2\nhi\n00:01:40 chapter 3', 100)).to.deep.equal([
{ timecode: 90, title: 'chapter 1' },
{ timecode: 95, title: 'chapter 2' }
])
})
it('Should respect the max length option', function () {
expect(parseChapters('my super description\n\n00:01:30 chapter 1\n00:01:35 chapter 2', 3)).to.deep.equal([
{ timecode: 90, title: 'cha' },
{ timecode: 95, title: 'cha' }
])
})
})

View File

@ -4,4 +4,5 @@ set -eu
# Backward path compatibility now upgrade.sh is in dist/scripts since v6
/bin/sh ../dist/scripts/upgrade.sh
/bin/sh ../dist/scripts/upgrade.sh ${1:-/var/www/peertube}

View File

@ -968,7 +968,7 @@ const MEMOIZE_LENGTH = {
VIDEO_DURATION: 200
}
const totalCPUs = cpus().length
const totalCPUs = Math.max(cpus().length, 1)
const WORKER_THREADS = {
DOWNLOAD_IMAGE: {

View File

@ -89,8 +89,11 @@ async function getUser (usernameOrEmail?: string, password?: string, bypassLogin
let user = await UserModel.loadByEmail(bypassLogin.user.email)
if (!user) user = await createUserFromExternal(bypassLogin.pluginName, bypassLogin.user)
else user = await updateUserFromExternal(user, bypassLogin.user, bypassLogin.userUpdater)
if (!user) {
user = await createUserFromExternal(bypassLogin.pluginName, bypassLogin.user)
} else if (user.pluginAuth === bypassLogin.pluginName) {
user = await updateUserFromExternal(user, bypassLogin.user, bypassLogin.userUpdater)
}
// Cannot create a user
if (!user) throw new AccessDeniedError('Cannot create such user: an actor with that name already exists.')

View File

@ -80,6 +80,10 @@ export class ActorHtml {
ogType,
twitterCard,
schemaType,
jsonldProfile: {
createdAt: entity.createdAt,
updatedAt: entity.updatedAt
},
indexationPolicy: entity.Actor.isOwned()
? 'always'

View File

@ -11,10 +11,16 @@ type Tags = {
url?: string
schemaType?: string
ogType?: string
twitterCard?: 'player' | 'summary' | 'summary_large_image'
schemaType?: string
jsonldProfile?: {
createdAt: Date
updatedAt: Date
}
list?: {
numberOfItems: number
}
@ -195,6 +201,28 @@ export class TagsHtml {
static generateSchemaTagsOptions (tags: Tags, context: HookContext) {
if (!tags.schemaType) return
if (tags.schemaType === 'ProfilePage') {
if (!tags.jsonldProfile) throw new Error('Missing `jsonldProfile` with ProfilePage schema type')
const profilePageSchema = {
'@context': 'http://schema.org',
'@type': tags.schemaType,
'dateCreated': tags.jsonldProfile.createdAt.toISOString(),
'dateModified': tags.jsonldProfile.updatedAt.toISOString(),
'mainEntity': {
'@id': '#main-author',
'@type': 'Person',
'name': tags.escapedTitle,
'description': tags.escapedTruncatedDescription,
'image': tags.image.url
}
}
return Hooks.wrapObject(profilePageSchema, 'filter:html.client.json-ld.result', context)
}
const schema = {
'@context': 'http://schema.org',
'@type': tags.schemaType,

View File

@ -38,7 +38,7 @@ export async function moveToJob (options: {
}
if (video.VideoStreamingPlaylists) {
logger.debug('Moving HLS playlist of %s.', video.uuid)
logger.debug('Moving HLS playlist of %s.', video.uuid, lTags)
await moveHLSFiles(video)
}

View File

@ -68,7 +68,9 @@ export class FFmpegTranscodingWrapper extends AbstractTranscodingWrapper {
logger.debug('Killing ffmpeg after live abort of ' + this.videoUUID, this.lTags())
this.ffmpegCommand.kill('SIGINT')
if (this.ffmpegCommand) {
this.ffmpegCommand.kill('SIGINT')
}
this.aborted = true
this.emit('end')

View File

@ -5,6 +5,7 @@ import { VideoChapterModel } from '@server/models/video/video-chapter.js'
import { MVideoImmutable } from '@server/types/models/index.js'
import { Transaction } from 'sequelize'
import { InternalEventEmitter } from './internal-event-emitter.js'
import { CONSTRAINTS_FIELDS } from '@server/initializers/constants.js'
const lTags = loggerTagsFactory('video', 'chapters')
@ -44,7 +45,7 @@ export async function replaceChaptersFromDescriptionIfNeeded (options: {
}) {
const { transaction, video, newDescription, oldDescription = '' } = options
const chaptersFromOldDescription = sortBy(parseChapters(oldDescription), 'timecode')
const chaptersFromOldDescription = sortBy(parseChapters(oldDescription, CONSTRAINTS_FIELDS.VIDEO_CHAPTERS.TITLE.max), 'timecode')
const existingChapters = await VideoChapterModel.listChaptersOfVideo(video.id, transaction)
logger.debug(
@ -54,7 +55,7 @@ export async function replaceChaptersFromDescriptionIfNeeded (options: {
// Then we can update chapters from the new description
if (areSameChapters(chaptersFromOldDescription, existingChapters)) {
const chaptersFromNewDescription = sortBy(parseChapters(newDescription), 'timecode')
const chaptersFromNewDescription = sortBy(parseChapters(newDescription, CONSTRAINTS_FIELDS.VIDEO_CHAPTERS.TITLE.max), 'timecode')
if (chaptersFromOldDescription.length === 0 && chaptersFromNewDescription.length === 0) return false
await replaceChapters({ video, chapters: chaptersFromNewDescription, transaction })

View File

@ -457,7 +457,7 @@ export class AccountModel extends Model<Partial<AttributesOnly<AccountModel>>> {
// Avoid error when running this method on MAccount... | MChannel...
getClientUrl (this: MAccountHost | MChannelHost) {
return WEBSERVER.URL + '/a/' + this.Actor.getIdentifier()
return WEBSERVER.URL + '/a/' + this.Actor.getIdentifier() + '/video-channels'
}
isBlocked () {

View File

@ -873,6 +873,8 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
}
isPasswordMatch (password: string) {
if (!password || !this.password) return false
return comparePassword(password, this.password)
}

View File

@ -841,7 +841,7 @@ export class VideoChannelModel extends Model<Partial<AttributesOnly<VideoChannel
// Avoid error when running this method on MAccount... | MChannel...
getClientUrl (this: MAccountHost | MChannelHost) {
return WEBSERVER.URL + '/c/' + this.Actor.getIdentifier()
return WEBSERVER.URL + '/c/' + this.Actor.getIdentifier() + '/videos'
}
getDisplayName () {

View File

@ -9773,10 +9773,10 @@ components:
description: P2P peers connected (doesn't include WebSeed peers)
resolutionChanges:
type: number
description: How many resolution changes occured since the last metric creation
description: How many resolution changes occurred since the last metric creation
errors:
type: number
description: How many errors occured since the last metric creation
description: How many errors occurred since the last metric creation
downloadedBytesP2P:
type: number
description: How many bytes were downloaded with P2P since the last metric creation

View File

@ -870,7 +870,7 @@ function register ({ registerClientRoute }) {
}
```
You can then access the page on `/p/my-super/route` (please note the additionnal `/p/` in the path).
You can then access the page on `/p/my-super/route` (please note the additional `/p/` in the path).
### Publishing

View File

@ -185,7 +185,7 @@ peertube-runner list-registered
## Server tools
Server tools are scripts that interect directly with the code of your PeerTube instance.
Server tools are scripts that interact directly with the code of your PeerTube instance.
They must be run on the server, in `peertube-latest` directory.
### Parse logs