aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models/video
diff options
context:
space:
mode:
Diffstat (limited to 'server/models/video')
-rw-r--r--server/models/video/thumbnail.ts5
-rw-r--r--server/models/video/video-caption.ts5
-rw-r--r--server/models/video/video-file.ts9
-rw-r--r--server/models/video/video-format-utils.ts49
-rw-r--r--server/models/video/video.ts54
5 files changed, 59 insertions, 63 deletions
diff --git a/server/models/video/thumbnail.ts b/server/models/video/thumbnail.ts
index 9533c8d19..319e1175a 100644
--- a/server/models/video/thumbnail.ts
+++ b/server/models/video/thumbnail.ts
@@ -15,7 +15,6 @@ import {
15 Table, 15 Table,
16 UpdatedAt 16 UpdatedAt
17} from 'sequelize-typescript' 17} from 'sequelize-typescript'
18import { buildRemoteVideoBaseUrl } from '@server/helpers/activitypub'
19import { afterCommitIfTransaction } from '@server/helpers/database-utils' 18import { afterCommitIfTransaction } from '@server/helpers/database-utils'
20import { MThumbnail, MThumbnailVideo, MVideoWithHost } from '@server/types/models' 19import { MThumbnail, MThumbnailVideo, MVideoWithHost } from '@server/types/models'
21import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' 20import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type'
@@ -168,10 +167,8 @@ export class ThumbnailModel extends Model {
168 const staticPath = ThumbnailModel.types[this.type].staticPath + this.filename 167 const staticPath = ThumbnailModel.types[this.type].staticPath + this.filename
169 168
170 if (video.isOwned()) return WEBSERVER.URL + staticPath 169 if (video.isOwned()) return WEBSERVER.URL + staticPath
171 if (this.fileUrl) return this.fileUrl
172 170
173 // Fallback if we don't have a file URL 171 return this.fileUrl
174 return buildRemoteVideoBaseUrl(video, staticPath)
175 } 172 }
176 173
177 getPath () { 174 getPath () {
diff --git a/server/models/video/video-caption.ts b/server/models/video/video-caption.ts
index 71b067335..0bbe9b752 100644
--- a/server/models/video/video-caption.ts
+++ b/server/models/video/video-caption.ts
@@ -16,7 +16,6 @@ import {
16 UpdatedAt 16 UpdatedAt
17} from 'sequelize-typescript' 17} from 'sequelize-typescript'
18import { v4 as uuidv4 } from 'uuid' 18import { v4 as uuidv4 } from 'uuid'
19import { buildRemoteVideoBaseUrl } from '@server/helpers/activitypub'
20import { MVideoCaption, MVideoCaptionFormattable, MVideoCaptionVideo, MVideoWithHost } from '@server/types/models' 19import { MVideoCaption, MVideoCaptionFormattable, MVideoCaptionVideo, MVideoWithHost } from '@server/types/models'
21import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model' 20import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model'
22import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions' 21import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions'
@@ -208,9 +207,7 @@ export class VideoCaptionModel extends Model {
208 if (!this.Video) this.Video = video as VideoModel 207 if (!this.Video) this.Video = video as VideoModel
209 208
210 if (video.isOwned()) return WEBSERVER.URL + this.getCaptionStaticPath() 209 if (video.isOwned()) return WEBSERVER.URL + this.getCaptionStaticPath()
211 if (this.fileUrl) return this.fileUrl
212 210
213 // Fallback if we don't have a file URL 211 return this.fileUrl
214 return buildRemoteVideoBaseUrl(video, this.getCaptionStaticPath())
215 } 212 }
216} 213}
diff --git a/server/models/video/video-file.ts b/server/models/video/video-file.ts
index 57807cbfd..5a3706259 100644
--- a/server/models/video/video-file.ts
+++ b/server/models/video/video-file.ts
@@ -427,10 +427,8 @@ export class VideoFileModel extends Model {
427 if (!this.Video) this.Video = video as VideoModel 427 if (!this.Video) this.Video = video as VideoModel
428 428
429 if (video.isOwned()) return WEBSERVER.URL + this.getFileStaticPath(video) 429 if (video.isOwned()) return WEBSERVER.URL + this.getFileStaticPath(video)
430 if (this.fileUrl) return this.fileUrl
431 430
432 // Fallback if we don't have a file URL 431 return this.fileUrl
433 return buildRemoteVideoBaseUrl(video, this.getFileStaticPath(video))
434 } 432 }
435 433
436 getFileStaticPath (video: MVideo) { 434 getFileStaticPath (video: MVideo) {
@@ -454,10 +452,7 @@ export class VideoFileModel extends Model {
454 getRemoteTorrentUrl (video: MVideoWithHost) { 452 getRemoteTorrentUrl (video: MVideoWithHost) {
455 if (video.isOwned()) throw new Error(`Video ${video.url} is not a remote video`) 453 if (video.isOwned()) throw new Error(`Video ${video.url} is not a remote video`)
456 454
457 if (this.torrentUrl) return this.torrentUrl 455 return this.torrentUrl
458
459 // Fallback if we don't have a torrent URL
460 return buildRemoteVideoBaseUrl(video, this.getTorrentStaticPath())
461 } 456 }
462 457
463 // We proxify torrent requests so use a local URL 458 // We proxify torrent requests so use a local URL
diff --git a/server/models/video/video-format-utils.ts b/server/models/video/video-format-utils.ts
index adf460734..9dc3e7722 100644
--- a/server/models/video/video-format-utils.ts
+++ b/server/models/video/video-format-utils.ts
@@ -14,8 +14,6 @@ import {
14} from '../../lib/activitypub/url' 14} from '../../lib/activitypub/url'
15import { 15import {
16 MStreamingPlaylistRedundanciesOpt, 16 MStreamingPlaylistRedundanciesOpt,
17 MStreamingPlaylistVideo,
18 MVideo,
19 MVideoAP, 17 MVideoAP,
20 MVideoFile, 18 MVideoFile,
21 MVideoFormattable, 19 MVideoFormattable,
@@ -127,8 +125,6 @@ function videoModelToFormattedDetailsJSON (video: MVideoFormattableDetails): Vid
127 } 125 }
128 }) 126 })
129 127
130 const { baseUrlHttp, baseUrlWs } = video.getBaseUrls()
131
132 const tags = video.Tags ? video.Tags.map(t => t.name) : [] 128 const tags = video.Tags ? video.Tags.map(t => t.name) : []
133 129
134 const streamingPlaylists = streamingPlaylistsModelToFormattedJSON(video, video.VideoStreamingPlaylists) 130 const streamingPlaylists = streamingPlaylistsModelToFormattedJSON(video, video.VideoStreamingPlaylists)
@@ -147,14 +143,14 @@ function videoModelToFormattedDetailsJSON (video: MVideoFormattableDetails): Vid
147 label: VideoModel.getStateLabel(video.state) 143 label: VideoModel.getStateLabel(video.state)
148 }, 144 },
149 145
150 trackerUrls: video.getTrackerUrls(baseUrlHttp, baseUrlWs), 146 trackerUrls: video.getTrackerUrls(),
151 147
152 files: [], 148 files: [],
153 streamingPlaylists 149 streamingPlaylists
154 } 150 }
155 151
156 // Format and sort video files 152 // Format and sort video files
157 detailsJson.files = videoFilesModelToFormattedJSON(video, video, baseUrlHttp, baseUrlWs, video.VideoFiles) 153 detailsJson.files = videoFilesModelToFormattedJSON(video, video.VideoFiles)
158 154
159 return Object.assign(formattedJson, detailsJson) 155 return Object.assign(formattedJson, detailsJson)
160} 156}
@@ -165,17 +161,13 @@ function streamingPlaylistsModelToFormattedJSON (
165): VideoStreamingPlaylist[] { 161): VideoStreamingPlaylist[] {
166 if (isArray(playlists) === false) return [] 162 if (isArray(playlists) === false) return []
167 163
168 const { baseUrlHttp, baseUrlWs } = video.getBaseUrls()
169
170 return playlists 164 return playlists
171 .map(playlist => { 165 .map(playlist => {
172 const playlistWithVideo = Object.assign(playlist, { Video: video })
173
174 const redundancies = isArray(playlist.RedundancyVideos) 166 const redundancies = isArray(playlist.RedundancyVideos)
175 ? playlist.RedundancyVideos.map(r => ({ baseUrl: r.fileUrl })) 167 ? playlist.RedundancyVideos.map(r => ({ baseUrl: r.fileUrl }))
176 : [] 168 : []
177 169
178 const files = videoFilesModelToFormattedJSON(playlistWithVideo, video, baseUrlHttp, baseUrlWs, playlist.VideoFiles) 170 const files = videoFilesModelToFormattedJSON(video, playlist.VideoFiles)
179 171
180 return { 172 return {
181 id: playlist.id, 173 id: playlist.id,
@@ -194,14 +186,12 @@ function sortByResolutionDesc (fileA: MVideoFile, fileB: MVideoFile) {
194 return -1 186 return -1
195} 187}
196 188
197// FIXME: refactor/merge model and video arguments
198function videoFilesModelToFormattedJSON ( 189function videoFilesModelToFormattedJSON (
199 model: MVideo | MStreamingPlaylistVideo,
200 video: MVideoFormattableDetails, 190 video: MVideoFormattableDetails,
201 baseUrlHttp: string,
202 baseUrlWs: string,
203 videoFiles: MVideoFileRedundanciesOpt[] 191 videoFiles: MVideoFileRedundanciesOpt[]
204): VideoFile[] { 192): VideoFile[] {
193 const trackerUrls = video.getTrackerUrls()
194
205 return [ ...videoFiles ] 195 return [ ...videoFiles ]
206 .filter(f => !f.isLive()) 196 .filter(f => !f.isLive())
207 .sort(sortByResolutionDesc) 197 .sort(sortByResolutionDesc)
@@ -213,7 +203,7 @@ function videoFilesModelToFormattedJSON (
213 }, 203 },
214 204
215 // FIXME: deprecated in 3.2 205 // FIXME: deprecated in 3.2
216 magnetUri: generateMagnetUri(model, video, videoFile, baseUrlHttp, baseUrlWs), 206 magnetUri: generateMagnetUri(video, videoFile, trackerUrls),
217 207
218 size: videoFile.size, 208 size: videoFile.size,
219 fps: videoFile.fps, 209 fps: videoFile.fps,
@@ -229,15 +219,13 @@ function videoFilesModelToFormattedJSON (
229 }) 219 })
230} 220}
231 221
232// FIXME: refactor/merge model and video arguments
233function addVideoFilesInAPAcc ( 222function addVideoFilesInAPAcc (
234 acc: ActivityUrlObject[] | ActivityTagObject[], 223 acc: ActivityUrlObject[] | ActivityTagObject[],
235 model: MVideoAP | MStreamingPlaylistVideo,
236 video: MVideoWithHost, 224 video: MVideoWithHost,
237 baseUrlHttp: string,
238 baseUrlWs: string,
239 files: MVideoFile[] 225 files: MVideoFile[]
240) { 226) {
227 const trackerUrls = video.getTrackerUrls()
228
241 const sortedFiles = [ ...files ] 229 const sortedFiles = [ ...files ]
242 .filter(f => !f.isLive()) 230 .filter(f => !f.isLive())
243 .sort(sortByResolutionDesc) 231 .sort(sortByResolutionDesc)
@@ -271,14 +259,13 @@ function addVideoFilesInAPAcc (
271 acc.push({ 259 acc.push({
272 type: 'Link', 260 type: 'Link',
273 mediaType: 'application/x-bittorrent;x-scheme-handler/magnet' as 'application/x-bittorrent;x-scheme-handler/magnet', 261 mediaType: 'application/x-bittorrent;x-scheme-handler/magnet' as 'application/x-bittorrent;x-scheme-handler/magnet',
274 href: generateMagnetUri(model, video, file, baseUrlHttp, baseUrlWs), 262 href: generateMagnetUri(video, file, trackerUrls),
275 height: file.resolution 263 height: file.resolution
276 }) 264 })
277 } 265 }
278} 266}
279 267
280function videoModelToActivityPubObject (video: MVideoAP): VideoObject { 268function videoModelToActivityPubObject (video: MVideoAP): VideoObject {
281 const { baseUrlHttp, baseUrlWs } = video.getBaseUrls()
282 if (!video.Tags) video.Tags = [] 269 if (!video.Tags) video.Tags = []
283 270
284 const tag = video.Tags.map(t => ({ 271 const tag = video.Tags.map(t => ({
@@ -319,7 +306,7 @@ function videoModelToActivityPubObject (video: MVideoAP): VideoObject {
319 } 306 }
320 ] 307 ]
321 308
322 addVideoFilesInAPAcc(url, video, video, baseUrlHttp, baseUrlWs, video.VideoFiles || []) 309 addVideoFilesInAPAcc(url, video, video.VideoFiles || [])
323 310
324 for (const playlist of (video.VideoStreamingPlaylists || [])) { 311 for (const playlist of (video.VideoStreamingPlaylists || [])) {
325 const tag = playlist.p2pMediaLoaderInfohashes 312 const tag = playlist.p2pMediaLoaderInfohashes
@@ -331,8 +318,7 @@ function videoModelToActivityPubObject (video: MVideoAP): VideoObject {
331 href: playlist.segmentsSha256Url 318 href: playlist.segmentsSha256Url
332 }) 319 })
333 320
334 const playlistWithVideo = Object.assign(playlist, { Video: video }) 321 addVideoFilesInAPAcc(tag, video, playlist.VideoFiles || [])
335 addVideoFilesInAPAcc(tag, playlistWithVideo, video, baseUrlHttp, baseUrlWs, playlist.VideoFiles || [])
336 322
337 url.push({ 323 url.push({
338 type: 'Link', 324 type: 'Link',
@@ -342,6 +328,19 @@ function videoModelToActivityPubObject (video: MVideoAP): VideoObject {
342 }) 328 })
343 } 329 }
344 330
331 for (const trackerUrl of video.getTrackerUrls()) {
332 const rel2 = trackerUrl.startsWith('http')
333 ? 'http'
334 : 'websocket'
335
336 url.push({
337 type: 'Link',
338 name: `tracker-${rel2}`,
339 rel: [ 'tracker', rel2 ],
340 href: trackerUrl
341 })
342 }
343
345 const subtitleLanguage = [] 344 const subtitleLanguage = []
346 for (const caption of video.VideoCaptions) { 345 for (const caption of video.VideoCaptions) {
347 subtitleLanguage.push({ 346 subtitleLanguage.push({
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 2e6b6aeec..9e67ca0f4 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -60,7 +60,6 @@ import {
60 API_VERSION, 60 API_VERSION,
61 CONSTRAINTS_FIELDS, 61 CONSTRAINTS_FIELDS,
62 LAZY_STATIC_PATHS, 62 LAZY_STATIC_PATHS,
63 REMOTE_SCHEME,
64 STATIC_PATHS, 63 STATIC_PATHS,
65 VIDEO_CATEGORIES, 64 VIDEO_CATEGORIES,
66 VIDEO_LANGUAGES, 65 VIDEO_LANGUAGES,
@@ -107,6 +106,8 @@ import { ActorModel } from '../activitypub/actor'
107import { AvatarModel } from '../avatar/avatar' 106import { AvatarModel } from '../avatar/avatar'
108import { VideoRedundancyModel } from '../redundancy/video-redundancy' 107import { VideoRedundancyModel } from '../redundancy/video-redundancy'
109import { ServerModel } from '../server/server' 108import { ServerModel } from '../server/server'
109import { TrackerModel } from '../server/tracker'
110import { VideoTrackerModel } from '../server/video-tracker'
110import { buildTrigramSearchIndex, buildWhereIdOrUUID, getVideoSort, isOutdated, throwIfNotValid } from '../utils' 111import { buildTrigramSearchIndex, buildWhereIdOrUUID, getVideoSort, isOutdated, throwIfNotValid } from '../utils'
111import { ScheduleVideoUpdateModel } from './schedule-video-update' 112import { ScheduleVideoUpdateModel } from './schedule-video-update'
112import { TagModel } from './tag' 113import { TagModel } from './tag'
@@ -137,6 +138,7 @@ export enum ScopeNames {
137 FOR_API = 'FOR_API', 138 FOR_API = 'FOR_API',
138 WITH_ACCOUNT_DETAILS = 'WITH_ACCOUNT_DETAILS', 139 WITH_ACCOUNT_DETAILS = 'WITH_ACCOUNT_DETAILS',
139 WITH_TAGS = 'WITH_TAGS', 140 WITH_TAGS = 'WITH_TAGS',
141 WITH_TRACKERS = 'WITH_TRACKERS',
140 WITH_WEBTORRENT_FILES = 'WITH_WEBTORRENT_FILES', 142 WITH_WEBTORRENT_FILES = 'WITH_WEBTORRENT_FILES',
141 WITH_SCHEDULED_UPDATE = 'WITH_SCHEDULED_UPDATE', 143 WITH_SCHEDULED_UPDATE = 'WITH_SCHEDULED_UPDATE',
142 WITH_BLACKLISTED = 'WITH_BLACKLISTED', 144 WITH_BLACKLISTED = 'WITH_BLACKLISTED',
@@ -320,6 +322,14 @@ export type AvailableForListIDsOptions = {
320 [ScopeNames.WITH_TAGS]: { 322 [ScopeNames.WITH_TAGS]: {
321 include: [ TagModel ] 323 include: [ TagModel ]
322 }, 324 },
325 [ScopeNames.WITH_TRACKERS]: {
326 include: [
327 {
328 attributes: [ 'id', 'url' ],
329 model: TrackerModel
330 }
331 ]
332 },
323 [ScopeNames.WITH_BLACKLISTED]: { 333 [ScopeNames.WITH_BLACKLISTED]: {
324 include: [ 334 include: [
325 { 335 {
@@ -616,6 +626,13 @@ export class VideoModel extends Model {
616 }) 626 })
617 Tags: TagModel[] 627 Tags: TagModel[]
618 628
629 @BelongsToMany(() => TrackerModel, {
630 foreignKey: 'videoId',
631 through: () => VideoTrackerModel,
632 onDelete: 'CASCADE'
633 })
634 Trackers: TrackerModel[]
635
619 @HasMany(() => ThumbnailModel, { 636 @HasMany(() => ThumbnailModel, {
620 foreignKey: { 637 foreignKey: {
621 name: 'videoId', 638 name: 'videoId',
@@ -1436,6 +1453,7 @@ export class VideoModel extends Model {
1436 ScopeNames.WITH_SCHEDULED_UPDATE, 1453 ScopeNames.WITH_SCHEDULED_UPDATE,
1437 ScopeNames.WITH_THUMBNAILS, 1454 ScopeNames.WITH_THUMBNAILS,
1438 ScopeNames.WITH_LIVE, 1455 ScopeNames.WITH_LIVE,
1456 ScopeNames.WITH_TRACKERS,
1439 { method: [ ScopeNames.WITH_WEBTORRENT_FILES, true ] }, 1457 { method: [ ScopeNames.WITH_WEBTORRENT_FILES, true ] },
1440 { method: [ ScopeNames.WITH_STREAMING_PLAYLISTS, true ] } 1458 { method: [ ScopeNames.WITH_STREAMING_PLAYLISTS, true ] }
1441 ] 1459 ]
@@ -1887,18 +1905,15 @@ export class VideoModel extends Model {
1887 } 1905 }
1888 1906
1889 getFormattedVideoFilesJSON (): VideoFile[] { 1907 getFormattedVideoFilesJSON (): VideoFile[] {
1890 const { baseUrlHttp, baseUrlWs } = this.getBaseUrls()
1891 let files: VideoFile[] = [] 1908 let files: VideoFile[] = []
1892 1909
1893 if (Array.isArray(this.VideoFiles)) { 1910 if (Array.isArray(this.VideoFiles)) {
1894 const result = videoFilesModelToFormattedJSON(this, this, baseUrlHttp, baseUrlWs, this.VideoFiles) 1911 const result = videoFilesModelToFormattedJSON(this, this.VideoFiles)
1895 files = files.concat(result) 1912 files = files.concat(result)
1896 } 1913 }
1897 1914
1898 for (const p of (this.VideoStreamingPlaylists || [])) { 1915 for (const p of (this.VideoStreamingPlaylists || [])) {
1899 p.Video = this 1916 const result = videoFilesModelToFormattedJSON(this, p.VideoFiles)
1900
1901 const result = videoFilesModelToFormattedJSON(p, this, baseUrlHttp, baseUrlWs, p.VideoFiles)
1902 files = files.concat(result) 1917 files = files.concat(result)
1903 } 1918 }
1904 1919
@@ -2030,25 +2045,18 @@ export class VideoModel extends Model {
2030 return false 2045 return false
2031 } 2046 }
2032 2047
2033 getBaseUrls () { 2048 getBandwidthBits (videoFile: MVideoFile) {
2034 if (this.isOwned()) { 2049 return Math.ceil((videoFile.size * 8) / this.duration)
2035 return {
2036 baseUrlHttp: WEBSERVER.URL,
2037 baseUrlWs: WEBSERVER.WS + '://' + WEBSERVER.HOSTNAME + ':' + WEBSERVER.PORT
2038 }
2039 }
2040
2041 return {
2042 baseUrlHttp: REMOTE_SCHEME.HTTP + '://' + this.VideoChannel.Account.Actor.Server.host,
2043 baseUrlWs: REMOTE_SCHEME.WS + '://' + this.VideoChannel.Account.Actor.Server.host
2044 }
2045 } 2050 }
2046 2051
2047 getTrackerUrls (baseUrlHttp: string, baseUrlWs: string) { 2052 getTrackerUrls () {
2048 return [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ] 2053 if (this.isOwned()) {
2049 } 2054 return [
2055 WEBSERVER.URL + '/tracker/announce',
2056 WEBSERVER.WS + '://' + WEBSERVER.HOSTNAME + ':' + WEBSERVER.PORT + '/tracker/socket'
2057 ]
2058 }
2050 2059
2051 getBandwidthBits (videoFile: MVideoFile) { 2060 return this.Trackers.map(t => t.url)
2052 return Math.ceil((videoFile.size * 8) / this.duration)
2053 } 2061 }
2054} 2062}