diff options
-rw-r--r-- | client/src/app/videos/+video-watch/modal/video-download.component.ts | 4 | ||||
-rw-r--r-- | server/controllers/static.ts | 38 | ||||
-rw-r--r-- | server/initializers/constants.ts | 5 | ||||
-rw-r--r-- | server/models/video/video.ts | 18 | ||||
-rw-r--r-- | shared/models/videos/video.model.ts | 2 |
5 files changed, 61 insertions, 6 deletions
diff --git a/client/src/app/videos/+video-watch/modal/video-download.component.ts b/client/src/app/videos/+video-watch/modal/video-download.component.ts index b06a7eef1..12f31b011 100644 --- a/client/src/app/videos/+video-watch/modal/video-download.component.ts +++ b/client/src/app/videos/+video-watch/modal/video-download.component.ts | |||
@@ -41,7 +41,7 @@ export class VideoDownloadComponent implements OnInit { | |||
41 | return | 41 | return |
42 | } | 42 | } |
43 | 43 | ||
44 | const link = this.downloadType === 'direct' ? file.fileUrl : file.torrentUrl | 44 | const link = this.downloadType === 'direct' ? file.fileDownloadUrl : file.torrentDownloadUrl |
45 | window.open(link) | 45 | window.location.assign(link) |
46 | } | 46 | } |
47 | } | 47 | } |
diff --git a/server/controllers/static.ts b/server/controllers/static.ts index c1bf384a4..8bebe6fa7 100644 --- a/server/controllers/static.ts +++ b/server/controllers/static.ts | |||
@@ -1,8 +1,9 @@ | |||
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 { CONFIG, STATIC_MAX_AGE, STATIC_PATHS } from '../initializers' | 3 | import { CONFIG, STATIC_DOWNLOAD_PATHS, STATIC_MAX_AGE, STATIC_PATHS } from '../initializers' |
4 | import { VideosPreviewCache } from '../lib/cache' | 4 | import { VideosPreviewCache } from '../lib/cache' |
5 | import { asyncMiddleware } from '../middlewares' | 5 | import { asyncMiddleware, videosGetValidator } from '../middlewares' |
6 | import { VideoModel } from '../models/video/video' | ||
6 | 7 | ||
7 | const staticRouter = express.Router() | 8 | const staticRouter = express.Router() |
8 | 9 | ||
@@ -16,6 +17,11 @@ staticRouter.use( | |||
16 | cors(), | 17 | cors(), |
17 | express.static(torrentsPhysicalPath, { maxAge: 0 }) // Don't cache because we could regenerate the torrent file | 18 | express.static(torrentsPhysicalPath, { maxAge: 0 }) // Don't cache because we could regenerate the torrent file |
18 | ) | 19 | ) |
20 | staticRouter.use( | ||
21 | STATIC_DOWNLOAD_PATHS.TORRENTS + ':id-:resolution([0-9]+).torrent', | ||
22 | asyncMiddleware(videosGetValidator), | ||
23 | asyncMiddleware(downloadTorrent) | ||
24 | ) | ||
19 | 25 | ||
20 | // Videos path for webseeding | 26 | // Videos path for webseeding |
21 | const videosPhysicalPath = CONFIG.STORAGE.VIDEOS_DIR | 27 | const videosPhysicalPath = CONFIG.STORAGE.VIDEOS_DIR |
@@ -24,6 +30,11 @@ staticRouter.use( | |||
24 | cors(), | 30 | cors(), |
25 | express.static(videosPhysicalPath, { maxAge: STATIC_MAX_AGE }) | 31 | express.static(videosPhysicalPath, { maxAge: STATIC_MAX_AGE }) |
26 | ) | 32 | ) |
33 | staticRouter.use( | ||
34 | STATIC_DOWNLOAD_PATHS.VIDEOS + ':id-:resolution([0-9]+).:extension', | ||
35 | asyncMiddleware(videosGetValidator), | ||
36 | asyncMiddleware(downloadVideoFile) | ||
37 | ) | ||
27 | 38 | ||
28 | // Thumbnails path for express | 39 | // Thumbnails path for express |
29 | const thumbnailsPhysicalPath = CONFIG.STORAGE.THUMBNAILS_DIR | 40 | const thumbnailsPhysicalPath = CONFIG.STORAGE.THUMBNAILS_DIR |
@@ -64,3 +75,26 @@ async function getPreview (req: express.Request, res: express.Response, next: ex | |||
64 | 75 | ||
65 | return res.sendFile(path, { maxAge: STATIC_MAX_AGE }) | 76 | return res.sendFile(path, { maxAge: STATIC_MAX_AGE }) |
66 | } | 77 | } |
78 | |||
79 | async function downloadTorrent (req: express.Request, res: express.Response, next: express.NextFunction) { | ||
80 | const { video, videoFile } = getVideoAndFileOr404(req, res) | ||
81 | if (!videoFile) return res.status(404).end() | ||
82 | |||
83 | return res.download(video.getTorrentFilePath(videoFile), `${video.name}-${videoFile.resolution}p.torrent`) | ||
84 | } | ||
85 | |||
86 | async function downloadVideoFile (req: express.Request, res: express.Response, next: express.NextFunction) { | ||
87 | const { video, videoFile } = getVideoAndFileOr404(req, res) | ||
88 | if (!videoFile) return res.status(404).end() | ||
89 | |||
90 | return res.download(video.getVideoFilePath(videoFile), `${video.name}-${videoFile.resolution}p${videoFile.extname}`) | ||
91 | } | ||
92 | |||
93 | function getVideoAndFileOr404 (req: express.Request, res: express.Response) { | ||
94 | const resolution = parseInt(req.params.resolution, 10) | ||
95 | const video: VideoModel = res.locals.video | ||
96 | |||
97 | const videoFile = video.VideoFiles.find(f => f.resolution === resolution) | ||
98 | |||
99 | return { video, videoFile } | ||
100 | } | ||
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index a35306730..26ee3db47 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -389,6 +389,10 @@ const STATIC_PATHS = { | |||
389 | WEBSEED: '/static/webseed/', | 389 | WEBSEED: '/static/webseed/', |
390 | AVATARS: '/static/avatars/' | 390 | AVATARS: '/static/avatars/' |
391 | } | 391 | } |
392 | const STATIC_DOWNLOAD_PATHS = { | ||
393 | TORRENTS: '/download/torrents/', | ||
394 | VIDEOS: '/download/videos/' | ||
395 | } | ||
392 | 396 | ||
393 | // Cache control | 397 | // Cache control |
394 | let STATIC_MAX_AGE = '30d' | 398 | let STATIC_MAX_AGE = '30d' |
@@ -493,6 +497,7 @@ export { | |||
493 | USER_PASSWORD_RESET_LIFETIME, | 497 | USER_PASSWORD_RESET_LIFETIME, |
494 | IMAGE_MIMETYPE_EXT, | 498 | IMAGE_MIMETYPE_EXT, |
495 | SCHEDULER_INTERVAL, | 499 | SCHEDULER_INTERVAL, |
500 | STATIC_DOWNLOAD_PATHS, | ||
496 | RATES_LIMIT, | 501 | RATES_LIMIT, |
497 | JOB_COMPLETED_LIFETIME, | 502 | JOB_COMPLETED_LIFETIME, |
498 | VIDEO_VIEW_LIFETIME | 503 | VIDEO_VIEW_LIFETIME |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 1640cd57f..5821ea397 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -29,7 +29,6 @@ import { VideoPrivacy, VideoResolution } from '../../../shared' | |||
29 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' | 29 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' |
30 | import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos' | 30 | import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos' |
31 | import { VideoFilter } from '../../../shared/models/videos/video-query.type' | 31 | import { VideoFilter } from '../../../shared/models/videos/video-query.type' |
32 | import { activityPubCollectionPagination } from '../../helpers/activitypub' | ||
33 | import { | 32 | import { |
34 | createTorrentPromise, | 33 | createTorrentPromise, |
35 | peertubeTruncate, | 34 | peertubeTruncate, |
@@ -59,6 +58,7 @@ import { | |||
59 | CONSTRAINTS_FIELDS, | 58 | CONSTRAINTS_FIELDS, |
60 | PREVIEWS_SIZE, | 59 | PREVIEWS_SIZE, |
61 | REMOTE_SCHEME, | 60 | REMOTE_SCHEME, |
61 | STATIC_DOWNLOAD_PATHS, | ||
62 | STATIC_PATHS, | 62 | STATIC_PATHS, |
63 | THUMBNAILS_SIZE, | 63 | THUMBNAILS_SIZE, |
64 | VIDEO_CATEGORIES, | 64 | VIDEO_CATEGORIES, |
@@ -979,6 +979,10 @@ export class VideoModel extends Model<VideoModel> { | |||
979 | ) | 979 | ) |
980 | } | 980 | } |
981 | 981 | ||
982 | getTorrentFilePath (videoFile: VideoFileModel) { | ||
983 | return join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile)) | ||
984 | } | ||
985 | |||
982 | getVideoFilePath (videoFile: VideoFileModel) { | 986 | getVideoFilePath (videoFile: VideoFileModel) { |
983 | return join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile)) | 987 | return join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile)) |
984 | } | 988 | } |
@@ -1112,7 +1116,9 @@ export class VideoModel extends Model<VideoModel> { | |||
1112 | magnetUri: this.generateMagnetUri(videoFile, baseUrlHttp, baseUrlWs), | 1116 | magnetUri: this.generateMagnetUri(videoFile, baseUrlHttp, baseUrlWs), |
1113 | size: videoFile.size, | 1117 | size: videoFile.size, |
1114 | torrentUrl: this.getTorrentUrl(videoFile, baseUrlHttp), | 1118 | torrentUrl: this.getTorrentUrl(videoFile, baseUrlHttp), |
1115 | fileUrl: this.getVideoFileUrl(videoFile, baseUrlHttp) | 1119 | torrentDownloadUrl: this.getTorrentDownloadUrl(videoFile, baseUrlHttp), |
1120 | fileUrl: this.getVideoFileUrl(videoFile, baseUrlHttp), | ||
1121 | fileDownloadUrl: this.getVideoFileDownloadUrl(videoFile, baseUrlHttp) | ||
1116 | } as VideoFile | 1122 | } as VideoFile |
1117 | }) | 1123 | }) |
1118 | .sort((a, b) => { | 1124 | .sort((a, b) => { |
@@ -1367,10 +1373,18 @@ export class VideoModel extends Model<VideoModel> { | |||
1367 | return baseUrlHttp + STATIC_PATHS.TORRENTS + this.getTorrentFileName(videoFile) | 1373 | return baseUrlHttp + STATIC_PATHS.TORRENTS + this.getTorrentFileName(videoFile) |
1368 | } | 1374 | } |
1369 | 1375 | ||
1376 | private getTorrentDownloadUrl (videoFile: VideoFileModel, baseUrlHttp: string) { | ||
1377 | return baseUrlHttp + STATIC_DOWNLOAD_PATHS.TORRENTS + this.getTorrentFileName(videoFile) | ||
1378 | } | ||
1379 | |||
1370 | private getVideoFileUrl (videoFile: VideoFileModel, baseUrlHttp: string) { | 1380 | private getVideoFileUrl (videoFile: VideoFileModel, baseUrlHttp: string) { |
1371 | return baseUrlHttp + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile) | 1381 | return baseUrlHttp + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile) |
1372 | } | 1382 | } |
1373 | 1383 | ||
1384 | private getVideoFileDownloadUrl (videoFile: VideoFileModel, baseUrlHttp: string) { | ||
1385 | return baseUrlHttp + STATIC_DOWNLOAD_PATHS.VIDEOS + this.getVideoFilename(videoFile) | ||
1386 | } | ||
1387 | |||
1374 | private generateMagnetUri (videoFile: VideoFileModel, baseUrlHttp: string, baseUrlWs: string) { | 1388 | private generateMagnetUri (videoFile: VideoFileModel, baseUrlHttp: string, baseUrlWs: string) { |
1375 | const xs = this.getTorrentUrl(videoFile, baseUrlHttp) | 1389 | const xs = this.getTorrentUrl(videoFile, baseUrlHttp) |
1376 | const announce = [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ] | 1390 | const announce = [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ] |
diff --git a/shared/models/videos/video.model.ts b/shared/models/videos/video.model.ts index eb40e82de..1c86545d3 100644 --- a/shared/models/videos/video.model.ts +++ b/shared/models/videos/video.model.ts | |||
@@ -14,7 +14,9 @@ export interface VideoFile { | |||
14 | resolution: VideoConstant<VideoResolution> | 14 | resolution: VideoConstant<VideoResolution> |
15 | size: number // Bytes | 15 | size: number // Bytes |
16 | torrentUrl: string | 16 | torrentUrl: string |
17 | torrentDownloadUrl: string | ||
17 | fileUrl: string | 18 | fileUrl: string |
19 | fileDownloadUrl: string | ||
18 | } | 20 | } |
19 | 21 | ||
20 | export interface Video { | 22 | export interface Video { |