aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2022-07-25 10:57:16 +0200
committerChocobozzz <me@florianbigard.com>2022-07-25 10:57:16 +0200
commit7b6b445d91d3f0bcbb526cb1e8c1f26b96f0a971 (patch)
tree1bc9724411941e0082243164d6cc53d02902b1f7 /server
parent4f50475c67356fb1fecd1de6d2551fdc5ad9a739 (diff)
downloadPeerTube-7b6b445d91d3f0bcbb526cb1e8c1f26b96f0a971.tar.gz
PeerTube-7b6b445d91d3f0bcbb526cb1e8c1f26b96f0a971.tar.zst
PeerTube-7b6b445d91d3f0bcbb526cb1e8c1f26b96f0a971.zip
Regenerate video filenames on transcoding
In particular when using manual transcoding, to invalidate potential HTTP caches in front of peertube
Diffstat (limited to 'server')
-rw-r--r--server/lib/object-storage/videos.ts5
-rw-r--r--server/lib/transcoding/transcoding.ts28
-rw-r--r--server/models/video/video-file.ts41
-rw-r--r--server/models/video/video.ts21
-rw-r--r--server/tests/api/transcoding/create-transcoding.ts14
5 files changed, 93 insertions, 16 deletions
diff --git a/server/lib/object-storage/videos.ts b/server/lib/object-storage/videos.ts
index 066b48ab0..66e738200 100644
--- a/server/lib/object-storage/videos.ts
+++ b/server/lib/object-storage/videos.ts
@@ -26,6 +26,10 @@ function removeHLSObjectStorage (playlist: MStreamingPlaylistVideo) {
26 return removePrefix(generateHLSObjectBaseStorageKey(playlist), CONFIG.OBJECT_STORAGE.STREAMING_PLAYLISTS) 26 return removePrefix(generateHLSObjectBaseStorageKey(playlist), CONFIG.OBJECT_STORAGE.STREAMING_PLAYLISTS)
27} 27}
28 28
29function removeHLSFileObjectStorage (playlist: MStreamingPlaylistVideo, filename: string) {
30 return removeObject(generateHLSObjectStorageKey(playlist, filename), CONFIG.OBJECT_STORAGE.STREAMING_PLAYLISTS)
31}
32
29function removeWebTorrentObjectStorage (videoFile: MVideoFile) { 33function removeWebTorrentObjectStorage (videoFile: MVideoFile) {
30 return removeObject(generateWebTorrentObjectStorageKey(videoFile.filename), CONFIG.OBJECT_STORAGE.VIDEOS) 34 return removeObject(generateWebTorrentObjectStorageKey(videoFile.filename), CONFIG.OBJECT_STORAGE.VIDEOS)
31} 35}
@@ -63,6 +67,7 @@ export {
63 storeHLSFile, 67 storeHLSFile,
64 68
65 removeHLSObjectStorage, 69 removeHLSObjectStorage,
70 removeHLSFileObjectStorage,
66 removeWebTorrentObjectStorage, 71 removeWebTorrentObjectStorage,
67 72
68 makeWebTorrentFileAvailable, 73 makeWebTorrentFileAvailable,
diff --git a/server/lib/transcoding/transcoding.ts b/server/lib/transcoding/transcoding.ts
index b64ce6e1f..69a973fbd 100644
--- a/server/lib/transcoding/transcoding.ts
+++ b/server/lib/transcoding/transcoding.ts
@@ -2,7 +2,9 @@ import { Job } from 'bull'
2import { copyFile, ensureDir, move, remove, stat } from 'fs-extra' 2import { copyFile, ensureDir, move, remove, stat } from 'fs-extra'
3import { basename, extname as extnameUtil, join } from 'path' 3import { basename, extname as extnameUtil, join } from 'path'
4import { toEven } from '@server/helpers/core-utils' 4import { toEven } from '@server/helpers/core-utils'
5import { retryTransactionWrapper } from '@server/helpers/database-utils'
5import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' 6import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent'
7import { sequelizeTypescript } from '@server/initializers/database'
6import { MStreamingPlaylistFilesVideo, MVideo, MVideoFile, MVideoFullLight } from '@server/types/models' 8import { MStreamingPlaylistFilesVideo, MVideo, MVideoFile, MVideoFullLight } from '@server/types/models'
7import { VideoResolution, VideoStorage } from '../../../shared/models/videos' 9import { VideoResolution, VideoStorage } from '../../../shared/models/videos'
8import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' 10import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type'
@@ -29,8 +31,6 @@ import {
29} from '../paths' 31} from '../paths'
30import { VideoPathManager } from '../video-path-manager' 32import { VideoPathManager } from '../video-path-manager'
31import { VideoTranscodingProfilesManager } from './default-transcoding-profiles' 33import { VideoTranscodingProfilesManager } from './default-transcoding-profiles'
32import { retryTransactionWrapper } from '@server/helpers/database-utils'
33import { sequelizeTypescript } from '@server/initializers/database'
34 34
35/** 35/**
36 * 36 *
@@ -259,6 +259,9 @@ async function onWebTorrentVideoFileTranscoding (
259 259
260 await createTorrentAndSetInfoHash(video, videoFile) 260 await createTorrentAndSetInfoHash(video, videoFile)
261 261
262 const oldFile = await VideoFileModel.loadWebTorrentFile({ videoId: video.id, fps: videoFile.fps, resolution: videoFile.resolution })
263 if (oldFile) await video.removeWebTorrentFileAndTorrent(oldFile)
264
262 await VideoFileModel.customUpsert(videoFile, 'video', undefined) 265 await VideoFileModel.customUpsert(videoFile, 'video', undefined)
263 video.VideoFiles = await video.$get('VideoFiles') 266 video.VideoFiles = await video.$get('VideoFiles')
264 267
@@ -311,17 +314,15 @@ async function generateHlsPlaylistCommon (options: {
311 await transcodeVOD(transcodeOptions) 314 await transcodeVOD(transcodeOptions)
312 315
313 // Create or update the playlist 316 // Create or update the playlist
314 const playlist = await retryTransactionWrapper(() => { 317 const { playlist, oldPlaylistFilename, oldSegmentsSha256Filename } = await retryTransactionWrapper(() => {
315 return sequelizeTypescript.transaction(async transaction => { 318 return sequelizeTypescript.transaction(async transaction => {
316 const playlist = await VideoStreamingPlaylistModel.loadOrGenerate(video, transaction) 319 const playlist = await VideoStreamingPlaylistModel.loadOrGenerate(video, transaction)
317 320
318 if (!playlist.playlistFilename) { 321 const oldPlaylistFilename = playlist.playlistFilename
319 playlist.playlistFilename = generateHLSMasterPlaylistFilename(video.isLive) 322 const oldSegmentsSha256Filename = playlist.segmentsSha256Filename
320 }
321 323
322 if (!playlist.segmentsSha256Filename) { 324 playlist.playlistFilename = generateHLSMasterPlaylistFilename(video.isLive)
323 playlist.segmentsSha256Filename = generateHlsSha256SegmentsFilename(video.isLive) 325 playlist.segmentsSha256Filename = generateHlsSha256SegmentsFilename(video.isLive)
324 }
325 326
326 playlist.p2pMediaLoaderInfohashes = [] 327 playlist.p2pMediaLoaderInfohashes = []
327 playlist.p2pMediaLoaderPeerVersion = P2P_MEDIA_LOADER_PEER_VERSION 328 playlist.p2pMediaLoaderPeerVersion = P2P_MEDIA_LOADER_PEER_VERSION
@@ -330,10 +331,13 @@ async function generateHlsPlaylistCommon (options: {
330 331
331 await playlist.save({ transaction }) 332 await playlist.save({ transaction })
332 333
333 return playlist 334 return { playlist, oldPlaylistFilename, oldSegmentsSha256Filename }
334 }) 335 })
335 }) 336 })
336 337
338 if (oldPlaylistFilename) await video.removeStreamingPlaylistFile(playlist, oldPlaylistFilename)
339 if (oldSegmentsSha256Filename) await video.removeStreamingPlaylistFile(playlist, oldSegmentsSha256Filename)
340
337 // Build the new playlist file 341 // Build the new playlist file
338 const extname = extnameUtil(videoFilename) 342 const extname = extnameUtil(videoFilename)
339 const newVideoFile = new VideoFileModel({ 343 const newVideoFile = new VideoFileModel({
@@ -364,11 +368,15 @@ async function generateHlsPlaylistCommon (options: {
364 368
365 await createTorrentAndSetInfoHash(playlist, newVideoFile) 369 await createTorrentAndSetInfoHash(playlist, newVideoFile)
366 370
371 const oldFile = await VideoFileModel.loadHLSFile({ playlistId: playlist.id, fps: newVideoFile.fps, resolution: newVideoFile.resolution })
372 if (oldFile) await video.removeStreamingPlaylistVideoFile(playlist, oldFile)
373
367 const savedVideoFile = await VideoFileModel.customUpsert(newVideoFile, 'streaming-playlist', undefined) 374 const savedVideoFile = await VideoFileModel.customUpsert(newVideoFile, 'streaming-playlist', undefined)
368 375
369 const playlistWithFiles = playlist as MStreamingPlaylistFilesVideo 376 const playlistWithFiles = playlist as MStreamingPlaylistFilesVideo
370 playlistWithFiles.VideoFiles = await playlist.$get('VideoFiles') 377 playlistWithFiles.VideoFiles = await playlist.$get('VideoFiles')
371 playlist.assignP2PMediaLoaderInfoHashes(video, playlistWithFiles.VideoFiles) 378 playlist.assignP2PMediaLoaderInfoHashes(video, playlistWithFiles.VideoFiles)
379 playlist.storage = VideoStorage.FILE_SYSTEM
372 380
373 await playlist.save() 381 await playlist.save()
374 382
diff --git a/server/models/video/video-file.ts b/server/models/video/video-file.ts
index 4aaee1ffa..d4f07f85f 100644
--- a/server/models/video/video-file.ts
+++ b/server/models/video/video-file.ts
@@ -405,15 +405,16 @@ export class VideoFileModel extends Model<Partial<AttributesOnly<VideoFileModel>
405 mode: 'streaming-playlist' | 'video', 405 mode: 'streaming-playlist' | 'video',
406 transaction: Transaction 406 transaction: Transaction
407 ) { 407 ) {
408 const baseWhere = { 408 const baseFind = {
409 fps: videoFile.fps, 409 fps: videoFile.fps,
410 resolution: videoFile.resolution 410 resolution: videoFile.resolution,
411 transaction
411 } 412 }
412 413
413 if (mode === 'streaming-playlist') Object.assign(baseWhere, { videoStreamingPlaylistId: videoFile.videoStreamingPlaylistId }) 414 const element = mode === 'streaming-playlist'
414 else Object.assign(baseWhere, { videoId: videoFile.videoId }) 415 ? await VideoFileModel.loadHLSFile({ ...baseFind, playlistId: videoFile.videoStreamingPlaylistId })
416 : await VideoFileModel.loadWebTorrentFile({ ...baseFind, videoId: videoFile.videoId })
415 417
416 const element = await VideoFileModel.findOne({ where: baseWhere, transaction })
417 if (!element) return videoFile.save({ transaction }) 418 if (!element) return videoFile.save({ transaction })
418 419
419 for (const k of Object.keys(videoFile.toJSON())) { 420 for (const k of Object.keys(videoFile.toJSON())) {
@@ -423,6 +424,36 @@ export class VideoFileModel extends Model<Partial<AttributesOnly<VideoFileModel>
423 return element.save({ transaction }) 424 return element.save({ transaction })
424 } 425 }
425 426
427 static async loadWebTorrentFile (options: {
428 videoId: number
429 fps: number
430 resolution: number
431 transaction?: Transaction
432 }) {
433 const where = {
434 fps: options.fps,
435 resolution: options.resolution,
436 videoId: options.videoId
437 }
438
439 return VideoFileModel.findOne({ where, transaction: options.transaction })
440 }
441
442 static async loadHLSFile (options: {
443 playlistId: number
444 fps: number
445 resolution: number
446 transaction?: Transaction
447 }) {
448 const where = {
449 fps: options.fps,
450 resolution: options.resolution,
451 videoStreamingPlaylistId: options.playlistId
452 }
453
454 return VideoFileModel.findOne({ where, transaction: options.transaction })
455 }
456
426 static removeHLSFilesOfVideoId (videoStreamingPlaylistId: number) { 457 static removeHLSFilesOfVideoId (videoStreamingPlaylistId: number) {
427 const options = { 458 const options = {
428 where: { videoStreamingPlaylistId } 459 where: { videoStreamingPlaylistId }
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 55da53058..27e605be6 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -26,7 +26,7 @@ import {
26} from 'sequelize-typescript' 26} from 'sequelize-typescript'
27import { getPrivaciesForFederation, isPrivacyForFederation, isStateForFederation } from '@server/helpers/video' 27import { getPrivaciesForFederation, isPrivacyForFederation, isStateForFederation } from '@server/helpers/video'
28import { LiveManager } from '@server/lib/live/live-manager' 28import { LiveManager } from '@server/lib/live/live-manager'
29import { removeHLSObjectStorage, removeWebTorrentObjectStorage } from '@server/lib/object-storage' 29import { removeHLSFileObjectStorage, removeHLSObjectStorage, removeWebTorrentObjectStorage } from '@server/lib/object-storage'
30import { getHLSDirectory, getHLSRedundancyDirectory } from '@server/lib/paths' 30import { getHLSDirectory, getHLSRedundancyDirectory } from '@server/lib/paths'
31import { VideoPathManager } from '@server/lib/video-path-manager' 31import { VideoPathManager } from '@server/lib/video-path-manager'
32import { getServerActor } from '@server/models/application/application' 32import { getServerActor } from '@server/models/application/application'
@@ -1816,6 +1816,25 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
1816 } 1816 }
1817 } 1817 }
1818 1818
1819 async removeStreamingPlaylistVideoFile (streamingPlaylist: MStreamingPlaylist, videoFile: MVideoFile) {
1820 const filePath = VideoPathManager.Instance.getFSHLSOutputPath(this, videoFile.filename)
1821 await videoFile.removeTorrent()
1822 await remove(filePath)
1823
1824 if (videoFile.storage === VideoStorage.OBJECT_STORAGE) {
1825 await removeHLSFileObjectStorage(streamingPlaylist.withVideo(this), videoFile.filename)
1826 }
1827 }
1828
1829 async removeStreamingPlaylistFile (streamingPlaylist: MStreamingPlaylist, filename: string) {
1830 const filePath = VideoPathManager.Instance.getFSHLSOutputPath(this, filename)
1831 await remove(filePath)
1832
1833 if (streamingPlaylist.storage === VideoStorage.OBJECT_STORAGE) {
1834 await removeHLSFileObjectStorage(streamingPlaylist.withVideo(this), filename)
1835 }
1836 }
1837
1819 isOutdated () { 1838 isOutdated () {
1820 if (this.isOwned()) return false 1839 if (this.isOwned()) return false
1821 1840
diff --git a/server/tests/api/transcoding/create-transcoding.ts b/server/tests/api/transcoding/create-transcoding.ts
index a4defdf51..e3867fdad 100644
--- a/server/tests/api/transcoding/create-transcoding.ts
+++ b/server/tests/api/transcoding/create-transcoding.ts
@@ -46,6 +46,8 @@ function runTests (objectStorage: boolean) {
46 let videoUUID: string 46 let videoUUID: string
47 let publishedAt: string 47 let publishedAt: string
48 48
49 let shouldBeDeleted: string[]
50
49 before(async function () { 51 before(async function () {
50 this.timeout(120000) 52 this.timeout(120000)
51 53
@@ -187,6 +189,12 @@ function runTests (objectStorage: boolean) {
187 expect(videoDetails.streamingPlaylists[0].files).to.have.lengthOf(1) 189 expect(videoDetails.streamingPlaylists[0].files).to.have.lengthOf(1)
188 190
189 if (objectStorage) await checkFilesInObjectStorage(videoDetails) 191 if (objectStorage) await checkFilesInObjectStorage(videoDetails)
192
193 shouldBeDeleted = [
194 videoDetails.streamingPlaylists[0].files[0].fileUrl,
195 videoDetails.streamingPlaylists[0].playlistUrl,
196 videoDetails.streamingPlaylists[0].segmentsSha256Url
197 ]
190 } 198 }
191 199
192 await servers[0].config.updateExistingSubConfig({ 200 await servers[0].config.updateExistingSubConfig({
@@ -227,6 +235,12 @@ function runTests (objectStorage: boolean) {
227 } 235 }
228 }) 236 })
229 237
238 it('Should have correctly deleted previous files', async function () {
239 for (const fileUrl of shouldBeDeleted) {
240 await makeRawRequest(fileUrl, HttpStatusCode.NOT_FOUND_404)
241 }
242 })
243
230 it('Should not have updated published at attributes', async function () { 244 it('Should not have updated published at attributes', async function () {
231 const video = await servers[0].videos.get({ id: videoUUID }) 245 const video = await servers[0].videos.get({ id: videoUUID })
232 246