Remove web video redundancy support

It's not used anymore in the player since several major versions now, so
there's no point in continuing to store these video files
This commit is contained in:
Chocobozzz 2025-01-31 10:13:56 +01:00
parent 23cd92430f
commit 05f105d03f
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
32 changed files with 669 additions and 1036 deletions

View File

@ -94,8 +94,7 @@ export class VideoRedundanciesListComponent extends RestTable implements OnInit
}
getTotalSize (redundancy: VideoRedundancy) {
return redundancy.redundancies.files.reduce((a, b) => a + b.size, 0) +
redundancy.redundancies.streamingPlaylists.reduce((a, b) => a + b.size, 0)
return redundancy.redundancies.streamingPlaylists.reduce((a, b) => a + b.size, 0)
}
onDisplayTypeChanged () {
@ -106,8 +105,9 @@ export class VideoRedundanciesListComponent extends RestTable implements OnInit
}
getRedundancyStrategy (redundancy: VideoRedundancy) {
if (redundancy.redundancies.files.length !== 0) return redundancy.redundancies.files[0].strategy
if (redundancy.redundancies.streamingPlaylists.length !== 0) return redundancy.redundancies.streamingPlaylists[0].strategy
if (redundancy.redundancies.streamingPlaylists.length !== 0) {
return redundancy.redundancies.streamingPlaylists[0].strategy
}
return ''
}

View File

@ -1,6 +1,6 @@
import { Component, Input } from '@angular/core'
import { PTDatePipe } from '@app/shared/shared-main/common/date.pipe'
import { FileRedundancyInformation, StreamingPlaylistRedundancyInformation } from '@peertube/peertube-models'
import { RedundancyInformation } from '@peertube/peertube-models'
import { BytesPipe } from '../../../shared/shared-main/common/bytes.pipe'
@Component({
@ -11,5 +11,5 @@ import { BytesPipe } from '../../../shared/shared-main/common/bytes.pipe'
imports: [ PTDatePipe, BytesPipe ]
})
export class VideoRedundancyInformationComponent {
@Input() redundancyElement: FileRedundancyInformation | StreamingPlaylistRedundancyInformation
@Input() redundancyElement: RedundancyInformation
}

View File

