diff options
Diffstat (limited to 'server/models/video/video.ts')
-rw-r--r-- | server/models/video/video.ts | 183 |
1 files changed, 152 insertions, 31 deletions
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 806b6e046..73626b6a0 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -52,7 +52,7 @@ import { | |||
52 | ACTIVITY_PUB, | 52 | ACTIVITY_PUB, |
53 | API_VERSION, | 53 | API_VERSION, |
54 | CONFIG, | 54 | CONFIG, |
55 | CONSTRAINTS_FIELDS, | 55 | CONSTRAINTS_FIELDS, HLS_PLAYLIST_DIRECTORY, HLS_REDUNDANCY_DIRECTORY, |
56 | PREVIEWS_SIZE, | 56 | PREVIEWS_SIZE, |
57 | REMOTE_SCHEME, | 57 | REMOTE_SCHEME, |
58 | STATIC_DOWNLOAD_PATHS, | 58 | STATIC_DOWNLOAD_PATHS, |
@@ -95,6 +95,7 @@ import * as validator from 'validator' | |||
95 | import { UserVideoHistoryModel } from '../account/user-video-history' | 95 | import { UserVideoHistoryModel } from '../account/user-video-history' |
96 | import { UserModel } from '../account/user' | 96 | import { UserModel } from '../account/user' |
97 | import { VideoImportModel } from './video-import' | 97 | import { VideoImportModel } from './video-import' |
98 | import { VideoStreamingPlaylistModel } from './video-streaming-playlist' | ||
98 | 99 | ||
99 | // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation | 100 | // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation |
100 | const indexes: Sequelize.DefineIndexesOptions[] = [ | 101 | const indexes: Sequelize.DefineIndexesOptions[] = [ |
@@ -160,7 +161,9 @@ export enum ScopeNames { | |||
160 | WITH_FILES = 'WITH_FILES', | 161 | WITH_FILES = 'WITH_FILES', |
161 | WITH_SCHEDULED_UPDATE = 'WITH_SCHEDULED_UPDATE', | 162 | WITH_SCHEDULED_UPDATE = 'WITH_SCHEDULED_UPDATE', |
162 | WITH_BLACKLISTED = 'WITH_BLACKLISTED', | 163 | WITH_BLACKLISTED = 'WITH_BLACKLISTED', |
163 | WITH_USER_HISTORY = 'WITH_USER_HISTORY' | 164 | WITH_USER_HISTORY = 'WITH_USER_HISTORY', |
165 | WITH_STREAMING_PLAYLISTS = 'WITH_STREAMING_PLAYLISTS', | ||
166 | WITH_USER_ID = 'WITH_USER_ID' | ||
164 | } | 167 | } |
165 | 168 | ||
166 | type ForAPIOptions = { | 169 | type ForAPIOptions = { |
@@ -464,6 +467,22 @@ type AvailableForListIDsOptions = { | |||
464 | 467 | ||
465 | return query | 468 | return query |
466 | }, | 469 | }, |
470 | [ ScopeNames.WITH_USER_ID ]: { | ||
471 | include: [ | ||
472 | { | ||
473 | attributes: [ 'accountId' ], | ||
474 | model: () => VideoChannelModel.unscoped(), | ||
475 | required: true, | ||
476 | include: [ | ||
477 | { | ||
478 | attributes: [ 'userId' ], | ||
479 | model: () => AccountModel.unscoped(), | ||
480 | required: true | ||
481 | } | ||
482 | ] | ||
483 | } | ||
484 | ] | ||
485 | }, | ||
467 | [ ScopeNames.WITH_ACCOUNT_DETAILS ]: { | 486 | [ ScopeNames.WITH_ACCOUNT_DETAILS ]: { |
468 | include: [ | 487 | include: [ |
469 | { | 488 | { |
@@ -528,22 +547,55 @@ type AvailableForListIDsOptions = { | |||
528 | } | 547 | } |
529 | ] | 548 | ] |
530 | }, | 549 | }, |
531 | [ ScopeNames.WITH_FILES ]: { | 550 | [ ScopeNames.WITH_FILES ]: (withRedundancies = false) => { |
532 | include: [ | 551 | let subInclude: any[] = [] |
533 | { | 552 | |
534 | model: () => VideoFileModel.unscoped(), | 553 | if (withRedundancies === true) { |
535 | // FIXME: typings | 554 | subInclude = [ |
536 | [ 'separate' as any ]: true, // We may have multiple files, having multiple redundancies so let's separate this join | 555 | { |
537 | required: false, | 556 | attributes: [ 'fileUrl' ], |
538 | include: [ | 557 | model: VideoRedundancyModel.unscoped(), |
539 | { | 558 | required: false |
540 | attributes: [ 'fileUrl' ], | 559 | } |
541 | model: () => VideoRedundancyModel.unscoped(), | 560 | ] |
542 | required: false | 561 | } |
543 | } | 562 | |
544 | ] | 563 | return { |
545 | } | 564 | include: [ |
546 | ] | 565 | { |
566 | model: VideoFileModel.unscoped(), | ||
567 | // FIXME: typings | ||
568 | [ 'separate' as any ]: true, // We may have multiple files, having multiple redundancies so let's separate this join | ||
569 | required: false, | ||
570 | include: subInclude | ||
571 | } | ||
572 | ] | ||
573 | } | ||
574 | }, | ||
575 | [ ScopeNames.WITH_STREAMING_PLAYLISTS ]: (withRedundancies = false) => { | ||
576 | let subInclude: any[] = [] | ||
577 | |||
578 | if (withRedundancies === true) { | ||
579 | subInclude = [ | ||
580 | { | ||
581 | attributes: [ 'fileUrl' ], | ||
582 | model: VideoRedundancyModel.unscoped(), | ||
583 | required: false | ||
584 | } | ||
585 | ] | ||
586 | } | ||
587 | |||
588 | return { | ||
589 | include: [ | ||
590 | { | ||
591 | model: VideoStreamingPlaylistModel.unscoped(), | ||
592 | // FIXME: typings | ||
593 | [ 'separate' as any ]: true, // We may have multiple streaming playlists, having multiple redundancies so let's separate this join | ||
594 | required: false, | ||
595 | include: subInclude | ||
596 | } | ||
597 | ] | ||
598 | } | ||
547 | }, | 599 | }, |
548 | [ ScopeNames.WITH_SCHEDULED_UPDATE ]: { | 600 | [ ScopeNames.WITH_SCHEDULED_UPDATE ]: { |
549 | include: [ | 601 | include: [ |
@@ -666,6 +718,10 @@ export class VideoModel extends Model<VideoModel> { | |||
666 | 718 | ||
667 | @AllowNull(false) | 719 | @AllowNull(false) |
668 | @Column | 720 | @Column |
721 | downloadEnabled: boolean | ||
722 | |||
723 | @AllowNull(false) | ||
724 | @Column | ||
669 | waitTranscoding: boolean | 725 | waitTranscoding: boolean |
670 | 726 | ||
671 | @AllowNull(false) | 727 | @AllowNull(false) |
@@ -726,6 +782,16 @@ export class VideoModel extends Model<VideoModel> { | |||
726 | }) | 782 | }) |
727 | VideoFiles: VideoFileModel[] | 783 | VideoFiles: VideoFileModel[] |
728 | 784 | ||
785 | @HasMany(() => VideoStreamingPlaylistModel, { | ||
786 | foreignKey: { | ||
787 | name: 'videoId', | ||
788 | allowNull: false | ||
789 | }, | ||
790 | hooks: true, | ||
791 | onDelete: 'cascade' | ||
792 | }) | ||
793 | VideoStreamingPlaylists: VideoStreamingPlaylistModel[] | ||
794 | |||
729 | @HasMany(() => VideoShareModel, { | 795 | @HasMany(() => VideoShareModel, { |
730 | foreignKey: { | 796 | foreignKey: { |
731 | name: 'videoId', | 797 | name: 'videoId', |
@@ -851,6 +917,9 @@ export class VideoModel extends Model<VideoModel> { | |||
851 | tasks.push(instance.removeFile(file)) | 917 | tasks.push(instance.removeFile(file)) |
852 | tasks.push(instance.removeTorrent(file)) | 918 | tasks.push(instance.removeTorrent(file)) |
853 | }) | 919 | }) |
920 | |||
921 | // Remove playlists file | ||
922 | tasks.push(instance.removeStreamingPlaylist()) | ||
854 | } | 923 | } |
855 | 924 | ||
856 | // Do not wait video deletion because we could be in a transaction | 925 | // Do not wait video deletion because we could be in a transaction |
@@ -862,10 +931,6 @@ export class VideoModel extends Model<VideoModel> { | |||
862 | return undefined | 931 | return undefined |
863 | } | 932 | } |
864 | 933 | ||
865 | static list () { | ||
866 | return VideoModel.scope(ScopeNames.WITH_FILES).findAll() | ||
867 | } | ||
868 | |||
869 | static listLocal () { | 934 | static listLocal () { |
870 | const query = { | 935 | const query = { |
871 | where: { | 936 | where: { |
@@ -873,7 +938,7 @@ export class VideoModel extends Model<VideoModel> { | |||
873 | } | 938 | } |
874 | } | 939 | } |
875 | 940 | ||
876 | return VideoModel.scope(ScopeNames.WITH_FILES).findAll(query) | 941 | return VideoModel.scope([ ScopeNames.WITH_FILES, ScopeNames.WITH_STREAMING_PLAYLISTS ]).findAll(query) |
877 | } | 942 | } |
878 | 943 | ||
879 | static listAllAndSharedByActorForOutbox (actorId: number, start: number, count: number) { | 944 | static listAllAndSharedByActorForOutbox (actorId: number, start: number, count: number) { |
@@ -1204,6 +1269,16 @@ export class VideoModel extends Model<VideoModel> { | |||
1204 | return VideoModel.findOne(options) | 1269 | return VideoModel.findOne(options) |
1205 | } | 1270 | } |
1206 | 1271 | ||
1272 | static loadWithRights (id: number | string, t?: Sequelize.Transaction) { | ||
1273 | const where = VideoModel.buildWhereIdOrUUID(id) | ||
1274 | const options = { | ||
1275 | where, | ||
1276 | transaction: t | ||
1277 | } | ||
1278 | |||
1279 | return VideoModel.scope([ ScopeNames.WITH_BLACKLISTED, ScopeNames.WITH_USER_ID ]).findOne(options) | ||
1280 | } | ||
1281 | |||
1207 | static loadOnlyId (id: number | string, t?: Sequelize.Transaction) { | 1282 | static loadOnlyId (id: number | string, t?: Sequelize.Transaction) { |
1208 | const where = VideoModel.buildWhereIdOrUUID(id) | 1283 | const where = VideoModel.buildWhereIdOrUUID(id) |
1209 | 1284 | ||
@@ -1216,8 +1291,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1216 | return VideoModel.findOne(options) | 1291 | return VideoModel.findOne(options) |
1217 | } | 1292 | } |
1218 | 1293 | ||
1219 | static loadWithFile (id: number, t?: Sequelize.Transaction, logging?: boolean) { | 1294 | static loadWithFiles (id: number, t?: Sequelize.Transaction, logging?: boolean) { |
1220 | return VideoModel.scope(ScopeNames.WITH_FILES) | 1295 | return VideoModel.scope([ ScopeNames.WITH_FILES, ScopeNames.WITH_STREAMING_PLAYLISTS ]) |
1221 | .findById(id, { transaction: t, logging }) | 1296 | .findById(id, { transaction: t, logging }) |
1222 | } | 1297 | } |
1223 | 1298 | ||
@@ -1228,9 +1303,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1228 | } | 1303 | } |
1229 | } | 1304 | } |
1230 | 1305 | ||
1231 | return VideoModel | 1306 | return VideoModel.findOne(options) |
1232 | .scope([ ScopeNames.WITH_FILES ]) | ||
1233 | .findOne(options) | ||
1234 | } | 1307 | } |
1235 | 1308 | ||
1236 | static loadByUrl (url: string, transaction?: Sequelize.Transaction) { | 1309 | static loadByUrl (url: string, transaction?: Sequelize.Transaction) { |
@@ -1252,7 +1325,11 @@ export class VideoModel extends Model<VideoModel> { | |||
1252 | transaction | 1325 | transaction |
1253 | } | 1326 | } |
1254 | 1327 | ||
1255 | return VideoModel.scope([ ScopeNames.WITH_ACCOUNT_DETAILS, ScopeNames.WITH_FILES ]).findOne(query) | 1328 | return VideoModel.scope([ |
1329 | ScopeNames.WITH_ACCOUNT_DETAILS, | ||
1330 | ScopeNames.WITH_FILES, | ||
1331 | ScopeNames.WITH_STREAMING_PLAYLISTS | ||
1332 | ]).findOne(query) | ||
1256 | } | 1333 | } |
1257 | 1334 | ||
1258 | static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Sequelize.Transaction, userId?: number) { | 1335 | static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Sequelize.Transaction, userId?: number) { |
@@ -1267,9 +1344,37 @@ export class VideoModel extends Model<VideoModel> { | |||
1267 | const scopes = [ | 1344 | const scopes = [ |
1268 | ScopeNames.WITH_TAGS, | 1345 | ScopeNames.WITH_TAGS, |
1269 | ScopeNames.WITH_BLACKLISTED, | 1346 | ScopeNames.WITH_BLACKLISTED, |
1347 | ScopeNames.WITH_ACCOUNT_DETAILS, | ||
1348 | ScopeNames.WITH_SCHEDULED_UPDATE, | ||
1270 | ScopeNames.WITH_FILES, | 1349 | ScopeNames.WITH_FILES, |
1350 | ScopeNames.WITH_STREAMING_PLAYLISTS | ||
1351 | ] | ||
1352 | |||
1353 | if (userId) { | ||
1354 | scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] } as any) // FIXME: typings | ||
1355 | } | ||
1356 | |||
1357 | return VideoModel | ||
1358 | .scope(scopes) | ||
1359 | .findOne(options) | ||
1360 | } | ||
1361 | |||
1362 | static loadForGetAPI (id: number | string, t?: Sequelize.Transaction, userId?: number) { | ||
1363 | const where = VideoModel.buildWhereIdOrUUID(id) | ||
1364 | |||
1365 | const options = { | ||
1366 | order: [ [ 'Tags', 'name', 'ASC' ] ], | ||
1367 | where, | ||
1368 | transaction: t | ||
1369 | } | ||
1370 | |||
1371 | const scopes = [ | ||
1372 | ScopeNames.WITH_TAGS, | ||
1373 | ScopeNames.WITH_BLACKLISTED, | ||
1271 | ScopeNames.WITH_ACCOUNT_DETAILS, | 1374 | ScopeNames.WITH_ACCOUNT_DETAILS, |
1272 | ScopeNames.WITH_SCHEDULED_UPDATE | 1375 | ScopeNames.WITH_SCHEDULED_UPDATE, |
1376 | { method: [ ScopeNames.WITH_FILES, true ] } as any, // FIXME: typings | ||
1377 | { method: [ ScopeNames.WITH_STREAMING_PLAYLISTS, true ] } as any // FIXME: typings | ||
1273 | ] | 1378 | ] |
1274 | 1379 | ||
1275 | if (userId) { | 1380 | if (userId) { |
@@ -1616,6 +1721,14 @@ export class VideoModel extends Model<VideoModel> { | |||
1616 | .catch(err => logger.warn('Cannot delete torrent %s.', torrentPath, { err })) | 1721 | .catch(err => logger.warn('Cannot delete torrent %s.', torrentPath, { err })) |
1617 | } | 1722 | } |
1618 | 1723 | ||
1724 | removeStreamingPlaylist (isRedundancy = false) { | ||
1725 | const baseDir = isRedundancy ? HLS_REDUNDANCY_DIRECTORY : HLS_PLAYLIST_DIRECTORY | ||
1726 | |||
1727 | const filePath = join(baseDir, this.uuid) | ||
1728 | return remove(filePath) | ||
1729 | .catch(err => logger.warn('Cannot delete playlist directory %s.', filePath, { err })) | ||
1730 | } | ||
1731 | |||
1619 | isOutdated () { | 1732 | isOutdated () { |
1620 | if (this.isOwned()) return false | 1733 | if (this.isOwned()) return false |
1621 | 1734 | ||
@@ -1650,7 +1763,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1650 | 1763 | ||
1651 | generateMagnetUri (videoFile: VideoFileModel, baseUrlHttp: string, baseUrlWs: string) { | 1764 | generateMagnetUri (videoFile: VideoFileModel, baseUrlHttp: string, baseUrlWs: string) { |
1652 | const xs = this.getTorrentUrl(videoFile, baseUrlHttp) | 1765 | const xs = this.getTorrentUrl(videoFile, baseUrlHttp) |
1653 | const announce = [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ] | 1766 | const announce = this.getTrackerUrls(baseUrlHttp, baseUrlWs) |
1654 | let urlList = [ this.getVideoFileUrl(videoFile, baseUrlHttp) ] | 1767 | let urlList = [ this.getVideoFileUrl(videoFile, baseUrlHttp) ] |
1655 | 1768 | ||
1656 | const redundancies = videoFile.RedundancyVideos | 1769 | const redundancies = videoFile.RedundancyVideos |
@@ -1667,6 +1780,10 @@ export class VideoModel extends Model<VideoModel> { | |||
1667 | return magnetUtil.encode(magnetHash) | 1780 | return magnetUtil.encode(magnetHash) |
1668 | } | 1781 | } |
1669 | 1782 | ||
1783 | getTrackerUrls (baseUrlHttp: string, baseUrlWs: string) { | ||
1784 | return [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ] | ||
1785 | } | ||
1786 | |||
1670 | getThumbnailUrl (baseUrlHttp: string) { | 1787 | getThumbnailUrl (baseUrlHttp: string) { |
1671 | return baseUrlHttp + STATIC_PATHS.THUMBNAILS + this.getThumbnailName() | 1788 | return baseUrlHttp + STATIC_PATHS.THUMBNAILS + this.getThumbnailName() |
1672 | } | 1789 | } |
@@ -1690,4 +1807,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1690 | getVideoFileDownloadUrl (videoFile: VideoFileModel, baseUrlHttp: string) { | 1807 | getVideoFileDownloadUrl (videoFile: VideoFileModel, baseUrlHttp: string) { |
1691 | return baseUrlHttp + STATIC_DOWNLOAD_PATHS.VIDEOS + this.getVideoFilename(videoFile) | 1808 | return baseUrlHttp + STATIC_DOWNLOAD_PATHS.VIDEOS + this.getVideoFilename(videoFile) |
1692 | } | 1809 | } |
1810 | |||
1811 | getBandwidthBits (videoFile: VideoFileModel) { | ||
1812 | return Math.ceil((videoFile.size * 8) / this.duration) | ||
1813 | } | ||
1693 | } | 1814 | } |