diff options
40 files changed, 637 insertions, 394 deletions
diff --git a/config/default.yaml b/config/default.yaml index 3bbb3e5c4..2d8afe1c3 100644 --- a/config/default.yaml +++ b/config/default.yaml | |||
@@ -197,6 +197,8 @@ cache: | |||
197 | size: 500 # Max number of previews you want to cache | 197 | size: 500 # Max number of previews you want to cache |
198 | captions: | 198 | captions: |
199 | size: 500 # Max number of video captions/subtitles you want to cache | 199 | size: 500 # Max number of video captions/subtitles you want to cache |
200 | torrents: | ||
201 | size: 500 # Max number of video torrents you want to cache | ||
200 | 202 | ||
201 | admin: | 203 | admin: |
202 | # Used to generate the root user at first startup | 204 | # Used to generate the root user at first startup |
diff --git a/config/production.yaml.example b/config/production.yaml.example index d75e30276..2794c543c 100644 --- a/config/production.yaml.example +++ b/config/production.yaml.example | |||
@@ -208,6 +208,8 @@ cache: | |||
208 | size: 500 # Max number of previews you want to cache | 208 | size: 500 # Max number of previews you want to cache |
209 | captions: | 209 | captions: |
210 | size: 500 # Max number of video captions/subtitles you want to cache | 210 | size: 500 # Max number of video captions/subtitles you want to cache |
211 | torrents: | ||
212 | size: 500 # Max number of video torrents you want to cache | ||
211 | 213 | ||
212 | admin: | 214 | admin: |
213 | # Used to generate the root user at first startup | 215 | # Used to generate the root user at first startup |
diff --git a/scripts/optimize-old-videos.ts b/scripts/optimize-old-videos.ts index d5696de67..8e2e7fcf4 100644 --- a/scripts/optimize-old-videos.ts +++ b/scripts/optimize-old-videos.ts | |||
@@ -34,7 +34,9 @@ async function run () { | |||
34 | 34 | ||
35 | const localVideos = await VideoModel.listLocal() | 35 | const localVideos = await VideoModel.listLocal() |
36 | 36 | ||
37 | for (const video of localVideos) { | 37 | for (const localVideo of localVideos) { |
38 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(localVideo.id) | ||
39 | |||
38 | currentVideoId = video.id | 40 | currentVideoId = video.id |
39 | 41 | ||
40 | for (const file of video.VideoFiles) { | 42 | for (const file of video.VideoFiles) { |
@@ -70,7 +72,7 @@ async function run () { | |||
70 | 72 | ||
71 | console.log('Failed to optimize %s, restoring original', basename(currentFile)) | 73 | console.log('Failed to optimize %s, restoring original', basename(currentFile)) |
72 | await move(backupFile, currentFile, { overwrite: true }) | 74 | await move(backupFile, currentFile, { overwrite: true }) |
73 | await createTorrentAndSetInfoHash(video, file) | 75 | await createTorrentAndSetInfoHash(video, video, file) |
74 | await file.save() | 76 | await file.save() |
75 | } | 77 | } |
76 | } | 78 | } |
diff --git a/scripts/update-host.ts b/scripts/update-host.ts index b030b21c3..d0a1b03cc 100755 --- a/scripts/update-host.ts +++ b/scripts/update-host.ts | |||
@@ -116,8 +116,10 @@ async function run () { | |||
116 | 116 | ||
117 | console.log('Updating video and torrent files.') | 117 | console.log('Updating video and torrent files.') |
118 | 118 | ||
119 | const videos = await VideoModel.listLocal() | 119 | const localVideos = await VideoModel.listLocal() |
120 | for (const video of videos) { | 120 | for (const localVideo of localVideos) { |
121 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(localVideo.id) | ||
122 | |||
121 | console.log('Updating video ' + video.uuid) | 123 | console.log('Updating video ' + video.uuid) |
122 | 124 | ||
123 | video.url = getLocalVideoActivityPubUrl(video) | 125 | video.url = getLocalVideoActivityPubUrl(video) |
@@ -125,7 +127,7 @@ async function run () { | |||
125 | 127 | ||
126 | for (const file of video.VideoFiles) { | 128 | for (const file of video.VideoFiles) { |
127 | console.log('Updating torrent file %s of video %s.', file.resolution, video.uuid) | 129 | console.log('Updating torrent file %s of video %s.', file.resolution, video.uuid) |
128 | await createTorrentAndSetInfoHash(video, file) | 130 | await createTorrentAndSetInfoHash(video, video, file) |
129 | } | 131 | } |
130 | 132 | ||
131 | for (const playlist of video.VideoStreamingPlaylists) { | 133 | for (const playlist of video.VideoStreamingPlaylists) { |
@@ -103,7 +103,8 @@ import { | |||
103 | webfingerRouter, | 103 | webfingerRouter, |
104 | trackerRouter, | 104 | trackerRouter, |
105 | createWebsocketTrackerServer, | 105 | createWebsocketTrackerServer, |
106 | botsRouter | 106 | botsRouter, |
107 | downloadRouter | ||
107 | } from './server/controllers' | 108 | } from './server/controllers' |
108 | import { advertiseDoNotTrack } from './server/middlewares/dnt' | 109 | import { advertiseDoNotTrack } from './server/middlewares/dnt' |
109 | import { Redis } from './server/lib/redis' | 110 | import { Redis } from './server/lib/redis' |
@@ -123,6 +124,7 @@ import { Hooks } from './server/lib/plugins/hooks' | |||
123 | import { PluginManager } from './server/lib/plugins/plugin-manager' | 124 | import { PluginManager } from './server/lib/plugins/plugin-manager' |
124 | import { LiveManager } from './server/lib/live-manager' | 125 | import { LiveManager } from './server/lib/live-manager' |
125 | import { HttpStatusCode } from './shared/core-utils/miscs/http-error-codes' | 126 | import { HttpStatusCode } from './shared/core-utils/miscs/http-error-codes' |
127 | import { VideosTorrentCache } from '@server/lib/files-cache/videos-torrent-cache' | ||
126 | 128 | ||
127 | // ----------- Command line ----------- | 129 | // ----------- Command line ----------- |
128 | 130 | ||
@@ -202,6 +204,7 @@ app.use('/', botsRouter) | |||
202 | 204 | ||
203 | // Static files | 205 | // Static files |
204 | app.use('/', staticRouter) | 206 | app.use('/', staticRouter) |
207 | app.use('/', downloadRouter) | ||
205 | app.use('/', lazyStaticRouter) | 208 | app.use('/', lazyStaticRouter) |
206 | 209 | ||
207 | // Client files, last valid routes! | 210 | // Client files, last valid routes! |
@@ -258,6 +261,7 @@ async function startApplication () { | |||
258 | // Caches initializations | 261 | // Caches initializations |
259 | VideosPreviewCache.Instance.init(CONFIG.CACHE.PREVIEWS.SIZE, FILES_CACHE.PREVIEWS.MAX_AGE) | 262 | VideosPreviewCache.Instance.init(CONFIG.CACHE.PREVIEWS.SIZE, FILES_CACHE.PREVIEWS.MAX_AGE) |
260 | VideosCaptionCache.Instance.init(CONFIG.CACHE.VIDEO_CAPTIONS.SIZE, FILES_CACHE.VIDEO_CAPTIONS.MAX_AGE) | 263 | VideosCaptionCache.Instance.init(CONFIG.CACHE.VIDEO_CAPTIONS.SIZE, FILES_CACHE.VIDEO_CAPTIONS.MAX_AGE) |
264 | VideosTorrentCache.Instance.init(CONFIG.CACHE.TORRENTS.SIZE, FILES_CACHE.TORRENTS.MAX_AGE) | ||
261 | 265 | ||
262 | // Enable Schedulers | 266 | // Enable Schedulers |
263 | ActorFollowScheduler.Instance.enable() | 267 | ActorFollowScheduler.Instance.enable() |
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index 9504c40a4..dcd6194ae 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts | |||
@@ -7,7 +7,7 @@ import { changeVideoChannelShare } from '@server/lib/activitypub/share' | |||
7 | import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url' | 7 | import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url' |
8 | import { LiveManager } from '@server/lib/live-manager' | 8 | import { LiveManager } from '@server/lib/live-manager' |
9 | import { addOptimizeOrMergeAudioJob, buildLocalVideoFromReq, buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video' | 9 | import { addOptimizeOrMergeAudioJob, buildLocalVideoFromReq, buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video' |
10 | import { getVideoFilePath } from '@server/lib/video-paths' | 10 | import { generateVideoFilename, getVideoFilePath } from '@server/lib/video-paths' |
11 | import { getServerActor } from '@server/models/application/application' | 11 | import { getServerActor } from '@server/models/application/application' |
12 | import { MVideoFullLight } from '@server/types/models' | 12 | import { MVideoFullLight } from '@server/types/models' |
13 | import { VideoCreate, VideoState, VideoUpdate } from '../../../../shared' | 13 | import { VideoCreate, VideoState, VideoUpdate } from '../../../../shared' |
@@ -189,6 +189,7 @@ async function addVideo (req: express.Request, res: express.Response) { | |||
189 | videoData.duration = videoPhysicalFile['duration'] // duration was added by a previous middleware | 189 | videoData.duration = videoPhysicalFile['duration'] // duration was added by a previous middleware |
190 | 190 | ||
191 | const video = new VideoModel(videoData) as MVideoFullLight | 191 | const video = new VideoModel(videoData) as MVideoFullLight |
192 | video.VideoChannel = res.locals.videoChannel | ||
192 | video.url = getLocalVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object | 193 | video.url = getLocalVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object |
193 | 194 | ||
194 | const videoFile = new VideoFileModel({ | 195 | const videoFile = new VideoFileModel({ |
@@ -205,6 +206,8 @@ async function addVideo (req: express.Request, res: express.Response) { | |||
205 | videoFile.resolution = (await getVideoFileResolution(videoPhysicalFile.path)).videoFileResolution | 206 | videoFile.resolution = (await getVideoFileResolution(videoPhysicalFile.path)).videoFileResolution |
206 | } | 207 | } |
207 | 208 | ||
209 | videoFile.filename = generateVideoFilename(video, false, videoFile.resolution, videoFile.extname) | ||
210 | |||
208 | // Move physical file | 211 | // Move physical file |
209 | const destination = getVideoFilePath(video, videoFile) | 212 | const destination = getVideoFilePath(video, videoFile) |
210 | await move(videoPhysicalFile.path, destination) | 213 | await move(videoPhysicalFile.path, destination) |
@@ -219,7 +222,7 @@ async function addVideo (req: express.Request, res: express.Response) { | |||
219 | }) | 222 | }) |
220 | 223 | ||
221 | // Create the torrent file | 224 | // Create the torrent file |
222 | await createTorrentAndSetInfoHash(video, videoFile) | 225 | await createTorrentAndSetInfoHash(video, video, videoFile) |
223 | 226 | ||
224 | const { videoCreated } = await sequelizeTypescript.transaction(async t => { | 227 | const { videoCreated } = await sequelizeTypescript.transaction(async t => { |
225 | const sequelizeOptions = { transaction: t } | 228 | const sequelizeOptions = { transaction: t } |
diff --git a/server/controllers/download.ts b/server/controllers/download.ts new file mode 100644 index 000000000..27caa1518 --- /dev/null +++ b/server/controllers/download.ts | |||
@@ -0,0 +1,78 @@ | |||
1 | import * as cors from 'cors' | ||
2 | import * as express from 'express' | ||
3 | import { VideosTorrentCache } from '@server/lib/files-cache/videos-torrent-cache' | ||
4 | import { getVideoFilePath } from '@server/lib/video-paths' | ||
5 | import { MVideoFile, MVideoFullLight } from '@server/types/models' | ||
6 | import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' | ||
7 | import { VideoStreamingPlaylistType } from '@shared/models' | ||
8 | import { STATIC_DOWNLOAD_PATHS } from '../initializers/constants' | ||
9 | import { asyncMiddleware, videosDownloadValidator } from '../middlewares' | ||
10 | |||
11 | const downloadRouter = express.Router() | ||
12 | |||
13 | downloadRouter.use(cors()) | ||
14 | |||
15 | downloadRouter.use( | ||
16 | STATIC_DOWNLOAD_PATHS.TORRENTS + ':filename', | ||
17 | downloadTorrent | ||
18 | ) | ||
19 | |||
20 | downloadRouter.use( | ||
21 | STATIC_DOWNLOAD_PATHS.VIDEOS + ':id-:resolution([0-9]+).:extension', | ||
22 | asyncMiddleware(videosDownloadValidator), | ||
23 | downloadVideoFile | ||
24 | ) | ||
25 | |||
26 | downloadRouter.use( | ||
27 | STATIC_DOWNLOAD_PATHS.HLS_VIDEOS + ':id-:resolution([0-9]+)-fragmented.:extension', | ||
28 | asyncMiddleware(videosDownloadValidator), | ||
29 | downloadHLSVideoFile | ||
30 | ) | ||
31 | |||
32 | // --------------------------------------------------------------------------- | ||
33 | |||
34 | export { | ||
35 | downloadRouter | ||
36 | } | ||
37 | |||
38 | // --------------------------------------------------------------------------- | ||
39 | |||
40 | async function downloadTorrent (req: express.Request, res: express.Response) { | ||
41 | const result = await VideosTorrentCache.Instance.getFilePath(req.params.filename) | ||
42 | if (!result) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) | ||
43 | |||
44 | return res.download(result.path, result.downloadName) | ||
45 | } | ||
46 | |||
47 | function downloadVideoFile (req: express.Request, res: express.Response) { | ||
48 | const video = res.locals.videoAll | ||
49 | |||
50 | const videoFile = getVideoFile(req, video.VideoFiles) | ||
51 | if (!videoFile) return res.status(HttpStatusCode.NOT_FOUND_404).end() | ||
52 | |||
53 | return res.download(getVideoFilePath(video, videoFile), `${video.name}-${videoFile.resolution}p${videoFile.extname}`) | ||
54 | } | ||
55 | |||
56 | function downloadHLSVideoFile (req: express.Request, res: express.Response) { | ||
57 | const video = res.locals.videoAll | ||
58 | const playlist = getHLSPlaylist(video) | ||
59 | if (!playlist) return res.status(HttpStatusCode.NOT_FOUND_404).end | ||
60 | |||
61 | const videoFile = getVideoFile(req, playlist.VideoFiles) | ||
62 | if (!videoFile) return res.status(HttpStatusCode.NOT_FOUND_404).end() | ||
63 | |||
64 | const filename = `${video.name}-${videoFile.resolution}p-${playlist.getStringType()}${videoFile.extname}` | ||
65 | return res.download(getVideoFilePath(playlist, videoFile), filename) | ||
66 | } | ||
67 | |||
68 | function getVideoFile (req: express.Request, files: MVideoFile[]) { | ||
69 | const resolution = parseInt(req.params.resolution, 10) | ||
70 | return files.find(f => f.resolution === resolution) | ||
71 | } | ||
72 | |||
73 | function getHLSPlaylist (video: MVideoFullLight) { | ||
74 | const playlist = video.VideoStreamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS) | ||
75 | if (!playlist) return undefined | ||
76 | |||
77 | return Object.assign(playlist, { Video: video }) | ||
78 | } | ||
diff --git a/server/controllers/index.ts b/server/controllers/index.ts index 5a199ae9c..fa27ecec2 100644 --- a/server/controllers/index.ts +++ b/server/controllers/index.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | export * from './activitypub' | 1 | export * from './activitypub' |
2 | export * from './api' | 2 | export * from './api' |
3 | export * from './client' | 3 | export * from './client' |
4 | export * from './download' | ||
4 | export * from './feeds' | 5 | export * from './feeds' |
5 | export * from './services' | 6 | export * from './services' |
6 | export * from './static' | 7 | export * from './static' |
diff --git a/server/controllers/lazy-static.ts b/server/controllers/lazy-static.ts index 656dea223..c2f5c7b56 100644 --- a/server/controllers/lazy-static.ts +++ b/server/controllers/lazy-static.ts | |||
@@ -1,12 +1,13 @@ | |||
1 | import * as cors from 'cors' | 1 | import * as cors from 'cors' |
2 | import * as express from 'express' | 2 | import * as express from 'express' |
3 | import { VideosTorrentCache } from '@server/lib/files-cache/videos-torrent-cache' | ||
4 | import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes' | ||
5 | import { logger } from '../helpers/logger' | ||
3 | import { LAZY_STATIC_PATHS, STATIC_MAX_AGE } from '../initializers/constants' | 6 | import { LAZY_STATIC_PATHS, STATIC_MAX_AGE } from '../initializers/constants' |
7 | import { avatarPathUnsafeCache, pushAvatarProcessInQueue } from '../lib/avatar' | ||
4 | import { VideosCaptionCache, VideosPreviewCache } from '../lib/files-cache' | 8 | import { VideosCaptionCache, VideosPreviewCache } from '../lib/files-cache' |
5 | import { asyncMiddleware } from '../middlewares' | 9 | import { asyncMiddleware } from '../middlewares' |
6 | import { AvatarModel } from '../models/avatar/avatar' | 10 | import { AvatarModel } from '../models/avatar/avatar' |
7 | import { logger } from '../helpers/logger' | ||
8 | import { avatarPathUnsafeCache, pushAvatarProcessInQueue } from '../lib/avatar' | ||
9 | import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes' | ||
10 | 11 | ||
11 | const lazyStaticRouter = express.Router() | 12 | const lazyStaticRouter = express.Router() |
12 | 13 | ||
@@ -27,6 +28,11 @@ lazyStaticRouter.use( | |||
27 | asyncMiddleware(getVideoCaption) | 28 | asyncMiddleware(getVideoCaption) |
28 | ) | 29 | ) |
29 | 30 | ||
31 | lazyStaticRouter.use( | ||
32 | LAZY_STATIC_PATHS.TORRENTS + ':filename', | ||
33 | asyncMiddleware(getTorrent) | ||
34 | ) | ||
35 | |||
30 | // --------------------------------------------------------------------------- | 36 | // --------------------------------------------------------------------------- |
31 | 37 | ||
32 | export { | 38 | export { |
@@ -67,19 +73,26 @@ async function getAvatar (req: express.Request, res: express.Response) { | |||
67 | const path = avatar.getPath() | 73 | const path = avatar.getPath() |
68 | 74 | ||
69 | avatarPathUnsafeCache.set(filename, path) | 75 | avatarPathUnsafeCache.set(filename, path) |
70 | return res.sendFile(path, { maxAge: STATIC_MAX_AGE.SERVER }) | 76 | return res.sendFile(path, { maxAge: STATIC_MAX_AGE.LAZY_SERVER }) |
71 | } | 77 | } |
72 | 78 | ||
73 | async function getPreview (req: express.Request, res: express.Response) { | 79 | async function getPreview (req: express.Request, res: express.Response) { |
74 | const result = await VideosPreviewCache.Instance.getFilePath(req.params.filename) | 80 | const result = await VideosPreviewCache.Instance.getFilePath(req.params.filename) |
75 | if (!result) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) | 81 | if (!result) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) |
76 | 82 | ||
77 | return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE.SERVER }) | 83 | return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE.LAZY_SERVER }) |
78 | } | 84 | } |
79 | 85 | ||
80 | async function getVideoCaption (req: express.Request, res: express.Response) { | 86 | async function getVideoCaption (req: express.Request, res: express.Response) { |
81 | const result = await VideosCaptionCache.Instance.getFilePath(req.params.filename) | 87 | const result = await VideosCaptionCache.Instance.getFilePath(req.params.filename) |
82 | if (!result) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) | 88 | if (!result) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) |
83 | 89 | ||
90 | return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE.LAZY_SERVER }) | ||
91 | } | ||
92 | |||
93 | async function getTorrent (req: express.Request, res: express.Response) { | ||
94 | const result = await VideosTorrentCache.Instance.getFilePath(req.params.filename) | ||
95 | if (!result) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) | ||
96 | |||
84 | return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE.SERVER }) | 97 | return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE.SERVER }) |
85 | } | 98 | } |
diff --git a/server/controllers/static.ts b/server/controllers/static.ts index 2064857eb..7cc7f2c62 100644 --- a/server/controllers/static.ts +++ b/server/controllers/static.ts | |||
@@ -3,10 +3,7 @@ import * as express from 'express' | |||
3 | import { join } from 'path' | 3 | import { join } from 'path' |
4 | import { getRegisteredPlugins, getRegisteredThemes } from '@server/controllers/api/config' | 4 | import { getRegisteredPlugins, getRegisteredThemes } from '@server/controllers/api/config' |
5 | import { serveIndexHTML } from '@server/lib/client-html' | 5 | import { serveIndexHTML } from '@server/lib/client-html' |
6 | import { getTorrentFilePath, getVideoFilePath } from '@server/lib/video-paths' | ||
7 | import { MVideoFile, MVideoFullLight } from '@server/types/models' | ||
8 | import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' | 6 | import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' |
9 | import { VideoStreamingPlaylistType } from '@shared/models/videos/video-streaming-playlist.type' | ||
10 | import { HttpNodeinfoDiasporaSoftwareNsSchema20 } from '../../shared/models/nodeinfo' | 7 | import { HttpNodeinfoDiasporaSoftwareNsSchema20 } from '../../shared/models/nodeinfo' |
11 | import { root } from '../helpers/core-utils' | 8 | import { root } from '../helpers/core-utils' |
12 | import { CONFIG, isEmailEnabled } from '../initializers/config' | 9 | import { CONFIG, isEmailEnabled } from '../initializers/config' |
@@ -16,14 +13,13 @@ import { | |||
16 | HLS_STREAMING_PLAYLIST_DIRECTORY, | 13 | HLS_STREAMING_PLAYLIST_DIRECTORY, |
17 | PEERTUBE_VERSION, | 14 | PEERTUBE_VERSION, |
18 | ROUTE_CACHE_LIFETIME, | 15 | ROUTE_CACHE_LIFETIME, |
19 | STATIC_DOWNLOAD_PATHS, | ||
20 | STATIC_MAX_AGE, | 16 | STATIC_MAX_AGE, |
21 | STATIC_PATHS, | 17 | STATIC_PATHS, |
22 | WEBSERVER | 18 | WEBSERVER |
23 | } from '../initializers/constants' | 19 | } from '../initializers/constants' |
24 | import { getThemeOrDefault } from '../lib/plugins/theme-utils' | 20 | import { getThemeOrDefault } from '../lib/plugins/theme-utils' |
25 | import { getEnabledResolutions } from '../lib/video-transcoding' | 21 | import { getEnabledResolutions } from '../lib/video-transcoding' |
26 | import { asyncMiddleware, videosDownloadValidator } from '../middlewares' | 22 | import { asyncMiddleware } from '../middlewares' |
27 | import { cacheRoute } from '../middlewares/cache' | 23 | import { cacheRoute } from '../middlewares/cache' |
28 | import { UserModel } from '../models/account/user' | 24 | import { UserModel } from '../models/account/user' |
29 | import { VideoModel } from '../models/video/video' | 25 | import { VideoModel } from '../models/video/video' |
@@ -37,47 +33,23 @@ staticRouter.use(cors()) | |||
37 | Cors is very important to let other servers access torrent and video files | 33 | Cors is very important to let other servers access torrent and video files |
38 | */ | 34 | */ |
39 | 35 | ||
36 | // FIXME: deprecated in 3.2, use lazy-statics instead | ||
40 | const torrentsPhysicalPath = CONFIG.STORAGE.TORRENTS_DIR | 37 | const torrentsPhysicalPath = CONFIG.STORAGE.TORRENTS_DIR |
41 | staticRouter.use( | 38 | staticRouter.use( |
42 | STATIC_PATHS.TORRENTS, | 39 | STATIC_PATHS.TORRENTS, |
43 | cors(), | ||
44 | express.static(torrentsPhysicalPath, { maxAge: 0 }) // Don't cache because we could regenerate the torrent file | 40 | express.static(torrentsPhysicalPath, { maxAge: 0 }) // Don't cache because we could regenerate the torrent file |
45 | ) | 41 | ) |
46 | staticRouter.use( | ||
47 | STATIC_DOWNLOAD_PATHS.TORRENTS + ':id-:resolution([0-9]+).torrent', | ||
48 | asyncMiddleware(videosDownloadValidator), | ||
49 | downloadTorrent | ||
50 | ) | ||
51 | staticRouter.use( | ||
52 | STATIC_DOWNLOAD_PATHS.TORRENTS + ':id-:resolution([0-9]+)-hls.torrent', | ||
53 | asyncMiddleware(videosDownloadValidator), | ||
54 | downloadHLSVideoFileTorrent | ||
55 | ) | ||
56 | 42 | ||
57 | // Videos path for webseeding | 43 | // Videos path for webseed |
58 | staticRouter.use( | 44 | staticRouter.use( |
59 | STATIC_PATHS.WEBSEED, | 45 | STATIC_PATHS.WEBSEED, |
60 | cors(), | ||
61 | express.static(CONFIG.STORAGE.VIDEOS_DIR, { fallthrough: false }) // 404 because we don't have this video | 46 | express.static(CONFIG.STORAGE.VIDEOS_DIR, { fallthrough: false }) // 404 because we don't have this video |
62 | ) | 47 | ) |
63 | staticRouter.use( | 48 | staticRouter.use( |
64 | STATIC_PATHS.REDUNDANCY, | 49 | STATIC_PATHS.REDUNDANCY, |
65 | cors(), | ||
66 | express.static(CONFIG.STORAGE.REDUNDANCY_DIR, { fallthrough: false }) // 404 because we don't have this video | 50 | express.static(CONFIG.STORAGE.REDUNDANCY_DIR, { fallthrough: false }) // 404 because we don't have this video |
67 | ) | 51 | ) |
68 | 52 | ||
69 | staticRouter.use( | ||
70 | STATIC_DOWNLOAD_PATHS.VIDEOS + ':id-:resolution([0-9]+).:extension', | ||
71 | asyncMiddleware(videosDownloadValidator), | ||
72 | downloadVideoFile | ||
73 | ) | ||
74 | |||
75 | staticRouter.use( | ||
76 | STATIC_DOWNLOAD_PATHS.HLS_VIDEOS + ':id-:resolution([0-9]+)-fragmented.:extension', | ||
77 | asyncMiddleware(videosDownloadValidator), | ||
78 | downloadHLSVideoFile | ||
79 | ) | ||
80 | |||
81 | // HLS | 53 | // HLS |
82 | staticRouter.use( | 54 | staticRouter.use( |
83 | STATIC_PATHS.STREAMING_PLAYLISTS.HLS, | 55 | STATIC_PATHS.STREAMING_PLAYLISTS.HLS, |
@@ -327,60 +299,6 @@ async function generateNodeinfo (req: express.Request, res: express.Response) { | |||
327 | return res.send(json).end() | 299 | return res.send(json).end() |
328 | } | 300 | } |
329 | 301 | ||
330 | function downloadTorrent (req: express.Request, res: express.Response) { | ||
331 | const video = res.locals.videoAll | ||
332 | |||
333 | const videoFile = getVideoFile(req, video.VideoFiles) | ||
334 | if (!videoFile) return res.status(HttpStatusCode.NOT_FOUND_404).end() | ||
335 | |||
336 | return res.download(getTorrentFilePath(video, videoFile), `${video.name}-${videoFile.resolution}p.torrent`) | ||
337 | } | ||
338 | |||
339 | function downloadHLSVideoFileTorrent (req: express.Request, res: express.Response) { | ||
340 | const video = res.locals.videoAll | ||
341 | |||
342 | const playlist = getHLSPlaylist(video) | ||
343 | if (!playlist) return res.status(HttpStatusCode.NOT_FOUND_404).end | ||
344 | |||
345 | const videoFile = getVideoFile(req, playlist.VideoFiles) | ||
346 | if (!videoFile) return res.status(HttpStatusCode.NOT_FOUND_404).end() | ||
347 | |||
348 | return res.download(getTorrentFilePath(playlist, videoFile), `${video.name}-${videoFile.resolution}p-hls.torrent`) | ||
349 | } | ||
350 | |||
351 | function downloadVideoFile (req: express.Request, res: express.Response) { | ||
352 | const video = res.locals.videoAll | ||
353 | |||
354 | const videoFile = getVideoFile(req, video.VideoFiles) | ||
355 | if (!videoFile) return res.status(HttpStatusCode.NOT_FOUND_404).end() | ||
356 | |||
357 | return res.download(getVideoFilePath(video, videoFile), `${video.name}-${videoFile.resolution}p${videoFile.extname}`) | ||
358 | } | ||
359 | |||
360 | function downloadHLSVideoFile (req: express.Request, res: express.Response) { | ||
361 | const video = res.locals.videoAll | ||
362 | const playlist = getHLSPlaylist(video) | ||
363 | if (!playlist) return res.status(HttpStatusCode.NOT_FOUND_404).end | ||
364 | |||
365 | const videoFile = getVideoFile(req, playlist.VideoFiles) | ||
366 | if (!videoFile) return res.status(HttpStatusCode.NOT_FOUND_404).end() | ||
367 | |||
368 | const filename = `${video.name}-${videoFile.resolution}p-${playlist.getStringType()}${videoFile.extname}` | ||
369 | return res.download(getVideoFilePath(playlist, videoFile), filename) | ||
370 | } | ||
371 | |||
372 | function getVideoFile (req: express.Request, files: MVideoFile[]) { | ||
373 | const resolution = parseInt(req.params.resolution, 10) | ||
374 | return files.find(f => f.resolution === resolution) | ||
375 | } | ||
376 | |||
377 | function getHLSPlaylist (video: MVideoFullLight) { | ||
378 | const playlist = video.VideoStreamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS) | ||
379 | if (!playlist) return undefined | ||
380 | |||
381 | return Object.assign(playlist, { Video: video }) | ||
382 | } | ||
383 | |||
384 | function getCup (req: express.Request, res: express.Response, next: express.NextFunction) { | 302 | function getCup (req: express.Request, res: express.Response, next: express.NextFunction) { |
385 | res.status(HttpStatusCode.I_AM_A_TEAPOT_418) | 303 | res.status(HttpStatusCode.I_AM_A_TEAPOT_418) |
386 | res.setHeader('Accept-Additions', 'Non-Dairy;1,Sugar;1') | 304 | res.setHeader('Accept-Additions', 'Non-Dairy;1,Sugar;1') |
diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts index 1188d6cf9..02a9d4026 100644 --- a/server/helpers/activitypub.ts +++ b/server/helpers/activitypub.ts | |||
@@ -1,13 +1,13 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import { URL } from 'url' | ||
2 | import validator from 'validator' | 3 | import validator from 'validator' |
4 | import { ContextType } from '@shared/models/activitypub/context' | ||
3 | import { ResultList } from '../../shared/models' | 5 | import { ResultList } from '../../shared/models' |
4 | import { Activity } from '../../shared/models/activitypub' | 6 | import { Activity } from '../../shared/models/activitypub' |
5 | import { ACTIVITY_PUB, REMOTE_SCHEME } from '../initializers/constants' | 7 | import { ACTIVITY_PUB, REMOTE_SCHEME } from '../initializers/constants' |
6 | import { signJsonLDObject } from './peertube-crypto' | 8 | import { MActor, MVideoWithHost } from '../types/models' |
7 | import { pageToStartAndCount } from './core-utils' | 9 | import { pageToStartAndCount } from './core-utils' |
8 | import { URL } from 'url' | 10 | import { signJsonLDObject } from './peertube-crypto' |
9 | import { MActor, MVideoAccountLight } from '../types/models' | ||
10 | import { ContextType } from '@shared/models/activitypub/context' | ||
11 | 11 | ||
12 | function getContextData (type: ContextType) { | 12 | function getContextData (type: ContextType) { |
13 | const context: any[] = [ | 13 | const context: any[] = [ |
@@ -201,8 +201,8 @@ function checkUrlsSameHost (url1: string, url2: string) { | |||
201 | return idHost && actorHost && idHost.toLowerCase() === actorHost.toLowerCase() | 201 | return idHost && actorHost && idHost.toLowerCase() === actorHost.toLowerCase() |
202 | } | 202 | } |
203 | 203 | ||
204 | function buildRemoteVideoBaseUrl (video: MVideoAccountLight, path: string) { | 204 | function buildRemoteVideoBaseUrl (video: MVideoWithHost, path: string) { |
205 | const host = video.VideoChannel.Account.Actor.Server.host | 205 | const host = video.VideoChannel.Actor.Server.host |
206 | 206 | ||
207 | return REMOTE_SCHEME.HTTP + '://' + host + path | 207 | return REMOTE_SCHEME.HTTP + '://' + host + path |
208 | } | 208 | } |
diff --git a/server/helpers/webtorrent.ts b/server/helpers/webtorrent.ts index 9c5df2083..73418aa0a 100644 --- a/server/helpers/webtorrent.ts +++ b/server/helpers/webtorrent.ts | |||
@@ -1,20 +1,19 @@ | |||
1 | import { logger } from './logger' | ||
2 | import { generateVideoImportTmpPath } from './utils' | ||
3 | import * as WebTorrent from 'webtorrent' | ||
4 | import { createWriteStream, ensureDir, remove, writeFile } from 'fs-extra' | ||
5 | import { CONFIG } from '../initializers/config' | ||
6 | import { dirname, join } from 'path' | ||
7 | import * as createTorrent from 'create-torrent' | 1 | import * as createTorrent from 'create-torrent' |
8 | import { promisify2 } from './core-utils' | 2 | import { createWriteStream, ensureDir, remove, writeFile } from 'fs-extra' |
9 | import { MVideo } from '@server/types/models/video/video' | ||
10 | import { MVideoFile, MVideoFileRedundanciesOpt } from '@server/types/models/video/video-file' | ||
11 | import { isStreamingPlaylist, MStreamingPlaylistVideo } from '@server/types/models/video/video-streaming-playlist' | ||
12 | import { WEBSERVER } from '@server/initializers/constants' | ||
13 | import * as parseTorrent from 'parse-torrent' | ||
14 | import * as magnetUtil from 'magnet-uri' | 3 | import * as magnetUtil from 'magnet-uri' |
4 | import * as parseTorrent from 'parse-torrent' | ||
5 | import { dirname, join } from 'path' | ||
6 | import * as WebTorrent from 'webtorrent' | ||
15 | import { isArray } from '@server/helpers/custom-validators/misc' | 7 | import { isArray } from '@server/helpers/custom-validators/misc' |
16 | import { getTorrentFileName, getVideoFilePath } from '@server/lib/video-paths' | 8 | import { WEBSERVER } from '@server/initializers/constants' |
17 | import { extractVideo } from '@server/helpers/video' | 9 | import { generateTorrentFileName, getVideoFilePath } from '@server/lib/video-paths' |
10 | import { MVideo, MVideoWithHost } from '@server/types/models/video/video' | ||
11 | import { MVideoFile, MVideoFileRedundanciesOpt } from '@server/types/models/video/video-file' | ||
12 | import { MStreamingPlaylistVideo } from '@server/types/models/video/video-streaming-playlist' | ||
13 | import { CONFIG } from '../initializers/config' | ||
14 | import { promisify2 } from './core-utils' | ||
15 | import { logger } from './logger' | ||
16 | import { generateVideoImportTmpPath } from './utils' | ||
18 | 17 | ||
19 | const createTorrentPromise = promisify2<string, any, any>(createTorrent) | 18 | const createTorrentPromise = promisify2<string, any, any>(createTorrent) |
20 | 19 | ||
@@ -78,10 +77,12 @@ async function downloadWebTorrentVideo (target: { magnetUri: string, torrentName | |||
78 | }) | 77 | }) |
79 | } | 78 | } |
80 | 79 | ||
81 | async function createTorrentAndSetInfoHash (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, videoFile: MVideoFile) { | 80 | // FIXME: refactor/merge videoOrPlaylist and video arguments |
82 | const video = extractVideo(videoOrPlaylist) | 81 | async function createTorrentAndSetInfoHash ( |
83 | const { baseUrlHttp } = video.getBaseUrls() | 82 | videoOrPlaylist: MVideo | MStreamingPlaylistVideo, |
84 | 83 | video: MVideoWithHost, | |
84 | videoFile: MVideoFile | ||
85 | ) { | ||
85 | const options = { | 86 | const options = { |
86 | // Keep the extname, it's used by the client to stream the file inside a web browser | 87 | // Keep the extname, it's used by the client to stream the file inside a web browser |
87 | name: `${video.name} ${videoFile.resolution}p${videoFile.extname}`, | 88 | name: `${video.name} ${videoFile.resolution}p${videoFile.extname}`, |
@@ -90,33 +91,33 @@ async function createTorrentAndSetInfoHash (videoOrPlaylist: MVideo | MStreaming | |||
90 | [ WEBSERVER.WS + '://' + WEBSERVER.HOSTNAME + ':' + WEBSERVER.PORT + '/tracker/socket' ], | 91 | [ WEBSERVER.WS + '://' + WEBSERVER.HOSTNAME + ':' + WEBSERVER.PORT + '/tracker/socket' ], |
91 | [ WEBSERVER.URL + '/tracker/announce' ] | 92 | [ WEBSERVER.URL + '/tracker/announce' ] |
92 | ], | 93 | ], |
93 | urlList: [ videoOrPlaylist.getVideoFileUrl(videoFile, baseUrlHttp) ] | 94 | urlList: [ videoFile.getFileUrl(video) ] |
94 | } | 95 | } |
95 | 96 | ||
96 | const torrent = await createTorrentPromise(getVideoFilePath(videoOrPlaylist, videoFile), options) | 97 | const torrent = await createTorrentPromise(getVideoFilePath(videoOrPlaylist, videoFile), options) |
97 | 98 | ||
98 | const filePath = join(CONFIG.STORAGE.TORRENTS_DIR, getTorrentFileName(videoOrPlaylist, videoFile)) | 99 | const torrentFilename = generateTorrentFileName(videoOrPlaylist, videoFile.resolution) |
99 | logger.info('Creating torrent %s.', filePath) | 100 | const torrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, torrentFilename) |
101 | logger.info('Creating torrent %s.', torrentPath) | ||
100 | 102 | ||
101 | await writeFile(filePath, torrent) | 103 | await writeFile(torrentPath, torrent) |
102 | 104 | ||
103 | const parsedTorrent = parseTorrent(torrent) | 105 | const parsedTorrent = parseTorrent(torrent) |
104 | videoFile.infoHash = parsedTorrent.infoHash | 106 | videoFile.infoHash = parsedTorrent.infoHash |
107 | videoFile.torrentFilename = torrentFilename | ||
105 | } | 108 | } |
106 | 109 | ||
110 | // FIXME: merge/refactor videoOrPlaylist and video arguments | ||
107 | function generateMagnetUri ( | 111 | function generateMagnetUri ( |
108 | videoOrPlaylist: MVideo | MStreamingPlaylistVideo, | 112 | videoOrPlaylist: MVideo | MStreamingPlaylistVideo, |
113 | video: MVideoWithHost, | ||
109 | videoFile: MVideoFileRedundanciesOpt, | 114 | videoFile: MVideoFileRedundanciesOpt, |
110 | baseUrlHttp: string, | 115 | baseUrlHttp: string, |
111 | baseUrlWs: string | 116 | baseUrlWs: string |
112 | ) { | 117 | ) { |
113 | const video = isStreamingPlaylist(videoOrPlaylist) | 118 | const xs = videoFile.getTorrentUrl() |
114 | ? videoOrPlaylist.Video | ||
115 | : videoOrPlaylist | ||
116 | |||
117 | const xs = videoOrPlaylist.getTorrentUrl(videoFile, baseUrlHttp) | ||
118 | const announce = videoOrPlaylist.getTrackerUrls(baseUrlHttp, baseUrlWs) | 119 | const announce = videoOrPlaylist.getTrackerUrls(baseUrlHttp, baseUrlWs) |
119 | let urlList = [ videoOrPlaylist.getVideoFileUrl(videoFile, baseUrlHttp) ] | 120 | let urlList = [ videoFile.getFileUrl(video) ] |
120 | 121 | ||
121 | const redundancies = videoFile.RedundancyVideos | 122 | const redundancies = videoFile.RedundancyVideos |
122 | if (isArray(redundancies)) urlList = urlList.concat(redundancies.map(r => r.fileUrl)) | 123 | if (isArray(redundancies)) urlList = urlList.concat(redundancies.map(r => r.fileUrl)) |
diff --git a/server/initializers/checker-before-init.ts b/server/initializers/checker-before-init.ts index a186afbdd..2578de5ed 100644 --- a/server/initializers/checker-before-init.ts +++ b/server/initializers/checker-before-init.ts | |||
@@ -17,7 +17,7 @@ function checkMissedConfig () { | |||
17 | 'log.level', | 17 | 'log.level', |
18 | 'user.video_quota', 'user.video_quota_daily', | 18 | 'user.video_quota', 'user.video_quota_daily', |
19 | 'csp.enabled', 'csp.report_only', 'csp.report_uri', | 19 | 'csp.enabled', 'csp.report_only', 'csp.report_uri', |
20 | 'cache.previews.size', 'admin.email', 'contact_form.enabled', | 20 | 'cache.previews.size', 'cache.captions.size', 'cache.torrents.size', 'admin.email', 'contact_form.enabled', |
21 | 'signup.enabled', 'signup.limit', 'signup.requires_email_verification', | 21 | 'signup.enabled', 'signup.limit', 'signup.requires_email_verification', |
22 | 'signup.filters.cidr.whitelist', 'signup.filters.cidr.blacklist', | 22 | 'signup.filters.cidr.whitelist', 'signup.filters.cidr.blacklist', |
23 | 'redundancy.videos.strategies', 'redundancy.videos.check_interval', | 23 | 'redundancy.videos.strategies', 'redundancy.videos.check_interval', |
diff --git a/server/initializers/config.ts b/server/initializers/config.ts index 930fd784e..21ca78584 100644 --- a/server/initializers/config.ts +++ b/server/initializers/config.ts | |||
@@ -266,6 +266,9 @@ const CONFIG = { | |||
266 | }, | 266 | }, |
267 | VIDEO_CAPTIONS: { | 267 | VIDEO_CAPTIONS: { |
268 | get SIZE () { return config.get<number>('cache.captions.size') } | 268 | get SIZE () { return config.get<number>('cache.captions.size') } |
269 | }, | ||
270 | TORRENTS: { | ||
271 | get SIZE () { return config.get<number>('cache.torrents.size') } | ||
269 | } | 272 | } |
270 | }, | 273 | }, |
271 | INSTANCE: { | 274 | INSTANCE: { |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index be5db8fe8..6b0984186 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -551,16 +551,13 @@ const NSFW_POLICY_TYPES: { [ id: string ]: NSFWPolicyType } = { | |||
551 | 551 | ||
552 | // Express static paths (router) | 552 | // Express static paths (router) |
553 | const STATIC_PATHS = { | 553 | const STATIC_PATHS = { |
554 | PREVIEWS: '/static/previews/', | ||
555 | THUMBNAILS: '/static/thumbnails/', | 554 | THUMBNAILS: '/static/thumbnails/', |
556 | TORRENTS: '/static/torrents/', | 555 | TORRENTS: '/static/torrents/', |
557 | WEBSEED: '/static/webseed/', | 556 | WEBSEED: '/static/webseed/', |
558 | REDUNDANCY: '/static/redundancy/', | 557 | REDUNDANCY: '/static/redundancy/', |
559 | STREAMING_PLAYLISTS: { | 558 | STREAMING_PLAYLISTS: { |
560 | HLS: '/static/streaming-playlists/hls' | 559 | HLS: '/static/streaming-playlists/hls' |
561 | }, | 560 | } |
562 | AVATARS: '/static/avatars/', | ||
563 | VIDEO_CAPTIONS: '/static/video-captions/' | ||
564 | } | 561 | } |
565 | const STATIC_DOWNLOAD_PATHS = { | 562 | const STATIC_DOWNLOAD_PATHS = { |
566 | TORRENTS: '/download/torrents/', | 563 | TORRENTS: '/download/torrents/', |
@@ -570,12 +567,14 @@ const STATIC_DOWNLOAD_PATHS = { | |||
570 | const LAZY_STATIC_PATHS = { | 567 | const LAZY_STATIC_PATHS = { |
571 | AVATARS: '/lazy-static/avatars/', | 568 | AVATARS: '/lazy-static/avatars/', |
572 | PREVIEWS: '/lazy-static/previews/', | 569 | PREVIEWS: '/lazy-static/previews/', |
573 | VIDEO_CAPTIONS: '/lazy-static/video-captions/' | 570 | VIDEO_CAPTIONS: '/lazy-static/video-captions/', |
571 | TORRENTS: '/lazy-static/torrents/' | ||
574 | } | 572 | } |
575 | 573 | ||
576 | // Cache control | 574 | // Cache control |
577 | const STATIC_MAX_AGE = { | 575 | const STATIC_MAX_AGE = { |
578 | SERVER: '2h', | 576 | SERVER: '2h', |
577 | LAZY_SERVER: '2d', | ||
579 | CLIENT: '30d' | 578 | CLIENT: '30d' |
580 | } | 579 | } |
581 | 580 | ||
@@ -609,6 +608,10 @@ const FILES_CACHE = { | |||
609 | VIDEO_CAPTIONS: { | 608 | VIDEO_CAPTIONS: { |
610 | DIRECTORY: join(CONFIG.STORAGE.CACHE_DIR, 'video-captions'), | 609 | DIRECTORY: join(CONFIG.STORAGE.CACHE_DIR, 'video-captions'), |
611 | MAX_AGE: 1000 * 3600 * 3 // 3 hours | 610 | MAX_AGE: 1000 * 3600 * 3 // 3 hours |
611 | }, | ||
612 | TORRENTS: { | ||
613 | DIRECTORY: join(CONFIG.STORAGE.CACHE_DIR, 'torrents'), | ||
614 | MAX_AGE: 1000 * 3600 * 3 // 3 hours | ||
612 | } | 615 | } |
613 | } | 616 | } |
614 | 617 | ||
diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts index 66981f43f..a5f6537eb 100644 --- a/server/lib/activitypub/videos.ts +++ b/server/lib/activitypub/videos.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import { maxBy, minBy } from 'lodash' | 2 | import { maxBy, minBy } from 'lodash' |
3 | import * as magnetUtil from 'magnet-uri' | 3 | import * as magnetUtil from 'magnet-uri' |
4 | import { join } from 'path' | 4 | import { basename, join } from 'path' |
5 | import * as request from 'request' | 5 | import * as request from 'request' |
6 | import * as sequelize from 'sequelize' | 6 | import * as sequelize from 'sequelize' |
7 | import { VideoLiveModel } from '@server/models/video/video-live' | 7 | import { VideoLiveModel } from '@server/models/video/video-live' |
@@ -30,11 +30,11 @@ import { doRequest } from '../../helpers/requests' | |||
30 | import { fetchVideoByUrl, getExtFromMimetype, VideoFetchByUrlType } from '../../helpers/video' | 30 | import { fetchVideoByUrl, getExtFromMimetype, VideoFetchByUrlType } from '../../helpers/video' |
31 | import { | 31 | import { |
32 | ACTIVITY_PUB, | 32 | ACTIVITY_PUB, |
33 | LAZY_STATIC_PATHS, | ||
33 | MIMETYPES, | 34 | MIMETYPES, |
34 | P2P_MEDIA_LOADER_PEER_VERSION, | 35 | P2P_MEDIA_LOADER_PEER_VERSION, |
35 | PREVIEWS_SIZE, | 36 | PREVIEWS_SIZE, |
36 | REMOTE_SCHEME, | 37 | REMOTE_SCHEME, |
37 | STATIC_PATHS, | ||
38 | THUMBNAILS_SIZE | 38 | THUMBNAILS_SIZE |
39 | } from '../../initializers/constants' | 39 | } from '../../initializers/constants' |
40 | import { sequelizeTypescript } from '../../initializers/database' | 40 | import { sequelizeTypescript } from '../../initializers/database' |
@@ -51,6 +51,8 @@ import { | |||
51 | MChannelDefault, | 51 | MChannelDefault, |
52 | MChannelId, | 52 | MChannelId, |
53 | MStreamingPlaylist, | 53 | MStreamingPlaylist, |
54 | MStreamingPlaylistFilesVideo, | ||
55 | MStreamingPlaylistVideo, | ||
54 | MVideo, | 56 | MVideo, |
55 | MVideoAccountLight, | 57 | MVideoAccountLight, |
56 | MVideoAccountLightBlacklistAllFiles, | 58 | MVideoAccountLightBlacklistAllFiles, |
@@ -61,7 +63,8 @@ import { | |||
61 | MVideoFullLight, | 63 | MVideoFullLight, |
62 | MVideoId, | 64 | MVideoId, |
63 | MVideoImmutable, | 65 | MVideoImmutable, |
64 | MVideoThumbnail | 66 | MVideoThumbnail, |
67 | MVideoWithHost | ||
65 | } from '../../types/models' | 68 | } from '../../types/models' |
66 | import { MThumbnail } from '../../types/models/video/thumbnail' | 69 | import { MThumbnail } from '../../types/models/video/thumbnail' |
67 | import { FilteredModelAttributes } from '../../types/sequelize' | 70 | import { FilteredModelAttributes } from '../../types/sequelize' |
@@ -72,6 +75,7 @@ import { PeerTubeSocket } from '../peertube-socket' | |||
72 | import { createPlaceholderThumbnail, createVideoMiniatureFromUrl } from '../thumbnail' | 75 | import { createPlaceholderThumbnail, createVideoMiniatureFromUrl } from '../thumbnail' |
73 | import { setVideoTags } from '../video' | 76 | import { setVideoTags } from '../video' |
74 | import { autoBlacklistVideoIfNeeded } from '../video-blacklist' | 77 | import { autoBlacklistVideoIfNeeded } from '../video-blacklist' |
78 | import { generateTorrentFileName } from '../video-paths' | ||
75 | import { getOrCreateActorAndServerAndModel } from './actor' | 79 | import { getOrCreateActorAndServerAndModel } from './actor' |
76 | import { crawlCollectionPage } from './crawl' | 80 | import { crawlCollectionPage } from './crawl' |
77 | import { sendCreateVideo, sendUpdateVideo } from './send' | 81 | import { sendCreateVideo, sendUpdateVideo } from './send' |
@@ -405,7 +409,8 @@ async function updateVideoFromAP (options: { | |||
405 | 409 | ||
406 | for (const playlistAttributes of streamingPlaylistAttributes) { | 410 | for (const playlistAttributes of streamingPlaylistAttributes) { |
407 | const streamingPlaylistModel = await VideoStreamingPlaylistModel.upsert(playlistAttributes, { returning: true, transaction: t }) | 411 | const streamingPlaylistModel = await VideoStreamingPlaylistModel.upsert(playlistAttributes, { returning: true, transaction: t }) |
408 | .then(([ streamingPlaylist ]) => streamingPlaylist) | 412 | .then(([ streamingPlaylist ]) => streamingPlaylist as MStreamingPlaylistFilesVideo) |
413 | streamingPlaylistModel.Video = videoUpdated | ||
409 | 414 | ||
410 | const newVideoFiles: MVideoFile[] = videoFileActivityUrlToDBAttributes(streamingPlaylistModel, playlistAttributes.tagAPObject) | 415 | const newVideoFiles: MVideoFile[] = videoFileActivityUrlToDBAttributes(streamingPlaylistModel, playlistAttributes.tagAPObject) |
411 | .map(a => new VideoFileModel(a)) | 416 | .map(a => new VideoFileModel(a)) |
@@ -637,13 +642,14 @@ async function createVideo (videoObject: VideoObject, channel: MChannelAccountLi | |||
637 | videoCreated.VideoStreamingPlaylists = [] | 642 | videoCreated.VideoStreamingPlaylists = [] |
638 | 643 | ||
639 | for (const playlistAttributes of streamingPlaylistsAttributes) { | 644 | for (const playlistAttributes of streamingPlaylistsAttributes) { |
640 | const playlistModel = await VideoStreamingPlaylistModel.create(playlistAttributes, { transaction: t }) | 645 | const playlist = await VideoStreamingPlaylistModel.create(playlistAttributes, { transaction: t }) as MStreamingPlaylistFilesVideo |
646 | playlist.Video = videoCreated | ||
641 | 647 | ||
642 | const playlistFiles = videoFileActivityUrlToDBAttributes(playlistModel, playlistAttributes.tagAPObject) | 648 | const playlistFiles = videoFileActivityUrlToDBAttributes(playlist, playlistAttributes.tagAPObject) |
643 | const videoFilePromises = playlistFiles.map(f => VideoFileModel.create(f, { transaction: t })) | 649 | const videoFilePromises = playlistFiles.map(f => VideoFileModel.create(f, { transaction: t })) |
644 | playlistModel.VideoFiles = await Promise.all(videoFilePromises) | 650 | playlist.VideoFiles = await Promise.all(videoFilePromises) |
645 | 651 | ||
646 | videoCreated.VideoStreamingPlaylists.push(playlistModel) | 652 | videoCreated.VideoStreamingPlaylists.push(playlist) |
647 | } | 653 | } |
648 | 654 | ||
649 | // Process tags | 655 | // Process tags |
@@ -766,7 +772,7 @@ function videoActivityObjectToDBAttributes (videoChannel: MChannelId, videoObjec | |||
766 | } | 772 | } |
767 | 773 | ||
768 | function videoFileActivityUrlToDBAttributes ( | 774 | function videoFileActivityUrlToDBAttributes ( |
769 | videoOrPlaylist: MVideo | MStreamingPlaylist, | 775 | videoOrPlaylist: MVideo | MStreamingPlaylistVideo, |
770 | urls: (ActivityTagObject | ActivityUrlObject)[] | 776 | urls: (ActivityTagObject | ActivityUrlObject)[] |
771 | ) { | 777 | ) { |
772 | const fileUrls = urls.filter(u => isAPVideoUrlObject(u)) as ActivityVideoUrlObject[] | 778 | const fileUrls = urls.filter(u => isAPVideoUrlObject(u)) as ActivityVideoUrlObject[] |
@@ -786,6 +792,10 @@ function videoFileActivityUrlToDBAttributes ( | |||
786 | throw new Error('Cannot parse magnet URI ' + magnet.href) | 792 | throw new Error('Cannot parse magnet URI ' + magnet.href) |
787 | } | 793 | } |
788 | 794 | ||
795 | const torrentUrl = Array.isArray(parsed.xs) | ||
796 | ? parsed.xs[0] | ||
797 | : parsed.xs | ||
798 | |||
789 | // Fetch associated metadata url, if any | 799 | // Fetch associated metadata url, if any |
790 | const metadata = urls.filter(isAPVideoFileMetadataObject) | 800 | const metadata = urls.filter(isAPVideoFileMetadataObject) |
791 | .find(u => { | 801 | .find(u => { |
@@ -794,18 +804,30 @@ function videoFileActivityUrlToDBAttributes ( | |||
794 | u.rel.includes(fileUrl.mediaType) | 804 | u.rel.includes(fileUrl.mediaType) |
795 | }) | 805 | }) |
796 | 806 | ||
797 | const mediaType = fileUrl.mediaType | 807 | const extname = getExtFromMimetype(MIMETYPES.VIDEO.MIMETYPE_EXT, fileUrl.mediaType) |
808 | const resolution = fileUrl.height | ||
809 | const videoId = (videoOrPlaylist as MStreamingPlaylist).playlistUrl ? null : videoOrPlaylist.id | ||
810 | const videoStreamingPlaylistId = (videoOrPlaylist as MStreamingPlaylist).playlistUrl ? videoOrPlaylist.id : null | ||
811 | |||
798 | const attribute = { | 812 | const attribute = { |
799 | extname: getExtFromMimetype(MIMETYPES.VIDEO.MIMETYPE_EXT, mediaType), | 813 | extname, |
800 | infoHash: parsed.infoHash, | 814 | infoHash: parsed.infoHash, |
801 | resolution: fileUrl.height, | 815 | resolution, |
802 | size: fileUrl.size, | 816 | size: fileUrl.size, |
803 | fps: fileUrl.fps || -1, | 817 | fps: fileUrl.fps || -1, |
804 | metadataUrl: metadata?.href, | 818 | metadataUrl: metadata?.href, |
805 | 819 | ||
820 | // Use the name of the remote file because we don't proxify video file requests | ||
821 | filename: basename(fileUrl.href), | ||
822 | fileUrl: fileUrl.href, | ||
823 | |||
824 | torrentUrl, | ||
825 | // Use our own torrent name since we proxify torrent requests | ||
826 | torrentFilename: generateTorrentFileName(videoOrPlaylist, resolution), | ||
827 | |||
806 | // This is a video file owned by a video or by a streaming playlist | 828 | // This is a video file owned by a video or by a streaming playlist |
807 | videoId: (videoOrPlaylist as MStreamingPlaylist).playlistUrl ? null : videoOrPlaylist.id, | 829 | videoId, |
808 | videoStreamingPlaylistId: (videoOrPlaylist as MStreamingPlaylist).playlistUrl ? videoOrPlaylist.id : null | 830 | videoStreamingPlaylistId |
809 | } | 831 | } |
810 | 832 | ||
811 | attributes.push(attribute) | 833 | attributes.push(attribute) |
@@ -862,8 +884,8 @@ function getPreviewFromIcons (videoObject: VideoObject) { | |||
862 | return maxBy(validIcons, 'width') | 884 | return maxBy(validIcons, 'width') |
863 | } | 885 | } |
864 | 886 | ||
865 | function getPreviewUrl (previewIcon: ActivityIconObject, video: MVideoAccountLight) { | 887 | function getPreviewUrl (previewIcon: ActivityIconObject, video: MVideoWithHost) { |
866 | return previewIcon | 888 | return previewIcon |
867 | ? previewIcon.url | 889 | ? previewIcon.url |
868 | : buildRemoteVideoBaseUrl(video, join(STATIC_PATHS.PREVIEWS, video.generatePreviewName())) | 890 | : buildRemoteVideoBaseUrl(video, join(LAZY_STATIC_PATHS.PREVIEWS, video.generatePreviewName())) |
869 | } | 891 | } |
diff --git a/server/lib/files-cache/abstract-video-static-file-cache.ts b/server/lib/files-cache/abstract-video-static-file-cache.ts index c06355446..af66689a0 100644 --- a/server/lib/files-cache/abstract-video-static-file-cache.ts +++ b/server/lib/files-cache/abstract-video-static-file-cache.ts | |||
@@ -2,7 +2,7 @@ import { remove } from 'fs-extra' | |||
2 | import { logger } from '../../helpers/logger' | 2 | import { logger } from '../../helpers/logger' |
3 | import * as memoizee from 'memoizee' | 3 | import * as memoizee from 'memoizee' |
4 | 4 | ||
5 | type GetFilePathResult = { isOwned: boolean, path: string } | undefined | 5 | type GetFilePathResult = { isOwned: boolean, path: string, downloadName?: string } | undefined |
6 | 6 | ||
7 | export abstract class AbstractVideoStaticFileCache <T> { | 7 | export abstract class AbstractVideoStaticFileCache <T> { |
8 | 8 | ||
diff --git a/server/lib/files-cache/videos-torrent-cache.ts b/server/lib/files-cache/videos-torrent-cache.ts new file mode 100644 index 000000000..ca0e1770d --- /dev/null +++ b/server/lib/files-cache/videos-torrent-cache.ts | |||
@@ -0,0 +1,54 @@ | |||
1 | import { join } from 'path' | ||
2 | import { doRequestAndSaveToFile } from '@server/helpers/requests' | ||
3 | import { VideoFileModel } from '@server/models/video/video-file' | ||
4 | import { CONFIG } from '../../initializers/config' | ||
5 | import { FILES_CACHE } from '../../initializers/constants' | ||
6 | import { VideoModel } from '../../models/video/video' | ||
7 | import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' | ||
8 | |||
9 | class VideosTorrentCache extends AbstractVideoStaticFileCache <string> { | ||
10 | |||
11 | private static instance: VideosTorrentCache | ||
12 | |||
13 | private constructor () { | ||
14 | super() | ||
15 | } | ||
16 | |||
17 | static get Instance () { | ||
18 | return this.instance || (this.instance = new this()) | ||
19 | } | ||
20 | |||
21 | async getFilePathImpl (filename: string) { | ||
22 | const file = await VideoFileModel.loadWithVideoOrPlaylistByTorrentFilename(filename) | ||
23 | if (!file) return undefined | ||
24 | |||
25 | if (file.getVideo().isOwned()) return { isOwned: true, path: join(CONFIG.STORAGE.TORRENTS_DIR, file.torrentFilename) } | ||
26 | |||
27 | return this.loadRemoteFile(filename) | ||
28 | } | ||
29 | |||
30 | // Key is the torrent filename | ||
31 | protected async loadRemoteFile (key: string) { | ||
32 | const file = await VideoFileModel.loadWithVideoOrPlaylistByTorrentFilename(key) | ||
33 | if (!file) return undefined | ||
34 | |||
35 | if (file.getVideo().isOwned()) throw new Error('Cannot load remote file of owned video.') | ||
36 | |||
37 | // Used to fetch the path | ||
38 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(file.getVideo().id) | ||
39 | if (!video) return undefined | ||
40 | |||
41 | const remoteUrl = file.getRemoteTorrentUrl(video) | ||
42 | const destPath = join(FILES_CACHE.TORRENTS.DIRECTORY, file.torrentFilename) | ||
43 | |||
44 | await doRequestAndSaveToFile({ uri: remoteUrl }, destPath) | ||
45 | |||
46 | const downloadName = `${video.name}-${file.resolution}p.torrent` | ||
47 | |||
48 | return { isOwned: false, path: destPath, downloadName } | ||
49 | } | ||
50 | } | ||
51 | |||
52 | export { | ||
53 | VideosTorrentCache | ||
54 | } | ||
diff --git a/server/lib/hls.ts b/server/lib/hls.ts index ef489097a..04187668c 100644 --- a/server/lib/hls.ts +++ b/server/lib/hls.ts | |||
@@ -12,7 +12,7 @@ import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION } from | |||
12 | import { sequelizeTypescript } from '../initializers/database' | 12 | import { sequelizeTypescript } from '../initializers/database' |
13 | import { VideoFileModel } from '../models/video/video-file' | 13 | import { VideoFileModel } from '../models/video/video-file' |
14 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' | 14 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' |
15 | import { getVideoFilename, getVideoFilePath } from './video-paths' | 15 | import { getVideoFilePath } from './video-paths' |
16 | 16 | ||
17 | async function updateStreamingPlaylistsInfohashesIfNeeded () { | 17 | async function updateStreamingPlaylistsInfohashesIfNeeded () { |
18 | const playlistsToUpdate = await VideoStreamingPlaylistModel.listByIncorrectPeerVersion() | 18 | const playlistsToUpdate = await VideoStreamingPlaylistModel.listByIncorrectPeerVersion() |
@@ -93,7 +93,7 @@ async function updateSha256VODSegments (video: MVideoWithFile) { | |||
93 | } | 93 | } |
94 | await close(fd) | 94 | await close(fd) |
95 | 95 | ||
96 | const videoFilename = getVideoFilename(hlsPlaylist, file) | 96 | const videoFilename = file.filename |
97 | json[videoFilename] = rangeHashes | 97 | json[videoFilename] = rangeHashes |
98 | } | 98 | } |
99 | 99 | ||
diff --git a/server/lib/job-queue/handlers/video-file-import.ts b/server/lib/job-queue/handlers/video-file-import.ts index cd95aa075..86c9b5c29 100644 --- a/server/lib/job-queue/handlers/video-file-import.ts +++ b/server/lib/job-queue/handlers/video-file-import.ts | |||
@@ -2,9 +2,9 @@ import * as Bull from 'bull' | |||
2 | import { copy, stat } from 'fs-extra' | 2 | import { copy, stat } from 'fs-extra' |
3 | import { extname } from 'path' | 3 | import { extname } from 'path' |
4 | import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' | 4 | import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' |
5 | import { getVideoFilePath } from '@server/lib/video-paths' | 5 | import { generateVideoFilename, getVideoFilePath } from '@server/lib/video-paths' |
6 | import { UserModel } from '@server/models/account/user' | 6 | import { UserModel } from '@server/models/account/user' |
7 | import { MVideoFile, MVideoWithFile } from '@server/types/models' | 7 | import { MVideoFile, MVideoFullLight } from '@server/types/models' |
8 | import { VideoFileImportPayload } from '@shared/models' | 8 | import { VideoFileImportPayload } from '@shared/models' |
9 | import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffprobe-utils' | 9 | import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffprobe-utils' |
10 | import { logger } from '../../../helpers/logger' | 10 | import { logger } from '../../../helpers/logger' |
@@ -50,14 +50,16 @@ export { | |||
50 | 50 | ||
51 | // --------------------------------------------------------------------------- | 51 | // --------------------------------------------------------------------------- |
52 | 52 | ||
53 | async function updateVideoFile (video: MVideoWithFile, inputFilePath: string) { | 53 | async function updateVideoFile (video: MVideoFullLight, inputFilePath: string) { |
54 | const { videoFileResolution } = await getVideoFileResolution(inputFilePath) | 54 | const { videoFileResolution } = await getVideoFileResolution(inputFilePath) |
55 | const { size } = await stat(inputFilePath) | 55 | const { size } = await stat(inputFilePath) |
56 | const fps = await getVideoFileFPS(inputFilePath) | 56 | const fps = await getVideoFileFPS(inputFilePath) |
57 | 57 | ||
58 | const fileExt = extname(inputFilePath) | ||
58 | let updatedVideoFile = new VideoFileModel({ | 59 | let updatedVideoFile = new VideoFileModel({ |
59 | resolution: videoFileResolution, | 60 | resolution: videoFileResolution, |
60 | extname: extname(inputFilePath), | 61 | extname: fileExt, |
62 | filename: generateVideoFilename(video, false, videoFileResolution, fileExt), | ||
61 | size, | 63 | size, |
62 | fps, | 64 | fps, |
63 | videoId: video.id | 65 | videoId: video.id |
@@ -68,7 +70,7 @@ async function updateVideoFile (video: MVideoWithFile, inputFilePath: string) { | |||
68 | if (currentVideoFile) { | 70 | if (currentVideoFile) { |
69 | // Remove old file and old torrent | 71 | // Remove old file and old torrent |
70 | await video.removeFile(currentVideoFile) | 72 | await video.removeFile(currentVideoFile) |
71 | await video.removeTorrent(currentVideoFile) | 73 | await currentVideoFile.removeTorrent() |
72 | // Remove the old video file from the array | 74 | // Remove the old video file from the array |
73 | video.VideoFiles = video.VideoFiles.filter(f => f !== currentVideoFile) | 75 | video.VideoFiles = video.VideoFiles.filter(f => f !== currentVideoFile) |
74 | 76 | ||
@@ -83,7 +85,7 @@ async function updateVideoFile (video: MVideoWithFile, inputFilePath: string) { | |||
83 | const outputPath = getVideoFilePath(video, updatedVideoFile) | 85 | const outputPath = getVideoFilePath(video, updatedVideoFile) |
84 | await copy(inputFilePath, outputPath) | 86 | await copy(inputFilePath, outputPath) |
85 | 87 | ||
86 | await createTorrentAndSetInfoHash(video, updatedVideoFile) | 88 | await createTorrentAndSetInfoHash(video, video, updatedVideoFile) |
87 | 89 | ||
88 | await updatedVideoFile.save() | 90 | await updatedVideoFile.save() |
89 | 91 | ||
diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts index 0d00c1b9d..8fa024105 100644 --- a/server/lib/job-queue/handlers/video-import.ts +++ b/server/lib/job-queue/handlers/video-import.ts | |||
@@ -6,7 +6,7 @@ import { isPostImportVideoAccepted } from '@server/lib/moderation' | |||
6 | import { Hooks } from '@server/lib/plugins/hooks' | 6 | import { Hooks } from '@server/lib/plugins/hooks' |
7 | import { isAbleToUploadVideo } from '@server/lib/user' | 7 | import { isAbleToUploadVideo } from '@server/lib/user' |
8 | import { addOptimizeOrMergeAudioJob } from '@server/lib/video' | 8 | import { addOptimizeOrMergeAudioJob } from '@server/lib/video' |
9 | import { getVideoFilePath } from '@server/lib/video-paths' | 9 | import { generateVideoFilename, getVideoFilePath } from '@server/lib/video-paths' |
10 | import { ThumbnailModel } from '@server/models/video/thumbnail' | 10 | import { ThumbnailModel } from '@server/models/video/thumbnail' |
11 | import { MVideoImportDefault, MVideoImportDefaultFiles, MVideoImportVideo } from '@server/types/models/video/video-import' | 11 | import { MVideoImportDefault, MVideoImportDefaultFiles, MVideoImportVideo } from '@server/types/models/video/video-import' |
12 | import { | 12 | import { |
@@ -116,10 +116,12 @@ async function processFile (downloader: () => Promise<string>, videoImport: MVid | |||
116 | const duration = await getDurationFromVideoFile(tempVideoPath) | 116 | const duration = await getDurationFromVideoFile(tempVideoPath) |
117 | 117 | ||
118 | // Prepare video file object for creation in database | 118 | // Prepare video file object for creation in database |
119 | const fileExt = extname(tempVideoPath) | ||
119 | const videoFileData = { | 120 | const videoFileData = { |
120 | extname: extname(tempVideoPath), | 121 | extname: fileExt, |
121 | resolution: videoFileResolution, | 122 | resolution: videoFileResolution, |
122 | size: stats.size, | 123 | size: stats.size, |
124 | filename: generateVideoFilename(videoImport.Video, false, videoFileResolution, fileExt), | ||
123 | fps, | 125 | fps, |
124 | videoId: videoImport.videoId | 126 | videoId: videoImport.videoId |
125 | } | 127 | } |
@@ -183,7 +185,7 @@ async function processFile (downloader: () => Promise<string>, videoImport: MVid | |||
183 | } | 185 | } |
184 | 186 | ||
185 | // Create torrent | 187 | // Create torrent |
186 | await createTorrentAndSetInfoHash(videoImportWithFiles.Video, videoFile) | 188 | await createTorrentAndSetInfoHash(videoImportWithFiles.Video, videoImportWithFiles.Video, videoFile) |
187 | 189 | ||
188 | const videoFileSave = videoFile.toJSON() | 190 | const videoFileSave = videoFile.toJSON() |
189 | 191 | ||
diff --git a/server/lib/job-queue/handlers/video-live-ending.ts b/server/lib/job-queue/handlers/video-live-ending.ts index 6d50635bb..d57202ca5 100644 --- a/server/lib/job-queue/handlers/video-live-ending.ts +++ b/server/lib/job-queue/handlers/video-live-ending.ts | |||
@@ -85,7 +85,7 @@ async function saveLive (video: MVideo, live: MVideoLive) { | |||
85 | await video.save() | 85 | await video.save() |
86 | 86 | ||
87 | // Remove old HLS playlist video files | 87 | // Remove old HLS playlist video files |
88 | const videoWithFiles = await VideoModel.loadWithFiles(video.id) | 88 | const videoWithFiles = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.id) |
89 | 89 | ||
90 | const hlsPlaylist = videoWithFiles.getHLSPlaylist() | 90 | const hlsPlaylist = videoWithFiles.getHLSPlaylist() |
91 | await VideoFileModel.removeHLSFilesOfVideoId(hlsPlaylist.id) | 91 | await VideoFileModel.removeHLSFilesOfVideoId(hlsPlaylist.id) |
diff --git a/server/lib/job-queue/handlers/video-transcoding.ts b/server/lib/job-queue/handlers/video-transcoding.ts index e248b645e..8573d4d12 100644 --- a/server/lib/job-queue/handlers/video-transcoding.ts +++ b/server/lib/job-queue/handlers/video-transcoding.ts | |||
@@ -128,7 +128,7 @@ async function onHlsPlaylistGeneration (video: MVideoFullLight, user: MUser, pay | |||
128 | // Remove webtorrent files if not enabled | 128 | // Remove webtorrent files if not enabled |
129 | for (const file of video.VideoFiles) { | 129 | for (const file of video.VideoFiles) { |
130 | await video.removeFile(file) | 130 | await video.removeFile(file) |
131 | await video.removeTorrent(file) | 131 | await file.removeTorrent() |
132 | await file.destroy() | 132 | await file.destroy() |
133 | } | 133 | } |
134 | 134 | ||
diff --git a/server/lib/live-manager.ts b/server/lib/live-manager.ts index 9f17b8820..b549c189f 100644 --- a/server/lib/live-manager.ts +++ b/server/lib/live-manager.ts | |||
@@ -16,7 +16,7 @@ import { VideoModel } from '@server/models/video/video' | |||
16 | import { VideoFileModel } from '@server/models/video/video-file' | 16 | import { VideoFileModel } from '@server/models/video/video-file' |
17 | import { VideoLiveModel } from '@server/models/video/video-live' | 17 | import { VideoLiveModel } from '@server/models/video/video-live' |
18 | import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist' | 18 | import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist' |
19 | import { MStreamingPlaylist, MUserId, MVideoLive, MVideoLiveVideo } from '@server/types/models' | 19 | import { MStreamingPlaylist, MStreamingPlaylistVideo, MUserId, MVideoLive, MVideoLiveVideo } from '@server/types/models' |
20 | import { VideoState, VideoStreamingPlaylistType } from '@shared/models' | 20 | import { VideoState, VideoStreamingPlaylistType } from '@shared/models' |
21 | import { federateVideoIfNeeded } from './activitypub/videos' | 21 | import { federateVideoIfNeeded } from './activitypub/videos' |
22 | import { buildSha256Segment } from './hls' | 22 | import { buildSha256Segment } from './hls' |
@@ -277,7 +277,7 @@ class LiveManager { | |||
277 | return this.runMuxing({ | 277 | return this.runMuxing({ |
278 | sessionId, | 278 | sessionId, |
279 | videoLive, | 279 | videoLive, |
280 | playlist: videoStreamingPlaylist, | 280 | playlist: Object.assign(videoStreamingPlaylist, { Video: video }), |
281 | rtmpUrl, | 281 | rtmpUrl, |
282 | fps, | 282 | fps, |
283 | allResolutions | 283 | allResolutions |
@@ -287,7 +287,7 @@ class LiveManager { | |||
287 | private async runMuxing (options: { | 287 | private async runMuxing (options: { |
288 | sessionId: string | 288 | sessionId: string |
289 | videoLive: MVideoLiveVideo | 289 | videoLive: MVideoLiveVideo |
290 | playlist: MStreamingPlaylist | 290 | playlist: MStreamingPlaylistVideo |
291 | rtmpUrl: string | 291 | rtmpUrl: string |
292 | fps: number | 292 | fps: number |
293 | allResolutions: number[] | 293 | allResolutions: number[] |
diff --git a/server/lib/schedulers/videos-redundancy-scheduler.ts b/server/lib/schedulers/videos-redundancy-scheduler.ts index 93e76626c..60008e695 100644 --- a/server/lib/schedulers/videos-redundancy-scheduler.ts +++ b/server/lib/schedulers/videos-redundancy-scheduler.ts | |||
@@ -18,14 +18,14 @@ import { VideosRedundancyStrategy } from '../../../shared/models/redundancy' | |||
18 | import { logger } from '../../helpers/logger' | 18 | import { logger } from '../../helpers/logger' |
19 | import { downloadWebTorrentVideo, generateMagnetUri } from '../../helpers/webtorrent' | 19 | import { downloadWebTorrentVideo, generateMagnetUri } from '../../helpers/webtorrent' |
20 | import { CONFIG } from '../../initializers/config' | 20 | import { CONFIG } from '../../initializers/config' |
21 | import { HLS_REDUNDANCY_DIRECTORY, REDUNDANCY, VIDEO_IMPORT_TIMEOUT, WEBSERVER } from '../../initializers/constants' | 21 | import { HLS_REDUNDANCY_DIRECTORY, REDUNDANCY, VIDEO_IMPORT_TIMEOUT } from '../../initializers/constants' |
22 | import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' | 22 | import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' |
23 | import { sendCreateCacheFile, sendUpdateCacheFile } from '../activitypub/send' | 23 | import { sendCreateCacheFile, sendUpdateCacheFile } from '../activitypub/send' |
24 | import { getLocalVideoCacheFileActivityPubUrl, getLocalVideoCacheStreamingPlaylistActivityPubUrl } from '../activitypub/url' | 24 | import { getLocalVideoCacheFileActivityPubUrl, getLocalVideoCacheStreamingPlaylistActivityPubUrl } from '../activitypub/url' |
25 | import { getOrCreateVideoAndAccountAndChannel } from '../activitypub/videos' | 25 | import { getOrCreateVideoAndAccountAndChannel } from '../activitypub/videos' |
26 | import { downloadPlaylistSegments } from '../hls' | 26 | import { downloadPlaylistSegments } from '../hls' |
27 | import { removeVideoRedundancy } from '../redundancy' | 27 | import { removeVideoRedundancy } from '../redundancy' |
28 | import { getVideoFilename } from '../video-paths' | 28 | import { generateHLSRedundancyUrl, generateWebTorrentRedundancyUrl } from '../video-paths' |
29 | import { AbstractScheduler } from './abstract-scheduler' | 29 | import { AbstractScheduler } from './abstract-scheduler' |
30 | 30 | ||
31 | type CandidateToDuplicate = { | 31 | type CandidateToDuplicate = { |
@@ -222,17 +222,17 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
222 | logger.info('Duplicating %s - %d in videos redundancy with "%s" strategy.', video.url, file.resolution, strategy) | 222 | logger.info('Duplicating %s - %d in videos redundancy with "%s" strategy.', video.url, file.resolution, strategy) |
223 | 223 | ||
224 | const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() | 224 | const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() |
225 | const magnetUri = generateMagnetUri(video, file, baseUrlHttp, baseUrlWs) | 225 | const magnetUri = generateMagnetUri(video, video, file, baseUrlHttp, baseUrlWs) |
226 | 226 | ||
227 | const tmpPath = await downloadWebTorrentVideo({ magnetUri }, VIDEO_IMPORT_TIMEOUT) | 227 | const tmpPath = await downloadWebTorrentVideo({ magnetUri }, VIDEO_IMPORT_TIMEOUT) |
228 | 228 | ||
229 | const destPath = join(CONFIG.STORAGE.REDUNDANCY_DIR, getVideoFilename(video, file)) | 229 | const destPath = join(CONFIG.STORAGE.REDUNDANCY_DIR, file.filename) |
230 | await move(tmpPath, destPath, { overwrite: true }) | 230 | await move(tmpPath, destPath, { overwrite: true }) |
231 | 231 | ||
232 | const createdModel: MVideoRedundancyFileVideo = await VideoRedundancyModel.create({ | 232 | const createdModel: MVideoRedundancyFileVideo = await VideoRedundancyModel.create({ |
233 | expiresOn, | 233 | expiresOn, |
234 | url: getLocalVideoCacheFileActivityPubUrl(file), | 234 | url: getLocalVideoCacheFileActivityPubUrl(file), |
235 | fileUrl: video.getVideoRedundancyUrl(file, WEBSERVER.URL), | 235 | fileUrl: generateWebTorrentRedundancyUrl(file), |
236 | strategy, | 236 | strategy, |
237 | videoFileId: file.id, | 237 | videoFileId: file.id, |
238 | actorId: serverActor.id | 238 | actorId: serverActor.id |
@@ -271,7 +271,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
271 | const createdModel: MVideoRedundancyStreamingPlaylistVideo = await VideoRedundancyModel.create({ | 271 | const createdModel: MVideoRedundancyStreamingPlaylistVideo = await VideoRedundancyModel.create({ |
272 | expiresOn, | 272 | expiresOn, |
273 | url: getLocalVideoCacheStreamingPlaylistActivityPubUrl(video, playlist), | 273 | url: getLocalVideoCacheStreamingPlaylistActivityPubUrl(video, playlist), |
274 | fileUrl: playlist.getVideoRedundancyUrl(WEBSERVER.URL), | 274 | fileUrl: generateHLSRedundancyUrl(video, playlistArg), |
275 | strategy, | 275 | strategy, |
276 | videoStreamingPlaylistId: playlist.id, | 276 | videoStreamingPlaylistId: playlist.id, |
277 | actorId: serverActor.id | 277 | actorId: serverActor.id |
diff --git a/server/lib/video-paths.ts b/server/lib/video-paths.ts index 53fc8e81d..0385e89cc 100644 --- a/server/lib/video-paths.ts +++ b/server/lib/video-paths.ts | |||
@@ -1,19 +1,23 @@ | |||
1 | import { isStreamingPlaylist, MStreamingPlaylistVideo, MVideo, MVideoFile, MVideoUUID } from '@server/types/models' | ||
2 | import { join } from 'path' | 1 | import { join } from 'path' |
3 | import { CONFIG } from '@server/initializers/config' | ||
4 | import { HLS_REDUNDANCY_DIRECTORY, HLS_STREAMING_PLAYLIST_DIRECTORY } from '@server/initializers/constants' | ||
5 | import { extractVideo } from '@server/helpers/video' | 2 | import { extractVideo } from '@server/helpers/video' |
3 | import { CONFIG } from '@server/initializers/config' | ||
4 | import { HLS_REDUNDANCY_DIRECTORY, HLS_STREAMING_PLAYLIST_DIRECTORY, STATIC_PATHS, WEBSERVER } from '@server/initializers/constants' | ||
5 | import { isStreamingPlaylist, MStreamingPlaylist, MStreamingPlaylistVideo, MVideo, MVideoFile, MVideoUUID } from '@server/types/models' | ||
6 | 6 | ||
7 | // ################## Video file name ################## | 7 | // ################## Video file name ################## |
8 | 8 | ||
9 | function getVideoFilename (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, videoFile: MVideoFile) { | 9 | function generateVideoFilename (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, isHls: boolean, resolution: number, extname: string) { |
10 | const video = extractVideo(videoOrPlaylist) | 10 | const video = extractVideo(videoOrPlaylist) |
11 | 11 | ||
12 | if (videoFile.isHLS()) { | 12 | // FIXME: use a generated uuid instead, that will break compatibility with PeerTube < 3.2 |
13 | return generateVideoStreamingPlaylistName(video.uuid, videoFile.resolution) | 13 | // const uuid = uuidv4() |
14 | const uuid = video.uuid | ||
15 | |||
16 | if (isHls) { | ||
17 | return generateVideoStreamingPlaylistName(uuid, resolution) | ||
14 | } | 18 | } |
15 | 19 | ||
16 | return generateWebTorrentVideoName(video.uuid, videoFile.resolution, videoFile.extname) | 20 | return generateWebTorrentVideoName(uuid, resolution, extname) |
17 | } | 21 | } |
18 | 22 | ||
19 | function generateVideoStreamingPlaylistName (uuid: string, resolution: number) { | 23 | function generateVideoStreamingPlaylistName (uuid: string, resolution: number) { |
@@ -28,36 +32,64 @@ function getVideoFilePath (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, vi | |||
28 | if (videoFile.isHLS()) { | 32 | if (videoFile.isHLS()) { |
29 | const video = extractVideo(videoOrPlaylist) | 33 | const video = extractVideo(videoOrPlaylist) |
30 | 34 | ||
31 | return join(getHLSDirectory(video), getVideoFilename(videoOrPlaylist, videoFile)) | 35 | return join(getHLSDirectory(video), videoFile.filename) |
32 | } | 36 | } |
33 | 37 | ||
34 | const baseDir = isRedundancy ? CONFIG.STORAGE.REDUNDANCY_DIR : CONFIG.STORAGE.VIDEOS_DIR | 38 | const baseDir = isRedundancy |
35 | return join(baseDir, getVideoFilename(videoOrPlaylist, videoFile)) | 39 | ? CONFIG.STORAGE.REDUNDANCY_DIR |
40 | : CONFIG.STORAGE.VIDEOS_DIR | ||
41 | |||
42 | return join(baseDir, videoFile.filename) | ||
43 | } | ||
44 | |||
45 | // ################## Redundancy ################## | ||
46 | |||
47 | function generateHLSRedundancyUrl (video: MVideo, playlist: MStreamingPlaylist) { | ||
48 | // Base URL used by our HLS player | ||
49 | return WEBSERVER.URL + STATIC_PATHS.REDUNDANCY + playlist.getStringType() + '/' + video.uuid | ||
50 | } | ||
51 | |||
52 | function generateWebTorrentRedundancyUrl (file: MVideoFile) { | ||
53 | return WEBSERVER.URL + STATIC_PATHS.REDUNDANCY + file.filename | ||
36 | } | 54 | } |
37 | 55 | ||
38 | // ################## Streaming playlist ################## | 56 | // ################## Streaming playlist ################## |
39 | 57 | ||
40 | function getHLSDirectory (video: MVideoUUID, isRedundancy = false) { | 58 | function getHLSDirectory (video: MVideoUUID, isRedundancy = false) { |
41 | const baseDir = isRedundancy ? HLS_REDUNDANCY_DIRECTORY : HLS_STREAMING_PLAYLIST_DIRECTORY | 59 | const baseDir = isRedundancy |
60 | ? HLS_REDUNDANCY_DIRECTORY | ||
61 | : HLS_STREAMING_PLAYLIST_DIRECTORY | ||
42 | 62 | ||
43 | return join(baseDir, video.uuid) | 63 | return join(baseDir, video.uuid) |
44 | } | 64 | } |
45 | 65 | ||
46 | // ################## Torrents ################## | 66 | // ################## Torrents ################## |
47 | 67 | ||
48 | function getTorrentFileName (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, videoFile: MVideoFile) { | 68 | function generateTorrentFileName (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, resolution: number) { |
49 | const video = extractVideo(videoOrPlaylist) | 69 | const video = extractVideo(videoOrPlaylist) |
50 | const extension = '.torrent' | 70 | const extension = '.torrent' |
51 | 71 | ||
72 | // FIXME: use a generated uuid instead, that will break compatibility with PeerTube < 3.2 | ||
73 | // const uuid = uuidv4() | ||
74 | const uuid = video.uuid | ||
75 | |||
52 | if (isStreamingPlaylist(videoOrPlaylist)) { | 76 | if (isStreamingPlaylist(videoOrPlaylist)) { |
53 | return `${video.uuid}-${videoFile.resolution}-${videoOrPlaylist.getStringType()}${extension}` | 77 | return `${uuid}-${resolution}-${videoOrPlaylist.getStringType()}${extension}` |
54 | } | 78 | } |
55 | 79 | ||
56 | return video.uuid + '-' + videoFile.resolution + extension | 80 | return uuid + '-' + resolution + extension |
81 | } | ||
82 | |||
83 | function getTorrentFilePath (videoFile: MVideoFile) { | ||
84 | return join(CONFIG.STORAGE.TORRENTS_DIR, videoFile.torrentFilename) | ||
57 | } | 85 | } |
58 | 86 | ||
59 | function getTorrentFilePath (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, videoFile: MVideoFile) { | 87 | // ################## Meta data ################## |
60 | return join(CONFIG.STORAGE.TORRENTS_DIR, getTorrentFileName(videoOrPlaylist, videoFile)) | 88 | |
89 | function getLocalVideoFileMetadataUrl (video: MVideoUUID, videoFile: MVideoFile) { | ||
90 | const path = '/api/v1/videos/' | ||
91 | |||
92 | return WEBSERVER.URL + path + video.uuid + '/metadata/' + videoFile.id | ||
61 | } | 93 | } |
62 | 94 | ||
63 | // --------------------------------------------------------------------------- | 95 | // --------------------------------------------------------------------------- |
@@ -65,11 +97,16 @@ function getTorrentFilePath (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, | |||
65 | export { | 97 | export { |
66 | generateVideoStreamingPlaylistName, | 98 | generateVideoStreamingPlaylistName, |
67 | generateWebTorrentVideoName, | 99 | generateWebTorrentVideoName, |
68 | getVideoFilename, | 100 | generateVideoFilename, |
69 | getVideoFilePath, | 101 | getVideoFilePath, |
70 | 102 | ||
71 | getTorrentFileName, | 103 | generateTorrentFileName, |
72 | getTorrentFilePath, | 104 | getTorrentFilePath, |
73 | 105 | ||
74 | getHLSDirectory | 106 | getHLSDirectory, |
107 | |||
108 | getLocalVideoFileMetadataUrl, | ||
109 | |||
110 | generateWebTorrentRedundancyUrl, | ||
111 | generateHLSRedundancyUrl | ||
75 | } | 112 | } |
diff --git a/server/lib/video-transcoding.ts b/server/lib/video-transcoding.ts index a58c9dd20..b366e2e44 100644 --- a/server/lib/video-transcoding.ts +++ b/server/lib/video-transcoding.ts | |||
@@ -2,7 +2,7 @@ import { Job } from 'bull' | |||
2 | import { copyFile, ensureDir, move, remove, stat } from 'fs-extra' | 2 | import { copyFile, ensureDir, move, remove, stat } from 'fs-extra' |
3 | import { basename, extname as extnameUtil, join } from 'path' | 3 | import { basename, extname as extnameUtil, join } from 'path' |
4 | import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' | 4 | import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' |
5 | import { MStreamingPlaylistFilesVideo, MVideoFile, MVideoWithAllFiles, MVideoWithFile } from '@server/types/models' | 5 | import { MStreamingPlaylistFilesVideo, MVideoFile, MVideoFullLight } from '@server/types/models' |
6 | import { VideoResolution } from '../../shared/models/videos' | 6 | import { VideoResolution } from '../../shared/models/videos' |
7 | import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type' | 7 | import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type' |
8 | import { transcode, TranscodeOptions, TranscodeOptionsType } from '../helpers/ffmpeg-utils' | 8 | import { transcode, TranscodeOptions, TranscodeOptionsType } from '../helpers/ffmpeg-utils' |
@@ -13,7 +13,7 @@ import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION, WEBSER | |||
13 | import { VideoFileModel } from '../models/video/video-file' | 13 | import { VideoFileModel } from '../models/video/video-file' |
14 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' | 14 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' |
15 | import { updateMasterHLSPlaylist, updateSha256VODSegments } from './hls' | 15 | import { updateMasterHLSPlaylist, updateSha256VODSegments } from './hls' |
16 | import { generateVideoStreamingPlaylistName, getVideoFilename, getVideoFilePath } from './video-paths' | 16 | import { generateVideoFilename, generateVideoStreamingPlaylistName, getVideoFilePath } from './video-paths' |
17 | import { VideoTranscodingProfilesManager } from './video-transcoding-profiles' | 17 | import { VideoTranscodingProfilesManager } from './video-transcoding-profiles' |
18 | 18 | ||
19 | /** | 19 | /** |
@@ -24,7 +24,7 @@ import { VideoTranscodingProfilesManager } from './video-transcoding-profiles' | |||
24 | */ | 24 | */ |
25 | 25 | ||
26 | // Optimize the original video file and replace it. The resolution is not changed. | 26 | // Optimize the original video file and replace it. The resolution is not changed. |
27 | async function optimizeOriginalVideofile (video: MVideoWithFile, inputVideoFile: MVideoFile, job?: Job) { | 27 | async function optimizeOriginalVideofile (video: MVideoFullLight, inputVideoFile: MVideoFile, job?: Job) { |
28 | const transcodeDirectory = CONFIG.STORAGE.TMP_DIR | 28 | const transcodeDirectory = CONFIG.STORAGE.TMP_DIR |
29 | const newExtname = '.mp4' | 29 | const newExtname = '.mp4' |
30 | 30 | ||
@@ -55,8 +55,9 @@ async function optimizeOriginalVideofile (video: MVideoWithFile, inputVideoFile: | |||
55 | try { | 55 | try { |
56 | await remove(videoInputPath) | 56 | await remove(videoInputPath) |
57 | 57 | ||
58 | // Important to do this before getVideoFilename() to take in account the new file extension | 58 | // Important to do this before getVideoFilename() to take in account the new filename |
59 | inputVideoFile.extname = newExtname | 59 | inputVideoFile.extname = newExtname |
60 | inputVideoFile.filename = generateVideoFilename(video, false, inputVideoFile.resolution, newExtname) | ||
60 | 61 | ||
61 | const videoOutputPath = getVideoFilePath(video, inputVideoFile) | 62 | const videoOutputPath = getVideoFilePath(video, inputVideoFile) |
62 | 63 | ||
@@ -72,7 +73,7 @@ async function optimizeOriginalVideofile (video: MVideoWithFile, inputVideoFile: | |||
72 | } | 73 | } |
73 | 74 | ||
74 | // Transcode the original video file to a lower resolution. | 75 | // Transcode the original video file to a lower resolution. |
75 | async function transcodeNewWebTorrentResolution (video: MVideoWithFile, resolution: VideoResolution, isPortrait: boolean, job: Job) { | 76 | async function transcodeNewWebTorrentResolution (video: MVideoFullLight, resolution: VideoResolution, isPortrait: boolean, job: Job) { |
76 | const transcodeDirectory = CONFIG.STORAGE.TMP_DIR | 77 | const transcodeDirectory = CONFIG.STORAGE.TMP_DIR |
77 | const extname = '.mp4' | 78 | const extname = '.mp4' |
78 | 79 | ||
@@ -82,11 +83,13 @@ async function transcodeNewWebTorrentResolution (video: MVideoWithFile, resoluti | |||
82 | const newVideoFile = new VideoFileModel({ | 83 | const newVideoFile = new VideoFileModel({ |
83 | resolution, | 84 | resolution, |
84 | extname, | 85 | extname, |
86 | filename: generateVideoFilename(video, false, resolution, extname), | ||
85 | size: 0, | 87 | size: 0, |
86 | videoId: video.id | 88 | videoId: video.id |
87 | }) | 89 | }) |
90 | |||
88 | const videoOutputPath = getVideoFilePath(video, newVideoFile) | 91 | const videoOutputPath = getVideoFilePath(video, newVideoFile) |
89 | const videoTranscodedPath = join(transcodeDirectory, getVideoFilename(video, newVideoFile)) | 92 | const videoTranscodedPath = join(transcodeDirectory, newVideoFile.filename) |
90 | 93 | ||
91 | const transcodeOptions = resolution === VideoResolution.H_NOVIDEO | 94 | const transcodeOptions = resolution === VideoResolution.H_NOVIDEO |
92 | ? { | 95 | ? { |
@@ -122,7 +125,7 @@ async function transcodeNewWebTorrentResolution (video: MVideoWithFile, resoluti | |||
122 | } | 125 | } |
123 | 126 | ||
124 | // Merge an image with an audio file to create a video | 127 | // Merge an image with an audio file to create a video |
125 | async function mergeAudioVideofile (video: MVideoWithAllFiles, resolution: VideoResolution, job: Job) { | 128 | async function mergeAudioVideofile (video: MVideoFullLight, resolution: VideoResolution, job: Job) { |
126 | const transcodeDirectory = CONFIG.STORAGE.TMP_DIR | 129 | const transcodeDirectory = CONFIG.STORAGE.TMP_DIR |
127 | const newExtname = '.mp4' | 130 | const newExtname = '.mp4' |
128 | 131 | ||
@@ -175,7 +178,7 @@ async function mergeAudioVideofile (video: MVideoWithAllFiles, resolution: Video | |||
175 | 178 | ||
176 | // Concat TS segments from a live video to a fragmented mp4 HLS playlist | 179 | // Concat TS segments from a live video to a fragmented mp4 HLS playlist |
177 | async function generateHlsPlaylistResolutionFromTS (options: { | 180 | async function generateHlsPlaylistResolutionFromTS (options: { |
178 | video: MVideoWithFile | 181 | video: MVideoFullLight |
179 | concatenatedTsFilePath: string | 182 | concatenatedTsFilePath: string |
180 | resolution: VideoResolution | 183 | resolution: VideoResolution |
181 | isPortraitMode: boolean | 184 | isPortraitMode: boolean |
@@ -193,7 +196,7 @@ async function generateHlsPlaylistResolutionFromTS (options: { | |||
193 | 196 | ||
194 | // Generate an HLS playlist from an input file, and update the master playlist | 197 | // Generate an HLS playlist from an input file, and update the master playlist |
195 | function generateHlsPlaylistResolution (options: { | 198 | function generateHlsPlaylistResolution (options: { |
196 | video: MVideoWithFile | 199 | video: MVideoFullLight |
197 | videoInputPath: string | 200 | videoInputPath: string |
198 | resolution: VideoResolution | 201 | resolution: VideoResolution |
199 | copyCodecs: boolean | 202 | copyCodecs: boolean |
@@ -235,7 +238,7 @@ export { | |||
235 | // --------------------------------------------------------------------------- | 238 | // --------------------------------------------------------------------------- |
236 | 239 | ||
237 | async function onWebTorrentVideoFileTranscoding ( | 240 | async function onWebTorrentVideoFileTranscoding ( |
238 | video: MVideoWithFile, | 241 | video: MVideoFullLight, |
239 | videoFile: MVideoFile, | 242 | videoFile: MVideoFile, |
240 | transcodingPath: string, | 243 | transcodingPath: string, |
241 | outputPath: string | 244 | outputPath: string |
@@ -250,7 +253,7 @@ async function onWebTorrentVideoFileTranscoding ( | |||
250 | videoFile.fps = fps | 253 | videoFile.fps = fps |
251 | videoFile.metadata = metadata | 254 | videoFile.metadata = metadata |
252 | 255 | ||
253 | await createTorrentAndSetInfoHash(video, videoFile) | 256 | await createTorrentAndSetInfoHash(video, video, videoFile) |
254 | 257 | ||
255 | await VideoFileModel.customUpsert(videoFile, 'video', undefined) | 258 | await VideoFileModel.customUpsert(videoFile, 'video', undefined) |
256 | video.VideoFiles = await video.$get('VideoFiles') | 259 | video.VideoFiles = await video.$get('VideoFiles') |
@@ -260,7 +263,7 @@ async function onWebTorrentVideoFileTranscoding ( | |||
260 | 263 | ||
261 | async function generateHlsPlaylistCommon (options: { | 264 | async function generateHlsPlaylistCommon (options: { |
262 | type: 'hls' | 'hls-from-ts' | 265 | type: 'hls' | 'hls-from-ts' |
263 | video: MVideoWithFile | 266 | video: MVideoFullLight |
264 | inputPath: string | 267 | inputPath: string |
265 | resolution: VideoResolution | 268 | resolution: VideoResolution |
266 | copyCodecs?: boolean | 269 | copyCodecs?: boolean |
@@ -318,10 +321,12 @@ async function generateHlsPlaylistCommon (options: { | |||
318 | videoStreamingPlaylist.Video = video | 321 | videoStreamingPlaylist.Video = video |
319 | 322 | ||
320 | // Build the new playlist file | 323 | // Build the new playlist file |
324 | const extname = extnameUtil(videoFilename) | ||
321 | const newVideoFile = new VideoFileModel({ | 325 | const newVideoFile = new VideoFileModel({ |
322 | resolution, | 326 | resolution, |
323 | extname: extnameUtil(videoFilename), | 327 | extname, |
324 | size: 0, | 328 | size: 0, |
329 | filename: generateVideoFilename(video, true, resolution, extname), | ||
325 | fps: -1, | 330 | fps: -1, |
326 | videoStreamingPlaylistId: videoStreamingPlaylist.id | 331 | videoStreamingPlaylistId: videoStreamingPlaylist.id |
327 | }) | 332 | }) |
@@ -344,7 +349,7 @@ async function generateHlsPlaylistCommon (options: { | |||
344 | newVideoFile.fps = await getVideoFileFPS(videoFilePath) | 349 | newVideoFile.fps = await getVideoFileFPS(videoFilePath) |
345 | newVideoFile.metadata = await getMetadataFromFile(videoFilePath) | 350 | newVideoFile.metadata = await getMetadataFromFile(videoFilePath) |
346 | 351 | ||
347 | await createTorrentAndSetInfoHash(videoStreamingPlaylist, newVideoFile) | 352 | await createTorrentAndSetInfoHash(videoStreamingPlaylist, video, newVideoFile) |
348 | 353 | ||
349 | await VideoFileModel.customUpsert(newVideoFile, 'streaming-playlist', undefined) | 354 | await VideoFileModel.customUpsert(newVideoFile, 'streaming-playlist', undefined) |
350 | videoStreamingPlaylist.VideoFiles = await videoStreamingPlaylist.$get('VideoFiles') | 355 | videoStreamingPlaylist.VideoFiles = await videoStreamingPlaylist.$get('VideoFiles') |
diff --git a/server/models/video/thumbnail.ts b/server/models/video/thumbnail.ts index 4185ec5f2..9533c8d19 100644 --- a/server/models/video/thumbnail.ts +++ b/server/models/video/thumbnail.ts | |||
@@ -17,7 +17,7 @@ import { | |||
17 | } from 'sequelize-typescript' | 17 | } from 'sequelize-typescript' |
18 | import { buildRemoteVideoBaseUrl } from '@server/helpers/activitypub' | 18 | import { buildRemoteVideoBaseUrl } from '@server/helpers/activitypub' |
19 | import { afterCommitIfTransaction } from '@server/helpers/database-utils' | 19 | import { afterCommitIfTransaction } from '@server/helpers/database-utils' |
20 | import { MThumbnail, MThumbnailVideo, MVideoAccountLight } from '@server/types/models' | 20 | import { MThumbnail, MThumbnailVideo, MVideoWithHost } from '@server/types/models' |
21 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' | 21 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' |
22 | import { logger } from '../../helpers/logger' | 22 | import { logger } from '../../helpers/logger' |
23 | import { CONFIG } from '../../initializers/config' | 23 | import { CONFIG } from '../../initializers/config' |
@@ -164,7 +164,7 @@ export class ThumbnailModel extends Model { | |||
164 | return join(directory, filename) | 164 | return join(directory, filename) |
165 | } | 165 | } |
166 | 166 | ||
167 | getFileUrl (video: MVideoAccountLight) { | 167 | getFileUrl (video: MVideoWithHost) { |
168 | const staticPath = ThumbnailModel.types[this.type].staticPath + this.filename | 168 | const staticPath = ThumbnailModel.types[this.type].staticPath + this.filename |
169 | 169 | ||
170 | if (video.isOwned()) return WEBSERVER.URL + staticPath | 170 | if (video.isOwned()) return WEBSERVER.URL + staticPath |
diff --git a/server/models/video/video-caption.ts b/server/models/video/video-caption.ts index a1553ea15..71b067335 100644 --- a/server/models/video/video-caption.ts +++ b/server/models/video/video-caption.ts | |||
@@ -15,8 +15,9 @@ import { | |||
15 | Table, | 15 | Table, |
16 | UpdatedAt | 16 | UpdatedAt |
17 | } from 'sequelize-typescript' | 17 | } from 'sequelize-typescript' |
18 | import { v4 as uuidv4 } from 'uuid' | ||
18 | import { buildRemoteVideoBaseUrl } from '@server/helpers/activitypub' | 19 | import { buildRemoteVideoBaseUrl } from '@server/helpers/activitypub' |
19 | import { MVideoAccountLight, MVideoCaption, MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/types/models' | 20 | import { MVideoCaption, MVideoCaptionFormattable, MVideoCaptionVideo, MVideoWithHost } from '@server/types/models' |
20 | import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model' | 21 | import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model' |
21 | import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions' | 22 | import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions' |
22 | import { logger } from '../../helpers/logger' | 23 | import { logger } from '../../helpers/logger' |
@@ -24,7 +25,6 @@ import { CONFIG } from '../../initializers/config' | |||
24 | import { CONSTRAINTS_FIELDS, LAZY_STATIC_PATHS, VIDEO_LANGUAGES, WEBSERVER } from '../../initializers/constants' | 25 | import { CONSTRAINTS_FIELDS, LAZY_STATIC_PATHS, VIDEO_LANGUAGES, WEBSERVER } from '../../initializers/constants' |
25 | import { buildWhereIdOrUUID, throwIfNotValid } from '../utils' | 26 | import { buildWhereIdOrUUID, throwIfNotValid } from '../utils' |
26 | import { VideoModel } from './video' | 27 | import { VideoModel } from './video' |
27 | import { v4 as uuidv4 } from 'uuid' | ||
28 | 28 | ||
29 | export enum ScopeNames { | 29 | export enum ScopeNames { |
30 | WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE' | 30 | WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE' |
@@ -204,7 +204,7 @@ export class VideoCaptionModel extends Model { | |||
204 | return remove(CONFIG.STORAGE.CAPTIONS_DIR + this.filename) | 204 | return remove(CONFIG.STORAGE.CAPTIONS_DIR + this.filename) |
205 | } | 205 | } |
206 | 206 | ||
207 | getFileUrl (video: MVideoAccountLight) { | 207 | getFileUrl (video: MVideoWithHost) { |
208 | if (!this.Video) this.Video = video as VideoModel | 208 | if (!this.Video) this.Video = video as VideoModel |
209 | 209 | ||
210 | if (video.isOwned()) return WEBSERVER.URL + this.getCaptionStaticPath() | 210 | if (video.isOwned()) return WEBSERVER.URL + this.getCaptionStaticPath() |
diff --git a/server/models/video/video-file.ts b/server/models/video/video-file.ts index 48b337c68..57807cbfd 100644 --- a/server/models/video/video-file.ts +++ b/server/models/video/video-file.ts | |||
@@ -1,3 +1,7 @@ | |||
1 | import { remove } from 'fs-extra' | ||
2 | import * as memoizee from 'memoizee' | ||
3 | import { join } from 'path' | ||
4 | import { FindOptions, Op, QueryTypes, Transaction } from 'sequelize' | ||
1 | import { | 5 | import { |
2 | AllowNull, | 6 | AllowNull, |
3 | BelongsTo, | 7 | BelongsTo, |
@@ -5,15 +9,22 @@ import { | |||
5 | CreatedAt, | 9 | CreatedAt, |
6 | DataType, | 10 | DataType, |
7 | Default, | 11 | Default, |
12 | DefaultScope, | ||
8 | ForeignKey, | 13 | ForeignKey, |
9 | HasMany, | 14 | HasMany, |
10 | Is, | 15 | Is, |
11 | Model, | 16 | Model, |
12 | Table, | ||
13 | UpdatedAt, | ||
14 | Scopes, | 17 | Scopes, |
15 | DefaultScope | 18 | Table, |
19 | UpdatedAt | ||
16 | } from 'sequelize-typescript' | 20 | } from 'sequelize-typescript' |
21 | import { Where } from 'sequelize/types/lib/utils' | ||
22 | import validator from 'validator' | ||
23 | import { buildRemoteVideoBaseUrl } from '@server/helpers/activitypub' | ||
24 | import { logger } from '@server/helpers/logger' | ||
25 | import { extractVideo } from '@server/helpers/video' | ||
26 | import { getTorrentFilePath } from '@server/lib/video-paths' | ||
27 | import { MStreamingPlaylistVideo, MVideo, MVideoWithHost } from '@server/types/models' | ||
17 | import { | 28 | import { |
18 | isVideoFileExtnameValid, | 29 | isVideoFileExtnameValid, |
19 | isVideoFileInfoHashValid, | 30 | isVideoFileInfoHashValid, |
@@ -21,20 +32,25 @@ import { | |||
21 | isVideoFileSizeValid, | 32 | isVideoFileSizeValid, |
22 | isVideoFPSResolutionValid | 33 | isVideoFPSResolutionValid |
23 | } from '../../helpers/custom-validators/videos' | 34 | } from '../../helpers/custom-validators/videos' |
35 | import { | ||
36 | LAZY_STATIC_PATHS, | ||
37 | MEMOIZE_LENGTH, | ||
38 | MEMOIZE_TTL, | ||
39 | MIMETYPES, | ||
40 | STATIC_DOWNLOAD_PATHS, | ||
41 | STATIC_PATHS, | ||
42 | WEBSERVER | ||
43 | } from '../../initializers/constants' | ||
44 | import { MVideoFile, MVideoFileStreamingPlaylistVideo, MVideoFileVideo } from '../../types/models/video/video-file' | ||
45 | import { VideoRedundancyModel } from '../redundancy/video-redundancy' | ||
24 | import { parseAggregateResult, throwIfNotValid } from '../utils' | 46 | import { parseAggregateResult, throwIfNotValid } from '../utils' |
25 | import { VideoModel } from './video' | 47 | import { VideoModel } from './video' |
26 | import { VideoRedundancyModel } from '../redundancy/video-redundancy' | ||
27 | import { VideoStreamingPlaylistModel } from './video-streaming-playlist' | 48 | import { VideoStreamingPlaylistModel } from './video-streaming-playlist' |
28 | import { FindOptions, Op, QueryTypes, Transaction } from 'sequelize' | ||
29 | import { MIMETYPES, MEMOIZE_LENGTH, MEMOIZE_TTL } from '../../initializers/constants' | ||
30 | import { MVideoFile, MVideoFileStreamingPlaylistVideo, MVideoFileVideo } from '../../types/models/video/video-file' | ||
31 | import { MStreamingPlaylistVideo, MVideo } from '@server/types/models' | ||
32 | import * as memoizee from 'memoizee' | ||
33 | import validator from 'validator' | ||
34 | 49 | ||
35 | export enum ScopeNames { | 50 | export enum ScopeNames { |
36 | WITH_VIDEO = 'WITH_VIDEO', | 51 | WITH_VIDEO = 'WITH_VIDEO', |
37 | WITH_METADATA = 'WITH_METADATA' | 52 | WITH_METADATA = 'WITH_METADATA', |
53 | WITH_VIDEO_OR_PLAYLIST = 'WITH_VIDEO_OR_PLAYLIST' | ||
38 | } | 54 | } |
39 | 55 | ||
40 | @DefaultScope(() => ({ | 56 | @DefaultScope(() => ({ |
@@ -51,6 +67,28 @@ export enum ScopeNames { | |||
51 | } | 67 | } |
52 | ] | 68 | ] |
53 | }, | 69 | }, |
70 | [ScopeNames.WITH_VIDEO_OR_PLAYLIST]: (options: { whereVideo?: Where } = {}) => { | ||
71 | return { | ||
72 | include: [ | ||
73 | { | ||
74 | model: VideoModel.unscoped(), | ||
75 | required: false, | ||
76 | where: options.whereVideo | ||
77 | }, | ||
78 | { | ||
79 | model: VideoStreamingPlaylistModel.unscoped(), | ||
80 | required: false, | ||
81 | include: [ | ||
82 | { | ||
83 | model: VideoModel.unscoped(), | ||
84 | required: true, | ||
85 | where: options.whereVideo | ||
86 | } | ||
87 | ] | ||
88 | } | ||
89 | ] | ||
90 | } | ||
91 | }, | ||
54 | [ScopeNames.WITH_METADATA]: { | 92 | [ScopeNames.WITH_METADATA]: { |
55 | attributes: { | 93 | attributes: { |
56 | include: [ 'metadata' ] | 94 | include: [ 'metadata' ] |
@@ -82,6 +120,16 @@ export enum ScopeNames { | |||
82 | }, | 120 | }, |
83 | 121 | ||
84 | { | 122 | { |
123 | fields: [ 'torrentFilename' ], | ||
124 | unique: true | ||
125 | }, | ||
126 | |||
127 | { | ||
128 | fields: [ 'filename' ], | ||
129 | unique: true | ||
130 | }, | ||
131 | |||
132 | { | ||
85 | fields: [ 'videoId', 'resolution', 'fps' ], | 133 | fields: [ 'videoId', 'resolution', 'fps' ], |
86 | unique: true, | 134 | unique: true, |
87 | where: { | 135 | where: { |
@@ -142,6 +190,24 @@ export class VideoFileModel extends Model { | |||
142 | @Column | 190 | @Column |
143 | metadataUrl: string | 191 | metadataUrl: string |
144 | 192 | ||
193 | @AllowNull(true) | ||
194 | @Column | ||
195 | fileUrl: string | ||
196 | |||
197 | // Could be null for live files | ||
198 | @AllowNull(true) | ||
199 | @Column | ||
200 | filename: string | ||
201 | |||
202 | @AllowNull(true) | ||
203 | @Column | ||
204 | torrentUrl: string | ||
205 | |||
206 | // Could be null for live files | ||
207 | @AllowNull(true) | ||
208 | @Column | ||
209 | torrentFilename: string | ||
210 | |||
145 | @ForeignKey(() => VideoModel) | 211 | @ForeignKey(() => VideoModel) |
146 | @Column | 212 | @Column |
147 | videoId: number | 213 | videoId: number |
@@ -199,6 +265,16 @@ export class VideoFileModel extends Model { | |||
199 | return !!videoFile | 265 | return !!videoFile |
200 | } | 266 | } |
201 | 267 | ||
268 | static loadWithVideoOrPlaylistByTorrentFilename (filename: string) { | ||
269 | const query = { | ||
270 | where: { | ||
271 | torrentFilename: filename | ||
272 | } | ||
273 | } | ||
274 | |||
275 | return VideoFileModel.scope(ScopeNames.WITH_VIDEO_OR_PLAYLIST).findOne(query) | ||
276 | } | ||
277 | |||
202 | static loadWithMetadata (id: number) { | 278 | static loadWithMetadata (id: number) { |
203 | return VideoFileModel.scope(ScopeNames.WITH_METADATA).findByPk(id) | 279 | return VideoFileModel.scope(ScopeNames.WITH_METADATA).findByPk(id) |
204 | } | 280 | } |
@@ -215,28 +291,11 @@ export class VideoFileModel extends Model { | |||
215 | const options = { | 291 | const options = { |
216 | where: { | 292 | where: { |
217 | id | 293 | id |
218 | }, | 294 | } |
219 | include: [ | ||
220 | { | ||
221 | model: VideoModel.unscoped(), | ||
222 | required: false, | ||
223 | where: whereVideo | ||
224 | }, | ||
225 | { | ||
226 | model: VideoStreamingPlaylistModel.unscoped(), | ||
227 | required: false, | ||
228 | include: [ | ||
229 | { | ||
230 | model: VideoModel.unscoped(), | ||
231 | required: true, | ||
232 | where: whereVideo | ||
233 | } | ||
234 | ] | ||
235 | } | ||
236 | ] | ||
237 | } | 295 | } |
238 | 296 | ||
239 | return VideoFileModel.findOne(options) | 297 | return VideoFileModel.scope({ method: [ ScopeNames.WITH_VIDEO_OR_PLAYLIST, whereVideo ] }) |
298 | .findOne(options) | ||
240 | .then(file => { | 299 | .then(file => { |
241 | // We used `required: false` so check we have at least a video or a streaming playlist | 300 | // We used `required: false` so check we have at least a video or a streaming playlist |
242 | if (!file.Video && !file.VideoStreamingPlaylist) return null | 301 | if (!file.Video && !file.VideoStreamingPlaylist) return null |
@@ -348,6 +407,10 @@ export class VideoFileModel extends Model { | |||
348 | return (this as MVideoFileStreamingPlaylistVideo).VideoStreamingPlaylist | 407 | return (this as MVideoFileStreamingPlaylistVideo).VideoStreamingPlaylist |
349 | } | 408 | } |
350 | 409 | ||
410 | getVideo (this: MVideoFileVideo | MVideoFileStreamingPlaylistVideo): MVideo { | ||
411 | return extractVideo(this.getVideoOrStreamingPlaylist()) | ||
412 | } | ||
413 | |||
351 | isAudio () { | 414 | isAudio () { |
352 | return !!MIMETYPES.AUDIO.EXT_MIMETYPE[this.extname] | 415 | return !!MIMETYPES.AUDIO.EXT_MIMETYPE[this.extname] |
353 | } | 416 | } |
@@ -360,6 +423,62 @@ export class VideoFileModel extends Model { | |||
360 | return !!this.videoStreamingPlaylistId | 423 | return !!this.videoStreamingPlaylistId |
361 | } | 424 | } |
362 | 425 | ||
426 | getFileUrl (video: MVideoWithHost) { | ||
427 | if (!this.Video) this.Video = video as VideoModel | ||
428 | |||
429 | if (video.isOwned()) return WEBSERVER.URL + this.getFileStaticPath(video) | ||
430 | if (this.fileUrl) return this.fileUrl | ||
431 | |||
432 | // Fallback if we don't have a file URL | ||
433 | return buildRemoteVideoBaseUrl(video, this.getFileStaticPath(video)) | ||
434 | } | ||
435 | |||
436 | getFileStaticPath (video: MVideo) { | ||
437 | if (this.isHLS()) return join(STATIC_PATHS.STREAMING_PLAYLISTS.HLS, video.uuid, this.filename) | ||
438 | |||
439 | return join(STATIC_PATHS.WEBSEED, this.filename) | ||
440 | } | ||
441 | |||
442 | getFileDownloadUrl (video: MVideoWithHost) { | ||
443 | const basePath = this.isHLS() | ||
444 | ? STATIC_DOWNLOAD_PATHS.HLS_VIDEOS | ||
445 | : STATIC_DOWNLOAD_PATHS.VIDEOS | ||
446 | const path = join(basePath, this.filename) | ||
447 | |||
448 | if (video.isOwned()) return WEBSERVER.URL + path | ||
449 | |||
450 | // FIXME: don't guess remote URL | ||
451 | return buildRemoteVideoBaseUrl(video, path) | ||
452 | } | ||
453 | |||
454 | getRemoteTorrentUrl (video: MVideoWithHost) { | ||
455 | if (video.isOwned()) throw new Error(`Video ${video.url} is not a remote video`) | ||
456 | |||
457 | if (this.torrentUrl) return this.torrentUrl | ||
458 | |||
459 | // Fallback if we don't have a torrent URL | ||
460 | return buildRemoteVideoBaseUrl(video, this.getTorrentStaticPath()) | ||
461 | } | ||
462 | |||
463 | // We proxify torrent requests so use a local URL | ||
464 | getTorrentUrl () { | ||
465 | return WEBSERVER.URL + this.getTorrentStaticPath() | ||
466 | } | ||
467 | |||
468 | getTorrentStaticPath () { | ||
469 | return join(LAZY_STATIC_PATHS.TORRENTS, this.torrentFilename) | ||
470 | } | ||
471 | |||
472 | getTorrentDownloadUrl () { | ||
473 | return WEBSERVER.URL + join(STATIC_DOWNLOAD_PATHS.TORRENTS, this.torrentFilename) | ||
474 | } | ||
475 | |||
476 | removeTorrent () { | ||
477 | const torrentPath = getTorrentFilePath(this) | ||
478 | return remove(torrentPath) | ||
479 | .catch(err => logger.warn('Cannot delete torrent %s.', torrentPath, { err })) | ||
480 | } | ||
481 | |||
363 | hasSameUniqueKeysThan (other: MVideoFile) { | 482 | hasSameUniqueKeysThan (other: MVideoFile) { |
364 | return this.fps === other.fps && | 483 | return this.fps === other.fps && |
365 | this.resolution === other.resolution && | 484 | this.resolution === other.resolution && |
diff --git a/server/models/video/video-format-utils.ts b/server/models/video/video-format-utils.ts index 77b8bcfe3..adf460734 100644 --- a/server/models/video/video-format-utils.ts +++ b/server/models/video/video-format-utils.ts | |||
@@ -1,16 +1,17 @@ | |||
1 | import { Video, VideoDetails } from '../../../shared/models/videos' | 1 | import { generateMagnetUri } from '@server/helpers/webtorrent' |
2 | import { VideoModel } from './video' | 2 | import { getLocalVideoFileMetadataUrl } from '@server/lib/video-paths' |
3 | import { VideoFile } from '@shared/models/videos/video-file.model' | ||
3 | import { ActivityTagObject, ActivityUrlObject, VideoObject } from '../../../shared/models/activitypub/objects' | 4 | import { ActivityTagObject, ActivityUrlObject, VideoObject } from '../../../shared/models/activitypub/objects' |
5 | import { Video, VideoDetails } from '../../../shared/models/videos' | ||
6 | import { VideoStreamingPlaylist } from '../../../shared/models/videos/video-streaming-playlist.model' | ||
7 | import { isArray } from '../../helpers/custom-validators/misc' | ||
4 | import { MIMETYPES, WEBSERVER } from '../../initializers/constants' | 8 | import { MIMETYPES, WEBSERVER } from '../../initializers/constants' |
5 | import { VideoCaptionModel } from './video-caption' | ||
6 | import { | 9 | import { |
7 | getLocalVideoCommentsActivityPubUrl, | 10 | getLocalVideoCommentsActivityPubUrl, |
8 | getLocalVideoDislikesActivityPubUrl, | 11 | getLocalVideoDislikesActivityPubUrl, |
9 | getLocalVideoLikesActivityPubUrl, | 12 | getLocalVideoLikesActivityPubUrl, |
10 | getLocalVideoSharesActivityPubUrl | 13 | getLocalVideoSharesActivityPubUrl |
11 | } from '../../lib/activitypub/url' | 14 | } from '../../lib/activitypub/url' |
12 | import { isArray } from '../../helpers/custom-validators/misc' | ||
13 | import { VideoStreamingPlaylist } from '../../../shared/models/videos/video-streaming-playlist.model' | ||
14 | import { | 15 | import { |
15 | MStreamingPlaylistRedundanciesOpt, | 16 | MStreamingPlaylistRedundanciesOpt, |
16 | MStreamingPlaylistVideo, | 17 | MStreamingPlaylistVideo, |
@@ -18,12 +19,12 @@ import { | |||
18 | MVideoAP, | 19 | MVideoAP, |
19 | MVideoFile, | 20 | MVideoFile, |
20 | MVideoFormattable, | 21 | MVideoFormattable, |
21 | MVideoFormattableDetails | 22 | MVideoFormattableDetails, |
23 | MVideoWithHost | ||
22 | } from '../../types/models' | 24 | } from '../../types/models' |
23 | import { MVideoFileRedundanciesOpt } from '../../types/models/video/video-file' | 25 | import { MVideoFileRedundanciesOpt } from '../../types/models/video/video-file' |
24 | import { VideoFile } from '@shared/models/videos/video-file.model' | 26 | import { VideoModel } from './video' |
25 | import { generateMagnetUri } from '@server/helpers/webtorrent' | 27 | import { VideoCaptionModel } from './video-caption' |
26 | import { extractVideo } from '@server/helpers/video' | ||
27 | 28 | ||
28 | export type VideoFormattingJSONOptions = { | 29 | export type VideoFormattingJSONOptions = { |
29 | completeDescription?: boolean | 30 | completeDescription?: boolean |
@@ -153,12 +154,15 @@ function videoModelToFormattedDetailsJSON (video: MVideoFormattableDetails): Vid | |||
153 | } | 154 | } |
154 | 155 | ||
155 | // Format and sort video files | 156 | // Format and sort video files |
156 | detailsJson.files = videoFilesModelToFormattedJSON(video, baseUrlHttp, baseUrlWs, video.VideoFiles) | 157 | detailsJson.files = videoFilesModelToFormattedJSON(video, video, baseUrlHttp, baseUrlWs, video.VideoFiles) |
157 | 158 | ||
158 | return Object.assign(formattedJson, detailsJson) | 159 | return Object.assign(formattedJson, detailsJson) |
159 | } | 160 | } |
160 | 161 | ||
161 | function streamingPlaylistsModelToFormattedJSON (video: MVideo, playlists: MStreamingPlaylistRedundanciesOpt[]): VideoStreamingPlaylist[] { | 162 | function streamingPlaylistsModelToFormattedJSON ( |
163 | video: MVideoFormattableDetails, | ||
164 | playlists: MStreamingPlaylistRedundanciesOpt[] | ||
165 | ): VideoStreamingPlaylist[] { | ||
162 | if (isArray(playlists) === false) return [] | 166 | if (isArray(playlists) === false) return [] |
163 | 167 | ||
164 | const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() | 168 | const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() |
@@ -171,7 +175,7 @@ function streamingPlaylistsModelToFormattedJSON (video: MVideo, playlists: MStre | |||
171 | ? playlist.RedundancyVideos.map(r => ({ baseUrl: r.fileUrl })) | 175 | ? playlist.RedundancyVideos.map(r => ({ baseUrl: r.fileUrl })) |
172 | : [] | 176 | : [] |
173 | 177 | ||
174 | const files = videoFilesModelToFormattedJSON(playlistWithVideo, baseUrlHttp, baseUrlWs, playlist.VideoFiles) | 178 | const files = videoFilesModelToFormattedJSON(playlistWithVideo, video, baseUrlHttp, baseUrlWs, playlist.VideoFiles) |
175 | 179 | ||
176 | return { | 180 | return { |
177 | id: playlist.id, | 181 | id: playlist.id, |
@@ -190,14 +194,14 @@ function sortByResolutionDesc (fileA: MVideoFile, fileB: MVideoFile) { | |||
190 | return -1 | 194 | return -1 |
191 | } | 195 | } |
192 | 196 | ||
197 | // FIXME: refactor/merge model and video arguments | ||
193 | function videoFilesModelToFormattedJSON ( | 198 | function videoFilesModelToFormattedJSON ( |
194 | model: MVideo | MStreamingPlaylistVideo, | 199 | model: MVideo | MStreamingPlaylistVideo, |
200 | video: MVideoFormattableDetails, | ||
195 | baseUrlHttp: string, | 201 | baseUrlHttp: string, |
196 | baseUrlWs: string, | 202 | baseUrlWs: string, |
197 | videoFiles: MVideoFileRedundanciesOpt[] | 203 | videoFiles: MVideoFileRedundanciesOpt[] |
198 | ): VideoFile[] { | 204 | ): VideoFile[] { |
199 | const video = extractVideo(model) | ||
200 | |||
201 | return [ ...videoFiles ] | 205 | return [ ...videoFiles ] |
202 | .filter(f => !f.isLive()) | 206 | .filter(f => !f.isLive()) |
203 | .sort(sortByResolutionDesc) | 207 | .sort(sortByResolutionDesc) |
@@ -207,21 +211,29 @@ function videoFilesModelToFormattedJSON ( | |||
207 | id: videoFile.resolution, | 211 | id: videoFile.resolution, |
208 | label: videoFile.resolution + 'p' | 212 | label: videoFile.resolution + 'p' |
209 | }, | 213 | }, |
210 | magnetUri: generateMagnetUri(model, videoFile, baseUrlHttp, baseUrlWs), | 214 | |
215 | // FIXME: deprecated in 3.2 | ||
216 | magnetUri: generateMagnetUri(model, video, videoFile, baseUrlHttp, baseUrlWs), | ||
217 | |||
211 | size: videoFile.size, | 218 | size: videoFile.size, |
212 | fps: videoFile.fps, | 219 | fps: videoFile.fps, |
213 | torrentUrl: model.getTorrentUrl(videoFile, baseUrlHttp), | 220 | |
214 | torrentDownloadUrl: model.getTorrentDownloadUrl(videoFile, baseUrlHttp), | 221 | torrentUrl: videoFile.getTorrentUrl(), |
215 | fileUrl: model.getVideoFileUrl(videoFile, baseUrlHttp), | 222 | torrentDownloadUrl: videoFile.getTorrentDownloadUrl(), |
216 | fileDownloadUrl: model.getVideoFileDownloadUrl(videoFile, baseUrlHttp), | 223 | |
217 | metadataUrl: video.getVideoFileMetadataUrl(videoFile, baseUrlHttp) | 224 | fileUrl: videoFile.getFileUrl(video), |
225 | fileDownloadUrl: videoFile.getFileDownloadUrl(video), | ||
226 | |||
227 | metadataUrl: videoFile.metadataUrl ?? getLocalVideoFileMetadataUrl(video, videoFile) | ||
218 | } as VideoFile | 228 | } as VideoFile |
219 | }) | 229 | }) |
220 | } | 230 | } |
221 | 231 | ||
232 | // FIXME: refactor/merge model and video arguments | ||
222 | function addVideoFilesInAPAcc ( | 233 | function addVideoFilesInAPAcc ( |
223 | acc: ActivityUrlObject[] | ActivityTagObject[], | 234 | acc: ActivityUrlObject[] | ActivityTagObject[], |
224 | model: MVideoAP | MStreamingPlaylistVideo, | 235 | model: MVideoAP | MStreamingPlaylistVideo, |
236 | video: MVideoWithHost, | ||
225 | baseUrlHttp: string, | 237 | baseUrlHttp: string, |
226 | baseUrlWs: string, | 238 | baseUrlWs: string, |
227 | files: MVideoFile[] | 239 | files: MVideoFile[] |
@@ -234,7 +246,7 @@ function addVideoFilesInAPAcc ( | |||
234 | acc.push({ | 246 | acc.push({ |
235 | type: 'Link', | 247 | type: 'Link', |
236 | mediaType: MIMETYPES.VIDEO.EXT_MIMETYPE[file.extname] as any, | 248 | mediaType: MIMETYPES.VIDEO.EXT_MIMETYPE[file.extname] as any, |
237 | href: model.getVideoFileUrl(file, baseUrlHttp), | 249 | href: file.getFileUrl(video), |
238 | height: file.resolution, | 250 | height: file.resolution, |
239 | size: file.size, | 251 | size: file.size, |
240 | fps: file.fps | 252 | fps: file.fps |
@@ -244,7 +256,7 @@ function addVideoFilesInAPAcc ( | |||
244 | type: 'Link', | 256 | type: 'Link', |
245 | rel: [ 'metadata', MIMETYPES.VIDEO.EXT_MIMETYPE[file.extname] ], | 257 | rel: [ 'metadata', MIMETYPES.VIDEO.EXT_MIMETYPE[file.extname] ], |
246 | mediaType: 'application/json' as 'application/json', | 258 | mediaType: 'application/json' as 'application/json', |
247 | href: extractVideo(model).getVideoFileMetadataUrl(file, baseUrlHttp), | 259 | href: getLocalVideoFileMetadataUrl(video, file), |
248 | height: file.resolution, | 260 | height: file.resolution, |
249 | fps: file.fps | 261 | fps: file.fps |
250 | }) | 262 | }) |
@@ -252,14 +264,14 @@ function addVideoFilesInAPAcc ( | |||
252 | acc.push({ | 264 | acc.push({ |
253 | type: 'Link', | 265 | type: 'Link', |
254 | mediaType: 'application/x-bittorrent' as 'application/x-bittorrent', | 266 | mediaType: 'application/x-bittorrent' as 'application/x-bittorrent', |
255 | href: model.getTorrentUrl(file, baseUrlHttp), | 267 | href: file.getTorrentUrl(), |
256 | height: file.resolution | 268 | height: file.resolution |
257 | }) | 269 | }) |
258 | 270 | ||
259 | acc.push({ | 271 | acc.push({ |
260 | type: 'Link', | 272 | type: 'Link', |
261 | mediaType: 'application/x-bittorrent;x-scheme-handler/magnet' as 'application/x-bittorrent;x-scheme-handler/magnet', | 273 | mediaType: 'application/x-bittorrent;x-scheme-handler/magnet' as 'application/x-bittorrent;x-scheme-handler/magnet', |
262 | href: generateMagnetUri(model, file, baseUrlHttp, baseUrlWs), | 274 | href: generateMagnetUri(model, video, file, baseUrlHttp, baseUrlWs), |
263 | height: file.resolution | 275 | height: file.resolution |
264 | }) | 276 | }) |
265 | } | 277 | } |
@@ -307,7 +319,7 @@ function videoModelToActivityPubObject (video: MVideoAP): VideoObject { | |||
307 | } | 319 | } |
308 | ] | 320 | ] |
309 | 321 | ||
310 | addVideoFilesInAPAcc(url, video, baseUrlHttp, baseUrlWs, video.VideoFiles || []) | 322 | addVideoFilesInAPAcc(url, video, video, baseUrlHttp, baseUrlWs, video.VideoFiles || []) |
311 | 323 | ||
312 | for (const playlist of (video.VideoStreamingPlaylists || [])) { | 324 | for (const playlist of (video.VideoStreamingPlaylists || [])) { |
313 | const tag = playlist.p2pMediaLoaderInfohashes | 325 | const tag = playlist.p2pMediaLoaderInfohashes |
@@ -320,7 +332,7 @@ function videoModelToActivityPubObject (video: MVideoAP): VideoObject { | |||
320 | }) | 332 | }) |
321 | 333 | ||
322 | const playlistWithVideo = Object.assign(playlist, { Video: video }) | 334 | const playlistWithVideo = Object.assign(playlist, { Video: video }) |
323 | addVideoFilesInAPAcc(tag, playlistWithVideo, baseUrlHttp, baseUrlWs, playlist.VideoFiles || []) | 335 | addVideoFilesInAPAcc(tag, playlistWithVideo, video, baseUrlHttp, baseUrlWs, playlist.VideoFiles || []) |
324 | 336 | ||
325 | url.push({ | 337 | url.push({ |
326 | type: 'Link', | 338 | type: 'Link', |
diff --git a/server/models/video/video-query-builder.ts b/server/models/video/video-query-builder.ts index 822d0c89b..af1878e7a 100644 --- a/server/models/video/video-query-builder.ts +++ b/server/models/video/video-query-builder.ts | |||
@@ -516,6 +516,10 @@ function wrapForAPIResults (baseQuery: string, replacements: any, options: Build | |||
516 | '"VideoFiles"."resolution"': '"VideoFiles.resolution"', | 516 | '"VideoFiles"."resolution"': '"VideoFiles.resolution"', |
517 | '"VideoFiles"."size"': '"VideoFiles.size"', | 517 | '"VideoFiles"."size"': '"VideoFiles.size"', |
518 | '"VideoFiles"."extname"': '"VideoFiles.extname"', | 518 | '"VideoFiles"."extname"': '"VideoFiles.extname"', |
519 | '"VideoFiles"."filename"': '"VideoFiles.filename"', | ||
520 | '"VideoFiles"."fileUrl"': '"VideoFiles.fileUrl"', | ||
521 | '"VideoFiles"."torrentFilename"': '"VideoFiles.torrentFilename"', | ||
522 | '"VideoFiles"."torrentUrl"': '"VideoFiles.torrentUrl"', | ||
519 | '"VideoFiles"."infoHash"': '"VideoFiles.infoHash"', | 523 | '"VideoFiles"."infoHash"': '"VideoFiles.infoHash"', |
520 | '"VideoFiles"."fps"': '"VideoFiles.fps"', | 524 | '"VideoFiles"."fps"': '"VideoFiles.fps"', |
521 | '"VideoFiles"."videoId"': '"VideoFiles.videoId"', | 525 | '"VideoFiles"."videoId"': '"VideoFiles.videoId"', |
@@ -529,6 +533,10 @@ function wrapForAPIResults (baseQuery: string, replacements: any, options: Build | |||
529 | '"VideoStreamingPlaylists->VideoFiles"."resolution"': '"VideoStreamingPlaylists.VideoFiles.resolution"', | 533 | '"VideoStreamingPlaylists->VideoFiles"."resolution"': '"VideoStreamingPlaylists.VideoFiles.resolution"', |
530 | '"VideoStreamingPlaylists->VideoFiles"."size"': '"VideoStreamingPlaylists.VideoFiles.size"', | 534 | '"VideoStreamingPlaylists->VideoFiles"."size"': '"VideoStreamingPlaylists.VideoFiles.size"', |
531 | '"VideoStreamingPlaylists->VideoFiles"."extname"': '"VideoStreamingPlaylists.VideoFiles.extname"', | 535 | '"VideoStreamingPlaylists->VideoFiles"."extname"': '"VideoStreamingPlaylists.VideoFiles.extname"', |
536 | '"VideoStreamingPlaylists->VideoFiles"."filename"': '"VideoStreamingPlaylists.VideoFiles.filename"', | ||
537 | '"VideoStreamingPlaylists->VideoFiles"."fileUrl"': '"VideoStreamingPlaylists.VideoFiles.fileUrl"', | ||
538 | '"VideoStreamingPlaylists->VideoFiles"."torrentFilename"': '"VideoStreamingPlaylists.VideoFiles.torrentFilename"', | ||
539 | '"VideoStreamingPlaylists->VideoFiles"."torrentUrl"': '"VideoStreamingPlaylists.VideoFiles.torrentUrl"', | ||
532 | '"VideoStreamingPlaylists->VideoFiles"."infoHash"': '"VideoStreamingPlaylists.VideoFiles.infoHash"', | 540 | '"VideoStreamingPlaylists->VideoFiles"."infoHash"': '"VideoStreamingPlaylists.VideoFiles.infoHash"', |
533 | '"VideoStreamingPlaylists->VideoFiles"."fps"': '"VideoStreamingPlaylists.VideoFiles.fps"', | 541 | '"VideoStreamingPlaylists->VideoFiles"."fps"': '"VideoStreamingPlaylists.VideoFiles.fps"', |
534 | '"VideoStreamingPlaylists->VideoFiles"."videoStreamingPlaylistId"': '"VideoStreamingPlaylists.VideoFiles.videoStreamingPlaylistId"', | 542 | '"VideoStreamingPlaylists->VideoFiles"."videoStreamingPlaylistId"': '"VideoStreamingPlaylists.VideoFiles.videoStreamingPlaylistId"', |
diff --git a/server/models/video/video-streaming-playlist.ts b/server/models/video/video-streaming-playlist.ts index 148768c21..c9375b433 100644 --- a/server/models/video/video-streaming-playlist.ts +++ b/server/models/video/video-streaming-playlist.ts | |||
@@ -1,28 +1,18 @@ | |||
1 | import * as memoizee from 'memoizee' | ||
2 | import { join } from 'path' | ||
3 | import { Op, QueryTypes } from 'sequelize' | ||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, HasMany, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' | 4 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, HasMany, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' |
2 | import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos' | 5 | import { VideoFileModel } from '@server/models/video/video-file' |
3 | import { throwIfNotValid } from '../utils' | 6 | import { MStreamingPlaylist } from '@server/types/models' |
4 | import { VideoModel } from './video' | ||
5 | import { VideoRedundancyModel } from '../redundancy/video-redundancy' | ||
6 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' | 7 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' |
7 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | ||
8 | import { | ||
9 | CONSTRAINTS_FIELDS, | ||
10 | MEMOIZE_LENGTH, | ||
11 | MEMOIZE_TTL, | ||
12 | P2P_MEDIA_LOADER_PEER_VERSION, | ||
13 | STATIC_DOWNLOAD_PATHS, | ||
14 | STATIC_PATHS | ||
15 | } from '../../initializers/constants' | ||
16 | import { join } from 'path' | ||
17 | import { sha1 } from '../../helpers/core-utils' | 8 | import { sha1 } from '../../helpers/core-utils' |
9 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | ||
18 | import { isArrayOf } from '../../helpers/custom-validators/misc' | 10 | import { isArrayOf } from '../../helpers/custom-validators/misc' |
19 | import { Op, QueryTypes } from 'sequelize' | 11 | import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos' |
20 | import { MStreamingPlaylist, MStreamingPlaylistVideo, MVideoFile } from '@server/types/models' | 12 | import { CONSTRAINTS_FIELDS, MEMOIZE_LENGTH, MEMOIZE_TTL, P2P_MEDIA_LOADER_PEER_VERSION, STATIC_PATHS } from '../../initializers/constants' |
21 | import { VideoFileModel } from '@server/models/video/video-file' | 13 | import { VideoRedundancyModel } from '../redundancy/video-redundancy' |
22 | import { getTorrentFileName, getTorrentFilePath, getVideoFilename } from '@server/lib/video-paths' | 14 | import { throwIfNotValid } from '../utils' |
23 | import * as memoizee from 'memoizee' | 15 | import { VideoModel } from './video' |
24 | import { remove } from 'fs-extra' | ||
25 | import { logger } from '@server/helpers/logger' | ||
26 | 16 | ||
27 | @Table({ | 17 | @Table({ |
28 | tableName: 'videoStreamingPlaylist', | 18 | tableName: 'videoStreamingPlaylist', |
@@ -196,26 +186,6 @@ export class VideoStreamingPlaylistModel extends Model { | |||
196 | return 'unknown' | 186 | return 'unknown' |
197 | } | 187 | } |
198 | 188 | ||
199 | getVideoRedundancyUrl (baseUrlHttp: string) { | ||
200 | return baseUrlHttp + STATIC_PATHS.REDUNDANCY + this.getStringType() + '/' + this.Video.uuid | ||
201 | } | ||
202 | |||
203 | getTorrentDownloadUrl (videoFile: MVideoFile, baseUrlHttp: string) { | ||
204 | return baseUrlHttp + STATIC_DOWNLOAD_PATHS.TORRENTS + getTorrentFileName(this, videoFile) | ||
205 | } | ||
206 | |||
207 | getVideoFileDownloadUrl (videoFile: MVideoFile, baseUrlHttp: string) { | ||
208 | return baseUrlHttp + STATIC_DOWNLOAD_PATHS.HLS_VIDEOS + getVideoFilename(this, videoFile) | ||
209 | } | ||
210 | |||
211 | getVideoFileUrl (videoFile: MVideoFile, baseUrlHttp: string) { | ||
212 | return baseUrlHttp + join(STATIC_PATHS.STREAMING_PLAYLISTS.HLS, this.Video.uuid, getVideoFilename(this, videoFile)) | ||
213 | } | ||
214 | |||
215 | getTorrentUrl (videoFile: MVideoFile, baseUrlHttp: string) { | ||
216 | return baseUrlHttp + join(STATIC_PATHS.TORRENTS, getTorrentFileName(this, videoFile)) | ||
217 | } | ||
218 | |||
219 | getTrackerUrls (baseUrlHttp: string, baseUrlWs: string) { | 189 | getTrackerUrls (baseUrlHttp: string, baseUrlWs: string) { |
220 | return [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ] | 190 | return [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ] |
221 | } | 191 | } |
@@ -224,10 +194,4 @@ export class VideoStreamingPlaylistModel extends Model { | |||
224 | return this.type === other.type && | 194 | return this.type === other.type && |
225 | this.videoId === other.videoId | 195 | this.videoId === other.videoId |
226 | } | 196 | } |
227 | |||
228 | removeTorrent (this: MStreamingPlaylistVideo, videoFile: MVideoFile) { | ||
229 | const torrentPath = getTorrentFilePath(this, videoFile) | ||
230 | return remove(torrentPath) | ||
231 | .catch(err => logger.warn('Cannot delete torrent %s.', torrentPath, { err })) | ||
232 | } | ||
233 | } | 197 | } |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 3321deed3..2e6b6aeec 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -24,10 +24,11 @@ import { | |||
24 | Table, | 24 | Table, |
25 | UpdatedAt | 25 | UpdatedAt |
26 | } from 'sequelize-typescript' | 26 | } from 'sequelize-typescript' |
27 | import { v4 as uuidv4 } from 'uuid' | ||
27 | import { buildNSFWFilter } from '@server/helpers/express-utils' | 28 | import { buildNSFWFilter } from '@server/helpers/express-utils' |
28 | import { getPrivaciesForFederation, isPrivacyForFederation, isStateForFederation } from '@server/helpers/video' | 29 | import { getPrivaciesForFederation, isPrivacyForFederation, isStateForFederation } from '@server/helpers/video' |
29 | import { LiveManager } from '@server/lib/live-manager' | 30 | import { LiveManager } from '@server/lib/live-manager' |
30 | import { getHLSDirectory, getTorrentFileName, getTorrentFilePath, getVideoFilename, getVideoFilePath } from '@server/lib/video-paths' | 31 | import { getHLSDirectory, getVideoFilePath } from '@server/lib/video-paths' |
31 | import { getServerActor } from '@server/models/application/application' | 32 | import { getServerActor } from '@server/models/application/application' |
32 | import { ModelCache } from '@server/models/model-cache' | 33 | import { ModelCache } from '@server/models/model-cache' |
33 | import { VideoFile } from '@shared/models/videos/video-file.model' | 34 | import { VideoFile } from '@shared/models/videos/video-file.model' |
@@ -60,7 +61,6 @@ import { | |||
60 | CONSTRAINTS_FIELDS, | 61 | CONSTRAINTS_FIELDS, |
61 | LAZY_STATIC_PATHS, | 62 | LAZY_STATIC_PATHS, |
62 | REMOTE_SCHEME, | 63 | REMOTE_SCHEME, |
63 | STATIC_DOWNLOAD_PATHS, | ||
64 | STATIC_PATHS, | 64 | STATIC_PATHS, |
65 | VIDEO_CATEGORIES, | 65 | VIDEO_CATEGORIES, |
66 | VIDEO_LANGUAGES, | 66 | VIDEO_LANGUAGES, |
@@ -78,6 +78,7 @@ import { | |||
78 | MStreamingPlaylistFilesVideo, | 78 | MStreamingPlaylistFilesVideo, |
79 | MUserAccountId, | 79 | MUserAccountId, |
80 | MUserId, | 80 | MUserId, |
81 | MVideo, | ||
81 | MVideoAccountLight, | 82 | MVideoAccountLight, |
82 | MVideoAccountLightBlacklistAllFiles, | 83 | MVideoAccountLightBlacklistAllFiles, |
83 | MVideoAP, | 84 | MVideoAP, |
@@ -130,7 +131,6 @@ import { VideoShareModel } from './video-share' | |||
130 | import { VideoStreamingPlaylistModel } from './video-streaming-playlist' | 131 | import { VideoStreamingPlaylistModel } from './video-streaming-playlist' |
131 | import { VideoTagModel } from './video-tag' | 132 | import { VideoTagModel } from './video-tag' |
132 | import { VideoViewModel } from './video-view' | 133 | import { VideoViewModel } from './video-view' |
133 | import { v4 as uuidv4 } from 'uuid' | ||
134 | 134 | ||
135 | export enum ScopeNames { | 135 | export enum ScopeNames { |
136 | AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS', | 136 | AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS', |
@@ -790,7 +790,7 @@ export class VideoModel extends Model { | |||
790 | // Remove physical files and torrents | 790 | // Remove physical files and torrents |
791 | instance.VideoFiles.forEach(file => { | 791 | instance.VideoFiles.forEach(file => { |
792 | tasks.push(instance.removeFile(file)) | 792 | tasks.push(instance.removeFile(file)) |
793 | tasks.push(instance.removeTorrent(file)) | 793 | tasks.push(file.removeTorrent()) |
794 | }) | 794 | }) |
795 | 795 | ||
796 | // Remove playlists file | 796 | // Remove playlists file |
@@ -853,18 +853,14 @@ export class VideoModel extends Model { | |||
853 | return undefined | 853 | return undefined |
854 | } | 854 | } |
855 | 855 | ||
856 | static listLocal (): Promise<MVideoWithAllFiles[]> { | 856 | static listLocal (): Promise<MVideo[]> { |
857 | const query = { | 857 | const query = { |
858 | where: { | 858 | where: { |
859 | remote: false | 859 | remote: false |
860 | } | 860 | } |
861 | } | 861 | } |
862 | 862 | ||
863 | return VideoModel.scope([ | 863 | return VideoModel.findAll(query) |
864 | ScopeNames.WITH_WEBTORRENT_FILES, | ||
865 | ScopeNames.WITH_STREAMING_PLAYLISTS, | ||
866 | ScopeNames.WITH_THUMBNAILS | ||
867 | ]).findAll(query) | ||
868 | } | 864 | } |
869 | 865 | ||
870 | static listAllAndSharedByActorForOutbox (actorId: number, start: number, count: number) { | 866 | static listAllAndSharedByActorForOutbox (actorId: number, start: number, count: number) { |
@@ -1623,6 +1619,10 @@ export class VideoModel extends Model { | |||
1623 | 'resolution', | 1619 | 'resolution', |
1624 | 'size', | 1620 | 'size', |
1625 | 'extname', | 1621 | 'extname', |
1622 | 'filename', | ||
1623 | 'fileUrl', | ||
1624 | 'torrentFilename', | ||
1625 | 'torrentUrl', | ||
1626 | 'infoHash', | 1626 | 'infoHash', |
1627 | 'fps', | 1627 | 'fps', |
1628 | 'videoId', | 1628 | 'videoId', |
@@ -1891,14 +1891,14 @@ export class VideoModel extends Model { | |||
1891 | let files: VideoFile[] = [] | 1891 | let files: VideoFile[] = [] |
1892 | 1892 | ||
1893 | if (Array.isArray(this.VideoFiles)) { | 1893 | if (Array.isArray(this.VideoFiles)) { |
1894 | const result = videoFilesModelToFormattedJSON(this, baseUrlHttp, baseUrlWs, this.VideoFiles) | 1894 | const result = videoFilesModelToFormattedJSON(this, this, baseUrlHttp, baseUrlWs, this.VideoFiles) |
1895 | files = files.concat(result) | 1895 | files = files.concat(result) |
1896 | } | 1896 | } |
1897 | 1897 | ||
1898 | for (const p of (this.VideoStreamingPlaylists || [])) { | 1898 | for (const p of (this.VideoStreamingPlaylists || [])) { |
1899 | p.Video = this | 1899 | p.Video = this |
1900 | 1900 | ||
1901 | const result = videoFilesModelToFormattedJSON(p, baseUrlHttp, baseUrlWs, p.VideoFiles) | 1901 | const result = videoFilesModelToFormattedJSON(p, this, baseUrlHttp, baseUrlWs, p.VideoFiles) |
1902 | files = files.concat(result) | 1902 | files = files.concat(result) |
1903 | } | 1903 | } |
1904 | 1904 | ||
@@ -1956,12 +1956,6 @@ export class VideoModel extends Model { | |||
1956 | .catch(err => logger.warn('Cannot delete file %s.', filePath, { err })) | 1956 | .catch(err => logger.warn('Cannot delete file %s.', filePath, { err })) |
1957 | } | 1957 | } |
1958 | 1958 | ||
1959 | removeTorrent (videoFile: MVideoFile) { | ||
1960 | const torrentPath = getTorrentFilePath(this, videoFile) | ||
1961 | return remove(torrentPath) | ||
1962 | .catch(err => logger.warn('Cannot delete torrent %s.', torrentPath, { err })) | ||
1963 | } | ||
1964 | |||
1965 | async removeStreamingPlaylistFiles (streamingPlaylist: MStreamingPlaylist, isRedundancy = false) { | 1959 | async removeStreamingPlaylistFiles (streamingPlaylist: MStreamingPlaylist, isRedundancy = false) { |
1966 | const directoryPath = getHLSDirectory(this, isRedundancy) | 1960 | const directoryPath = getHLSDirectory(this, isRedundancy) |
1967 | 1961 | ||
@@ -1977,7 +1971,7 @@ export class VideoModel extends Model { | |||
1977 | 1971 | ||
1978 | // Remove physical files and torrents | 1972 | // Remove physical files and torrents |
1979 | await Promise.all( | 1973 | await Promise.all( |
1980 | streamingPlaylistWithFiles.VideoFiles.map(file => streamingPlaylistWithFiles.removeTorrent(file)) | 1974 | streamingPlaylistWithFiles.VideoFiles.map(file => file.removeTorrent()) |
1981 | ) | 1975 | ) |
1982 | } | 1976 | } |
1983 | } | 1977 | } |
@@ -2054,34 +2048,6 @@ export class VideoModel extends Model { | |||
2054 | return [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ] | 2048 | return [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ] |
2055 | } | 2049 | } |
2056 | 2050 | ||
2057 | getTorrentUrl (videoFile: MVideoFile, baseUrlHttp: string) { | ||
2058 | return baseUrlHttp + STATIC_PATHS.TORRENTS + getTorrentFileName(this, videoFile) | ||
2059 | } | ||
2060 | |||
2061 | getTorrentDownloadUrl (videoFile: MVideoFile, baseUrlHttp: string) { | ||
2062 | return baseUrlHttp + STATIC_DOWNLOAD_PATHS.TORRENTS + getTorrentFileName(this, videoFile) | ||
2063 | } | ||
2064 | |||
2065 | getVideoFileUrl (videoFile: MVideoFile, baseUrlHttp: string) { | ||
2066 | return baseUrlHttp + STATIC_PATHS.WEBSEED + getVideoFilename(this, videoFile) | ||
2067 | } | ||
2068 | |||
2069 | getVideoFileMetadataUrl (videoFile: MVideoFile, baseUrlHttp: string) { | ||
2070 | const path = '/api/v1/videos/' | ||
2071 | |||
2072 | return this.isOwned() | ||
2073 | ? baseUrlHttp + path + this.uuid + '/metadata/' + videoFile.id | ||
2074 | : videoFile.metadataUrl | ||
2075 | } | ||
2076 | |||
2077 | getVideoRedundancyUrl (videoFile: MVideoFile, baseUrlHttp: string) { | ||
2078 | return baseUrlHttp + STATIC_PATHS.REDUNDANCY + getVideoFilename(this, videoFile) | ||
2079 | } | ||
2080 | |||
2081 | getVideoFileDownloadUrl (videoFile: MVideoFile, baseUrlHttp: string) { | ||
2082 | return baseUrlHttp + STATIC_DOWNLOAD_PATHS.VIDEOS + getVideoFilename(this, videoFile) | ||
2083 | } | ||
2084 | |||
2085 | getBandwidthBits (videoFile: MVideoFile) { | 2051 | getBandwidthBits (videoFile: MVideoFile) { |
2086 | return Math.ceil((videoFile.size * 8) / this.duration) | 2052 | return Math.ceil((videoFile.size * 8) / this.duration) |
2087 | } | 2053 | } |
diff --git a/server/tests/api/videos/video-hls.ts b/server/tests/api/videos/video-hls.ts index db551dd9e..03ac3f321 100644 --- a/server/tests/api/videos/video-hls.ts +++ b/server/tests/api/videos/video-hls.ts | |||
@@ -52,7 +52,7 @@ async function checkHlsPlaylist (servers: ServerInfo[], videoUUID: string, hlsOn | |||
52 | expect(file).to.not.be.undefined | 52 | expect(file).to.not.be.undefined |
53 | 53 | ||
54 | expect(file.magnetUri).to.have.lengthOf.above(2) | 54 | expect(file.magnetUri).to.have.lengthOf.above(2) |
55 | expect(file.torrentUrl).to.equal(`${baseUrl}/static/torrents/${videoDetails.uuid}-${file.resolution.id}-hls.torrent`) | 55 | expect(file.torrentUrl).to.equal(`http://${server.host}/lazy-static/torrents/${videoDetails.uuid}-${file.resolution.id}-hls.torrent`) |
56 | expect(file.fileUrl).to.equal( | 56 | expect(file.fileUrl).to.equal( |
57 | `${baseUrl}/static/streaming-playlists/hls/${videoDetails.uuid}/${videoDetails.uuid}-${file.resolution.id}-fragmented.mp4` | 57 | `${baseUrl}/static/streaming-playlists/hls/${videoDetails.uuid}/${videoDetails.uuid}-${file.resolution.id}-fragmented.mp4` |
58 | ) | 58 | ) |
diff --git a/server/tests/cli/create-import-video-file-job.ts b/server/tests/cli/create-import-video-file-job.ts index dac049fe4..7eaf2c19e 100644 --- a/server/tests/cli/create-import-video-file-job.ts +++ b/server/tests/cli/create-import-video-file-job.ts | |||
@@ -2,7 +2,7 @@ | |||
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import * as chai from 'chai' | 4 | import * as chai from 'chai' |
5 | import { VideoDetails } from '../../../shared/models/videos' | 5 | import { VideoFile } from '@shared/models/videos/video-file.model' |
6 | import { | 6 | import { |
7 | cleanupTests, | 7 | cleanupTests, |
8 | doubleFollow, | 8 | doubleFollow, |
@@ -16,7 +16,7 @@ import { | |||
16 | uploadVideo | 16 | uploadVideo |
17 | } from '../../../shared/extra-utils' | 17 | } from '../../../shared/extra-utils' |
18 | import { waitJobs } from '../../../shared/extra-utils/server/jobs' | 18 | import { waitJobs } from '../../../shared/extra-utils/server/jobs' |
19 | import { VideoFile } from '@shared/models/videos/video-file.model' | 19 | import { VideoDetails } from '../../../shared/models/videos' |
20 | 20 | ||
21 | const expect = chai.expect | 21 | const expect = chai.expect |
22 | 22 | ||
@@ -62,7 +62,6 @@ describe('Test create import video jobs', function () { | |||
62 | 62 | ||
63 | await waitJobs(servers) | 63 | await waitJobs(servers) |
64 | 64 | ||
65 | let magnetUri: string | ||
66 | for (const server of servers) { | 65 | for (const server of servers) { |
67 | const { data: videos } = (await getVideosList(server.url)).body | 66 | const { data: videos } = (await getVideosList(server.url)).body |
68 | expect(videos).to.have.lengthOf(2) | 67 | expect(videos).to.have.lengthOf(2) |
@@ -74,9 +73,6 @@ describe('Test create import video jobs', function () { | |||
74 | const [ originalVideo, transcodedVideo ] = videoDetail.files | 73 | const [ originalVideo, transcodedVideo ] = videoDetail.files |
75 | assertVideoProperties(originalVideo, 720, 'webm', 218910) | 74 | assertVideoProperties(originalVideo, 720, 'webm', 218910) |
76 | assertVideoProperties(transcodedVideo, 480, 'webm', 69217) | 75 | assertVideoProperties(transcodedVideo, 480, 'webm', 69217) |
77 | |||
78 | if (!magnetUri) magnetUri = transcodedVideo.magnetUri | ||
79 | else expect(transcodedVideo.magnetUri).to.equal(magnetUri) | ||
80 | } | 76 | } |
81 | }) | 77 | }) |
82 | 78 | ||
@@ -86,7 +82,6 @@ describe('Test create import video jobs', function () { | |||
86 | 82 | ||
87 | await waitJobs(servers) | 83 | await waitJobs(servers) |
88 | 84 | ||
89 | let magnetUri: string | ||
90 | for (const server of servers) { | 85 | for (const server of servers) { |
91 | const { data: videos } = (await getVideosList(server.url)).body | 86 | const { data: videos } = (await getVideosList(server.url)).body |
92 | expect(videos).to.have.lengthOf(2) | 87 | expect(videos).to.have.lengthOf(2) |
@@ -100,9 +95,6 @@ describe('Test create import video jobs', function () { | |||
100 | assertVideoProperties(transcodedVideo420, 480, 'mp4') | 95 | assertVideoProperties(transcodedVideo420, 480, 'mp4') |
101 | assertVideoProperties(transcodedVideo320, 360, 'mp4') | 96 | assertVideoProperties(transcodedVideo320, 360, 'mp4') |
102 | assertVideoProperties(transcodedVideo240, 240, 'mp4') | 97 | assertVideoProperties(transcodedVideo240, 240, 'mp4') |
103 | |||
104 | if (!magnetUri) magnetUri = originalVideo.magnetUri | ||
105 | else expect(originalVideo.magnetUri).to.equal(magnetUri) | ||
106 | } | 98 | } |
107 | }) | 99 | }) |
108 | 100 | ||
@@ -112,7 +104,6 @@ describe('Test create import video jobs', function () { | |||
112 | 104 | ||
113 | await waitJobs(servers) | 105 | await waitJobs(servers) |
114 | 106 | ||
115 | let magnetUri: string | ||
116 | for (const server of servers) { | 107 | for (const server of servers) { |
117 | const { data: videos } = (await getVideosList(server.url)).body | 108 | const { data: videos } = (await getVideosList(server.url)).body |
118 | expect(videos).to.have.lengthOf(2) | 109 | expect(videos).to.have.lengthOf(2) |
@@ -124,9 +115,6 @@ describe('Test create import video jobs', function () { | |||
124 | const [ video720, video480 ] = videoDetail.files | 115 | const [ video720, video480 ] = videoDetail.files |
125 | assertVideoProperties(video720, 720, 'webm', 942961) | 116 | assertVideoProperties(video720, 720, 'webm', 942961) |
126 | assertVideoProperties(video480, 480, 'webm', 69217) | 117 | assertVideoProperties(video480, 480, 'webm', 69217) |
127 | |||
128 | if (!magnetUri) magnetUri = video720.magnetUri | ||
129 | else expect(video720.magnetUri).to.equal(magnetUri) | ||
130 | } | 118 | } |
131 | }) | 119 | }) |
132 | 120 | ||
diff --git a/server/types/models/video/video-channels.ts b/server/types/models/video/video-channels.ts index 2e05d8753..77790daa4 100644 --- a/server/types/models/video/video-channels.ts +++ b/server/types/models/video/video-channels.ts | |||
@@ -17,6 +17,7 @@ import { | |||
17 | MActorDefault, | 17 | MActorDefault, |
18 | MActorDefaultLight, | 18 | MActorDefaultLight, |
19 | MActorFormattable, | 19 | MActorFormattable, |
20 | MActorHost, | ||
20 | MActorLight, | 21 | MActorLight, |
21 | MActorSummary, | 22 | MActorSummary, |
22 | MActorSummaryFormattable, MActorUrl | 23 | MActorSummaryFormattable, MActorUrl |
@@ -71,6 +72,10 @@ export type MChannelAccountLight = | |||
71 | Use<'Actor', MActorDefaultLight> & | 72 | Use<'Actor', MActorDefaultLight> & |
72 | Use<'Account', MAccountLight> | 73 | Use<'Account', MAccountLight> |
73 | 74 | ||
75 | export type MChannelHost = | ||
76 | MChannelId & | ||
77 | Use<'Actor', MActorHost> | ||
78 | |||
74 | // ############################################################################ | 79 | // ############################################################################ |
75 | 80 | ||
76 | // Account associations | 81 | // Account associations |
diff --git a/server/types/models/video/video.ts b/server/types/models/video/video.ts index ae23cc30f..92dcbaf59 100644 --- a/server/types/models/video/video.ts +++ b/server/types/models/video/video.ts | |||
@@ -1,27 +1,28 @@ | |||
1 | import { VideoModel } from '../../../models/video/video' | ||
2 | import { PickWith, PickWithOpt } from '@shared/core-utils' | 1 | import { PickWith, PickWithOpt } from '@shared/core-utils' |
2 | import { VideoModel } from '../../../models/video/video' | ||
3 | import { MUserVideoHistoryTime } from '../user/user-video-history' | ||
4 | import { MScheduleVideoUpdate } from './schedule-video-update' | ||
5 | import { MTag } from './tag' | ||
6 | import { MThumbnail } from './thumbnail' | ||
7 | import { MVideoBlacklist, MVideoBlacklistLight, MVideoBlacklistUnfederated } from './video-blacklist' | ||
8 | import { MVideoCaptionLanguage, MVideoCaptionLanguageUrl } from './video-caption' | ||
3 | import { | 9 | import { |
4 | MChannelAccountDefault, | 10 | MChannelAccountDefault, |
5 | MChannelAccountLight, | 11 | MChannelAccountLight, |
6 | MChannelAccountSummaryFormattable, | 12 | MChannelAccountSummaryFormattable, |
7 | MChannelActor, | 13 | MChannelActor, |
8 | MChannelFormattable, | 14 | MChannelFormattable, |
15 | MChannelHost, | ||
9 | MChannelUserId | 16 | MChannelUserId |
10 | } from './video-channels' | 17 | } from './video-channels' |
11 | import { MTag } from './tag' | 18 | import { MVideoFile, MVideoFileRedundanciesAll, MVideoFileRedundanciesOpt } from './video-file' |
12 | import { MVideoCaptionLanguage, MVideoCaptionLanguageUrl } from './video-caption' | 19 | import { MVideoLive } from './video-live' |
13 | import { | 20 | import { |
14 | MStreamingPlaylistFiles, | 21 | MStreamingPlaylistFiles, |
15 | MStreamingPlaylistRedundancies, | 22 | MStreamingPlaylistRedundancies, |
16 | MStreamingPlaylistRedundanciesAll, | 23 | MStreamingPlaylistRedundanciesAll, |
17 | MStreamingPlaylistRedundanciesOpt | 24 | MStreamingPlaylistRedundanciesOpt |
18 | } from './video-streaming-playlist' | 25 | } from './video-streaming-playlist' |
19 | import { MVideoFile, MVideoFileRedundanciesAll, MVideoFileRedundanciesOpt } from './video-file' | ||
20 | import { MThumbnail } from './thumbnail' | ||
21 | import { MVideoBlacklist, MVideoBlacklistLight, MVideoBlacklistUnfederated } from './video-blacklist' | ||
22 | import { MScheduleVideoUpdate } from './schedule-video-update' | ||
23 | import { MUserVideoHistoryTime } from '../user/user-video-history' | ||
24 | import { MVideoLive } from './video-live' | ||
25 | 26 | ||
26 | type Use<K extends keyof VideoModel, M> = PickWith<VideoModel, K, M> | 27 | type Use<K extends keyof VideoModel, M> = PickWith<VideoModel, K, M> |
27 | 28 | ||
@@ -143,6 +144,10 @@ export type MVideoWithChannelActor = | |||
143 | MVideo & | 144 | MVideo & |
144 | Use<'VideoChannel', MChannelActor> | 145 | Use<'VideoChannel', MChannelActor> |
145 | 146 | ||
147 | export type MVideoWithHost = | ||
148 | MVideo & | ||
149 | Use<'VideoChannel', MChannelHost> | ||
150 | |||
146 | export type MVideoFullLight = | 151 | export type MVideoFullLight = |
147 | MVideo & | 152 | MVideo & |
148 | Use<'Thumbnails', MThumbnail[]> & | 153 | Use<'Thumbnails', MThumbnail[]> & |
diff --git a/shared/extra-utils/videos/videos.ts b/shared/extra-utils/videos/videos.ts index f94fa233c..929eb42ca 100644 --- a/shared/extra-utils/videos/videos.ts +++ b/shared/extra-utils/videos/videos.ts | |||
@@ -11,7 +11,7 @@ import validator from 'validator' | |||
11 | import { loadLanguages, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../server/initializers/constants' | 11 | import { loadLanguages, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../server/initializers/constants' |
12 | import { VideoDetails, VideoPrivacy } from '../../models/videos' | 12 | import { VideoDetails, VideoPrivacy } from '../../models/videos' |
13 | import { buildAbsoluteFixturePath, buildServerDirectory, dateIsValid, immutableAssign, testImage, webtorrentAdd } from '../miscs/miscs' | 13 | import { buildAbsoluteFixturePath, buildServerDirectory, dateIsValid, immutableAssign, testImage, webtorrentAdd } from '../miscs/miscs' |
14 | import { makeGetRequest, makePutBodyRequest, makeUploadRequest } from '../requests/requests' | 14 | import { makeGetRequest, makePutBodyRequest, makeRawRequest, makeUploadRequest } from '../requests/requests' |
15 | import { waitJobs } from '../server/jobs' | 15 | import { waitJobs } from '../server/jobs' |
16 | import { ServerInfo } from '../server/servers' | 16 | import { ServerInfo } from '../server/servers' |
17 | import { getMyUserInformation } from '../users/users' | 17 | import { getMyUserInformation } from '../users/users' |
@@ -544,6 +544,9 @@ async function completeVideoCheck ( | |||
544 | if (!attributes.likes) attributes.likes = 0 | 544 | if (!attributes.likes) attributes.likes = 0 |
545 | if (!attributes.dislikes) attributes.dislikes = 0 | 545 | if (!attributes.dislikes) attributes.dislikes = 0 |
546 | 546 | ||
547 | const host = new URL(url).host | ||
548 | const originHost = attributes.account.host | ||
549 | |||
547 | expect(video.name).to.equal(attributes.name) | 550 | expect(video.name).to.equal(attributes.name) |
548 | expect(video.category.id).to.equal(attributes.category) | 551 | expect(video.category.id).to.equal(attributes.category) |
549 | expect(video.category.label).to.equal(attributes.category !== null ? VIDEO_CATEGORIES[attributes.category] : 'Misc') | 552 | expect(video.category.label).to.equal(attributes.category !== null ? VIDEO_CATEGORIES[attributes.category] : 'Misc') |
@@ -603,8 +606,21 @@ async function completeVideoCheck ( | |||
603 | if (attributes.files.length > 1) extension = '.mp4' | 606 | if (attributes.files.length > 1) extension = '.mp4' |
604 | 607 | ||
605 | expect(file.magnetUri).to.have.lengthOf.above(2) | 608 | expect(file.magnetUri).to.have.lengthOf.above(2) |
606 | expect(file.torrentUrl).to.equal(`http://${attributes.account.host}/static/torrents/${videoDetails.uuid}-${file.resolution.id}.torrent`) | 609 | |
607 | expect(file.fileUrl).to.equal(`http://${attributes.account.host}/static/webseed/${videoDetails.uuid}-${file.resolution.id}${extension}`) | 610 | expect(file.torrentDownloadUrl).to.equal(`http://${host}/download/torrents/${videoDetails.uuid}-${file.resolution.id}.torrent`) |
611 | expect(file.torrentUrl).to.equal(`http://${host}/lazy-static/torrents/${videoDetails.uuid}-${file.resolution.id}.torrent`) | ||
612 | |||
613 | expect(file.fileUrl).to.equal(`http://${originHost}/static/webseed/${videoDetails.uuid}-${file.resolution.id}${extension}`) | ||
614 | expect(file.fileDownloadUrl).to.equal(`http://${originHost}/download/videos/${videoDetails.uuid}-${file.resolution.id}${extension}`) | ||
615 | |||
616 | await Promise.all([ | ||
617 | makeRawRequest(file.torrentUrl, 200), | ||
618 | makeRawRequest(file.torrentDownloadUrl, 200), | ||
619 | makeRawRequest(file.metadataUrl, 200), | ||
620 | // Backward compatibility | ||
621 | makeRawRequest(`http://${originHost}/static/torrents/${videoDetails.uuid}-${file.resolution.id}.torrent`, 200) | ||
622 | ]) | ||
623 | |||
608 | expect(file.resolution.id).to.equal(attributeFile.resolution) | 624 | expect(file.resolution.id).to.equal(attributeFile.resolution) |
609 | expect(file.resolution.label).to.equal(attributeFile.resolution + 'p') | 625 | expect(file.resolution.label).to.equal(attributeFile.resolution + 'p') |
610 | 626 | ||
diff --git a/shared/models/videos/video-file.model.ts b/shared/models/videos/video-file.model.ts index e9839229d..f3d93f0ed 100644 --- a/shared/models/videos/video-file.model.ts +++ b/shared/models/videos/video-file.model.ts | |||
@@ -3,14 +3,20 @@ import { VideoFileMetadata } from './video-file-metadata' | |||
3 | import { VideoResolution } from './video-resolution.enum' | 3 | import { VideoResolution } from './video-resolution.enum' |
4 | 4 | ||
5 | export interface VideoFile { | 5 | export interface VideoFile { |
6 | magnetUri: string | ||
7 | resolution: VideoConstant<VideoResolution> | 6 | resolution: VideoConstant<VideoResolution> |
8 | size: number // Bytes | 7 | size: number // Bytes |
8 | |||
9 | torrentUrl: string | 9 | torrentUrl: string |
10 | torrentDownloadUrl: string | 10 | torrentDownloadUrl: string |
11 | |||
11 | fileUrl: string | 12 | fileUrl: string |
12 | fileDownloadUrl: string | 13 | fileDownloadUrl: string |
14 | |||
13 | fps: number | 15 | fps: number |
16 | |||
14 | metadata?: VideoFileMetadata | 17 | metadata?: VideoFileMetadata |
15 | metadataUrl?: string | 18 | metadataUrl?: string |
19 | |||
20 | // FIXME: deprecated in 3.2 | ||
21 | magnetUri: string | ||
16 | } | 22 | } |