@ -53,7 +53,6 @@ export class RedundancyService {
removeVideoRedundancies (redundancy: VideoRedundancy) {
const observables = redundancy.redundancies.streamingPlaylists.map(r => r.id)
.concat(redundancy.redundancies.files.map(r => r.id))
.map(id => this.removeRedundancy(id))
return concat(...observables)

View File

@ -91,6 +91,9 @@
"@types/express": "4.17.9",
"@types/express-serve-static-core": "4.19.5"
},
"optionalDependencies": {
"webtorrent": "2.5.17"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.190.0",
"@aws-sdk/lib-storage": "^3.190.0",
@ -187,7 +190,6 @@
"useragent": "^2.3.0",
"validator": "^13.0.0",
"webfinger.js": "^2.6.6",
"webtorrent": "2.1.27",
"winston": "3.14.2",
"ws": "^8.0.0",
"yauzl": "^3.1.0"

View File

@ -1,9 +1,9 @@
import { ActivityVideoUrlObject, ActivityPlaylistUrlObject } from './common-objects.js'
import { ActivityPlaylistUrlObject } from './common-objects.js'
export interface CacheFileObject {
id: string
type: 'CacheFile'
object: string
expires: string
url: ActivityVideoUrlObject | ActivityPlaylistUrlObject
url: ActivityPlaylistUrlObject
}

View File

@ -5,13 +5,14 @@ export interface VideoRedundancy {
uuid: string
redundancies: {
files: FileRedundancyInformation[]
// FIXME: remove in v8
files: []
streamingPlaylists: StreamingPlaylistRedundancyInformation[]
streamingPlaylists: RedundancyInformation[]
}
}
interface RedundancyInformation {
export interface RedundancyInformation {
id: number
fileUrl: string
strategy: string
@ -23,13 +24,3 @@ interface RedundancyInformation {
size: number
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface FileRedundancyInformation extends RedundancyInformation {
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface StreamingPlaylistRedundancyInformation extends RedundancyInformation {
}

View File

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { expect } from 'chai'
import { VideoPrivacy, VideoRedundanciesTarget } from '@peertube/peertube-models'
import {
cleanupTests,
createMultipleServers,
@ -10,7 +10,7 @@ import {
setAccessTokensToServers,
waitJobs
} from '@peertube/peertube-server-commands'
import { VideoPrivacy, VideoRedundanciesTarget } from '@peertube/peertube-models'
import { expect } from 'chai'
describe('Test manage videos redundancy', function () {
const targets: VideoRedundanciesTarget[] = [ 'my-videos', 'remote-videos' ]
@ -95,7 +95,7 @@ describe('Test manage videos redundancy', function () {
this.timeout(120000)
await waitJobs(servers)
await servers[0].servers.waitUntilLog('Duplicated ', 10)
await servers[0].servers.waitUntilLog('Duplicated playlist ', 2)
await waitJobs(servers)
const body = await commands[1].listVideos({ target: 'remote-videos' })
@ -119,12 +119,10 @@ describe('Test manage videos redundancy', function () {
expect(videos1.name).to.equal('video 1 server 2')
expect(videos2.name).to.equal('video 2 server 2')
expect(videos1.redundancies.files).to.have.lengthOf(4)
expect(videos1.redundancies.files).to.have.lengthOf(0)
expect(videos1.redundancies.streamingPlaylists).to.have.lengthOf(1)
const redundancies = videos1.redundancies.files.concat(videos1.redundancies.streamingPlaylists)
for (const r of redundancies) {
for (const r of videos1.redundancies.streamingPlaylists) {
expect(r.strategy).to.be.null
expect(r.fileUrl).to.exist
expect(r.createdAt).to.exist
@ -155,12 +153,10 @@ describe('Test manage videos redundancy', function () {
expect(videos1.name).to.equal('video 1 server 2')
expect(videos2.name).to.equal('video 2 server 2')
expect(videos1.redundancies.files).to.have.lengthOf(4)
expect(videos1.redundancies.files).to.have.lengthOf(0)
expect(videos1.redundancies.streamingPlaylists).to.have.lengthOf(1)
const redundancies = videos1.redundancies.files.concat(videos1.redundancies.streamingPlaylists)
for (const r of redundancies) {
for (const r of videos1.redundancies.streamingPlaylists) {
expect(r.strategy).to.equal('recently-added')
expect(r.fileUrl).to.exist
expect(r.createdAt).to.exist
@ -218,7 +214,7 @@ describe('Test manage videos redundancy', function () {
await commands[0].addVideo({ videoId })
await waitJobs(servers)
await servers[0].servers.waitUntilLog('Duplicated ', 15)
await servers[0].servers.waitUntilLog('Duplicated playlist ', 3)
await waitJobs(servers)
{
@ -232,12 +228,10 @@ describe('Test manage videos redundancy', function () {
const video = body.data[0]
expect(video.name).to.equal('video 3 server 2')
expect(video.redundancies.files).to.have.lengthOf(4)
expect(video.redundancies.files).to.have.lengthOf(0)
expect(video.redundancies.streamingPlaylists).to.have.lengthOf(1)
const redundancies = video.redundancies.files.concat(video.redundancies.streamingPlaylists)
for (const r of redundancies) {
for (const r of video.redundancies.streamingPlaylists) {
redundanciesToRemove.push(r.id)
expect(r.strategy).to.equal('manual')
@ -257,12 +251,10 @@ describe('Test manage videos redundancy', function () {
const video = body.data[0]
expect(video.name).to.equal('video 3 server 2')
expect(video.redundancies.files).to.have.lengthOf(4)
expect(video.redundancies.files).to.have.lengthOf(0)
expect(video.redundancies.streamingPlaylists).to.have.lengthOf(1)
const redundancies = video.redundancies.files.concat(video.redundancies.streamingPlaylists)
for (const r of redundancies) {
for (const r of video.redundancies.streamingPlaylists) {
expect(r.strategy).to.be.null
expect(r.fileUrl).to.exist
expect(r.createdAt).to.exist
@ -292,12 +284,10 @@ describe('Test manage videos redundancy', function () {
const video = videos[0]
expect(video.name).to.equal('video 2 server 2')
expect(video.redundancies.files).to.have.lengthOf(4)
expect(video.redundancies.files).to.have.lengthOf(0)
expect(video.redundancies.streamingPlaylists).to.have.lengthOf(1)
const redundancies = video.redundancies.files.concat(video.redundancies.streamingPlaylists)
redundanciesToRemove = redundancies.map(r => r.id)
redundanciesToRemove = video.redundancies.streamingPlaylists.map(r => r.id)
}
})

View File

@ -92,7 +92,7 @@ describe('Test redundancy constraints', function () {
this.timeout(120000)
await waitJobs(servers)
await remoteServer.servers.waitUntilLog('Duplicated ', 5)
await remoteServer.servers.waitUntilLog('Duplicated playlist ', 1)
await waitJobs(servers)
{
@ -121,7 +121,7 @@ describe('Test redundancy constraints', function () {
await uploadWrapper('video 2 server 2')
await remoteServer.servers.waitUntilLog('Duplicated ', 10)
await remoteServer.servers.waitUntilLog('Duplicated playlist ', 2)
await waitJobs(servers)
{
@ -150,7 +150,7 @@ describe('Test redundancy constraints', function () {
await uploadWrapper('video 3 server 2')
await remoteServer.servers.waitUntilLog('Duplicated ', 15)
await remoteServer.servers.waitUntilLog('Duplicated playlist ', 3)
await waitJobs(servers)
{
@ -171,7 +171,7 @@ describe('Test redundancy constraints', function () {
await waitJobs(servers)
await uploadWrapper('video 4 server 2')
await remoteServer.servers.waitUntilLog('Duplicated ', 20)
await remoteServer.servers.waitUntilLog('Duplicated playlist ', 4)
await waitJobs(servers)
{

View File

@ -1,13 +1,8 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { expect } from 'chai'
import { readdir } from 'fs/promises'
import { basename, join } from 'path'
import { wait } from '@peertube/peertube-core-utils'
import {
HttpStatusCode,
VideoDetails,
VideoFile,
VideoPrivacy,
VideoRedundancyStrategy,
VideoRedundancyStrategyWithManual
@ -17,33 +12,19 @@ import {
createMultipleServers,
doubleFollow,
killallServers,
makeRawRequest,
PeerTubeServer,
setAccessTokensToServers,
waitJobs
} from '@peertube/peertube-server-commands'
import { checkSegmentHash } from '@tests/shared/streaming-playlists.js'
import { checkVideoFilesWereRemoved, saveVideoInServers } from '@tests/shared/videos.js'
import { magnetUriDecode } from '@tests/shared/webtorrent.js'
import { expect } from 'chai'
import { readdir } from 'fs/promises'
import { basename, join } from 'path'
let servers: PeerTubeServer[] = []
let video1Server2: VideoDetails
async function checkMagnetWebseeds (file: VideoFile, baseWebseeds: string[], server: PeerTubeServer) {
const parsed = await magnetUriDecode(file.magnetUri)
for (const ws of baseWebseeds) {
const found = parsed.urlList.find(url => url === `${ws}${basename(file.fileUrl)}`)
expect(found, `Webseed ${ws} not found in ${file.magnetUri} on server ${server.url}`).to.not.be.undefined
}
expect(parsed.urlList).to.have.lengthOf(baseWebseeds.length)
for (const url of parsed.urlList) {
await makeRawRequest({ url, expectedStatus: HttpStatusCode.OK_200 })
}
}
async function createServers (strategy: VideoRedundancyStrategy | null, additionalParams: any = {}, withWebVideo = true) {
const strategies: any[] = []
@ -52,7 +33,7 @@ async function createServers (strategy: VideoRedundancyStrategy | null, addition
{
min_lifetime: '1 hour',
strategy,
size: '400KB',
size: '200KB',
...additionalParams
}
@ -100,83 +81,26 @@ async function createServers (strategy: VideoRedundancyStrategy | null, addition
await waitJobs(servers)
}
async function ensureSameFilenames (videoUUID: string) {
let webVideoFilenames: string[]
async function ensureSameFilenames (videoUUID: string, serverArgs = servers) {
let hlsFilenames: string[]
for (const server of servers) {
for (const server of serverArgs) {
const video = await server.videos.getWithToken({ id: videoUUID })
// Ensure we use the same filenames that the origin
const localWebVideoFilenames = video.files.map(f => basename(f.fileUrl)).sort()
// Ensure we use the same filenames as the origin
const localHLSFilenames = video.streamingPlaylists[0].files.map(f => basename(f.fileUrl)).sort()
if (webVideoFilenames) expect(webVideoFilenames).to.deep.equal(localWebVideoFilenames)
else webVideoFilenames = localWebVideoFilenames
if (hlsFilenames) expect(hlsFilenames).to.deep.equal(localHLSFilenames)
else hlsFilenames = localHLSFilenames
}
return { webVideoFilenames, hlsFilenames }
return { hlsFilenames }
}
async function check1WebSeed (videoUUID?: string) {
async function check0PlaylistRedundancies (videoUUID?: string, serverArgs = servers) {
if (!videoUUID) videoUUID = video1Server2.uuid
const webseeds = [
`${servers[1].url}/static/web-videos/`
]
for (const server of servers) {
// With token to avoid issues with video follow constraints
const video = await server.videos.getWithToken({ id: videoUUID })
for (const f of video.files) {
await checkMagnetWebseeds(f, webseeds, server)
}
}
await ensureSameFilenames(videoUUID)
}
async function check2Webseeds (videoUUID?: string) {
if (!videoUUID) videoUUID = video1Server2.uuid
const webseeds = [
`${servers[0].url}/static/redundancy/`,
`${servers[1].url}/static/web-videos/`
]
for (const server of servers) {
const video = await server.videos.get({ id: videoUUID })
for (const file of video.files) {
await checkMagnetWebseeds(file, webseeds, server)
}
}
const { webVideoFilenames } = await ensureSameFilenames(videoUUID)
const directories = [
servers[0].getDirectoryPath('redundancy'),
servers[1].getDirectoryPath('web-videos')
]
for (const directory of directories) {
const files = await readdir(directory)
expect(files).to.have.length.at.least(4)
// Ensure we files exist on disk
expect(files.find(f => webVideoFilenames.includes(f))).to.exist
}
}
async function check0PlaylistRedundancies (videoUUID?: string) {
if (!videoUUID) videoUUID = video1Server2.uuid
for (const server of servers) {
for (const server of serverArgs) {
// With token to avoid issues with video follow constraints
const video = await server.videos.getWithToken({ id: videoUUID })
@ -185,7 +109,7 @@ async function check0PlaylistRedundancies (videoUUID?: string) {
expect(video.streamingPlaylists[0].redundancies).to.have.lengthOf(0)
}
await ensureSameFilenames(videoUUID)
await ensureSameFilenames(videoUUID, serverArgs)
}
async function check1PlaylistRedundancies (videoUUID?: string) {
@ -233,7 +157,7 @@ async function checkStatsGlobal (strategy: VideoRedundancyStrategyWithManual) {
let statsLength = 1
if (strategy !== 'manual') {
totalSize = 409600
totalSize = 204800
statsLength = 2
}
@ -247,11 +171,11 @@ async function checkStatsGlobal (strategy: VideoRedundancyStrategyWithManual) {
return stat
}
async function checkStatsWith1Redundancy (strategy: VideoRedundancyStrategyWithManual, onlyHls = false) {
async function checkStatsWith1Redundancy (strategy: VideoRedundancyStrategyWithManual) {
const stat = await checkStatsGlobal(strategy)
expect(stat.totalUsed).to.be.at.least(1).and.below(409601)
expect(stat.totalVideoFiles).to.equal(onlyHls ? 4 : 8)
expect(stat.totalUsed).to.be.at.least(1).and.below(204801)
expect(stat.totalVideoFiles).to.equal(4)
expect(stat.totalVideos).to.equal(1)
}
@ -307,8 +231,7 @@ describe('Test videos redundancy', function () {
return createServers(strategy)
})
it('Should have 1 webseed on the first video', async function () {
await check1WebSeed()
it('Should not have redundancy', async function () {
await check0PlaylistRedundancies()
await checkStatsWithoutRedundancy(strategy)
})
@ -317,14 +240,13 @@ describe('Test videos redundancy', function () {
return enableRedundancyOnServer1()
})
it('Should have 2 webseeds on the first video', async function () {
it('Should have redundancy on the first video', async function () {
this.timeout(80000)
await waitJobs(servers)
await servers[0].servers.waitUntilLog('Duplicated ', 5)
await servers[0].servers.waitUntilLog('Duplicated playlist ', 1)
await waitJobs(servers)
await check2Webseeds()
await check1PlaylistRedundancies()
await checkStatsWith1Redundancy(strategy)
})
@ -337,7 +259,6 @@ describe('Test videos redundancy', function () {
await waitJobs(servers)
await wait(5000)
await check1WebSeed()
await check0PlaylistRedundancies()
await checkVideoFilesWereRemoved({ server: servers[0], video: video1Server2, onlyVideoFiles: true })
@ -357,8 +278,7 @@ describe('Test videos redundancy', function () {
return createServers(strategy)
})
it('Should have 1 webseed on the first video', async function () {
await check1WebSeed()
it('Should not have redundancy on the first video', async function () {
await check0PlaylistRedundancies()
await checkStatsWithoutRedundancy(strategy)
})
@ -367,14 +287,13 @@ describe('Test videos redundancy', function () {
return enableRedundancyOnServer1()
})
it('Should have 2 webseeds on the first video', async function () {
it('Should have redundancy on the first video', async function () {
this.timeout(80000)
await waitJobs(servers)
await servers[0].servers.waitUntilLog('Duplicated ', 5)
await servers[0].servers.waitUntilLog('Duplicated playlist ', 1)
await waitJobs(servers)
await check2Webseeds()
await check1PlaylistRedundancies()
await checkStatsWith1Redundancy(strategy)
})
@ -387,7 +306,6 @@ describe('Test videos redundancy', function () {
await waitJobs(servers)
await wait(5000)
await check2Webseeds()
await check1PlaylistRedundancies()
await checkStatsWith1Redundancy(strategy)
})
@ -400,7 +318,6 @@ describe('Test videos redundancy', function () {
await waitJobs(servers)
await wait(5000)
await check1WebSeed()
await check0PlaylistRedundancies()
await checkVideoFilesWereRemoved({ server: servers[0], video: video1Server2, onlyVideoFiles: true })
@ -420,8 +337,7 @@ describe('Test videos redundancy', function () {
return createServers(strategy, { min_views: 3 })
})
it('Should have 1 webseed on the first video', async function () {
await check1WebSeed()
it('Should not have redundancy on the first video', async function () {
await check0PlaylistRedundancies()
await checkStatsWithoutRedundancy(strategy)
})
@ -430,14 +346,13 @@ describe('Test videos redundancy', function () {
return enableRedundancyOnServer1()
})
it('Should still have 1 webseed on the first video', async function () {
it('Should still not have redundancy on the first video', async function () {
this.timeout(80000)
await waitJobs(servers)
await wait(15000)
await waitJobs(servers)
await check1WebSeed()
await check0PlaylistRedundancies()
await checkStatsWithoutRedundancy(strategy)
})
@ -452,14 +367,13 @@ describe('Test videos redundancy', function () {
await waitJobs(servers)
})
it('Should have 2 webseeds on the first video', async function () {
it('Should now have redundancy on the first video', async function () {
this.timeout(80000)
await waitJobs(servers)
await servers[0].servers.waitUntilLog('Duplicated ', 5)
await servers[0].servers.waitUntilLog('Duplicated playlist ', 1)
await waitJobs(servers)
await check2Webseeds()
await check1PlaylistRedundancies()
await checkStatsWith1Redundancy(strategy)
})
@ -492,7 +406,6 @@ describe('Test videos redundancy', function () {
})
it('Should have 0 playlist redundancy on the first video', async function () {
await check1WebSeed()
await check0PlaylistRedundancies()
})
@ -521,11 +434,11 @@ describe('Test videos redundancy', function () {
await waitJobs(servers)
await waitJobs(servers)
await servers[0].servers.waitUntilLog('Duplicated ', 1)
await servers[0].servers.waitUntilLog('Duplicated playlist ', 1)
await waitJobs(servers)
await check1PlaylistRedundancies()
await checkStatsWith1Redundancy(strategy, true)
await checkStatsWith1Redundancy(strategy)
})
it('Should remove the video and the redundancy files', async function () {
@ -553,8 +466,7 @@ describe('Test videos redundancy', function () {
return createServers(null)
})
it('Should have 1 webseed on the first video', async function () {
await check1WebSeed()
it('Should not have redundancy on the first video', async function () {
await check0PlaylistRedundancies()
await checkStatsWithoutRedundancy('manual')
})
@ -563,14 +475,13 @@ describe('Test videos redundancy', function () {
await servers[0].redundancy.addVideo({ videoId: video1Server2.id })
})
it('Should have 2 webseeds on the first video', async function () {
it('Should now have redundancy on the first video', async function () {
this.timeout(80000)
await waitJobs(servers)
await servers[0].servers.waitUntilLog('Duplicated ', 5)
await servers[0].servers.waitUntilLog('Duplicated playlist ', 1)
await waitJobs(servers)
await check2Webseeds()
await check1PlaylistRedundancies()
await checkStatsWith1Redundancy('manual')
})
@ -585,14 +496,13 @@ describe('Test videos redundancy', function () {
const video = videos[0]
for (const r of video.redundancies.files.concat(video.redundancies.streamingPlaylists)) {
for (const r of video.redundancies.streamingPlaylists) {
await servers[0].redundancy.removeVideo({ redundancyId: r.id })
}
await waitJobs(servers)
await wait(5000)
await check1WebSeed()
await check0PlaylistRedundancies()
await checkVideoFilesWereRemoved({ server: servers[0], video: video1Server2, onlyVideoFiles: true })
@ -606,26 +516,6 @@ describe('Test videos redundancy', function () {
describe('Test expiration', function () {
const strategy = 'recently-added'
async function checkContains (servers: PeerTubeServer[], str: string) {
for (const server of servers) {
const video = await server.videos.get({ id: video1Server2.uuid })
for (const f of video.files) {
expect(f.magnetUri).to.contain(str)
}
}
}
async function checkNotContains (servers: PeerTubeServer[], str: string) {
for (const server of servers) {
const video = await server.videos.get({ id: video1Server2.uuid })
for (const f of video.files) {
expect(f.magnetUri).to.not.contain(str)
}
}
}
before(async function () {
this.timeout(240000)
@ -634,18 +524,18 @@ describe('Test videos redundancy', function () {
await enableRedundancyOnServer1()
})
it('Should still have 2 webseeds after 10 seconds', async function () {
it('Should still have redundancy after 10 seconds', async function () {
this.timeout(80000)
await wait(10000)
try {
await checkContains(servers, 'http%3A%2F%2F' + servers[0].hostname + '%3A' + servers[0].port)
await check1PlaylistRedundancies()
} catch {
// Maybe a server deleted a redundancy in the scheduler
await wait(2000)
await checkContains(servers, 'http%3A%2F%2F' + servers[0].hostname + '%3A' + servers[0].port)
await check1PlaylistRedundancies()
}
})
@ -656,7 +546,7 @@ describe('Test videos redundancy', function () {
await wait(15000)
await checkNotContains([ servers[1], servers[2] ], 'http%3A%2F%2F' + servers[0].port + '%3A' + servers[0].port)
await check0PlaylistRedundancies(video1Server2.uuid, [ servers[1], servers[2] ])
})
after(async function () {
@ -676,10 +566,9 @@ describe('Test videos redundancy', function () {
await enableRedundancyOnServer1()
await waitJobs(servers)
await servers[0].servers.waitUntilLog('Duplicated ', 5)
await servers[0].servers.waitUntilLog('Duplicated playlist ', 1)
await waitJobs(servers)
await check2Webseeds()
await check1PlaylistRedundancies()
await checkStatsWith1Redundancy(strategy)
@ -692,7 +581,7 @@ describe('Test videos redundancy', function () {
await servers[1].videos.update({ id: video2Server2UUID, attributes: { privacy: VideoPrivacy.PUBLIC } })
})
it('Should cache video 2 webseeds on the first video', async function () {
it('Should replace first video redundancy by video 2', async function () {
this.timeout(240000)
await waitJobs(servers)
@ -703,10 +592,8 @@ describe('Test videos redundancy', function () {
await wait(1000)
try {
await check1WebSeed()
await check0PlaylistRedundancies()
await check2Webseeds(video2Server2UUID)
await check1PlaylistRedundancies(video2Server2UUID)
checked = true

View File

@ -49,7 +49,7 @@ import {
getVideoLocalViewerValidator,
videoCommentGetValidator
} from '../../middlewares/validators/index.js'
import { videoFileRedundancyGetValidator, videoPlaylistRedundancyGetValidator } from '../../middlewares/validators/redundancy.js'
import { videoPlaylistRedundancyGetValidator } from '../../middlewares/validators/redundancy.js'
import { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from '../../middlewares/validators/videos/video-playlists.js'
import { AccountVideoRateModel } from '../../models/account/account-video-rate.js'
import { AccountModel } from '../../models/account/account.js'
@ -224,12 +224,6 @@ activityPubClientRouter.get('/video-channels/:nameWithHost/playlists',
asyncMiddleware(videoChannelPlaylistsController)
)
activityPubClientRouter.get('/redundancy/videos/:videoId/:resolution([0-9]+)(-:fps([0-9]+))?',
executeIfActivityPub,
activityPubRateLimiter,
asyncMiddleware(videoFileRedundancyGetValidator),
asyncMiddleware(videoRedundancyController)
)
activityPubClientRouter.get('/redundancy/streaming-playlists/:streamingPlaylistType/:videoId',
executeIfActivityPub,
activityPubRateLimiter,

View File

@ -1,3 +1,11 @@
import { promisify2 } from '@peertube/peertube-core-utils'
import { sha1 } from '@peertube/peertube-node-utils'
import { WEBSERVER } from '@server/initializers/constants.js'
import { generateTorrentFileName } from '@server/lib/paths.js'
import { VideoPathManager } from '@server/lib/video-path-manager.js'
import { MVideoFile } from '@server/types/models/video/video-file.js'
import { MStreamingPlaylistVideo } from '@server/types/models/video/video-streaming-playlist.js'
import { MVideo } from '@server/types/models/video/video.js'
import bencode from 'bencode'
import createTorrent from 'create-torrent'
import { createWriteStream } from 'fs'
@ -7,15 +15,6 @@ import { encode as magnetUriEncode } from 'magnet-uri'
import parseTorrent from 'parse-torrent'
import { dirname, join } from 'path'
import { pipeline } from 'stream'
import { promisify2 } from '@peertube/peertube-core-utils'
import { isArray } from '@server/helpers/custom-validators/misc.js'
import { WEBSERVER } from '@server/initializers/constants.js'
import { generateTorrentFileName } from '@server/lib/paths.js'
import { VideoPathManager } from '@server/lib/video-path-manager.js'
import { MVideoFile, MVideoFileRedundanciesOpt } from '@server/types/models/video/video-file.js'
import { MStreamingPlaylistVideo } from '@server/types/models/video/video-streaming-playlist.js'
import { MVideo } from '@server/types/models/video/video.js'
import { sha1 } from '@peertube/peertube-node-utils'
import { CONFIG } from '../initializers/config.js'
import { logger } from './logger.js'
import { generateVideoImportTmpPath } from './utils.js'
@ -25,7 +24,7 @@ import type { Instance, TorrentFile } from 'webtorrent'
const createTorrentPromise = promisify2<string, any, any>(createTorrent)
async function downloadWebTorrentVideo (target: { uri: string, torrentName?: string }, timeout: number) {
export async function downloadWebTorrentVideo (target: { uri: string, torrentName?: string }, timeout: number) {
const id = target.uri || target.torrentName
let timer
@ -39,7 +38,10 @@ async function downloadWebTorrentVideo (target: { uri: string, torrentName?: str
const webtorrent = new (await import('webtorrent')).default({
natUpnp: false,
natPmp: false,
utp: false
utp: false,
lsd: false,
downloadLimit: 5_000_000,
uploadLimit: 5_000_000
} as any)
return new Promise<string>((res, rej) => {
@ -99,13 +101,13 @@ async function downloadWebTorrentVideo (target: { uri: string, torrentName?: str
})
}
function createTorrentAndSetInfoHash (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, videoFile: MVideoFile) {
export function createTorrentAndSetInfoHash (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, videoFile: MVideoFile) {
return VideoPathManager.Instance.makeAvailableVideoFile(videoFile.withVideoOrPlaylist(videoOrPlaylist), videoPath => {
return createTorrentAndSetInfoHashFromPath(videoOrPlaylist, videoFile, videoPath)
})
}
async function createTorrentAndSetInfoHashFromPath (
export async function createTorrentAndSetInfoHashFromPath (
videoOrPlaylist: MVideo | MStreamingPlaylistVideo,
videoFile: MVideoFile,
filePath: string
@ -139,7 +141,7 @@ async function createTorrentAndSetInfoHashFromPath (
videoFile.torrentFilename = torrentFilename
}
async function updateTorrentMetadata (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, videoFile: MVideoFile) {
export async function updateTorrentMetadata (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, videoFile: MVideoFile) {
const video = extractVideo(videoOrPlaylist)
if (!videoFile.torrentFilename) {
@ -177,21 +179,18 @@ async function updateTorrentMetadata (videoOrPlaylist: MVideo | MStreamingPlayli
videoFile.infoHash = sha1(bencode.encode(decoded.info))
}
function generateMagnetUri (
export function generateMagnetUri (
video: MVideo,
videoFile: MVideoFileRedundanciesOpt,
videoFile: MVideoFile,
trackerUrls: string[]
) {
const xs = videoFile.getTorrentUrl()
const announce = trackerUrls
let urlList = video.hasPrivateStaticPath()
const urlList = video.hasPrivateStaticPath()
? []
: [ videoFile.getFileUrl(video) ]
const redundancies = videoFile.RedundancyVideos
if (isArray(redundancies)) urlList = urlList.concat(redundancies.map(r => r.fileUrl))
const magnetHash = {
xs,
announce,
@ -204,18 +203,7 @@ function generateMagnetUri (
}
// ---------------------------------------------------------------------------
export {
createTorrentPromise,
updateTorrentMetadata,
createTorrentAndSetInfoHash,
createTorrentAndSetInfoHashFromPath,
generateMagnetUri,
downloadWebTorrentVideo
}
// Private
// ---------------------------------------------------------------------------
function safeWebtorrentDestroy (

View File

@ -0,0 +1,71 @@
import { logger } from '@server/helpers/logger.js'
import { getServerActor } from '@server/models/application/application.js'
import { VideoFileModel } from '@server/models/video/video-file.js'
import { remove } from 'fs-extra'
import { join } from 'path'
import * as Sequelize from 'sequelize'
import { QueryTypes } from 'sequelize'
import { CONFIG } from '../config.js'
async function up (utils: {
transaction: Sequelize.Transaction
queryInterface: Sequelize.QueryInterface
sequelize: Sequelize.Sequelize
}): Promise<void> {
const { transaction } = utils
const actor = await getServerActor()
{
const query = 'SELECT "videoFileId" FROM "videoRedundancy" WHERE "actor" = :actorId AND "videoFileId" IS NOT NULL'
const rows = await utils.sequelize.query<{ videoFileId: number }>(query, {
bind: { actorId: actor.id },
transaction,
type: QueryTypes.SELECT as QueryTypes.SELECT
})
for (const { videoFileId } of rows) {
try {
const videoFile = await VideoFileModel.loadWithVideo(videoFileId, transaction)
const filePath = join(CONFIG.STORAGE.REDUNDANCY_DIR, videoFile.filename)
await remove(filePath)
} catch (err) {
logger.error(`Cannot delete redundancy file (videoFileId: ${videoFileId}).`, { err })
}
}
}
{
const query = 'DELETE FROM "videoRedundancy" WHERE "videoFileId" IS NOT NULL'
await utils.sequelize.query(query, { transaction })
}
{
await utils.sequelize.query('DROP INDEX IF EXISTS video_redundancy_video_file_id')
}
{
await utils.queryInterface.removeColumn('videoRedundancy', 'videoFileId', { transaction })
}
{
const data = {
type: Sequelize.INTEGER,
defaultValue: null,
allowNull: false
}
await utils.queryInterface.changeColumn('videoRedundancy', 'videoStreamingPlaylistId', data, { transaction })
}
}
function down (options) {
throw new Error('Not implemented.')
}
export {
down, up
}

View File

@ -1,8 +1,8 @@
import { Transaction } from 'sequelize'
import { MActorId, MVideoRedundancy, MVideoWithAllFiles } from '@server/types/models/index.js'
import { CacheFileObject, VideoStreamingPlaylistType } from '@peertube/peertube-models'
import { logger } from '@server/helpers/logger.js'
import { MActorId, MVideoRedundancy, MVideoWithAllFiles } from '@server/types/models/index.js'
import { Transaction } from 'sequelize'
import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy.js'
import { exists } from '@server/helpers/custom-validators/misc.js'
async function createOrUpdateCacheFile (cacheFileObject: CacheFileObject, video: MVideoWithAllFiles, byActor: MActorId, t: Transaction) {
const redundancyModel = await VideoRedundancyModel.loadByUrl(cacheFileObject.id, t)
@ -48,40 +48,22 @@ function updateCacheFile (
}
function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject, video: MVideoWithAllFiles, byActor: MActorId) {
if (cacheFileObject.url.mediaType === 'application/x-mpegURL') {
const url = cacheFileObject.url
const playlist = video.VideoStreamingPlaylists.find(t => t.type === VideoStreamingPlaylistType.HLS)
if (!playlist) throw new Error('Cannot find HLS playlist of video ' + video.url)
return {
expiresOn: cacheFileObject.expires ? new Date(cacheFileObject.expires) : null,
url: cacheFileObject.id,
fileUrl: url.href,
strategy: null,
videoStreamingPlaylistId: playlist.id,
actorId: byActor.id
}
if (cacheFileObject.url.mediaType !== 'application/x-mpegURL') {
logger.debug('Do not create remoet cache file of non application/x-mpegURL media type', { cacheFileObject })
return
}
const url = cacheFileObject.url
const urlFPS = exists(url.fps) // TODO: compat with < 6.1, remove in 8.0
? url.fps
: url['_:fps']
const videoFile = video.VideoFiles.find(f => {
return f.resolution === url.height && f.fps === urlFPS
})
if (!videoFile) throw new Error(`Cannot find video file ${url.height} ${urlFPS} of video ${video.url}`)
const playlist = video.VideoStreamingPlaylists.find(t => t.type === VideoStreamingPlaylistType.HLS)
if (!playlist) throw new Error('Cannot find HLS playlist of video ' + video.url)
return {
expiresOn: cacheFileObject.expires ? new Date(cacheFileObject.expires) : null,
url: cacheFileObject.id,
fileUrl: url.href,
strategy: null,
videoFileId: videoFile.id,
videoStreamingPlaylistId: playlist.id,
actorId: byActor.id
}
}

View File

@ -19,7 +19,6 @@ import {
MLocalVideoViewerWithWatchSections,
MVideoAP, MVideoAccountLight,
MVideoPlaylistFull,
MVideoRedundancyFileVideo,
MVideoRedundancyStreamingPlaylistVideo
} from '../../../types/models/index.js'
import { audiencify, getAudience } from '../audience.js'
@ -60,7 +59,7 @@ export async function sendCreateVideo (video: MVideoAP, transaction: Transaction
export async function sendCreateCacheFile (
byActor: MActorLight,
video: MVideoAccountLight,
fileRedundancy: MVideoRedundancyStreamingPlaylistVideo | MVideoRedundancyFileVideo
fileRedundancy: MVideoRedundancyStreamingPlaylistVideo
) {
logger.info('Creating job to send file cache of %s.', fileRedundancy.url, lTags(video.uuid))

View File

@ -13,7 +13,6 @@ import {
MVideoUrl,
MVideoWithHost
} from '../../types/models/index.js'
import { MVideoFileVideoUUID } from '../../types/models/video/video-file.js'
import { MVideoPlaylist, MVideoPlaylistUUID } from '../../types/models/video/video-playlist.js'
import { MStreamingPlaylist } from '../../types/models/video/video-streaming-playlist.js'
@ -29,12 +28,6 @@ export function getLocalVideoPlaylistElementActivityPubUrl (playlist: MVideoPlay
return WEBSERVER.URL + '/video-playlists/' + playlist.uuid + '/videos/' + element.id
}
export function getLocalVideoCacheFileActivityPubUrl (videoFile: MVideoFileVideoUUID) {
const suffixFPS = videoFile.fps && videoFile.fps !== -1 ? '-' + videoFile.fps : ''
return `${WEBSERVER.URL}/redundancy/videos/${videoFile.Video.uuid}/${videoFile.resolution}${suffixFPS}`
}
export function getLocalVideoCacheStreamingPlaylistActivityPubUrl (video: MVideoUUID, playlist: MStreamingPlaylist) {
return `${WEBSERVER.URL}/redundancy/streaming-playlists/${playlist.getStringType()}/${video.uuid}`
}

View File

@ -1,29 +1,25 @@
import { move } from 'fs-extra/esm'
import { join } from 'path'
import { VideosRedundancyStrategy } from '@peertube/peertube-models'
import { getServerActor } from '@server/models/application/application.js'
import { VideoModel } from '@server/models/video/video.js'
import {
MStreamingPlaylistFiles,
MVideoAccountLight,
MVideoFile,
MVideoFileVideo,
MVideoRedundancyFileVideo,
MVideoRedundancyStreamingPlaylistVideo,
MVideoRedundancyVideo,
MVideoWithAllFiles
} from '@server/types/models/index.js'
import { VideosRedundancyStrategy } from '@peertube/peertube-models'
import { join } from 'path'
import { logger, loggerTagsFactory } from '../../helpers/logger.js'
import { downloadWebTorrentVideo } from '../../helpers/webtorrent.js'
import { CONFIG } from '../../initializers/config.js'
import { DIRECTORIES, REDUNDANCY, VIDEO_IMPORT_TIMEOUT } from '../../initializers/constants.js'
import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy.js'
import { sendCreateCacheFile, sendUpdateCacheFile } from '../activitypub/send/index.js'
import { getLocalVideoCacheFileActivityPubUrl, getLocalVideoCacheStreamingPlaylistActivityPubUrl } from '../activitypub/url.js'
import { getLocalVideoCacheStreamingPlaylistActivityPubUrl } from '../activitypub/url.js'
import { getOrCreateAPVideo } from '../activitypub/videos/index.js'
import { downloadPlaylistSegments } from '../hls.js'
import { removeVideoRedundancy } from '../redundancy.js'
import { generateHLSRedundancyUrl, generateWebVideoRedundancyUrl } from '../video-urls.js'
import { generateHLSRedundancyUrl } from '../video-urls.js'
import { AbstractScheduler } from './abstract-scheduler.js'
const lTags = loggerTagsFactory('redundancy')
@ -31,16 +27,9 @@ const lTags = loggerTagsFactory('redundancy')
type CandidateToDuplicate = {
redundancy: VideosRedundancyStrategy
video: MVideoWithAllFiles
files: MVideoFile[]
streamingPlaylists: MStreamingPlaylistFiles[]
}
function isMVideoRedundancyFileVideo (
o: MVideoRedundancyFileVideo | MVideoRedundancyStreamingPlaylistVideo
): o is MVideoRedundancyFileVideo {
return !!(o as MVideoRedundancyFileVideo).VideoFile
}
export class VideosRedundancyScheduler extends AbstractScheduler {
private static instance: VideosRedundancyScheduler
@ -62,7 +51,6 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
return this.createVideoRedundancies({
video: videoToDuplicate,
redundancy: null,
files: videoToDuplicate.VideoFiles,
streamingPlaylists: videoToDuplicate.VideoStreamingPlaylists
})
}
@ -197,17 +185,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
return
}
for (const file of data.files) {
const existingRedundancy = await VideoRedundancyModel.loadLocalByFileId(file.id)
if (existingRedundancy) {
await this.extendsRedundancy(existingRedundancy)
continue
}
await this.createVideoFileRedundancy(data.redundancy, video, file)
}
// Only HLS player supports redundancy, so do not duplicate web videos
for (const streamingPlaylist of data.streamingPlaylists) {
const existingRedundancy = await VideoRedundancyModel.loadLocalByStreamingPlaylistId(streamingPlaylist.id)
if (existingRedundancy) {
@ -220,43 +198,6 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
}
}
private async createVideoFileRedundancy (redundancy: VideosRedundancyStrategy | null, video: MVideoAccountLight, fileArg: MVideoFile) {
let strategy = 'manual'
let expiresOn: Date = null
if (redundancy) {
strategy = redundancy.strategy
expiresOn = this.buildNewExpiration(redundancy.minLifetime)
}
const file = fileArg as MVideoFileVideo
file.Video = video
const serverActor = await getServerActor()
logger.info('Duplicating %s - %d in videos redundancy with "%s" strategy.', video.url, file.resolution, strategy, lTags(video.uuid))
const tmpPath = await downloadWebTorrentVideo({ uri: file.torrentUrl }, VIDEO_IMPORT_TIMEOUT)
const destPath = join(CONFIG.STORAGE.REDUNDANCY_DIR, file.filename)
await move(tmpPath, destPath, { overwrite: true })
const createdModel: MVideoRedundancyFileVideo = await VideoRedundancyModel.create({
expiresOn,
url: getLocalVideoCacheFileActivityPubUrl(file),
fileUrl: generateWebVideoRedundancyUrl(file),
strategy,
videoFileId: file.id,
actorId: serverActor.id
})
createdModel.VideoFile = file
await sendCreateCacheFile(serverActor, video, createdModel)
logger.info('Duplicated %s - %d -> %s.', video.url, file.resolution, createdModel.url, lTags(video.uuid))
}
private async createStreamingPlaylistRedundancy (
redundancy: VideosRedundancyStrategy,
video: MVideoAccountLight,
@ -278,7 +219,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
const destDirectory = join(DIRECTORIES.HLS_REDUNDANCY, video.uuid)
const masterPlaylistUrl = playlist.getMasterPlaylistUrl(video)
const maxSizeKB = this.getTotalFileSizes([], [ playlist ]) / 1000
const maxSizeKB = this.getTotalFileSizes([ playlist ]) / 1000
const toleranceKB = maxSizeKB + ((5 * maxSizeKB) / 100) // 5% more tolerance
await downloadPlaylistSegments(masterPlaylistUrl, destDirectory, VIDEO_IMPORT_TIMEOUT, toleranceKB)
@ -315,11 +256,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
const toDelete = await VideoRedundancyModel.loadOldestLocalExpired(redundancy.strategy, redundancy.minLifetime)
if (!toDelete) return
const videoId = toDelete.VideoFile
? toDelete.VideoFile.videoId
: toDelete.VideoStreamingPlaylist.videoId
const redundancies = await VideoRedundancyModel.listLocalByVideoId(videoId)
const redundancies = await VideoRedundancyModel.listLocalByStreamingPlaylistId(toDelete.VideoStreamingPlaylist.id)
for (const redundancy of redundancies) {
await removeVideoRedundancy(redundancy)
@ -332,7 +269,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
const { totalUsed: alreadyUsed } = await VideoRedundancyModel.getStats(candidateToDuplicate.redundancy.strategy)
const videoSize = this.getTotalFileSizes(candidateToDuplicate.files, candidateToDuplicate.streamingPlaylists)
const videoSize = this.getTotalFileSizes(candidateToDuplicate.streamingPlaylists)
const willUse = alreadyUsed + videoSize
logger.debug('Checking candidate size.', { maxSize, alreadyUsed, videoSize, willUse, ...lTags(candidateToDuplicate.video.uuid) })
@ -344,16 +281,14 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
return new Date(Date.now() + expiresAfterMs)
}
private buildEntryLogId (object: MVideoRedundancyFileVideo | MVideoRedundancyStreamingPlaylistVideo) {
if (isMVideoRedundancyFileVideo(object)) return `${object.VideoFile.Video.url}-${object.VideoFile.resolution}`
private buildEntryLogId (object: MVideoRedundancyStreamingPlaylistVideo) {
return `${object.VideoStreamingPlaylist.getMasterPlaylistUrl(object.VideoStreamingPlaylist.Video)}`
}
private getTotalFileSizes (files: MVideoFile[], playlists: MStreamingPlaylistFiles[]): number {
private getTotalFileSizes (playlists: MStreamingPlaylistFiles[]): number {
const fileReducer = (previous: number, current: MVideoFile) => previous + current.size
let allFiles = files
let allFiles: MVideoFile[] = []
for (const p of playlists) {
allFiles = allFiles.concat(p.VideoFiles)
}

View File

@ -17,7 +17,7 @@ import { Mutex } from 'async-mutex'
import { remove } from 'fs-extra/esm'
import { extname, join } from 'path'
import { makeHLSFileAvailable, makeWebVideoFileAvailable } from './object-storage/index.js'
import { getHLSDirectory, getHLSRedundancyDirectory, getHlsResolutionPlaylistFilename } from './paths.js'
import { getHLSDirectory, getHlsResolutionPlaylistFilename } from './paths.js'
import { isVideoInPrivateDirectory } from './video-privacy.js'
type MakeAvailableCB <T> = (path: string) => Awaitable<T>
@ -42,16 +42,6 @@ class VideoPathManager {
return join(base, filename)
}
getFSRedundancyVideoFilePath (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, videoFile: MVideoFile) {
if (videoFile.isHLS()) {
const video = extractVideo(videoOrPlaylist)
return join(getHLSRedundancyDirectory(video), videoFile.filename)
}
return join(CONFIG.STORAGE.REDUNDANCY_DIR, videoFile.filename)
}
getFSVideoFileOutputPath (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, videoFile: MVideoFile) {
const video = extractVideo(videoOrPlaylist)

View File

@ -3,28 +3,15 @@ import { MStreamingPlaylist, MVideo, MVideoFile, MVideoUUID } from '@server/type
// ################## Redundancy ##################
function generateHLSRedundancyUrl (video: MVideo, playlist: MStreamingPlaylist) {
export function generateHLSRedundancyUrl (video: MVideo, playlist: MStreamingPlaylist) {
// Base URL used by our HLS player
return WEBSERVER.URL + STATIC_PATHS.REDUNDANCY + playlist.getStringType() + '/' + video.uuid
}
function generateWebVideoRedundancyUrl (file: MVideoFile) {
return WEBSERVER.URL + STATIC_PATHS.REDUNDANCY + file.filename
}
// ################## Meta data ##################
function getLocalVideoFileMetadataUrl (video: MVideoUUID, videoFile: MVideoFile) {
export function getLocalVideoFileMetadataUrl (video: MVideoUUID, videoFile: MVideoFile) {
const path = '/api/v1/videos/'
return WEBSERVER.URL + path + video.uuid + '/metadata/' + videoFile.id
}
// ---------------------------------------------------------------------------
export {
getLocalVideoFileMetadataUrl,
generateWebVideoRedundancyUrl,
generateHLSRedundancyUrl
}

View File

@ -18,52 +18,6 @@ import { ServerModel } from '../../models/server/server.js'
import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from './shared/index.js'
import { canVideoBeFederated } from '@server/lib/activitypub/videos/federate.js'
const videoFileRedundancyGetValidator = [
isValidVideoIdParam('videoId'),
param('resolution')
.customSanitizer(toIntOrNull)
.custom(exists),
param('fps')
.optional()
.customSanitizer(toIntOrNull)
.custom(exists),
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.videoId, res)) return
if (!canVideoBeFederated(res.locals.onlyVideo)) return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
const video = res.locals.videoAll
const paramResolution = req.params.resolution as unknown as number // We casted to int above
const paramFPS = req.params.fps as unknown as number // We casted to int above
const videoFile = video.VideoFiles.find(f => {
return f.resolution === paramResolution && (!req.params.fps || paramFPS)
})
if (!videoFile) {
return res.fail({
status: HttpStatusCode.NOT_FOUND_404,
message: 'Video file not found.'
})
}
res.locals.videoFile = videoFile
const videoRedundancy = await VideoRedundancyModel.loadLocalByFileId(videoFile.id)
if (!videoRedundancy) {
return res.fail({
status: HttpStatusCode.NOT_FOUND_404,
message: 'Video redundancy not found.'
})
}
res.locals.videoRedundancy = videoRedundancy
return next()
}
]
const videoPlaylistRedundancyGetValidator = [
isValidVideoIdParam('videoId'),
@ -192,7 +146,6 @@ const removeVideoRedundancyValidator = [
// ---------------------------------------------------------------------------
export {
videoFileRedundancyGetValidator,
videoPlaylistRedundancyGetValidator,
updateServerRedundancyValidator,
listVideoRedundanciesValidator,

View File

@ -1,8 +1,6 @@
import {
ActivityVideoUrlObject,
CacheFileObject,
FileRedundancyInformation,
StreamingPlaylistRedundancyInformation,
RedundancyInformation,
VideoPrivacy,
VideoRedundanciesTarget,
VideoRedundancy,
@ -10,7 +8,6 @@ import {
VideoRedundancyStrategyWithManual
} from '@peertube/peertube-models'
import { isTestInstance } from '@peertube/peertube-node-utils'
import { getVideoFileMimeType } from '@server/lib/video-file.js'
import { getServerActor } from '@server/models/application/application.js'
import { MActor, MVideoForRedundancyAPI, MVideoRedundancy, MVideoRedundancyAP, MVideoRedundancyVideo } from '@server/types/models/index.js'
import sample from 'lodash-es/sample.js'
@ -47,16 +44,6 @@ export enum ScopeNames {
@Scopes(() => ({
[ScopeNames.WITH_VIDEO]: {
include: [
{
model: VideoFileModel,
required: false,
include: [
{
model: VideoModel,
required: true
}
]
},
{
model: VideoStreamingPlaylistModel,
required: false,
@ -75,7 +62,7 @@ export enum ScopeNames {
tableName: 'videoRedundancy',
indexes: [
{
fields: [ 'videoFileId' ]
fields: [ 'videoStreamingPlaylistId' ]
},
{
fields: [ 'actorId' ]
@ -114,25 +101,13 @@ export class VideoRedundancyModel extends SequelizeModel<VideoRedundancyModel> {
@Column
strategy: string // Only used by us
@ForeignKey(() => VideoFileModel)
@Column
videoFileId: number
@BelongsTo(() => VideoFileModel, {
foreignKey: {
allowNull: true
},
onDelete: 'cascade'
})
VideoFile: Awaited<VideoFileModel>
@ForeignKey(() => VideoStreamingPlaylistModel)
@Column
videoStreamingPlaylistId: number
@BelongsTo(() => VideoStreamingPlaylistModel, {
foreignKey: {
allowNull: true
allowNull: false
},
onDelete: 'cascade'
})
@ -154,91 +129,28 @@ export class VideoRedundancyModel extends SequelizeModel<VideoRedundancyModel> {
static async removeFile (instance: VideoRedundancyModel) {
if (!instance.isOwned()) return
if (instance.videoFileId) {
const videoFile = await VideoFileModel.loadWithVideo(instance.videoFileId)
const videoStreamingPlaylist = await VideoStreamingPlaylistModel.loadWithVideo(instance.videoStreamingPlaylistId)
const logIdentifier = `${videoFile.Video.uuid}-${videoFile.resolution}`
logger.info('Removing duplicated video file %s.', logIdentifier)
const videoUUID = videoStreamingPlaylist.Video.uuid
logger.info('Removing duplicated video streaming playlist %s.', videoUUID)
videoFile.Video.removeWebVideoFile(videoFile, true)
.catch(err => logger.error('Cannot delete %s files.', logIdentifier, { err }))
}
if (instance.videoStreamingPlaylistId) {
const videoStreamingPlaylist = await VideoStreamingPlaylistModel.loadWithVideo(instance.videoStreamingPlaylistId)
const videoUUID = videoStreamingPlaylist.Video.uuid
logger.info('Removing duplicated video streaming playlist %s.', videoUUID)
videoStreamingPlaylist.Video.removeStreamingPlaylistFiles(videoStreamingPlaylist, true)
.catch(err => logger.error('Cannot delete video streaming playlist files of %s.', videoUUID, { err }))
}
videoStreamingPlaylist.Video.removeStreamingPlaylistFiles(videoStreamingPlaylist, true)
.catch(err => logger.error('Cannot delete video streaming playlist files of %s.', videoUUID, { err }))
return undefined
}
static async loadLocalByFileId (videoFileId: number): Promise<MVideoRedundancyVideo> {
static async listLocalByStreamingPlaylistId (videoStreamingPlaylistId: number): Promise<MVideoRedundancyVideo[]> {
const actor = await getServerActor()
const query = {
where: {
actorId: actor.id,
videoFileId
videoStreamingPlaylistId
}
}
return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query)
}
static async listLocalByVideoId (videoId: number): Promise<MVideoRedundancyVideo[]> {
const actor = await getServerActor()
const queryStreamingPlaylist = {
where: {
actorId: actor.id
},
include: [
{
model: VideoStreamingPlaylistModel.unscoped(),
required: true,
include: [
{
model: VideoModel.unscoped(),
required: true,
where: {
id: videoId
}
}
]
}
]
}
const queryFiles = {
where: {
actorId: actor.id
},
include: [
{
model: VideoFileModel,
required: true,
include: [
{
model: VideoModel,
required: true,
where: {
id: videoId
}
}
]
}
]
}
return Promise.all([
VideoRedundancyModel.findAll(queryStreamingPlaylist),
VideoRedundancyModel.findAll(queryFiles)
]).then(([ r1, r2 ]) => r1.concat(r2))
return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findAll(query)
}
static async loadLocalByStreamingPlaylistId (videoStreamingPlaylistId: number): Promise<MVideoRedundancyVideo> {
@ -274,6 +186,87 @@ export class VideoRedundancyModel extends SequelizeModel<VideoRedundancyModel> {
return VideoRedundancyModel.findOne(query)
}
// ---------------------------------------------------------------------------
// Select redundancy candidates
// ---------------------------------------------------------------------------
static async findMostViewToDuplicate (randomizedFactor: number) {
const peertubeActor = await getServerActor()
// On VideoModel!
const query = {
attributes: [ 'id', 'views' ],
limit: randomizedFactor,
order: getVideoSort('-views'),
where: {
...this.buildVideoCandidateWhere(),
...this.buildVideoIdsForDuplication(peertubeActor)
},
include: [
VideoRedundancyModel.buildRedundancyAllowedInclude(),
VideoRedundancyModel.buildStreamingPlaylistRequiredInclude()
]
}
return VideoRedundancyModel.getVideoSample(VideoModel.unscoped().findAll(query))
}
static async findTrendingToDuplicate (randomizedFactor: number) {
const peertubeActor = await getServerActor()
// On VideoModel!
const query = {
attributes: [ 'id', 'views' ],
subQuery: false,
group: 'VideoModel.id',
limit: randomizedFactor,
order: getVideoSort('-trending'),
where: {
...this.buildVideoCandidateWhere(),
...this.buildVideoIdsForDuplication(peertubeActor)
},
include: [
VideoRedundancyModel.buildRedundancyAllowedInclude(),
VideoRedundancyModel.buildStreamingPlaylistRequiredInclude(),
VideoModel.buildTrendingQuery(CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS)
]
}
return VideoRedundancyModel.getVideoSample(VideoModel.unscoped().findAll(query))
}
static async findRecentlyAddedToDuplicate (randomizedFactor: number, minViews: number) {
const peertubeActor = await getServerActor()
// On VideoModel!
const query = {
attributes: [ 'id', 'publishedAt' ],
limit: randomizedFactor,
order: getVideoSort('-publishedAt'),
where: {
...this.buildVideoCandidateWhere(),
...this.buildVideoIdsForDuplication(peertubeActor),
views: {
[Op.gte]: minViews
}
},
include: [
VideoRedundancyModel.buildRedundancyAllowedInclude(),
VideoRedundancyModel.buildStreamingPlaylistRequiredInclude(),
// Required by publishedAt sort
{
model: ScheduleVideoUpdateModel.unscoped(),
required: false
}
]
}
return VideoRedundancyModel.getVideoSample(VideoModel.unscoped().findAll(query))
}
static async isLocalByVideoUUIDExists (uuid: string) {
const actor = await getServerActor()
@ -285,13 +278,12 @@ export class VideoRedundancyModel extends SequelizeModel<VideoRedundancyModel> {
},
include: [
{
attributes: [],
model: VideoFileModel,
model: VideoStreamingPlaylistModel.unscoped(),
required: true,
include: [
{
attributes: [],
model: VideoModel,
model: VideoModel.unscoped(),
required: true,
where: {
uuid
@ -316,82 +308,49 @@ export class VideoRedundancyModel extends SequelizeModel<VideoRedundancyModel> {
return VideoModel.loadWithFiles(id, undefined, !isTestInstance())
}
static async findMostViewToDuplicate (randomizedFactor: number) {
const peertubeActor = await getServerActor()
// On VideoModel!
const query = {
attributes: [ 'id', 'views' ],
limit: randomizedFactor,
order: getVideoSort('-views'),
where: {
privacy: VideoPrivacy.PUBLIC,
isLive: false,
...this.buildVideoIdsForDuplication(peertubeActor)
},
include: [
VideoRedundancyModel.buildServerRedundancyInclude()
]
private static buildVideoCandidateWhere () {
return {
privacy: VideoPrivacy.PUBLIC,
remote: true,
isLive: false
}
return VideoRedundancyModel.getVideoSample(VideoModel.unscoped().findAll(query))
}
static async findTrendingToDuplicate (randomizedFactor: number) {
const peertubeActor = await getServerActor()
// On VideoModel!
const query = {
attributes: [ 'id', 'views' ],
subQuery: false,
group: 'VideoModel.id',
limit: randomizedFactor,
order: getVideoSort('-trending'),
where: {
privacy: VideoPrivacy.PUBLIC,
isLive: false,
...this.buildVideoIdsForDuplication(peertubeActor)
},
private static buildRedundancyAllowedInclude () {
return {
attributes: [],
model: VideoChannelModel.unscoped(),
required: true,
include: [
VideoRedundancyModel.buildServerRedundancyInclude(),
VideoModel.buildTrendingQuery(CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS)
]
}
return VideoRedundancyModel.getVideoSample(VideoModel.unscoped().findAll(query))
}
static async findRecentlyAddedToDuplicate (randomizedFactor: number, minViews: number) {
const peertubeActor = await getServerActor()
// On VideoModel!
const query = {
attributes: [ 'id', 'publishedAt' ],
limit: randomizedFactor,
order: getVideoSort('-publishedAt'),
where: {
privacy: VideoPrivacy.PUBLIC,
isLive: false,
views: {
[Op.gte]: minViews
},
...this.buildVideoIdsForDuplication(peertubeActor)
},
include: [
VideoRedundancyModel.buildServerRedundancyInclude(),
// Required by publishedAt sort
{
model: ScheduleVideoUpdateModel.unscoped(),
required: false
attributes: [],
model: ActorModel.unscoped(),
required: true,
include: [
{
attributes: [],
model: ServerModel.unscoped(),
required: true,
where: {
redundancyAllowed: true
}
}
]
}
]
}
return VideoRedundancyModel.getVideoSample(VideoModel.unscoped().findAll(query))
}
private static buildStreamingPlaylistRequiredInclude () {
return {
attributes: [],
required: true,
model: VideoStreamingPlaylistModel.unscoped()
}
}
// ---------------------------------------------------------------------------
static async loadOldestLocalExpired (strategy: VideoRedundancyStrategy, expiresAfterMs: number): Promise<MVideoRedundancyVideo> {
const expiredDate = new Date()
expiredDate.setMilliseconds(expiredDate.getMilliseconds() - expiresAfterMs)
@ -446,60 +405,38 @@ export class VideoRedundancyModel extends SequelizeModel<VideoRedundancyModel> {
static async listLocalOfServer (serverId: number) {
const actor = await getServerActor()
const buildVideoInclude = () => ({
model: VideoModel,
required: true,
include: [
{
attributes: [],
model: VideoChannelModel.unscoped(),
required: true,
include: [
{
attributes: [],
model: ActorModel.unscoped(),
required: true,
where: {
serverId
}
}
]
}
]
})
const query = {
where: {
[Op.and]: [
{
actorId: actor.id
},
{
[Op.or]: [
{
'$VideoStreamingPlaylist.id$': {
[Op.ne]: null
}
},
{
'$VideoFile.id$': {
[Op.ne]: null
}
}
]
}
]
actorId: actor.id
},
include: [
{
model: VideoFileModel.unscoped(),
required: false,
include: [ buildVideoInclude() ]
},
{
model: VideoStreamingPlaylistModel.unscoped(),
required: false,
include: [ buildVideoInclude() ]
required: true,
include: [
{
model: VideoModel,
required: true,
include: [
{
attributes: [],
model: VideoChannelModel.unscoped(),
required: true,
include: [
{
attributes: [],
model: ActorModel.unscoped(),
required: true,
where: {
serverId
}
}
]
}
]
}
]
}
]
}
@ -517,98 +454,60 @@ export class VideoRedundancyModel extends SequelizeModel<VideoRedundancyModel> {
const { start, count, sort, target, strategy } = options
const redundancyWhere: WhereOptions = {}
const videosWhere: WhereOptions = {}
let redundancySqlSuffix = ''
if (target === 'my-videos') {
Object.assign(videosWhere, { remote: false })
} else if (target === 'remote-videos') {
Object.assign(videosWhere, { remote: true })
Object.assign(redundancyWhere, { strategy: { [Op.ne]: null } })
redundancySqlSuffix = ' AND "videoRedundancy"."strategy" IS NOT NULL'
}
if (strategy) {
Object.assign(redundancyWhere, { strategy })
}
const videoFilterWhere = {
[Op.and]: [
{
[Op.or]: [
{
id: {
[Op.in]: literal(
'(' +
'SELECT "videoId" FROM "videoFile" ' +
'INNER JOIN "videoRedundancy" ON "videoRedundancy"."videoFileId" = "videoFile".id' +
redundancySqlSuffix +
')'
)
}
},
{
id: {
[Op.in]: literal(
'(' +
'select "videoId" FROM "videoStreamingPlaylist" ' +
'INNER JOIN "videoRedundancy" ON "videoRedundancy"."videoStreamingPlaylistId" = "videoStreamingPlaylist".id' +
redundancySqlSuffix +
')'
)
}
}
]
},
videosWhere
]
}
// /!\ On video model /!\
const findOptions = {
offset: start,
limit: count,
order: getSort(sort),
where: videosWhere,
include: [
{
required: false,
model: VideoFileModel,
include: [
{
model: VideoRedundancyModel.unscoped(),
required: false,
where: redundancyWhere
}
]
},
{
required: false,
required: true,
model: VideoStreamingPlaylistModel.unscoped(),
include: [
{
model: VideoRedundancyModel.unscoped(),
required: false,
required: true,
where: redundancyWhere
},
{
model: VideoFileModel,
required: false
required: true
}
]
}
],
where: videoFilterWhere
}
// /!\ On video model /!\
const countOptions = {
where: videoFilterWhere
]
}
return Promise.all([
VideoModel.findAll(findOptions),
VideoModel.count(countOptions)
VideoModel.count({
where: {
...videosWhere,
id: {
[Op.in]: literal(
'(' +
'SELECT "videoId" FROM "videoStreamingPlaylist" ' +
'INNER JOIN "videoRedundancy" ON "videoRedundancy"."videoStreamingPlaylistId" = "videoStreamingPlaylist".id' +
')'
)
}
}
})
]).then(([ data, total ]) => ({ total, data }))
}
@ -617,22 +516,16 @@ export class VideoRedundancyModel extends SequelizeModel<VideoRedundancyModel> {
const sql = `WITH "tmp" AS ` +
`(` +
`SELECT "videoFile"."size" AS "videoFileSize", "videoStreamingFile"."size" AS "videoStreamingFileSize", ` +
`"videoFile"."videoId" AS "videoFileVideoId", "videoStreamingPlaylist"."videoId" AS "videoStreamingVideoId"` +
`SELECT "videoStreamingFile"."size" AS "videoStreamingFileSize", "videoStreamingPlaylist"."videoId" AS "videoStreamingVideoId"` +
`FROM "videoRedundancy" AS "videoRedundancy" ` +
`LEFT JOIN "videoFile" AS "videoFile" ON "videoRedundancy"."videoFileId" = "videoFile"."id" ` +
`LEFT JOIN "videoStreamingPlaylist" ON "videoRedundancy"."videoStreamingPlaylistId" = "videoStreamingPlaylist"."id" ` +
`LEFT JOIN "videoFile" AS "videoStreamingFile" ` +
`ON "videoStreamingPlaylist"."id" = "videoStreamingFile"."videoStreamingPlaylistId" ` +
`WHERE "videoRedundancy"."strategy" = :strategy AND "videoRedundancy"."actorId" = :actorId` +
`), ` +
`"videoIds" AS (` +
`SELECT "videoFileVideoId" AS "videoId" FROM "tmp" ` +
`UNION SELECT "videoStreamingVideoId" AS "videoId" FROM "tmp" ` +
`) ` +
`SELECT ` +
`COALESCE(SUM("videoFileSize"), '0') + COALESCE(SUM("videoStreamingFileSize"), '0') AS "totalUsed", ` +
`(SELECT COUNT("videoIds"."videoId") FROM "videoIds") AS "totalVideos", ` +
`COALESCE(SUM("videoStreamingFileSize"), '0') AS "totalUsed", ` +
`COUNT(DISTINCT "videoStreamingVideoId") AS "totalVideos", ` +
`COUNT(*) AS "totalVideoFiles" ` +
`FROM "tmp"`
@ -647,22 +540,7 @@ export class VideoRedundancyModel extends SequelizeModel<VideoRedundancyModel> {
}
static toFormattedJSONStatic (video: MVideoForRedundancyAPI): VideoRedundancy {
const filesRedundancies: FileRedundancyInformation[] = []
const streamingPlaylistsRedundancies: StreamingPlaylistRedundancyInformation[] = []
for (const file of video.VideoFiles) {
for (const redundancy of file.RedundancyVideos) {
filesRedundancies.push({
id: redundancy.id,
fileUrl: redundancy.fileUrl,
strategy: redundancy.strategy,
createdAt: redundancy.createdAt,
updatedAt: redundancy.updatedAt,
expiresOn: redundancy.expiresOn,
size: file.size
})
}
}
const streamingPlaylistsRedundancies: RedundancyInformation[] = []
for (const playlist of video.VideoStreamingPlaylists) {
const size = playlist.VideoFiles.reduce((a, b) => a + b.size, 0)
@ -687,25 +565,18 @@ export class VideoRedundancyModel extends SequelizeModel<VideoRedundancyModel> {
uuid: video.uuid,
redundancies: {
files: filesRedundancies,
files: [],
streamingPlaylists: streamingPlaylistsRedundancies
}
}
}
getVideo () {
if (this.VideoFile?.Video) return this.VideoFile.Video
if (this.VideoStreamingPlaylist?.Video) return this.VideoStreamingPlaylist.Video
return undefined
return this.VideoStreamingPlaylist.Video
}
getVideoUUID () {
const video = this.getVideo()
if (!video) return undefined
return video.uuid
return this.getVideo()?.uuid
}
isOwned () {
@ -713,37 +584,16 @@ export class VideoRedundancyModel extends SequelizeModel<VideoRedundancyModel> {
}
toActivityPubObject (this: MVideoRedundancyAP): CacheFileObject {
if (this.VideoStreamingPlaylist) {
return {
id: this.url,
type: 'CacheFile' as 'CacheFile',
object: this.VideoStreamingPlaylist.Video.url,
expires: this.expiresOn ? this.expiresOn.toISOString() : null,
url: {
type: 'Link',
mediaType: 'application/x-mpegURL',
href: this.fileUrl
}
}
}
return {
id: this.url,
type: 'CacheFile' as 'CacheFile',
object: this.VideoFile.Video.url,
expires: this.expiresOn
? this.expiresOn.toISOString()
: null,
object: this.VideoStreamingPlaylist.Video.url,
expires: this.expiresOn ? this.expiresOn.toISOString() : null,
url: {
type: 'Link',
mediaType: getVideoFileMimeType(this.VideoFile.extname, this.VideoFile.isAudio()),
href: this.fileUrl,
height: this.VideoFile.resolution,
size: this.VideoFile.size,
fps: this.VideoFile.fps
} as ActivityVideoUrlObject
mediaType: 'application/x-mpegURL',
href: this.fileUrl
}
}
}
@ -751,10 +601,6 @@ export class VideoRedundancyModel extends SequelizeModel<VideoRedundancyModel> {
private static buildVideoIdsForDuplication (peertubeActor: MActor) {
const notIn = literal(
'(' +
`SELECT "videoFile"."videoId" AS "videoId" FROM "videoRedundancy" ` +
`INNER JOIN "videoFile" ON "videoFile"."id" = "videoRedundancy"."videoFileId" ` +
`WHERE "videoRedundancy"."actorId" = ${peertubeActor.id} ` +
`UNION ` +
`SELECT "videoStreamingPlaylist"."videoId" AS "videoId" FROM "videoRedundancy" ` +
`INNER JOIN "videoStreamingPlaylist" ON "videoStreamingPlaylist"."id" = "videoRedundancy"."videoStreamingPlaylistId" ` +
`WHERE "videoRedundancy"."actorId" = ${peertubeActor.id} ` +
@ -767,29 +613,4 @@ export class VideoRedundancyModel extends SequelizeModel<VideoRedundancyModel> {
}
}
}
private static buildServerRedundancyInclude () {
return {
attributes: [],
model: VideoChannelModel.unscoped(),
required: true,
include: [
{
attributes: [],
model: ActorModel.unscoped(),
required: true,
include: [
{
attributes: [],
model: ServerModel.unscoped(),
required: true,
where: {
redundancyAllowed: true
}
}
]
}
]
}
}
}

View File

@ -24,7 +24,7 @@ import {
VIDEO_STATES
} from '../../../initializers/constants.js'
import { MServer, MStreamingPlaylistRedundanciesOpt, MVideoFormattable, MVideoFormattableDetails } from '../../../types/models/index.js'
import { MVideoFileRedundanciesOpt } from '../../../types/models/video/video-file.js'
import { MVideoFile } from '../../../types/models/video/video-file.js'
import { sortByResolutionDesc } from './shared/index.js'
export type VideoFormattingJSONOptions = {
@ -208,7 +208,7 @@ export function streamingPlaylistsModelToFormattedJSON (
export function videoFilesModelToFormattedJSON (
video: MVideoFormattable,
videoFiles: MVideoFileRedundanciesOpt[],
videoFiles: MVideoFile[],
options: {
includeMagnet?: boolean // default true
} = {}

View File

@ -293,19 +293,6 @@ export class AbstractVideoQueryBuilder extends AbstractRunQuery {
}
}
protected includeWebVideoRedundancies () {
this.addJoin(
'LEFT OUTER JOIN "videoRedundancy" AS "VideoFiles->RedundancyVideos" ON ' +
'"VideoFiles"."id" = "VideoFiles->RedundancyVideos"."videoFileId"'
)
this.attributes = {
...this.attributes,
...this.buildAttributesObject('VideoFiles->RedundancyVideos', this.tables.getRedundancyAttributes())
}
}
protected includeStreamingPlaylistRedundancies () {
this.addJoin(
'LEFT OUTER JOIN "videoRedundancy" AS "VideoStreamingPlaylists->RedundancyVideos" ' +

View File

@ -44,10 +44,6 @@ export class VideoFileQueryBuilder extends AbstractVideoQueryBuilder {
this.includeWebVideoFiles()
if (options.includeRedundancy) {
this.includeWebVideoRedundancies()
}
this.whereId(options)
this.query = this.buildQuery()

View File

@ -167,7 +167,6 @@ export class VideoModelBuilder {
const videoModel = this.videosMemo[row.id]
this.addWebVideoFile(row, videoModel)
this.addRedundancy(row, 'VideoFiles', this.videoFileMemo[id])
}
}
@ -314,7 +313,7 @@ export class VideoModelBuilder {
this.videoFileMemo[id] = videoFileModel
}
private addRedundancy (row: SQLRow, prefix: string, to: VideoFileModel | VideoStreamingPlaylistModel) {
private addRedundancy (row: SQLRow, prefix: string, to: VideoStreamingPlaylistModel) {
if (!to.RedundancyVideos) to.RedundancyVideos = []
const redundancyPrefix = `${prefix}.RedundancyVideos`

View File

@ -34,7 +34,6 @@ import {
Default,
DefaultScope,
ForeignKey,
HasMany,
Is, Scopes,
Table,
UpdatedAt
@ -56,7 +55,6 @@ import {
WEBSERVER
} from '../../initializers/constants.js'
import { MVideoFile, MVideoFileStreamingPlaylistVideo, MVideoFileVideo } from '../../types/models/video/video-file.js'
import { VideoRedundancyModel } from '../redundancy/video-redundancy.js'
import { SequelizeModel, doesExist, parseAggregateResult, throwIfNotValid } from '../shared/index.js'
import { VideoStreamingPlaylistModel } from './video-streaming-playlist.js'
import { VideoModel } from './video.js'
@ -252,15 +250,6 @@ export class VideoFileModel extends SequelizeModel<VideoFileModel> {
})
VideoStreamingPlaylist: Awaited<VideoStreamingPlaylistModel>
@HasMany(() => VideoRedundancyModel, {
foreignKey: {
allowNull: true
},
onDelete: 'CASCADE',
hooks: true
})
RedundancyVideos: Awaited<VideoRedundancyModel>[]
static doesInfohashExistCached = memoizee(VideoFileModel.doesInfohashExist.bind(VideoFileModel), {
promise: true,
max: MEMOIZE_LENGTH.INFO_HASH_EXISTS,
@ -331,11 +320,11 @@ export class VideoFileModel extends SequelizeModel<VideoFileModel> {
}
static loadWithMetadata (id: number) {
return VideoFileModel.scope(ScopeNames.WITH_METADATA).findByPk(id)
return VideoFileModel.scope(ScopeNames.WITH_METADATA).findByPk<MVideoFile>(id)
}
static loadWithVideo (id: number) {
return VideoFileModel.scope(ScopeNames.WITH_VIDEO).findByPk(id)
static loadWithVideo (id: number, transaction?: Transaction) {
return VideoFileModel.scope(ScopeNames.WITH_VIDEO).findByPk<MVideoFileVideo>(id, { transaction })
}
static loadWithVideoOrPlaylist (id: number, videoIdOrUUID: number | string) {

View File

@ -40,7 +40,7 @@ import { ModelCache } from '@server/models/shared/model-cache.js'
import { MVideoSource } from '@server/types/models/video/video-source.js'
import Bluebird from 'bluebird'
import { remove } from 'fs-extra/esm'
import { FindOptions, IncludeOptions, Includeable, Op, QueryTypes, ScopeOptions, Sequelize, Transaction, WhereOptions } from 'sequelize'
import { FindOptions, Includeable, Op, QueryTypes, ScopeOptions, Sequelize, Transaction, WhereOptions } from 'sequelize'
import {
AfterCreate,
AfterDestroy,
@ -114,7 +114,6 @@ import { AccountModel } from '../account/account.js'
import { ActorImageModel } from '../actor/actor-image.js'
import { ActorModel } from '../actor/actor.js'
import { VideoAutomaticTagModel } from '../automatic-tag/video-automatic-tag.js'
import { VideoRedundancyModel } from '../redundancy/video-redundancy.js'
import { ServerModel } from '../server/server.js'
import { TrackerModel } from '../server/tracker.js'
import { VideoTrackerModel } from '../server/video-tracker.js'
@ -308,53 +307,30 @@ export type ForAPIOptions = {
}
]
},
[ScopeNames.WITH_WEB_VIDEO_FILES]: (withRedundancies = false) => {
let subInclude: any[] = []
if (withRedundancies === true) {
subInclude = [
{
attributes: [ 'fileUrl' ],
model: VideoRedundancyModel.unscoped(),
required: false
}
]
}
[ScopeNames.WITH_WEB_VIDEO_FILES]: () => {
return {
include: [
{
model: VideoFileModel,
separate: true,
required: false,
include: subInclude
required: false
}
]
}
},
[ScopeNames.WITH_STREAMING_PLAYLISTS]: (withRedundancies = false) => {
const subInclude: IncludeOptions[] = [
{
model: VideoFileModel,
required: false
}
]
if (withRedundancies === true) {
subInclude.push({
attributes: [ 'fileUrl' ],
model: VideoRedundancyModel.unscoped(),
required: false
})
}
[ScopeNames.WITH_STREAMING_PLAYLISTS]: () => {
return {
include: [
{
model: VideoStreamingPlaylistModel.unscoped(),
required: false,
separate: true,
include: subInclude
include: [
{
model: VideoFileModel,
required: false
}
]
}
]
}
@ -2015,19 +1991,19 @@ export class VideoModel extends SequelizeModel<VideoModel> {
// ---------------------------------------------------------------------------
removeWebVideoFile (videoFile: MVideoFile, isRedundancy = false) {
const filePath = isRedundancy
? VideoPathManager.Instance.getFSRedundancyVideoFilePath(this, videoFile)
: VideoPathManager.Instance.getFSVideoFileOutputPath(this, videoFile)
removeWebVideoFile (videoFile: MVideoFile) {
const filePath = VideoPathManager.Instance.getFSVideoFileOutputPath(this, videoFile)
const promises: Promise<any>[] = [ remove(filePath) ]
if (!isRedundancy) promises.push(videoFile.removeTorrent())
const promises: Promise<any>[] = [
remove(filePath),
videoFile.removeTorrent()
]
if (videoFile.storage === FileStorage.OBJECT_STORAGE) {
promises.push(removeWebVideoObjectStorage(videoFile))
}
logger.debug(`Removing files associated to web video ${videoFile.filename}`, { videoFile, isRedundancy, ...lTags(this.uuid) })
logger.debug(`Removing files associated to web video ${videoFile.filename}`, { videoFile, ...lTags(this.uuid) })
return Promise.all(promises)
}

View File

@ -1,14 +1,13 @@
import { PickWith, PickWithOpt } from '@peertube/peertube-typescript-utils'
import { PickWith } from '@peertube/peertube-typescript-utils'
import { VideoFileModel } from '../../../models/video/video-file.js'
import { MVideo, MVideoUUID } from './video.js'
import { MVideoRedundancy, MVideoRedundancyFileUrl } from './video-redundancy.js'
import { MStreamingPlaylist, MStreamingPlaylistVideo } from './video-streaming-playlist.js'
import { MVideo, MVideoUUID } from './video.js'
type Use<K extends keyof VideoFileModel, M> = PickWith<VideoFileModel, K, M>
// ############################################################################
export type MVideoFile = Omit<VideoFileModel, 'Video' | 'RedundancyVideos' | 'VideoStreamingPlaylist'>
export type MVideoFile = Omit<VideoFileModel, 'Video' | 'VideoStreamingPlaylist'>
export type MVideoFileVideo =
MVideoFile &
@ -26,14 +25,6 @@ export type MVideoFileVideoUUID =
MVideoFile &
Use<'Video', MVideoUUID>
export type MVideoFileRedundanciesAll =
MVideoFile &
PickWithOpt<VideoFileModel, 'RedundancyVideos', MVideoRedundancy[]>
export type MVideoFileRedundanciesOpt =
MVideoFile &
PickWithOpt<VideoFileModel, 'RedundancyVideos', MVideoRedundancyFileUrl[]>
export function isStreamingPlaylistFile (file: any): file is MVideoFileStreamingPlaylist {
return !!file.videoStreamingPlaylistId
}

View File

@ -1,36 +1,25 @@
import { VideoFileModel } from '@server/models/video/video-file.js'
import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist.js'
import { PickWith, PickWithOpt } from '@peertube/peertube-typescript-utils'
import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist.js'
import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy.js'
import { MVideoUrl } from './video.js'
import { MVideoFile, MVideoFileVideo } from './video-file.js'
import { MStreamingPlaylistVideo } from './video-streaming-playlist.js'
import { MVideoUrl } from './video.js'
type Use<K extends keyof VideoRedundancyModel, M> = PickWith<VideoRedundancyModel, K, M>
// ############################################################################
export type MVideoRedundancy = Omit<VideoRedundancyModel, 'VideoFile' | 'VideoStreamingPlaylist' | 'Actor'>
export type MVideoRedundancy = Omit<VideoRedundancyModel, 'VideoStreamingPlaylist' | 'Actor'>
export type MVideoRedundancyFileUrl = Pick<MVideoRedundancy, 'fileUrl'>
// ############################################################################
export type MVideoRedundancyFile =
MVideoRedundancy &
Use<'VideoFile', MVideoFile>
export type MVideoRedundancyFileVideo =
MVideoRedundancy &
Use<'VideoFile', MVideoFileVideo>
export type MVideoRedundancyStreamingPlaylistVideo =
MVideoRedundancy &
Use<'VideoStreamingPlaylist', MStreamingPlaylistVideo>
export type MVideoRedundancyVideo =
MVideoRedundancy &
Use<'VideoFile', MVideoFileVideo> &
Use<'VideoStreamingPlaylist', MStreamingPlaylistVideo>
// ############################################################################
@ -39,5 +28,4 @@ export type MVideoRedundancyVideo =
export type MVideoRedundancyAP =
MVideoRedundancy &
PickWithOpt<VideoRedundancyModel, 'VideoFile', MVideoFile & PickWith<VideoFileModel, 'Video', MVideoUrl>> &
PickWithOpt<VideoRedundancyModel, 'VideoStreamingPlaylist', PickWith<VideoStreamingPlaylistModel, 'Video', MVideoUrl>>

View File

@ -18,7 +18,7 @@ import {
MChannelHostOnly,
MChannelUserId
} from './video-channel.js'
import { MVideoFile, MVideoFileRedundanciesAll, MVideoFileRedundanciesOpt } from './video-file.js'
import { MVideoFile } from './video-file.js'
import { MVideoLive } from './video-live.js'
import {
MStreamingPlaylistFiles,
@ -181,7 +181,7 @@ export type MVideoAP =
Use<'VideoStreamingPlaylists', MStreamingPlaylistFiles[]> &
Use<'VideoCaptions', MVideoCaptionLanguageUrl[]> &
Use<'VideoBlacklist', MVideoBlacklistUnfederated> &
Use<'VideoFiles', MVideoFileRedundanciesOpt[]> &
Use<'VideoFiles', MVideoFile[]> &
Use<'Thumbnails', MThumbnail[]> &
Use<'VideoLive', MVideoLive> &
Use<'Storyboard', MStoryboard>
@ -197,7 +197,7 @@ export type MVideoDetails =
Use<'Thumbnails', MThumbnail[]> &
Use<'UserVideoHistories', MUserVideoHistoryTime[]> &
Use<'VideoStreamingPlaylists', MStreamingPlaylistRedundancies[]> &
Use<'VideoFiles', MVideoFileRedundanciesOpt[]> &
Use<'VideoFiles', MVideoFile[]> &
Use<'Trackers', MTrackerUrl[]>
export type MVideoForUser =
@ -209,7 +209,6 @@ export type MVideoForUser =
export type MVideoForRedundancyAPI =
MVideo &
Use<'VideoFiles', MVideoFileRedundanciesAll[]> &
Use<'VideoStreamingPlaylists', MStreamingPlaylistRedundanciesAll[]>
// ############################################################################
@ -230,5 +229,5 @@ export type MVideoFormattableDetails =
Use<'VideoChannel', MChannelFormattable> &
Use<'Tags', MTag[]> &
Use<'VideoStreamingPlaylists', MStreamingPlaylistRedundanciesOpt[]> &
Use<'VideoFiles', MVideoFileRedundanciesOpt[]> &
Use<'VideoFiles', MVideoFile[]> &
PickWithOpt<VideoModel, 'Trackers', MTrackerUrl[]>

View File

@ -303,11 +303,8 @@ class FSPruner {
return !!redundancy
}
const file = await VideoFileModel.loadByFilename(basename(filePath))
if (!file) return false
const redundancy = await VideoRedundancyModel.loadLocalByFileId(file.id)
return !!redundancy
// WebTorrent support redundancy has been removed from PeerTube
return false
}
}

View File

@ -8502,10 +8502,6 @@ components:
redundancies:
type: object
properties:
files:
type: array
items:
$ref: '#/components/schemas/FileRedundancyInformation'
streamingPlaylists:
type: array
items:

397
yarn.lock
View File

@ -2460,27 +2460,18 @@
dependencies:
defer-to-connect "^2.0.1"
"@thaunknown/idb-chunk-store@^1.0.4":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@thaunknown/idb-chunk-store/-/idb-chunk-store-1.0.4.tgz#901c0c51a07c0c91e50da681c8179f57f384fe72"
integrity sha512-4/XDQZHKHyJCGeqnVjHyqeAXClZJ9l90rRvoTslUiuvwTGAUpIb3poL0LfGJEdSuWV+zzGdDjIm/3L4x6crwbg==
"@thaunknown/simple-peer@^10.0.11", "@thaunknown/simple-peer@^10.0.8":
version "10.0.11"
resolved "https://registry.yarnpkg.com/@thaunknown/simple-peer/-/simple-peer-10.0.11.tgz#0b5ec1ed8b1c6aaddf846ce2274604d511c1648a"
integrity sha512-A5MdmtZ6HUzRa4gwPOS4jG+09HvpTv2rFo4kk7Vwveo2ELm+WmbO124ZrJrQnZc2D7z2Q3AWKSitjl9OKXO88g==
dependencies:
idb "^7.1.1"
queue-microtask "^1.2.3"
"@thaunknown/simple-peer@^9.12.1":
version "9.12.1"
resolved "https://registry.yarnpkg.com/@thaunknown/simple-peer/-/simple-peer-9.12.1.tgz#c712335a1043f85ac305a54c8c33abc181e26c74"
integrity sha512-IS5BXvXx7cvBAzaxqotJf4s4rJCPk5JABLK6Gbnn7oAmWVcH4hYABabBBrvvJtv/xyUqR4v/H3LalnGRJJfEog==
dependencies:
debug "^4.3.2"
debug "^4.3.7"
err-code "^3.0.1"
get-browser-rtc "^1.1.0"
queue-microtask "^1.2.3"
streamx "^2.13.2"
uint8-util "^2.1.9"
streamx "^2.20.1"
uint8-util "^2.2.5"
webrtc-polyfill "^1.1.10"
"@thaunknown/simple-websocket@^9.1.0":
"@thaunknown/simple-websocket@^9.1.3":
version "9.1.3"
resolved "https://registry.yarnpkg.com/@thaunknown/simple-websocket/-/simple-websocket-9.1.3.tgz#843065027c6cf4470fb08ca78dbf9e48afc56ea6"
integrity sha512-pf/FCJsgWtLJiJmIpiSI7acOZVq3bIQCpnNo222UFc8Ph1lOUOTpe6LoYhhiOSKB9GUaWJEVUtZ+sK1/aBgU5Q==
@ -2491,7 +2482,7 @@
uint8-util "^2.2.5"
ws "^8.17.1"
"@thaunknown/thirty-two@^1.0.3":
"@thaunknown/thirty-two@^1.0.3", "@thaunknown/thirty-two@^1.0.5":
version "1.0.5"
resolved "https://registry.yarnpkg.com/@thaunknown/thirty-two/-/thirty-two-1.0.5.tgz#c41c1756e150854cb7a264f343c0de2d0ea7cbb8"
integrity sha512-Q53KyCXweV1CS62EfqtPDqfpksn5keQ59PGqzzkK+g8Vif1jB4inoBCcs/BUSdsqddhE3G+2Fn+4RX3S6RqT0A==
@ -3667,18 +3658,18 @@ binary-extensions@^2.0.0:
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
bitfield@^4.0.0, bitfield@^4.1.0:
bitfield@^4.0.0, bitfield@^4.1.0, bitfield@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/bitfield/-/bitfield-4.2.0.tgz#fecb620bbe38e16526fbb62048f6c4af712ace91"
integrity sha512-kUTatQb/mBd8uhvdLrUkouGDBUQiJaIOvPlptUwOWp6MFqih4d1MiVf0m3ATxfZSzu+LjW/awFeABltYa62uIA==
bittorrent-dht@^11.0.5:
version "11.0.8"
resolved "https://registry.yarnpkg.com/bittorrent-dht/-/bittorrent-dht-11.0.8.tgz#623b489c12e6ac141e3e9c7ce71e555edbd285cb"
integrity sha512-hWNmv297wWLpTAkdhG15MJLDXkPXaG//9jRoT62WHja565fjlZojO3WZ7vlzbGRp7o58jnd1fx78dZlpq5d3zA==
bittorrent-dht@^11.0.9:
version "11.0.9"
resolved "https://registry.yarnpkg.com/bittorrent-dht/-/bittorrent-dht-11.0.9.tgz#cd4c169e9dabc7d54856fb7971a9ab164d683160"
integrity sha512-aM6m9zvIGi8lMANaxUWcF3yytUxloUCc4gzqa0SOvo22FyeNDHecOXccw6FIZyk4I0IN9KDCj7We+n+RpqnYgg==
dependencies:
bencode "^4.0.0"
debug "^4.3.7"
debug "^4.4.0"
k-bucket "^5.1.0"
k-rpc "^5.1.0"
last-one-wins "^1.0.4"
@ -3694,55 +3685,63 @@ bittorrent-lsd@^2.0.0:
chrome-dgram "^3.0.6"
debug "^4.2.0"
bittorrent-peerid@^1.3.3, bittorrent-peerid@^1.3.6:
bittorrent-peerid@^1.3.6:
version "1.3.6"
resolved "https://registry.yarnpkg.com/bittorrent-peerid/-/bittorrent-peerid-1.3.6.tgz#3688705a64937a8176ac2ded1178fc7bd91b61db"
integrity sha512-VyLcUjVMEOdSpHaCG/7odvCdLbAB1y3l9A2V6WIje24uV7FkJPrQrH/RrlFmKxP89pFVDEnE+YlHaFujlFIZsg==
bittorrent-protocol@^4.1.11:
version "4.1.15"
resolved "https://registry.yarnpkg.com/bittorrent-protocol/-/bittorrent-protocol-4.1.15.tgz#1ea471df34b4d1d4be59dea5a0c0f8815c113894"
integrity sha512-41W08svaxGrNtxwMl7DbOcYnp44wcNs1B4szSfdLNjCRQH7yWdGdSOTNOvEi+FtsRVyNWabadM6IZLbhj5SS2w==
bittorrent-protocol@^4.1.16:
version "4.1.16"
resolved "https://registry.yarnpkg.com/bittorrent-protocol/-/bittorrent-protocol-4.1.16.tgz#fa9536eafe7e8abab941e5da853b09da6668de60"
integrity sha512-93t8h77uAyD8BGSpBo8SqxYyKBA/xgv9N8+WghnXpH2I+JmlmJmddUt8nugPRgj/LNuL1VrWJ26jhYhiVWpRaQ==
dependencies:
bencode "^4.0.0"
bitfield "^4.1.0"
debug "^4.3.7"
debug "^4.4.0"
rc4 "^0.1.5"
streamx "^2.15.1"
throughput "^1.0.1"
uint8-util "^2.2.5"
unordered-array-remove "^1.0.2"
bittorrent-tracker@^10.0.12:
version "10.0.12"
resolved "https://registry.yarnpkg.com/bittorrent-tracker/-/bittorrent-tracker-10.0.12.tgz#084fb250317f69033f5f1c4ed6a9cddf6b9acf61"
integrity sha512-EYQEwhOYkrRiiwkCFcM9pbzJInsAe7UVmUgevW133duwlZzjwf5ABwDE7pkkmNRS6iwN0b8LbI/94q16dYqiow==
bittorrent-tracker@^11.2.1:
version "11.2.1"
resolved "https://registry.yarnpkg.com/bittorrent-tracker/-/bittorrent-tracker-11.2.1.tgz#b45ff4bd70c2c582bc60d4a8bb6b5bdcb487df9a"
integrity sha512-SffBgHzNrhn+HBwdRD2st+TYJOs2LhF3ljJFPCYGv592LpGtPxw41UZHTUeY5muWnQl+wopcU8qXM9UEk2WKrA==
dependencies:
"@thaunknown/simple-peer" "^9.12.1"
"@thaunknown/simple-websocket" "^9.1.0"
"@thaunknown/simple-peer" "^10.0.8"
"@thaunknown/simple-websocket" "^9.1.3"
bencode "^4.0.0"
bittorrent-peerid "^1.3.3"
bittorrent-peerid "^1.3.6"
chrome-dgram "^3.0.6"
clone "^2.0.0"
compact2string "^1.4.1"
debug "^4.1.1"
ip "^1.1.5"
cross-fetch-ponyfill "^1.0.3"
debug "^4.3.4"
ip "^2.0.1"
lru "^3.1.0"
minimist "^1.2.5"
minimist "^1.2.8"
once "^1.4.0"
queue-microtask "^1.2.3"
random-iterate "^1.0.1"
run-parallel "^1.2.0"
run-series "^1.1.9"
simple-get "^4.0.0"
socks "^2.0.0"
string2compact "^2.0.0"
uint8-util "^2.1.9"
socks "^2.8.3"
string2compact "^2.0.1"
uint8-util "^2.2.5"
unordered-array-remove "^1.0.2"
ws "^8.0.0"
ws "^8.17.0"
optionalDependencies:
bufferutil "^4.0.3"
utf-8-validate "^5.0.5"
bufferutil "^4.0.8"
utf-8-validate "^6.0.4"
bl@^4.0.3:
version "4.1.0"
resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==
dependencies:
buffer "^5.5.0"
inherits "^2.0.4"
readable-stream "^3.4.0"
block-iterator@^1.1.1:
version "1.1.1"
@ -3852,7 +3851,7 @@ buffer@5.6.0:
base64-js "^1.0.2"
ieee754 "^1.1.4"
buffer@^5.2.0, buffer@^5.2.1:
buffer@^5.2.0, buffer@^5.2.1, buffer@^5.5.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
@ -3868,7 +3867,7 @@ buffer@^6.0.3:
base64-js "^1.3.1"
ieee754 "^1.2.1"
bufferutil@^4.0.3, bufferutil@^4.0.8:
bufferutil@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.8.tgz#1de6a71092d65d7766c4d8a522b261a6e787e8ea"
integrity sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==
@ -4114,6 +4113,11 @@ chokidar@^3.4.2, chokidar@^3.5.3:
optionalDependencies:
fsevents "~2.3.2"
chownr@^1.1.1:
version "1.1.4"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
chownr@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
@ -4194,11 +4198,6 @@ cliui@^8.0.1:
strip-ansi "^6.0.1"
wrap-ansi "^7.0.0"
clone@^2.0.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==
cluster-key-slot@^1.1.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac"
@ -4472,6 +4471,24 @@ create-torrent@^6.0.15:
run-parallel "^1.2.0"
uint8-util "^2.2.5"
create-torrent@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/create-torrent/-/create-torrent-6.1.0.tgz#c8ed4e5f1575729bf5ebba141b81e4479c77ec54"
integrity sha512-War593HCsg4TotHgMGWTJqnDHN0pmEU2RM13xUzzSZ78TpRNOC2bbcsC5yMO3pqIkedHEWFzYNqH1yhwuuBYTg==
dependencies:
bencode "^4.0.0"
block-iterator "^1.1.1"
fast-readable-async-iterator "^2.0.0"
is-file "^1.0.0"
join-async-iterator "^1.1.1"
junk "^4.0.1"
minimist "^1.2.8"
once "^1.4.0"
piece-length "^2.0.1"
queue-microtask "^1.2.3"
run-parallel "^1.2.0"
uint8-util "^2.2.5"
cron-parser@^4.6.0:
version "4.9.0"
resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-4.9.0.tgz#0340694af3e46a0894978c6f52a6dbb5c0f11ad5"
@ -4599,7 +4616,7 @@ debug@2.6.9:
dependencies:
ms "2.0.0"
debug@4, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@^4.3.5, debug@^4.3.7, debug@~4.3.1, debug@~4.3.2, debug@~4.3.4:
debug@4, debug@^4.2.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@^4.3.5, debug@^4.3.7, debug@~4.3.1, debug@~4.3.2, debug@~4.3.4:
version "4.3.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"
integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
@ -4613,6 +4630,13 @@ debug@^3.2.7:
dependencies:
ms "^2.1.1"
debug@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a"
integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==
dependencies:
ms "^2.1.3"
decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
@ -4988,7 +5012,7 @@ encoding-japanese@2.1.0:
resolved "https://registry.yarnpkg.com/encoding-japanese/-/encoding-japanese-2.1.0.tgz#5d3c2b652c84ca563783b86907bf5cdfe9a597e2"
integrity sha512-58XySVxUgVlBikBTbQ8WdDxBDHIdXucB16LO5PBHR8t75D54wQrNo4cg+58+R1CtJfKnsVsvt9XlteRaR8xw1w==
end-of-stream@^1.0.0, end-of-stream@^1.1.0:
end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1:
version "1.4.4"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
@ -5561,6 +5585,11 @@ exif-parser@^0.1.12:
resolved "https://registry.yarnpkg.com/exif-parser/-/exif-parser-0.1.12.tgz#58a9d2d72c02c1f6f02a0ef4a9166272b7760922"
integrity sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==
expand-template@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==
expand-tilde@^2.0.0, expand-tilde@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502"
@ -5953,6 +5982,11 @@ fs-chunk-store@^4.1.0:
run-parallel "^1.1.2"
thunky "^1.0.1"
fs-constants@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
fs-extra@^11.1.0:
version "11.2.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b"
@ -5982,7 +6016,7 @@ fs.realpath@^1.0.0:
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
fsa-chunk-store@^1.1.5:
fsa-chunk-store@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/fsa-chunk-store/-/fsa-chunk-store-1.3.0.tgz#e43a6104a01dbff1e9a13fecd8a715dcfcc42987"
integrity sha512-0WCfuxqqSB6Tz/g7Ar/nwAxMoigXaIXuvfrnLIEFYIA9uc6w9eNaHuBGzU1X3lyM4cpLKCOTUmKAA/gCiTvzMQ==
@ -6029,11 +6063,6 @@ gauge@^3.0.0:
strip-ansi "^6.0.1"
wide-align "^1.1.2"
get-browser-rtc@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/get-browser-rtc/-/get-browser-rtc-1.1.0.tgz#d1494e299b00f33fc8e9d6d3343ba4ba99711a2c"
integrity sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ==
get-caller-file@^2.0.1, get-caller-file@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
@ -6121,6 +6150,11 @@ gifwrap@^0.10.1:
image-q "^4.0.0"
omggif "^1.0.10"
github-from-package@0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce"
integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==
glob-parent@^5.1.2, glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
@ -6494,16 +6528,6 @@ human-signals@^8.0.0:
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-8.0.0.tgz#2d3d63481c7c2319f0373428b01ffe30da6df852"
integrity sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==
hybrid-chunk-store@^1.2.2:
version "1.2.6"
resolved "https://registry.yarnpkg.com/hybrid-chunk-store/-/hybrid-chunk-store-1.2.6.tgz#161f506bc49899c03937cfc2e2b0946a5bcc8eac"
integrity sha512-D8DkY6FT+exjw4b6uQ8z5QfUokcIb0YYPHaa/zpBdFIoS1CS7mjM4wnd2mGoo2XUeM5Y10C23AXOQRExoifPbA==
dependencies:
"@thaunknown/idb-chunk-store" "^1.0.4"
cache-chunk-store "^3.2.2"
fsa-chunk-store "^1.1.5"
memory-chunk-store "^1.3.5"
hyperid@^3.0.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/hyperid/-/hyperid-3.3.0.tgz#2042bb296b7f1d5ba0797a5705469af0899c8556"
@ -6546,11 +6570,6 @@ iconv-lite@0.6.3:
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
idb@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/idb/-/idb-7.1.1.tgz#d910ded866d32c7ced9befc5bfdf36f572ced72b"
integrity sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==
ieee754@^1.1.13, ieee754@^1.1.4, ieee754@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
@ -6685,11 +6704,6 @@ ip-set@^2.1.0:
dependencies:
ip "^2.0.1"
ip@^1.1.5:
version "1.1.9"
resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.9.tgz#8dfbcc99a754d07f425310b86a99546b1151e396"
integrity sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==
ip@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.1.tgz#e8f3595d33a3ea66490204234b77636965307105"
@ -7579,10 +7593,10 @@ lru@^3.1.0:
dependencies:
inherits "^2.0.1"
lt_donthave@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/lt_donthave/-/lt_donthave-2.0.3.tgz#ec8149a4adce1d2b6d9d8a8657520808f24d5f83"
integrity sha512-wC1ATeT+y6CRZ7RFm6LFFuQvAa1rWVeW6KZa7VqTYuqA5yS+69ddSrtKMjPqQ2Vh+kX2jY5wwxaEjgXAuLlXeA==
lt_donthave@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/lt_donthave/-/lt_donthave-2.0.4.tgz#94460f5c2498826c833c90010bfcda32d473147f"
integrity sha512-VIKjdxflF8+6vFb3t8LQ4czRYvw6OyxPLDr5YV5qOieu4qwl0wX2DA18WyaHJjBKyKSHXvdo1JcrrUag5MmMiA==
dependencies:
debug "^4.2.0"
unordered-array-remove "^1.0.2"
@ -7608,6 +7622,15 @@ magnet-uri@^7.0.5:
bep53-range "^2.0.0"
uint8-util "^2.1.9"
magnet-uri@^7.0.7:
version "7.0.7"
resolved "https://registry.yarnpkg.com/magnet-uri/-/magnet-uri-7.0.7.tgz#ffc6c7e731f6d8cae2c9b17593a4f92243f4dbde"
integrity sha512-z/+dB2NQsXaDuxVBjoPLpZT8ePaacUmoontoFheRBl++nALHYs4qV9MmhTur9e4SaMbkCR/uPX43UMzEOoeyaw==
dependencies:
"@thaunknown/thirty-two" "^1.0.5"
bep53-range "^2.0.0"
uint8-util "^2.2.5"
mailparser-mit@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/mailparser-mit/-/mailparser-mit-1.0.0.tgz#19df8436c2a02e1d34a03ec518a2eb065e0a94a4"
@ -7843,7 +7866,7 @@ minimatch@^9.0.4:
dependencies:
brace-expansion "^2.0.1"
minimist@^1.1.0, minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6, minimist@^1.2.8:
minimist@^1.1.0, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6, minimist@^1.2.8:
version "1.2.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
@ -7873,6 +7896,11 @@ minizlib@^2.1.1:
minipass "^3.0.0"
yallist "^4.0.0"
mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
version "0.5.3"
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
mkdirp@^0.5.4:
version "0.5.6"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6"
@ -8038,6 +8066,11 @@ nanoid@^3.3.7:
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
napi-build-utils@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-2.0.0.tgz#13c22c0187fcfccce1461844136372a47ddc027e"
integrity sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==
napi-macros@^2.0.0:
version "2.2.2"
resolved "https://registry.yarnpkg.com/napi-macros/-/napi-macros-2.2.2.tgz#817fef20c3e0e40a963fbf7b37d1600bd0201044"
@ -8078,6 +8111,13 @@ nice-try@^1.0.4:
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
node-abi@^3.3.0:
version "3.73.0"
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.73.0.tgz#4459ea77e71969edba8588387eecb05e2c2cff3b"
integrity sha512-z8iYzQGBu35ZkTQ9mtR8RqugJZ9RCLn8fv3d7LsgDBzOijGQP3RdKTX4LA7LXw03ZhU5z0l4xfhIMgSES31+cg==
dependencies:
semver "^7.3.5"
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"
@ -8093,11 +8133,24 @@ node-cleanup@^2.1.2:
resolved "https://registry.yarnpkg.com/node-cleanup/-/node-cleanup-2.1.2.tgz#7ac19abd297e09a7f72a71545d951b517e4dde2c"
integrity sha512-qN8v/s2PAJwGUtr1/hYTpNKlD6Y9rc4p8KSmJXyGdYGZsDGKXrGThikLFP9OCHFeLeEpQzPwiAtdIvBLqm//Hw==
node-datachannel@^v0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/node-datachannel/-/node-datachannel-0.12.0.tgz#7700767bf178bc6d258560c78181b56abb800e44"
integrity sha512-pZ9FsVZpHdUKqyWynuCc9IBLkZPJMpDzpNk4YNPCizbIXHYifpYeWqSF35REHGIWi9JMCf11QzapsyQGo/Y4Ig==
dependencies:
node-domexception "^2.0.1"
prebuild-install "^7.0.1"
node-domexception@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
node-domexception@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-2.0.1.tgz#83b0d101123b5bbf91018fd569a58b88ae985e5b"
integrity sha512-M85rnSC7WQ7wnfQTARPT4LrK7nwCHLdDFOCcItZMhTQjyCebJH8GciKqYJNgaOFZs9nFmTmd/VMyi3OW5jA47w==
node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.7:
version "2.7.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
@ -8538,6 +8591,18 @@ parse-torrent@^11.0.14:
queue-microtask "^1.2.3"
uint8-util "^2.2.5"
parse-torrent@^11.0.18:
version "11.0.18"
resolved "https://registry.yarnpkg.com/parse-torrent/-/parse-torrent-11.0.18.tgz#2ae7e52160fd0e59b6f1e539fb3670a71882d7a6"
integrity sha512-C1igbmTrQQuKlspAfP1wcLaOPlvtu5qi4pMdPoCCfepHmxDOk8iArJ2J1yblLx11UefZJUaKEPSxIwMdG11SuA==
dependencies:
bencode "^4.0.0"
cross-fetch-ponyfill "^1.0.3"
get-stdin "^9.0.0"
magnet-uri "^7.0.7"
queue-microtask "^1.2.3"
uint8-util "^2.2.5"
parse5-htmlparser2-tree-adapter@^7.0.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz#b5a806548ed893a43e24ccb42fbb78069311e81b"
@ -8862,6 +8927,24 @@ postgres-range@^1.1.1:
resolved "https://registry.yarnpkg.com/postgres-range/-/postgres-range-1.1.4.tgz#a59c5f9520909bcec5e63e8cf913a92e4c952863"
integrity sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==
prebuild-install@^7.0.1:
version "7.1.3"
resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.3.tgz#d630abad2b147443f20a212917beae68b8092eec"
integrity sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==
dependencies:
detect-libc "^2.0.0"
expand-template "^2.0.3"
github-from-package "0.0.0"
minimist "^1.2.3"
mkdirp-classic "^0.5.3"
napi-build-utils "^2.0.0"
node-abi "^3.3.0"
pump "^3.0.0"
rc "^1.2.7"
simple-get "^4.0.0"
tar-fs "^2.0.0"
tunnel-agent "^0.6.0"
prelude-ls@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
@ -9074,7 +9157,7 @@ pump@^2.0.0:
end-of-stream "^1.1.0"
once "^1.3.1"
pump@^3.0.0:
pump@^3.0.0, pump@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.2.tgz#836f3edd6bc2ee599256c924ffe0d88573ddcbf8"
integrity sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==
@ -9199,7 +9282,7 @@ rc4@^0.1.5:
resolved "https://registry.yarnpkg.com/rc4/-/rc4-0.1.5.tgz#08c6e04a0168f6eb621c22ab6cb1151bd9f4a64d"
integrity sha512-xdDTNV90z5x5u25Oc871Xnvu7yAr4tV7Eluh0VSvrhUkry39q1k+zkz7xroqHbRq+8PiazySHJPArqifUvz9VA==
rc@^1.2.8:
rc@^1.2.7, rc@^1.2.8:
version "1.2.8"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
@ -9256,7 +9339,7 @@ readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@^2.2.2, readable
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
readable-stream@^3.0.2, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0, readable-stream@^3.6.2:
readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0, readable-stream@^3.6.2:
version "3.6.2"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967"
integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
@ -9911,7 +9994,7 @@ socket.io@^4.5.4:
socket.io-adapter "~2.5.2"
socket.io-parser "~4.2.4"
socks@^2.0.0:
socks@^2.8.3:
version "2.8.3"
resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.3.tgz#1ebd0f09c52ba95a09750afe3f3f9f724a800cb5"
integrity sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==
@ -10066,7 +10149,18 @@ streamsearch@^1.1.0:
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
streamx@^2.10.3, streamx@^2.13.2, streamx@^2.15.0, streamx@^2.15.1, streamx@^2.17.0, streamx@^2.20.0:
streamx@2.21.1:
version "2.21.1"
resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.21.1.tgz#f02979d8395b6b637d08a589fb514498bed55845"
integrity sha512-PhP9wUnFLa+91CPy3N6tiQsK+gnYyUNuk15S3YG/zjYE7RuPeCjJngqnzpC31ow0lzBHQ+QGO4cNJnd0djYUsw==
dependencies:
fast-fifo "^1.3.2"
queue-tick "^1.0.1"
text-decoder "^1.1.0"
optionalDependencies:
bare-events "^2.2.0"
streamx@^2.10.3, streamx@^2.15.0, streamx@^2.15.1, streamx@^2.17.0, streamx@^2.20.0:
version "2.20.2"
resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.20.2.tgz#6a8911959d6f307c19781a1d19ecd94b5f042d78"
integrity sha512-aDGDLU+j9tJcUdPGOaHmVF1u/hhI+CsGkT02V3OKlHDV7IukOI+nTWAGkiZEKCO35rWN1wIr4tS7YFr1f4qSvA==
@ -10077,6 +10171,16 @@ streamx@^2.10.3, streamx@^2.13.2, streamx@^2.15.0, streamx@^2.15.1, streamx@^2.1
optionalDependencies:
bare-events "^2.2.0"
streamx@^2.20.1:
version "2.22.0"
resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.22.0.tgz#cd7b5e57c95aaef0ff9b2aef7905afa62ec6e4a7"
integrity sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==
dependencies:
fast-fifo "^1.3.2"
text-decoder "^1.1.0"
optionalDependencies:
bare-events "^2.2.0"
string-argv@^0.3.1:
version "0.3.2"
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6"
@ -10142,7 +10246,7 @@ string.prototype.trimstart@^1.0.8:
define-properties "^1.2.1"
es-object-atoms "^1.0.0"
string2compact@^2.0.0, string2compact@^2.0.1:
string2compact@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/string2compact/-/string2compact-2.0.1.tgz#a640e70413e8875c3fc34de6184f57abe8b34868"
integrity sha512-Bm/T8lHMTRXw+u83LE+OW7fXmC/wM+Mbccfdo533ajSBNxddDHlRrvxE49NdciGHgXkUQM5WYskJ7uTkbBUI0A==
@ -10296,6 +10400,27 @@ swagger-cli@^4.0.2:
dependencies:
"@apidevtools/swagger-cli" "4.0.4"
tar-fs@^2.0.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.2.tgz#425f154f3404cb16cb8ff6e671d45ab2ed9596c5"
integrity sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==
dependencies:
chownr "^1.1.1"
mkdirp-classic "^0.5.2"
pump "^3.0.0"
tar-stream "^2.1.4"
tar-stream@^2.1.4:
version "2.2.0"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
dependencies:
bl "^4.0.3"
end-of-stream "^1.4.1"
fs-constants "^1.0.0"
inherits "^2.0.3"
readable-stream "^3.1.1"
tar-stream@^3.0.0:
version "3.1.7"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-3.1.7.tgz#24b3fb5eabada19fe7338ed6d26e5f7c482e792b"
@ -10476,21 +10601,21 @@ toposort-class@^1.0.1:
resolved "https://registry.yarnpkg.com/toposort-class/-/toposort-class-1.0.1.tgz#7ffd1f78c8be28c3ba45cd4e1a3f5ee193bd9988"
integrity sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg==
torrent-discovery@^10.0.16:
version "10.0.16"
resolved "https://registry.yarnpkg.com/torrent-discovery/-/torrent-discovery-10.0.16.tgz#e9f5948201ecf1ffeb04923760b4543168aa35df"
integrity sha512-HUvCgL3JAyk9VKUfFBOD7Fx/MWVNmjiCjaEOEc6P7ijm2BpPWpdjlXydP+/12f/NB3T4ItuyMjcGdPseGGjNTw==
torrent-discovery@^11.0.15:
version "11.0.15"
resolved "https://registry.yarnpkg.com/torrent-discovery/-/torrent-discovery-11.0.15.tgz#e3f0cfa5859163344fa51b8ec5853488deb07568"
integrity sha512-O5kCZ/PDcK0PMD5lH4VdwUrL4Wfe1Kt3pjcrMp3yieNQq/ZcnLuae6jnjSvpzoa7DxpYc5OqhkiIOYGyvj1tbA==
dependencies:
bittorrent-dht "^11.0.5"
bittorrent-dht "^11.0.9"
bittorrent-lsd "^2.0.0"
bittorrent-tracker "^10.0.12"
debug "^4.3.4"
bittorrent-tracker "^11.2.1"
debug "^4.4.0"
run-parallel "^1.2.0"
torrent-piece@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/torrent-piece/-/torrent-piece-3.0.0.tgz#395e37c39e62dec75ed601f79c90e4a62e639be7"
integrity sha512-j0tRX7qq22nIuVFF57Tg/wAvFq79F1eM9pcMxY+b0qCCe7yXJnIrqF+Q5YEJ94tNisDnJzcqDHNrPmD9X/yAIg==
torrent-piece@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/torrent-piece/-/torrent-piece-3.0.1.tgz#3f5899aac991256618812d066d7917f809a1ba21"
integrity sha512-EvCqfOkNm3PXqgaGPVVmp0JlGC8fDpH+8Yt5uUiF4oCrAqy3htyUFxK1DJpneWfg1fFdeTKsstxLxQUrHpmocA==
dependencies:
uint8-util "^2.1.9"
@ -10554,6 +10679,13 @@ tsx@^4.7.1:
optionalDependencies:
fsevents "~2.3.3"
tunnel-agent@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==
dependencies:
safe-buffer "^5.0.1"
tv4@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/tv4/-/tv4-1.3.0.tgz#d020c846fadd50c855abb25ebaecc68fc10f7963"
@ -10661,7 +10793,7 @@ uid-safe@2.1.5:
dependencies:
random-bytes "~1.0.0"
uint8-util@^2.1.3, uint8-util@^2.1.9, uint8-util@^2.2.2, uint8-util@^2.2.4, uint8-util@^2.2.5:
uint8-util@^2.1.3, uint8-util@^2.1.9, uint8-util@^2.2.2, uint8-util@^2.2.5:
version "2.2.5"
resolved "https://registry.yarnpkg.com/uint8-util/-/uint8-util-2.2.5.tgz#f1a8ff800e4e10a3ac1c82ee3667c99245123896"
integrity sha512-/QxVQD7CttWpVUKVPz9znO+3Dd4BdTSnFQ7pv/4drVhC9m4BaL2LFHTkJn6EsYoxT79VDq/2Gg8L0H22PrzyMw==
@ -10764,13 +10896,6 @@ ut_pex@^4.0.4:
compact2string "^1.4.1"
string2compact "^2.0.1"
utf-8-validate@^5.0.5:
version "5.0.10"
resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.10.tgz#d7d10ea39318171ca982718b6b96a8d2442571a2"
integrity sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==
dependencies:
node-gyp-build "^4.3.0"
utf-8-validate@^6.0.4:
version "6.0.5"
resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-6.0.5.tgz#8087d39902be2cc15bdb21a426697ff256d65aab"
@ -10887,47 +11012,55 @@ webidl-conversions@^3.0.0:
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
webtorrent@2.1.27:
version "2.1.27"
resolved "https://registry.yarnpkg.com/webtorrent/-/webtorrent-2.1.27.tgz#a6748edd1ea43da7e5f9f3645fc327e45dcf2d55"
integrity sha512-LkAcAOReF82MH9hB64m8xTxTPFHc6cqVGE0Kg9Icpp697wc/rCQbiTtuoOGSzs4u6sMoGRc21iqoktrUq70Zyg==
webrtc-polyfill@^1.1.10:
version "1.1.10"
resolved "https://registry.yarnpkg.com/webrtc-polyfill/-/webrtc-polyfill-1.1.10.tgz#1a140c42afd9bcd041a63174810795b35be5e26d"
integrity sha512-sOn0bj3/noUdzQX7rvk0jFbBurqWDGGo2ipl+WfgoOe/x3cxbGLk/ZUY+WHCISSlLaIeBumi1X3wxQZnUESExQ==
dependencies:
node-datachannel "^v0.12.0"
node-domexception "^1.0.0"
webtorrent@2.5.17:
version "2.5.17"
resolved "https://registry.yarnpkg.com/webtorrent/-/webtorrent-2.5.17.tgz#df5dcda706996fc07ce09d5c1160ea7ff04bd601"
integrity sha512-0YKibmeq4rq9CqGF5PopZVjyrXS7IW6M/kQ4pRo5DiFoco7OQm9Hyh+0SGGJSfkDBgCurXPnId84rvaoi3u3hw==
dependencies:
"@silentbot1/nat-api" "^0.4.7"
"@thaunknown/simple-peer" "^9.12.1"
"@thaunknown/simple-peer" "^10.0.11"
"@webtorrent/http-node" "^1.3.0"
addr-to-ip-port "^2.0.0"
bitfield "^4.1.0"
bittorrent-dht "^11.0.5"
bittorrent-protocol "^4.1.11"
bitfield "^4.2.0"
bittorrent-dht "^11.0.9"
bittorrent-protocol "^4.1.16"
cache-chunk-store "^3.2.2"
chunk-store-iterator "^1.0.3"
cpus "^1.0.3"
create-torrent "^6.0.15"
create-torrent "^6.1.0"
cross-fetch-ponyfill "^1.0.3"
debug "^4.3.4"
debug "^4.4.0"
escape-html "^1.0.3"
fs-chunk-store "^4.1.0"
hybrid-chunk-store "^1.2.2"
fsa-chunk-store "^1.3.0"
immediate-chunk-store "^2.2.0"
join-async-iterator "^1.1.1"
load-ip-set "^3.0.1"
lt_donthave "^2.0.0"
lt_donthave "^2.0.4"
memory-chunk-store "^1.3.5"
mime "^3.0.0"
once "^1.4.0"
parse-torrent "^11.0.14"
pump "^3.0.0"
parse-torrent "^11.0.18"
pump "^3.0.2"
queue-microtask "^1.2.3"
random-iterate "^1.0.1"
range-parser "^1.2.1"
run-parallel "^1.2.0"
run-parallel-limit "^1.1.0"
speed-limiter "^1.0.2"
streamx "^2.15.1"
streamx "2.21.1"
throughput "^1.0.1"
torrent-discovery "^10.0.16"
torrent-piece "^3.0.0"
uint8-util "^2.2.4"
torrent-discovery "^11.0.15"
torrent-piece "^3.0.1"
uint8-util "^2.2.5"
unordered-array-remove "^1.0.2"
ut_metadata "^4.0.3"
ut_pex "^4.0.4"