import { VideosRedundancyScheduler } from './server/lib/schedulers/videos-redundancy-scheduler'
import { isHTTPSignatureDigestValid } from './server/helpers/peertube-crypto'
import { PeerTubeSocket } from './server/lib/peertube-socket'
+import { updateStreamingPlaylistsInfohashesIfNeeded } from './server/lib/hls'
// ----------- Command line -----------
PeerTubeSocket.Instance.init(server)
+ updateStreamingPlaylistsInfohashesIfNeeded()
+ .catch(err => logger.error('Cannot update streaming playlist infohashes.', { err }))
+
// Make server listening
server.listen(port, hostname, () => {
logger.info('Server listening on %s:%d', hostname, port)
}
}
- throw new Error('Duration could not be properly parsed')
+ throw new Error(`Duration ${duration} could not be properly parsed`)
}
export function parseBytes (value: string | number): number {
-import { CONFIG } from '../initializers'
import { VideoModel } from '../models/video/video'
-import { UserRight } from '../../shared'
-import { UserModel } from '../models/account/user'
type VideoFetchType = 'all' | 'only-video' | 'only-video-with-rights' | 'id' | 'none'
// ---------------------------------------------------------------------------
-const LAST_MIGRATION_VERSION = 350
+const LAST_MIGRATION_VERSION = 355
// ---------------------------------------------------------------------------
ANNOUNCES_PER_IP: 30 // maximum announces for all our torrents in the interval
}
+const P2P_MEDIA_LOADER_PEER_VERSION = 2
+
// ---------------------------------------------------------------------------
// Special constants for a test instance
export {
API_VERSION,
HLS_REDUNDANCY_DIRECTORY,
+ P2P_MEDIA_LOADER_PEER_VERSION,
AVATARS_SIZE,
ACCEPT_HEADERS,
BCRYPT_SALT_SIZE,
--- /dev/null
+import * as Sequelize from 'sequelize'
+
+async function up (utils: {
+ transaction: Sequelize.Transaction,
+ queryInterface: Sequelize.QueryInterface,
+ sequelize: Sequelize.Sequelize,
+ db: any
+}): Promise<void> {
+
+ {
+ const data = {
+ type: Sequelize.INTEGER,
+ allowNull: true,
+ defaultValue: null
+ }
+ await utils.queryInterface.addColumn('videoStreamingPlaylist', 'p2pMediaLoaderPeerVersion', data)
+ }
+
+ {
+ const query = `UPDATE "videoStreamingPlaylist" SET "p2pMediaLoaderPeerVersion" = 0;`
+ await utils.sequelize.query(query)
+ }
+
+ {
+ const data = {
+ type: Sequelize.INTEGER,
+ allowNull: false,
+ defaultValue: null
+ }
+ await utils.queryInterface.changeColumn('videoStreamingPlaylist', 'p2pMediaLoaderPeerVersion', data)
+ }
+}
+
+function down (options) {
+ throw new Error('Not implemented.')
+}
+
+export {
+ up,
+ down
+}
}
{
- const streamingPlaylistAttributes = streamingPlaylistActivityUrlToDBAttributes(options.video, options.videoObject)
+ const streamingPlaylistAttributes = streamingPlaylistActivityUrlToDBAttributes(
+ options.video,
+ options.videoObject,
+ options.video.VideoFiles
+ )
const newStreamingPlaylists = streamingPlaylistAttributes.map(a => new VideoStreamingPlaylistModel(a))
// Remove video files that do not exist anymore
}
const videoFilePromises = videoFileAttributes.map(f => VideoFileModel.create(f, { transaction: t }))
- await Promise.all(videoFilePromises)
+ const videoFiles = await Promise.all(videoFilePromises)
- const videoStreamingPlaylists = streamingPlaylistActivityUrlToDBAttributes(videoCreated, videoObject)
+ const videoStreamingPlaylists = streamingPlaylistActivityUrlToDBAttributes(videoCreated, videoObject, videoFiles)
const playlistPromises = videoStreamingPlaylists.map(p => VideoStreamingPlaylistModel.create(p, { transaction: t }))
await Promise.all(playlistPromises)
return attributes
}
-function streamingPlaylistActivityUrlToDBAttributes (video: VideoModel, videoObject: VideoTorrentObject) {
+function streamingPlaylistActivityUrlToDBAttributes (video: VideoModel, videoObject: VideoTorrentObject, videoFiles: VideoFileModel[]) {
const playlistUrls = videoObject.url.filter(u => isAPStreamingPlaylistUrlObject(u)) as ActivityPlaylistUrlObject[]
if (playlistUrls.length === 0) return []
const attributes: FilteredModelAttributes<VideoStreamingPlaylistModel>[] = []
for (const playlistUrlObject of playlistUrls) {
- const p2pMediaLoaderInfohashes = playlistUrlObject.tag
- .filter(t => t.type === 'Infohash')
- .map(t => t.name)
- if (p2pMediaLoaderInfohashes.length === 0) {
- logger.warn('No infohashes found in AP playlist object.', { playlistUrl: playlistUrlObject })
- continue
- }
-
const segmentsSha256UrlObject = playlistUrlObject.tag
.find(t => {
return isAPPlaylistSegmentHashesUrlObject(t)
type: VideoStreamingPlaylistType.HLS,
playlistUrl: playlistUrlObject.href,
segmentsSha256Url: segmentsSha256UrlObject.href,
- p2pMediaLoaderInfohashes,
+ p2pMediaLoaderInfohashes: VideoStreamingPlaylistModel.buildP2PMediaLoaderInfoHashes(playlistUrlObject.href, videoFiles),
videoId: video.id
}
import { VideoModel } from '../models/video/video'
-import { basename, join, dirname } from 'path'
-import { CONFIG, HLS_STREAMING_PLAYLIST_DIRECTORY } from '../initializers'
+import { basename, dirname, join } from 'path'
+import { CONFIG, HLS_STREAMING_PLAYLIST_DIRECTORY, sequelizeTypescript } from '../initializers'
import { close, ensureDir, move, open, outputJSON, pathExists, read, readFile, remove, writeFile } from 'fs-extra'
import { getVideoFileSize } from '../helpers/ffmpeg-utils'
import { sha256 } from '../helpers/core-utils'
import { doRequest, doRequestAndSaveToFile } from '../helpers/requests'
import { generateRandomString } from '../helpers/utils'
import { flatten, uniq } from 'lodash'
+import { VideoFileModel } from '../models/video/video-file'
+
+async function updateStreamingPlaylistsInfohashesIfNeeded () {
+ const playlistsToUpdate = await VideoStreamingPlaylistModel.listByIncorrectPeerVersion()
+
+ // Use separate SQL queries, because we could have many videos to update
+ for (const playlist of playlistsToUpdate) {
+ await sequelizeTypescript.transaction(async t => {
+ const videoFiles = await VideoFileModel.listByStreamingPlaylist(playlist.id, t)
+
+ playlist.p2pMediaLoaderInfohashes = await VideoStreamingPlaylistModel.buildP2PMediaLoaderInfoHashes(playlist.playlistUrl, videoFiles)
+ await playlist.save({ transaction: t })
+ })
+ }
+}
async function updateMasterHLSPlaylist (video: VideoModel) {
const directory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)
export {
updateMasterHLSPlaylist,
updateSha256Segments,
- downloadPlaylistSegments
+ downloadPlaylistSegments,
+ updateStreamingPlaylistsInfohashesIfNeeded
}
// ---------------------------------------------------------------------------
import { VideoModel } from './video'
import * as Sequelize from 'sequelize'
import { VideoRedundancyModel } from '../redundancy/video-redundancy'
+import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
@Table({
tableName: 'videoFile',
return VideoFileModel.findByPk(id, options)
}
+ static listByStreamingPlaylist (streamingPlaylistId: number, transaction: Sequelize.Transaction) {
+ const query = {
+ include: [
+ {
+ model: VideoModel.unscoped(),
+ required: true,
+ include: [
+ {
+ model: VideoStreamingPlaylistModel.unscoped(),
+ required: true,
+ where: {
+ id: streamingPlaylistId
+ }
+ }
+ ]
+ }
+ ],
+ transaction
+ }
+
+ return VideoFileModel.findAll(query)
+ }
+
static async getStats () {
let totalLocalVideoFilesSize = await VideoFileModel.sum('size', {
include: [
import { VideoRedundancyModel } from '../redundancy/video-redundancy'
import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type'
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
-import { CONSTRAINTS_FIELDS, STATIC_PATHS } from '../../initializers'
+import { CONSTRAINTS_FIELDS, STATIC_PATHS, P2P_MEDIA_LOADER_PEER_VERSION } from '../../initializers'
import { VideoFileModel } from './video-file'
import { join } from 'path'
import { sha1 } from '../../helpers/core-utils'
@Column(DataType.ARRAY(DataType.STRING))
p2pMediaLoaderInfohashes: string[]
+ @AllowNull(false)
+ @Column
+ p2pMediaLoaderPeerVersion: number
+
@AllowNull(false)
@Is('VideoStreamingSegmentsSha256Url', value => throwIfNotValid(value, isActivityPubUrlValid, 'segments sha256 url'))
@Column
static buildP2PMediaLoaderInfoHashes (playlistUrl: string, videoFiles: VideoFileModel[]) {
const hashes: string[] = []
- // https://github.com/Novage/p2p-media-loader/blob/master/p2p-media-loader-core/lib/p2p-media-manager.ts#L97
+ // https://github.com/Novage/p2p-media-loader/blob/master/p2p-media-loader-core/lib/p2p-media-manager.ts#L115
for (let i = 0; i < videoFiles.length; i++) {
- hashes.push(sha1(`1${playlistUrl}+V${i}`))
+ hashes.push(sha1(`${P2P_MEDIA_LOADER_PEER_VERSION}${playlistUrl}+V${i}`))
}
return hashes
}
+ static listByIncorrectPeerVersion () {
+ const query = {
+ where: {
+ p2pMediaLoaderPeerVersion: {
+ [Sequelize.Op.ne]: P2P_MEDIA_LOADER_PEER_VERSION
+ }
+ }
+ }
+
+ return VideoStreamingPlaylistModel.findAll(query)
+ }
+
static loadWithVideo (id: number) {
const options = {
include: [