X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Fmodels%2Fvideo%2Fvideo.ts;h=01a801da30cd4f6f726d92ea93f9980b91a7b5d7;hb=a041b1714715593b46805d7fd0106501770d11c3;hp=c376d769ed989ca7bdb56d77fafe672afd661085;hpb=aa8b6df4a51c82eb91e6fd71a090b2128098af6b;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/models/video/video.ts b/server/models/video/video.ts index c376d769e..01a801da3 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts @@ -1,12 +1,12 @@ import * as safeBuffer from 'safe-buffer' const Buffer = safeBuffer.Buffer -import * as ffmpeg from 'fluent-ffmpeg' import * as magnetUtil from 'magnet-uri' import { map } from 'lodash' import * as parseTorrent from 'parse-torrent' import { join } from 'path' import * as Sequelize from 'sequelize' import * as Promise from 'bluebird' +import { maxBy } from 'lodash' import { TagInstance } from './tag-interface' import { @@ -23,7 +23,10 @@ import { renamePromise, writeFilePromise, createTorrentPromise, - statPromise + statPromise, + generateImageFromVideoFile, + transcode, + getVideoFileHeight } from '../../helpers' import { CONFIG, @@ -32,8 +35,7 @@ import { VIDEO_CATEGORIES, VIDEO_LICENCES, VIDEO_LANGUAGES, - THUMBNAILS_SIZE, - VIDEO_FILE_RESOLUTIONS + THUMBNAILS_SIZE } from '../../initializers' import { removeVideoToFriends } from '../../lib' import { VideoResolution } from '../../../shared' @@ -46,16 +48,19 @@ import { VideoMethods } from './video-interface' +import { PREVIEWS_SIZE } from '../../initializers/constants' let Video: Sequelize.Model let getOriginalFile: VideoMethods.GetOriginalFile -let generateMagnetUri: VideoMethods.GenerateMagnetUri let getVideoFilename: VideoMethods.GetVideoFilename let getThumbnailName: VideoMethods.GetThumbnailName +let getThumbnailPath: VideoMethods.GetThumbnailPath let getPreviewName: VideoMethods.GetPreviewName +let getPreviewPath: VideoMethods.GetPreviewPath let getTorrentFileName: VideoMethods.GetTorrentFileName let isOwned: VideoMethods.IsOwned let toFormattedJSON: VideoMethods.ToFormattedJSON +let toFormattedDetailsJSON: VideoMethods.ToFormattedDetailsJSON let toAddRemoteJSON: VideoMethods.ToAddRemoteJSON let toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON let optimizeOriginalVideofile: VideoMethods.OptimizeOriginalVideofile @@ -65,9 +70,9 @@ let createThumbnail: VideoMethods.CreateThumbnail let getVideoFilePath: VideoMethods.GetVideoFilePath let createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash let getOriginalFileHeight: VideoMethods.GetOriginalFileHeight +let getEmbedPath: VideoMethods.GetEmbedPath let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData -let getDurationFromFile: VideoMethods.GetDurationFromFile let list: VideoMethods.List let listForApi: VideoMethods.ListForApi let loadByHostAndUUID: VideoMethods.LoadByHostAndUUID @@ -75,6 +80,7 @@ let listOwnedAndPopulateAuthorAndTags: VideoMethods.ListOwnedAndPopulateAuthorAn let listOwnedByAuthor: VideoMethods.ListOwnedByAuthor let load: VideoMethods.Load let loadByUUID: VideoMethods.LoadByUUID +let loadLocalVideoByUUID: VideoMethods.LoadLocalVideoByUUID let loadAndPopulateAuthor: VideoMethods.LoadAndPopulateAuthor let loadAndPopulateAuthorAndPodAndTags: VideoMethods.LoadAndPopulateAuthorAndPodAndTags let loadByUUIDAndPopulateAuthorAndPodAndTags: VideoMethods.LoadByUUIDAndPopulateAuthorAndPodAndTags @@ -201,9 +207,6 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da }, { indexes: [ - { - fields: [ 'authorId' ] - }, { fields: [ 'name' ] }, @@ -221,6 +224,9 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da }, { fields: [ 'uuid' ] + }, + { + fields: [ 'channelId' ] } ], hooks: { @@ -233,7 +239,6 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da associate, generateThumbnailFromData, - getDurationFromFile, list, listForApi, listOwnedAndPopulateAuthorAndTags, @@ -243,6 +248,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da loadAndPopulateAuthorAndPodAndTags, loadByHostAndUUID, loadByUUID, + loadLocalVideoByUUID, loadByUUIDAndPopulateAuthorAndPodAndTags, searchAndPopulateAuthorAndPodAndTags ] @@ -250,9 +256,10 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da createPreview, createThumbnail, createTorrentAndSetInfoHash, - generateMagnetUri, getPreviewName, + getPreviewPath, getThumbnailName, + getThumbnailPath, getTorrentFileName, getVideoFilename, getVideoFilePath, @@ -264,10 +271,12 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da removeTorrent, toAddRemoteJSON, toFormattedJSON, + toFormattedDetailsJSON, toUpdateRemoteJSON, optimizeOriginalVideofile, transcodeOriginalVideofile, - getOriginalFileHeight + getOriginalFileHeight, + getEmbedPath ] addMethodsToModel(Video, classMethods, instanceMethods) @@ -277,9 +286,9 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da // ------------------------------ METHODS ------------------------------ function associate (models) { - Video.belongsTo(models.Author, { + Video.belongsTo(models.VideoChannel, { foreignKey: { - name: 'authorId', + name: 'channelId', allowNull: false }, onDelete: 'cascade' @@ -327,22 +336,26 @@ function afterDestroy (video: VideoInstance, options: { transaction: Sequelize.T // Remove physical files and torrents video.VideoFiles.forEach(file => { - video.removeFile(file), - video.removeTorrent(file) + tasks.push(video.removeFile(file)) + tasks.push(video.removeTorrent(file)) }) } return Promise.all(tasks) + .catch(err => { + logger.error('Some errors when removing files of video %d in after destroy hook.', video.uuid, err) + }) } getOriginalFile = function (this: VideoInstance) { if (Array.isArray(this.VideoFiles) === false) return undefined - return this.VideoFiles.find(file => file.resolution === VideoResolution.ORIGINAL) + // The original file is the file that have the higher resolution + return maxBy(this.VideoFiles, file => file.resolution) } getVideoFilename = function (this: VideoInstance, videoFile: VideoFileInstance) { - return this.uuid + '-' + VIDEO_FILE_RESOLUTIONS[videoFile.resolution] + videoFile.extname + return this.uuid + '-' + videoFile.resolution + videoFile.extname } getThumbnailName = function (this: VideoInstance) { @@ -358,7 +371,7 @@ getPreviewName = function (this: VideoInstance) { getTorrentFileName = function (this: VideoInstance, videoFile: VideoFileInstance) { const extension = '.torrent' - return this.uuid + '-' + VIDEO_FILE_RESOLUTIONS[videoFile.resolution] + extension + return this.uuid + '-' + videoFile.resolution + extension } isOwned = function (this: VideoInstance) { @@ -366,11 +379,25 @@ isOwned = function (this: VideoInstance) { } createPreview = function (this: VideoInstance, videoFile: VideoFileInstance) { - return generateImage(this, this.getVideoFilePath(videoFile), CONFIG.STORAGE.PREVIEWS_DIR, this.getPreviewName(), null) + const imageSize = PREVIEWS_SIZE.width + 'x' + PREVIEWS_SIZE.height + + return generateImageFromVideoFile( + this.getVideoFilePath(videoFile), + CONFIG.STORAGE.PREVIEWS_DIR, + this.getPreviewName(), + imageSize + ) } createThumbnail = function (this: VideoInstance, videoFile: VideoFileInstance) { - return generateImage(this, this.getVideoFilePath(videoFile), CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName(), THUMBNAILS_SIZE) + const imageSize = THUMBNAILS_SIZE.width + 'x' + THUMBNAILS_SIZE.height + + return generateImageFromVideoFile( + this.getVideoFilePath(videoFile), + CONFIG.STORAGE.THUMBNAILS_DIR, + this.getThumbnailName(), + imageSize + ) } getVideoFilePath = function (this: VideoInstance, videoFile: VideoFileInstance) { @@ -401,38 +428,75 @@ createTorrentAndSetInfoHash = function (this: VideoInstance, videoFile: VideoFil }) } -generateMagnetUri = function (this: VideoInstance, videoFile: VideoFileInstance) { - let baseUrlHttp - let baseUrlWs +getEmbedPath = function (this: VideoInstance) { + return '/videos/embed/' + this.uuid +} - if (this.isOwned()) { - baseUrlHttp = CONFIG.WEBSERVER.URL - baseUrlWs = CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT +getThumbnailPath = function (this: VideoInstance) { + return join(STATIC_PATHS.THUMBNAILS, this.getThumbnailName()) +} + +getPreviewPath = function (this: VideoInstance) { + return join(STATIC_PATHS.PREVIEWS, this.getPreviewName()) +} + +toFormattedJSON = function (this: VideoInstance) { + let podHost + + if (this.VideoChannel.Author.Pod) { + podHost = this.VideoChannel.Author.Pod.host } else { - baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + this.Author.Pod.host - baseUrlWs = REMOTE_SCHEME.WS + '://' + this.Author.Pod.host + // It means it's our video + podHost = CONFIG.WEBSERVER.HOST } - const xs = baseUrlHttp + STATIC_PATHS.TORRENTS + this.getTorrentFileName(videoFile) - const announce = [ baseUrlWs + '/tracker/socket' ] - const urlList = [ baseUrlHttp + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile) ] + // Maybe our pod is not up to date and there are new categories since our version + let categoryLabel = VIDEO_CATEGORIES[this.category] + if (!categoryLabel) categoryLabel = 'Misc' - const magnetHash = { - xs, - announce, - urlList, - infoHash: videoFile.infoHash, - name: this.name + // Maybe our pod is not up to date and there are new licences since our version + let licenceLabel = VIDEO_LICENCES[this.licence] + if (!licenceLabel) licenceLabel = 'Unknown' + + // Language is an optional attribute + let languageLabel = VIDEO_LANGUAGES[this.language] + if (!languageLabel) languageLabel = 'Unknown' + + const json = { + id: this.id, + uuid: this.uuid, + name: this.name, + category: this.category, + categoryLabel, + licence: this.licence, + licenceLabel, + language: this.language, + languageLabel, + nsfw: this.nsfw, + description: this.description, + podHost, + isLocal: this.isOwned(), + author: this.VideoChannel.Author.name, + duration: this.duration, + views: this.views, + likes: this.likes, + dislikes: this.dislikes, + tags: map(this.Tags, 'name'), + thumbnailPath: this.getThumbnailPath(), + previewPath: this.getPreviewPath(), + embedPath: this.getEmbedPath(), + createdAt: this.createdAt, + updatedAt: this.updatedAt } - return magnetUtil.encode(magnetHash) + return json } -toFormattedJSON = function (this: VideoInstance) { +toFormattedDetailsJSON = function (this: VideoInstance) { let podHost - if (this.Author.Pod) { - podHost = this.Author.Pod.host + if (this.VideoChannel.Author.Pod) { + podHost = this.VideoChannel.Author.Pod.host } else { // It means it's our video podHost = CONFIG.WEBSERVER.HOST @@ -464,30 +528,34 @@ toFormattedJSON = function (this: VideoInstance) { description: this.description, podHost, isLocal: this.isOwned(), - author: this.Author.name, + author: this.VideoChannel.Author.name, duration: this.duration, views: this.views, likes: this.likes, dislikes: this.dislikes, tags: map(this.Tags, 'name'), - thumbnailPath: join(STATIC_PATHS.THUMBNAILS, this.getThumbnailName()), - previewPath: join(STATIC_PATHS.PREVIEWS, this.getPreviewName()), + thumbnailPath: this.getThumbnailPath(), + previewPath: this.getPreviewPath(), + embedPath: this.getEmbedPath(), createdAt: this.createdAt, updatedAt: this.updatedAt, + channel: this.VideoChannel.toFormattedJSON(), files: [] } // Format and sort video files + const { baseUrlHttp, baseUrlWs } = getBaseUrls(this) json.files = this.VideoFiles .map(videoFile => { - let resolutionLabel = VIDEO_FILE_RESOLUTIONS[videoFile.resolution] - if (!resolutionLabel) resolutionLabel = 'Unknown' + let resolutionLabel = videoFile.resolution + 'p' const videoFileJson = { resolution: videoFile.resolution, resolutionLabel, - magnetUri: this.generateMagnetUri(videoFile), - size: videoFile.size + magnetUri: generateMagnetUri(this, videoFile, baseUrlHttp, baseUrlWs), + size: videoFile.size, + torrentUrl: getTorrentUrl(this, videoFile, baseUrlHttp), + fileUrl: getVideoFileUrl(this, videoFile, baseUrlHttp) } return videoFileJson @@ -514,7 +582,7 @@ toAddRemoteJSON = function (this: VideoInstance) { language: this.language, nsfw: this.nsfw, description: this.description, - author: this.Author.name, + channelUUID: this.VideoChannel.uuid, duration: this.duration, thumbnailData: thumbnailData.toString('binary'), tags: map(this.Tags, 'name'), @@ -548,7 +616,6 @@ toUpdateRemoteJSON = function (this: VideoInstance) { language: this.language, nsfw: this.nsfw, description: this.description, - author: this.Author.name, duration: this.duration, tags: map(this.Tags, 'name'), createdAt: this.createdAt, @@ -578,46 +645,42 @@ optimizeOriginalVideofile = function (this: VideoInstance) { const videoInputPath = join(videosDirectory, this.getVideoFilename(inputVideoFile)) const videoOutputPath = join(videosDirectory, this.id + '-transcoded' + newExtname) - return new Promise((res, rej) => { - ffmpeg(videoInputPath) - .output(videoOutputPath) - .videoCodec('libx264') - .outputOption('-threads ' + CONFIG.TRANSCODING.THREADS) - .outputOption('-movflags faststart') - .on('error', rej) - .on('end', () => { - - return unlinkPromise(videoInputPath) - .then(() => { - // Important to do this before getVideoFilename() to take in account the new file extension - inputVideoFile.set('extname', newExtname) - - return renamePromise(videoOutputPath, this.getVideoFilePath(inputVideoFile)) - }) - .then(() => { - return statPromise(this.getVideoFilePath(inputVideoFile)) - }) - .then(stats => { - return inputVideoFile.set('size', stats.size) - }) - .then(() => { - return this.createTorrentAndSetInfoHash(inputVideoFile) - }) - .then(() => { - return inputVideoFile.save() - }) - .then(() => { - return res() - }) - .catch(err => { - // Auto destruction... - this.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', err)) - - return rej(err) - }) - }) - .run() - }) + const transcodeOptions = { + inputPath: videoInputPath, + outputPath: videoOutputPath + } + + return transcode(transcodeOptions) + .then(() => { + return unlinkPromise(videoInputPath) + }) + .then(() => { + // Important to do this before getVideoFilename() to take in account the new file extension + inputVideoFile.set('extname', newExtname) + + return renamePromise(videoOutputPath, this.getVideoFilePath(inputVideoFile)) + }) + .then(() => { + return statPromise(this.getVideoFilePath(inputVideoFile)) + }) + .then(stats => { + return inputVideoFile.set('size', stats.size) + }) + .then(() => { + return this.createTorrentAndSetInfoHash(inputVideoFile) + }) + .then(() => { + return inputVideoFile.save() + }) + .then(() => { + return undefined + }) + .catch(err => { + // Auto destruction... + this.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', err)) + + throw err + }) } transcodeOriginalVideofile = function (this: VideoInstance, resolution: VideoResolution) { @@ -634,52 +697,37 @@ transcodeOriginalVideofile = function (this: VideoInstance, resolution: VideoRes videoId: this.id }) const videoOutputPath = join(videosDirectory, this.getVideoFilename(newVideoFile)) - const resolutionOption = `${resolution}x?` // '720x?' for example - - return new Promise((res, rej) => { - ffmpeg(videoInputPath) - .output(videoOutputPath) - .videoCodec('libx264') - .size(resolutionOption) - .outputOption('-threads ' + CONFIG.TRANSCODING.THREADS) - .outputOption('-movflags faststart') - .on('error', rej) - .on('end', () => { - return statPromise(videoOutputPath) - .then(stats => { - newVideoFile.set('size', stats.size) - - return undefined - }) - .then(() => { - return this.createTorrentAndSetInfoHash(newVideoFile) - }) - .then(() => { - return newVideoFile.save() - }) - .then(() => { - return this.VideoFiles.push(newVideoFile) - }) - .then(() => { - return res() - }) - .catch(rej) - }) - .run() - }) + + const transcodeOptions = { + inputPath: videoInputPath, + outputPath: videoOutputPath, + resolution + } + return transcode(transcodeOptions) + .then(() => { + return statPromise(videoOutputPath) + }) + .then(stats => { + newVideoFile.set('size', stats.size) + + return undefined + }) + .then(() => { + return this.createTorrentAndSetInfoHash(newVideoFile) + }) + .then(() => { + return newVideoFile.save() + }) + .then(() => { + return this.VideoFiles.push(newVideoFile) + }) + .then(() => undefined) } getOriginalFileHeight = function (this: VideoInstance) { const originalFilePath = this.getVideoFilePath(this.getOriginalFile()) - return new Promise((res, rej) => { - ffmpeg.ffprobe(originalFilePath, (err, metadata) => { - if (err) return rej(err) - - const videoStream = metadata.streams.find(s => s.codec_type === 'video') - return res(videoStream.height) - }) - }) + return getVideoFileHeight(originalFilePath) } removeThumbnail = function (this: VideoInstance) { @@ -714,16 +762,6 @@ generateThumbnailFromData = function (video: VideoInstance, thumbnailData: strin }) } -getDurationFromFile = function (videoPath: string) { - return new Promise((res, rej) => { - ffmpeg.ffprobe(videoPath, (err, metadata) => { - if (err) return rej(err) - - return res(Math.floor(metadata.format.duration)) - }) - }) -} - list = function () { const query = { include: [ Video['sequelize'].models.VideoFile ] @@ -741,8 +779,18 @@ listForApi = function (start: number, count: number, sort: string) { order: [ getSort(sort), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ], include: [ { - model: Video['sequelize'].models.Author, - include: [ { model: Video['sequelize'].models.Pod, required: false } ] + model: Video['sequelize'].models.VideoChannel, + include: [ + { + model: Video['sequelize'].models.Author, + include: [ + { + model: Video['sequelize'].models.Pod, + required: false + } + ] + } + ] }, Video['sequelize'].models.Tag, Video['sequelize'].models.VideoFile @@ -758,8 +806,8 @@ listForApi = function (start: number, count: number, sort: string) { }) } -loadByHostAndUUID = function (fromHost: string, uuid: string) { - const query = { +loadByHostAndUUID = function (fromHost: string, uuid: string, t?: Sequelize.Transaction) { + const query: Sequelize.FindOptions = { where: { uuid }, @@ -768,20 +816,27 @@ loadByHostAndUUID = function (fromHost: string, uuid: string) { model: Video['sequelize'].models.VideoFile }, { - model: Video['sequelize'].models.Author, + model: Video['sequelize'].models.VideoChannel, include: [ { - model: Video['sequelize'].models.Pod, - required: true, - where: { - host: fromHost - } + model: Video['sequelize'].models.Author, + include: [ + { + model: Video['sequelize'].models.Pod, + required: true, + where: { + host: fromHost + } + } + ] } ] } ] } + if (t !== undefined) query.transaction = t + return Video.findOne(query) } @@ -792,7 +847,10 @@ listOwnedAndPopulateAuthorAndTags = function () { }, include: [ Video['sequelize'].models.VideoFile, - Video['sequelize'].models.Author, + { + model: Video['sequelize'].models.VideoChannel, + include: [ Video['sequelize'].models.Author ] + }, Video['sequelize'].models.Tag ] } @@ -810,10 +868,15 @@ listOwnedByAuthor = function (author: string) { model: Video['sequelize'].models.VideoFile }, { - model: Video['sequelize'].models.Author, - where: { - name: author - } + model: Video['sequelize'].models.VideoChannel, + include: [ + { + model: Video['sequelize'].models.Author, + where: { + name: author + } + } + ] } ] } @@ -825,19 +888,42 @@ load = function (id: number) { return Video.findById(id) } -loadByUUID = function (uuid: string) { - const query = { +loadByUUID = function (uuid: string, t?: Sequelize.Transaction) { + const query: Sequelize.FindOptions = { where: { uuid }, include: [ Video['sequelize'].models.VideoFile ] } + + if (t !== undefined) query.transaction = t + + return Video.findOne(query) +} + +loadLocalVideoByUUID = function (uuid: string, t?: Sequelize.Transaction) { + const query: Sequelize.FindOptions = { + where: { + uuid, + remote: false + }, + include: [ Video['sequelize'].models.VideoFile ] + } + + if (t !== undefined) query.transaction = t + return Video.findOne(query) } loadAndPopulateAuthor = function (id: number) { const options = { - include: [ Video['sequelize'].models.VideoFile, Video['sequelize'].models.Author ] + include: [ + Video['sequelize'].models.VideoFile, + { + model: Video['sequelize'].models.VideoChannel, + include: [ Video['sequelize'].models.Author ] + } + ] } return Video.findById(id, options) @@ -847,8 +933,13 @@ loadAndPopulateAuthorAndPodAndTags = function (id: number) { const options = { include: [ { - model: Video['sequelize'].models.Author, - include: [ { model: Video['sequelize'].models.Pod, required: false } ] + model: Video['sequelize'].models.VideoChannel, + include: [ + { + model: Video['sequelize'].models.Author, + include: [ { model: Video['sequelize'].models.Pod, required: false } ] + } + ] }, Video['sequelize'].models.Tag, Video['sequelize'].models.VideoFile @@ -865,8 +956,13 @@ loadByUUIDAndPopulateAuthorAndPodAndTags = function (uuid: string) { }, include: [ { - model: Video['sequelize'].models.Author, - include: [ { model: Video['sequelize'].models.Pod, required: false } ] + model: Video['sequelize'].models.VideoChannel, + include: [ + { + model: Video['sequelize'].models.Author, + include: [ { model: Video['sequelize'].models.Pod, required: false } ] + } + ] }, Video['sequelize'].models.Tag, Video['sequelize'].models.VideoFile @@ -884,9 +980,13 @@ searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, s const authorInclude: Sequelize.IncludeOptions = { model: Video['sequelize'].models.Author, - include: [ - podInclude - ] + include: [ podInclude ] + } + + const videoChannelInclude: Sequelize.IncludeOptions = { + model: Video['sequelize'].models.VideoChannel, + include: [ authorInclude ], + required: true } const tagInclude: Sequelize.IncludeOptions = { @@ -933,8 +1033,6 @@ searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, s $iLike: '%' + value + '%' } } - - // authorInclude.or = true } else { query.where[field] = { $iLike: '%' + value + '%' @@ -942,7 +1040,7 @@ searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, s } query.include = [ - authorInclude, tagInclude, videoFileInclude + videoChannelInclude, tagInclude, videoFileInclude ] return Video.findAndCountAll(query).then(({ rows, count }) => { @@ -965,21 +1063,41 @@ function createBaseVideosWhere () { } } -function generateImage (video: VideoInstance, videoPath: string, folder: string, imageName: string, size: string) { - const options = { - filename: imageName, - count: 1, - folder +function getBaseUrls (video: VideoInstance) { + let baseUrlHttp + let baseUrlWs + + if (video.isOwned()) { + baseUrlHttp = CONFIG.WEBSERVER.URL + baseUrlWs = CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT + } else { + baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + video.VideoChannel.Author.Pod.host + baseUrlWs = REMOTE_SCHEME.WS + '://' + video.VideoChannel.Author.Pod.host } - if (size) { - options['size'] = size + return { baseUrlHttp, baseUrlWs } +} + +function getTorrentUrl (video: VideoInstance, videoFile: VideoFileInstance, baseUrlHttp: string) { + return baseUrlHttp + STATIC_PATHS.TORRENTS + video.getTorrentFileName(videoFile) +} + +function getVideoFileUrl (video: VideoInstance, videoFile: VideoFileInstance, baseUrlHttp: string) { + return baseUrlHttp + STATIC_PATHS.WEBSEED + video.getVideoFilename(videoFile) +} + +function generateMagnetUri (video: VideoInstance, videoFile: VideoFileInstance, baseUrlHttp: string, baseUrlWs: string) { + const xs = getTorrentUrl(video, videoFile, baseUrlHttp) + const announce = [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ] + const urlList = [ getVideoFileUrl(video, videoFile, baseUrlHttp) ] + + const magnetHash = { + xs, + announce, + urlList, + infoHash: videoFile.infoHash, + name: video.name } - return new Promise((res, rej) => { - ffmpeg(videoPath) - .on('error', rej) - .on('end', () => res(imageName)) - .thumbnail(options) - }) + return magnetUtil.encode(magnetHash) }