X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Fmodels%2Fvideo%2Fvideo-streaming-playlist.ts;h=a85c79c9f1d96c64c2542926aa964954bcfba0b8;hb=8c4bbd946d2247c2e239cbbf8773d2d31c1a57aa;hp=b8dc7c450340920a9de516b0cd8f83eb91463835;hpb=c6c0fa6cd8fe8f752463d8982c3dbcd448739c4e;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/models/video/video-streaming-playlist.ts b/server/models/video/video-streaming-playlist.ts index b8dc7c450..a85c79c9f 100644 --- a/server/models/video/video-streaming-playlist.ts +++ b/server/models/video/video-streaming-playlist.ts @@ -1,28 +1,44 @@ -import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, HasMany, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' -import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos' -import { throwIfNotValid } from '../utils' -import { VideoModel } from './video' -import { VideoRedundancyModel } from '../redundancy/video-redundancy' +import memoizee from 'memoizee' +import { join } from 'path' +import { Op, Transaction } from 'sequelize' +import { + AllowNull, + BelongsTo, + Column, + CreatedAt, + DataType, + Default, + ForeignKey, + HasMany, + Is, + Model, + Table, + UpdatedAt +} from 'sequelize-typescript' +import { CONFIG } from '@server/initializers/config' +import { getHLSPrivateFileUrl, getHLSPublicFileUrl } from '@server/lib/object-storage' +import { generateHLSMasterPlaylistFilename, generateHlsSha256SegmentsFilename } from '@server/lib/paths' +import { isVideoInPrivateDirectory } from '@server/lib/video-privacy' +import { VideoFileModel } from '@server/models/video/video-file' +import { MStreamingPlaylist, MStreamingPlaylistFilesVideo, MVideo } from '@server/types/models' +import { sha1 } from '@shared/extra-utils' +import { VideoStorage } from '@shared/models' +import { AttributesOnly } from '@shared/typescript-utils' import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' +import { isArrayOf } from '../../helpers/custom-validators/misc' +import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos' import { CONSTRAINTS_FIELDS, MEMOIZE_LENGTH, MEMOIZE_TTL, P2P_MEDIA_LOADER_PEER_VERSION, - STATIC_DOWNLOAD_PATHS, - STATIC_PATHS + STATIC_PATHS, + WEBSERVER } from '../../initializers/constants' -import { join } from 'path' -import { sha1 } from '../../helpers/core-utils' -import { isArrayOf } from '../../helpers/custom-validators/misc' -import { Op, QueryTypes } from 'sequelize' -import { MStreamingPlaylist, MStreamingPlaylistVideo, MVideoFile } from '@server/types/models' -import { VideoFileModel } from '@server/models/video/video-file' -import { getTorrentFileName, getTorrentFilePath, getVideoFilename } from '@server/lib/video-paths' -import * as memoizee from 'memoizee' -import { remove } from 'fs-extra' -import { logger } from '@server/helpers/logger' +import { VideoRedundancyModel } from '../redundancy/video-redundancy' +import { doesExist, throwIfNotValid } from '../shared' +import { VideoModel } from './video' @Table({ tableName: 'videoStreamingPlaylist', @@ -40,7 +56,7 @@ import { logger } from '@server/helpers/logger' } ] }) -export class VideoStreamingPlaylistModel extends Model { +export class VideoStreamingPlaylistModel extends Model>> { @CreatedAt createdAt: Date @@ -52,7 +68,11 @@ export class VideoStreamingPlaylistModel extends Model throwIfNotValid(value, isActivityPubUrlValid, 'playlist url')) + @Column + playlistFilename: string + + @AllowNull(true) + @Is('PlaylistUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'playlist url', true)) @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.URL.max)) playlistUrl: string @@ -66,7 +86,11 @@ export class VideoStreamingPlaylistModel extends Model throwIfNotValid(value, isActivityPubUrlValid, 'segments sha256 url')) + @Column + segmentsSha256Filename: string + + @AllowNull(true) + @Is('VideoStreamingSegmentsSha256Url', value => throwIfNotValid(value, isActivityPubUrlValid, 'segments sha256 url', true)) @Column segmentsSha256Url: string @@ -74,6 +98,11 @@ export class VideoStreamingPlaylistModel extends Model VideoModel, { foreignKey: { allowNull: false @@ -107,14 +136,8 @@ export class VideoStreamingPlaylistModel extends Model(query, options) - .then(results => results.length === 1) + return doesExist(this.sequelize, query, { infoHash }) } static buildP2PMediaLoaderInfoHashes (playlistUrl: string, files: unknown[]) { @@ -134,12 +157,34 @@ export class VideoStreamingPlaylistModel extends Model(id, options) + } + static loadWithVideo (id: number) { const options = { include: [ @@ -153,56 +198,103 @@ export class VideoStreamingPlaylistModel extends Model { + const options = { + where: { + type: VideoStreamingPlaylistType.HLS, + videoId + }, + transaction + } - static getMasterHlsPlaylistFilename () { - return 'master.m3u8' + return VideoStreamingPlaylistModel.findOne(options) } - static getHlsSha256SegmentsFilename () { - return 'segments-sha256.json' - } + static async loadOrGenerate (video: MVideo, transaction?: Transaction) { + let playlist = await VideoStreamingPlaylistModel.loadHLSPlaylistByVideo(video.id, transaction) + + if (!playlist) { + playlist = new VideoStreamingPlaylistModel({ + p2pMediaLoaderPeerVersion: P2P_MEDIA_LOADER_PEER_VERSION, + type: VideoStreamingPlaylistType.HLS, + storage: VideoStorage.FILE_SYSTEM, + p2pMediaLoaderInfohashes: [], + playlistFilename: generateHLSMasterPlaylistFilename(video.isLive), + segmentsSha256Filename: generateHlsSha256SegmentsFilename(video.isLive), + videoId: video.id + }) + + await playlist.save({ transaction }) + } - static getHlsMasterPlaylistStaticPath (videoUUID: string) { - return join(STATIC_PATHS.STREAMING_PLAYLISTS.HLS, videoUUID, VideoStreamingPlaylistModel.getMasterHlsPlaylistFilename()) + return Object.assign(playlist, { Video: video }) } - static getHlsPlaylistStaticPath (videoUUID: string, resolution: number) { - return join(STATIC_PATHS.STREAMING_PLAYLISTS.HLS, videoUUID, VideoStreamingPlaylistModel.getHlsPlaylistFilename(resolution)) + static doesOwnedHLSPlaylistExist (videoUUID: string) { + const query = `SELECT 1 FROM "videoStreamingPlaylist" ` + + `INNER JOIN "video" ON "video"."id" = "videoStreamingPlaylist"."videoId" ` + + `AND "video"."remote" IS FALSE AND "video"."uuid" = $videoUUID ` + + `AND "storage" = ${VideoStorage.FILE_SYSTEM} LIMIT 1` + + return doesExist(this.sequelize, query, { videoUUID }) } - static getHlsSha256SegmentsStaticPath (videoUUID: string, isLive: boolean) { - if (isLive) return join('/live', 'segments-sha256', videoUUID) + assignP2PMediaLoaderInfoHashes (video: MVideo, files: unknown[]) { + const masterPlaylistUrl = this.getMasterPlaylistUrl(video) - return join(STATIC_PATHS.STREAMING_PLAYLISTS.HLS, videoUUID, VideoStreamingPlaylistModel.getHlsSha256SegmentsFilename()) + this.p2pMediaLoaderInfohashes = VideoStreamingPlaylistModel.buildP2PMediaLoaderInfoHashes(masterPlaylistUrl, files) } - getStringType () { - if (this.type === VideoStreamingPlaylistType.HLS) return 'hls' + // --------------------------------------------------------------------------- - return 'unknown' - } + getMasterPlaylistUrl (video: MVideo) { + if (video.isOwned()) { + if (this.storage === VideoStorage.OBJECT_STORAGE) { + return this.getMasterPlaylistObjectStorageUrl(video) + } - getVideoRedundancyUrl (baseUrlHttp: string) { - return baseUrlHttp + STATIC_PATHS.REDUNDANCY + this.getStringType() + '/' + this.Video.uuid + return WEBSERVER.URL + this.getMasterPlaylistStaticPath(video) + } + + return this.playlistUrl } - getTorrentDownloadUrl (videoFile: MVideoFile, baseUrlHttp: string) { - return baseUrlHttp + STATIC_DOWNLOAD_PATHS.TORRENTS + getTorrentFileName(this, videoFile) + private getMasterPlaylistObjectStorageUrl (video: MVideo) { + if (video.hasPrivateStaticPath() && CONFIG.OBJECT_STORAGE.PROXY.PROXIFY_PRIVATE_FILES === true) { + return getHLSPrivateFileUrl(video, this.playlistFilename) + } + + return getHLSPublicFileUrl(this.playlistUrl) } - getVideoFileDownloadUrl (videoFile: MVideoFile, baseUrlHttp: string) { - return baseUrlHttp + STATIC_DOWNLOAD_PATHS.HLS_VIDEOS + getVideoFilename(this, videoFile) + // --------------------------------------------------------------------------- + + getSha256SegmentsUrl (video: MVideo) { + if (video.isOwned()) { + if (this.storage === VideoStorage.OBJECT_STORAGE) { + return this.getSha256SegmentsObjectStorageUrl(video) + } + + return WEBSERVER.URL + this.getSha256SegmentsStaticPath(video) + } + + return this.segmentsSha256Url } - getVideoFileUrl (videoFile: MVideoFile, baseUrlHttp: string) { - return baseUrlHttp + join(STATIC_PATHS.STREAMING_PLAYLISTS.HLS, this.Video.uuid, getVideoFilename(this, videoFile)) + private getSha256SegmentsObjectStorageUrl (video: MVideo) { + if (video.hasPrivateStaticPath() && CONFIG.OBJECT_STORAGE.PROXY.PROXIFY_PRIVATE_FILES === true) { + return getHLSPrivateFileUrl(video, this.segmentsSha256Filename) + } + + return getHLSPublicFileUrl(this.segmentsSha256Url) } - getTorrentUrl (videoFile: MVideoFile, baseUrlHttp: string) { - return baseUrlHttp + join(STATIC_PATHS.TORRENTS, getTorrentFileName(this, videoFile)) + // --------------------------------------------------------------------------- + + getStringType () { + if (this.type === VideoStreamingPlaylistType.HLS) return 'hls' + + return 'unknown' } getTrackerUrls (baseUrlHttp: string, baseUrlWs: string) { @@ -214,9 +306,23 @@ export class VideoStreamingPlaylistModel extends Model logger.warn('Cannot delete torrent %s.', torrentPath, { err })) + withVideo (video: MVideo) { + return Object.assign(this, { Video: video }) + } + + private getMasterPlaylistStaticPath (video: MVideo) { + if (isVideoInPrivateDirectory(video.privacy)) { + return join(STATIC_PATHS.STREAMING_PLAYLISTS.PRIVATE_HLS, video.uuid, this.playlistFilename) + } + + return join(STATIC_PATHS.STREAMING_PLAYLISTS.HLS, video.uuid, this.playlistFilename) + } + + private getSha256SegmentsStaticPath (video: MVideo) { + if (isVideoInPrivateDirectory(video.privacy)) { + return join(STATIC_PATHS.STREAMING_PLAYLISTS.PRIVATE_HLS, video.uuid, this.segmentsSha256Filename) + } + + return join(STATIC_PATHS.STREAMING_PLAYLISTS.HLS, video.uuid, this.segmentsSha256Filename) } }