]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/controllers/static.ts
Create a dedicated table to track video thumbnails
[github/Chocobozzz/PeerTube.git] / server / controllers / static.ts
CommitLineData
4d4e5cd4 1import * as cors from 'cors'
50d6de9c 2import * as express from 'express'
9c6ca37f 3import {
9c6ca37f
C
4 HLS_STREAMING_PLAYLIST_DIRECTORY,
5 ROUTE_CACHE_LIFETIME,
6 STATIC_DOWNLOAD_PATHS,
7 STATIC_MAX_AGE,
6dd9de95
C
8 STATIC_PATHS,
9 WEBSERVER
74dc3bca 10} from '../initializers/constants'
d74d29ad 11import { VideosCaptionCache, VideosPreviewCache } from '../lib/files-cache'
98d3324d 12import { cacheRoute } from '../middlewares/cache'
02756fbd
C
13import { asyncMiddleware, videosGetValidator } from '../middlewares'
14import { VideoModel } from '../models/video/video'
3f6d68d9
RK
15import { UserModel } from '../models/account/user'
16import { VideoCommentModel } from '../models/video/video-comment'
c75937d0 17import { HttpNodeinfoDiasporaSoftwareNsSchema20 } from '../../shared/models/nodeinfo'
aac0118d
C
18import { join } from 'path'
19import { root } from '../helpers/core-utils'
6dd9de95 20import { CONFIG } from '../initializers/config'
65fcc311 21
3f6d68d9 22const packageJSON = require('../../../package.json')
65fcc311
C
23const staticRouter = express.Router()
24
62945f06
C
25staticRouter.use(cors())
26
65fcc311 27/*
60862425 28 Cors is very important to let other servers access torrent and video files
65fcc311
C
29*/
30
31const torrentsPhysicalPath = CONFIG.STORAGE.TORRENTS_DIR
32staticRouter.use(
33 STATIC_PATHS.TORRENTS,
34 cors(),
49379960 35 express.static(torrentsPhysicalPath, { maxAge: 0 }) // Don't cache because we could regenerate the torrent file
65fcc311 36)
02756fbd
C
37staticRouter.use(
38 STATIC_DOWNLOAD_PATHS.TORRENTS + ':id-:resolution([0-9]+).torrent',
39 asyncMiddleware(videosGetValidator),
40 asyncMiddleware(downloadTorrent)
41)
65fcc311
C
42
43// Videos path for webseeding
65fcc311
C
44staticRouter.use(
45 STATIC_PATHS.WEBSEED,
46 cors(),
b9fffa29 47 express.static(CONFIG.STORAGE.VIDEOS_DIR, { fallthrough: false }) // 404 because we don't have this video
65fcc311 48)
6040f87d 49staticRouter.use(
b9fffa29 50 STATIC_PATHS.REDUNDANCY,
6040f87d 51 cors(),
b9fffa29 52 express.static(CONFIG.STORAGE.REDUNDANCY_DIR, { fallthrough: false }) // 404 because we don't have this video
6040f87d
C
53)
54
02756fbd
C
55staticRouter.use(
56 STATIC_DOWNLOAD_PATHS.VIDEOS + ':id-:resolution([0-9]+).:extension',
57 asyncMiddleware(videosGetValidator),
58 asyncMiddleware(downloadVideoFile)
59)
65fcc311 60
09209296
C
61// HLS
62staticRouter.use(
9c6ca37f 63 STATIC_PATHS.STREAMING_PLAYLISTS.HLS,
09209296 64 cors(),
9c6ca37f 65 express.static(HLS_STREAMING_PLAYLIST_DIRECTORY, { fallthrough: false }) // 404 if the file does not exist
09209296
C
66)
67
65fcc311
C
68// Thumbnails path for express
69const thumbnailsPhysicalPath = CONFIG.STORAGE.THUMBNAILS_DIR
70staticRouter.use(
71 STATIC_PATHS.THUMBNAILS,
a8bf1d82 72 express.static(thumbnailsPhysicalPath, { maxAge: STATIC_MAX_AGE, fallthrough: false }) // 404 if the file does not exist
65fcc311
C
73)
74
c5911fd3
C
75const avatarsPhysicalPath = CONFIG.STORAGE.AVATARS_DIR
76staticRouter.use(
77 STATIC_PATHS.AVATARS,
a8bf1d82 78 express.static(avatarsPhysicalPath, { maxAge: STATIC_MAX_AGE, fallthrough: false }) // 404 if the file does not exist
c5911fd3
C
79)
80
40e87e9e 81// We don't have video previews, fetch them from the origin instance
65fcc311 82staticRouter.use(
f981dae8 83 STATIC_PATHS.PREVIEWS + ':uuid.jpg',
eb080476 84 asyncMiddleware(getPreview)
65fcc311
C
85)
86
40e87e9e
C
87// We don't have video captions, fetch them from the origin instance
88staticRouter.use(
89 STATIC_PATHS.VIDEO_CAPTIONS + ':videoId-:captionLanguage([a-z]+).vtt',
90 asyncMiddleware(getVideoCaption)
91)
92
ac235c37 93// robots.txt service
3f6d68d9 94staticRouter.get('/robots.txt',
98d3324d 95 asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.ROBOTS)),
3f6d68d9
RK
96 (_, res: express.Response) => {
97 res.type('text/plain')
98 return res.send(CONFIG.INSTANCE.ROBOTS)
99 }
100)
101
5447516b
AH
102// security.txt service
103staticRouter.get('/security.txt',
104 (_, res: express.Response) => {
105 return res.redirect(301, '/.well-known/security.txt')
106 }
107)
108
109staticRouter.get('/.well-known/security.txt',
110 asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.SECURITYTXT)),
111 (_, res: express.Response) => {
112 res.type('text/plain')
113 return res.send(CONFIG.INSTANCE.SECURITYTXT + CONFIG.INSTANCE.SECURITYTXT_CONTACT)
114 }
115)
116
3f6d68d9
RK
117// nodeinfo service
118staticRouter.use('/.well-known/nodeinfo',
98d3324d 119 asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.NODEINFO)),
3f6d68d9
RK
120 (_, res: express.Response) => {
121 return res.json({
122 links: [
123 {
124 rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0',
6dd9de95 125 href: WEBSERVER.URL + '/nodeinfo/2.0.json'
3f6d68d9
RK
126 }
127 ]
128 })
129 }
130)
131staticRouter.use('/nodeinfo/:version.json',
aad0ec24 132 asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.NODEINFO)),
3f6d68d9
RK
133 asyncMiddleware(generateNodeinfo)
134)
ac235c37 135
aad0ec24
RK
136// dnt-policy.txt service (see https://www.eff.org/dnt-policy)
137staticRouter.use('/.well-known/dnt-policy.txt',
138 asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.DNT_POLICY)),
139 (_, res: express.Response) => {
140 res.type('text/plain')
aac0118d 141
d1105b97 142 return res.sendFile(join(root(), 'dist/server/static/dnt-policy/dnt-policy-1.0.txt'))
aad0ec24
RK
143 }
144)
145
146// dnt service (see https://www.w3.org/TR/tracking-dnt/#status-resource)
147staticRouter.use('/.well-known/dnt/',
148 (_, res: express.Response) => {
149 res.json({ tracking: 'N' })
31414127
RK
150 }
151)
152
153staticRouter.use('/.well-known/change-password',
154 (_, res: express.Response) => {
155 res.redirect('/my-account/settings')
aad0ec24
RK
156 }
157)
158
65fcc311
C
159// ---------------------------------------------------------------------------
160
161export {
162 staticRouter
163}
f981dae8
C
164
165// ---------------------------------------------------------------------------
166
e8bafea3 167async function getPreview (req: express.Request, res: express.Response) {
40e87e9e
C
168 const path = await VideosPreviewCache.Instance.getFilePath(req.params.uuid)
169 if (!path) return res.sendStatus(404)
170
171 return res.sendFile(path, { maxAge: STATIC_MAX_AGE })
172}
173
174async function getVideoCaption (req: express.Request, res: express.Response) {
175 const path = await VideosCaptionCache.Instance.getFilePath({
176 videoId: req.params.videoId,
177 language: req.params.captionLanguage
178 })
eb080476 179 if (!path) return res.sendStatus(404)
f981dae8 180
eb080476 181 return res.sendFile(path, { maxAge: STATIC_MAX_AGE })
f981dae8 182}
02756fbd 183
3f6d68d9
RK
184async function generateNodeinfo (req: express.Request, res: express.Response, next: express.NextFunction) {
185 const { totalVideos } = await VideoModel.getStats()
186 const { totalLocalVideoComments } = await VideoCommentModel.getStats()
187 const { totalUsers } = await UserModel.getStats()
188 let json = {}
189
190 if (req.params.version && (req.params.version === '2.0')) {
191 json = {
192 version: '2.0',
193 software: {
194 name: 'peertube',
195 version: packageJSON.version
196 },
197 protocols: [
198 'activitypub'
199 ],
200 services: {
201 inbound: [],
202 outbound: [
203 'atom1.0',
204 'rss2.0'
205 ]
206 },
207 openRegistrations: CONFIG.SIGNUP.ENABLED,
208 usage: {
209 users: {
210 total: totalUsers
211 },
212 localPosts: totalVideos,
213 localComments: totalLocalVideoComments
214 },
215 metadata: {
216 taxonomy: {
217 postsName: 'Videos'
218 },
219 nodeName: CONFIG.INSTANCE.NAME,
220 nodeDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION
221 }
222 } as HttpNodeinfoDiasporaSoftwareNsSchema20
98d3324d 223 res.contentType('application/json; profile="http://nodeinfo.diaspora.software/ns/schema/2.0#"')
3f6d68d9
RK
224 } else {
225 json = { error: 'Nodeinfo schema version not handled' }
226 res.status(404)
227 }
228
98d3324d 229 return res.send(json).end()
3f6d68d9
RK
230}
231
02756fbd 232async function downloadTorrent (req: express.Request, res: express.Response, next: express.NextFunction) {
9118bca3 233 const { video, videoFile } = getVideoAndFile(req, res)
02756fbd
C
234 if (!videoFile) return res.status(404).end()
235
236 return res.download(video.getTorrentFilePath(videoFile), `${video.name}-${videoFile.resolution}p.torrent`)
237}
238
239async function downloadVideoFile (req: express.Request, res: express.Response, next: express.NextFunction) {
9118bca3 240 const { video, videoFile } = getVideoAndFile(req, res)
02756fbd
C
241 if (!videoFile) return res.status(404).end()
242
243 return res.download(video.getVideoFilePath(videoFile), `${video.name}-${videoFile.resolution}p${videoFile.extname}`)
244}
245
9118bca3 246function getVideoAndFile (req: express.Request, res: express.Response) {
02756fbd 247 const resolution = parseInt(req.params.resolution, 10)
dae86118 248 const video = res.locals.video
02756fbd
C
249
250 const videoFile = video.VideoFiles.find(f => f.resolution === resolution)
251
252 return { video, videoFile }
253}