Add import.video.torrent configuration

This commit is contained in:
Chocobozzz 2018-08-07 10:07:53 +02:00
parent 990b6a0b0c
commit a84b8fa5cf
23 changed files with 112 additions and 106 deletions

View File

@ -108,6 +108,11 @@
i18n-labelText labelText="Video import with HTTP enabled" i18n-labelText labelText="Video import with HTTP enabled"
></my-peertube-checkbox> ></my-peertube-checkbox>
<my-peertube-checkbox
inputName="importVideosTorrentEnabled" formControlName="importVideosTorrentEnabled"
i18n-labelText labelText="Video import with a torrent file or a magnet URI enabled"
></my-peertube-checkbox>
<div i18n class="inner-form-title">Administrator</div> <div i18n class="inner-form-title">Administrator</div>
<div class="form-group"> <div class="form-group">

View File

@ -72,6 +72,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
signupEnabled: null, signupEnabled: null,
signupLimit: this.customConfigValidatorsService.SIGNUP_LIMIT, signupLimit: this.customConfigValidatorsService.SIGNUP_LIMIT,
importVideosHttpEnabled: null, importVideosHttpEnabled: null,
importVideosTorrentEnabled: null,
adminEmail: this.customConfigValidatorsService.ADMIN_EMAIL, adminEmail: this.customConfigValidatorsService.ADMIN_EMAIL,
userVideoQuota: this.userValidatorsService.USER_VIDEO_QUOTA, userVideoQuota: this.userValidatorsService.USER_VIDEO_QUOTA,
transcodingThreads: this.customConfigValidatorsService.TRANSCODING_THREADS, transcodingThreads: this.customConfigValidatorsService.TRANSCODING_THREADS,
@ -189,6 +190,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
videos: { videos: {
http: { http: {
enabled: this.form.value['importVideosHttpEnabled'] enabled: this.form.value['importVideosHttpEnabled']
},
torrent: {
enabled: this.form.value['importVideosTorrentEnabled']
} }
} }
} }
@ -231,7 +235,8 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
transcodingEnabled: this.customConfig.transcoding.enabled, transcodingEnabled: this.customConfig.transcoding.enabled,
customizationJavascript: this.customConfig.instance.customizations.javascript, customizationJavascript: this.customConfig.instance.customizations.javascript,
customizationCSS: this.customConfig.instance.customizations.css, customizationCSS: this.customConfig.instance.customizations.css,
importVideosHttpEnabled: this.customConfig.import.videos.http.enabled importVideosHttpEnabled: this.customConfig.import.videos.http.enabled,
importVideosTorrentEnabled: this.customConfig.import.videos.torrent.enabled
} }
for (const resolution of this.resolutions) { for (const resolution of this.resolutions) {

View File

@ -73,6 +73,9 @@ export class ServerService {
videos: { videos: {
http: { http: {
enabled: false enabled: false
},
torrent: {
enabled: false
} }
} }
} }

View File

@ -50,6 +50,7 @@ $background-color: #F7F7F7;
border-radius: 3px; border-radius: 3px;
width: 100%; width: 100%;
min-height: 440px; min-height: 440px;
padding-bottom: 20px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;

View File

@ -40,6 +40,6 @@ export class VideoAddComponent implements CanComponentDeactivate {
} }
isVideoImportTorrentEnabled () { isVideoImportTorrentEnabled () {
return this.serverService.getConfig().import.videos.http.enabled return this.serverService.getConfig().import.videos.torrent.enabled
} }
} }

View File

@ -97,6 +97,8 @@ import:
videos: videos:
http: # Classic HTTP or all sites supported by youtube-dl https://rg3.github.io/youtube-dl/supportedsites.html http: # Classic HTTP or all sites supported by youtube-dl https://rg3.github.io/youtube-dl/supportedsites.html
enabled: false enabled: false
torrent: # Magnet URI or torrent file (use classic TCP/UDP/WebSeed to download the file)
enabled: false
instance: instance:
name: 'PeerTube' name: 'PeerTube'

View File

@ -111,6 +111,8 @@ import:
videos: videos:
http: # Classic HTTP or all sites supported by youtube-dl https://rg3.github.io/youtube-dl/supportedsites.html http: # Classic HTTP or all sites supported by youtube-dl https://rg3.github.io/youtube-dl/supportedsites.html
enabled: false enabled: false
torrent: # Magnet URI or torrent file (use classic TCP/UDP/WebSeed to download the file)
enabled: false
# Instance settings # Instance settings
instance: instance:

View File

@ -44,6 +44,8 @@ import:
videos: videos:
http: http:
enabled: true enabled: true
torrent:
enabled: true
instance: instance:
default_nsfw_policy: 'display' default_nsfw_policy: 'display'

View File

@ -9,7 +9,7 @@ import { CONFIG, CONSTRAINTS_FIELDS, reloadConfig } from '../../initializers'
import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares' import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares'
import { customConfigUpdateValidator } from '../../middlewares/validators/config' import { customConfigUpdateValidator } from '../../middlewares/validators/config'
import { ClientHtml } from '../../lib/client-html' import { ClientHtml } from '../../lib/client-html'
import { CustomConfigAuditView, auditLoggerFactory } from '../../helpers/audit-logger' import { auditLoggerFactory, CustomConfigAuditView } from '../../helpers/audit-logger'
const packageJSON = require('../../../../package.json') const packageJSON = require('../../../../package.json')
const configRouter = express.Router() const configRouter = express.Router()
@ -69,6 +69,9 @@ async function getConfig (req: express.Request, res: express.Response, next: exp
videos: { videos: {
http: { http: {
enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED
},
torrent: {
enabled: CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED
} }
} }
}, },
@ -237,6 +240,9 @@ function customConfig (): CustomConfig {
videos: { videos: {
http: { http: {
enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED
},
torrent: {
enabled: CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED
} }
} }
} }

View File

@ -196,7 +196,7 @@ async function getUserVideos (req: express.Request, res: express.Response, next:
async function getUserVideoImports (req: express.Request, res: express.Response, next: express.NextFunction) { async function getUserVideoImports (req: express.Request, res: express.Response, next: express.NextFunction) {
const user = res.locals.oauth.token.User as UserModel const user = res.locals.oauth.token.User as UserModel
const resultList = await VideoImportModel.listUserVideoImportsForApi( const resultList = await VideoImportModel.listUserVideoImportsForApi(
user.Account.id, user.id,
req.query.start as number, req.query.start as number,
req.query.count as number, req.query.count as number,
req.query.sort req.query.sort

View File

@ -61,12 +61,13 @@ export {
function addVideoImport (req: express.Request, res: express.Response) { function addVideoImport (req: express.Request, res: express.Response) {
if (req.body.targetUrl) return addYoutubeDLImport(req, res) if (req.body.targetUrl) return addYoutubeDLImport(req, res)
const file = req.files['torrentfile'][0] const file = req.files && req.files['torrentfile'] ? req.files['torrentfile'][0] : undefined
if (req.body.magnetUri || file) return addTorrentImport(req, res, file) if (req.body.magnetUri || file) return addTorrentImport(req, res, file)
} }
async function addTorrentImport (req: express.Request, res: express.Response, torrentfile: Express.Multer.File) { async function addTorrentImport (req: express.Request, res: express.Response, torrentfile: Express.Multer.File) {
const body: VideoImportCreate = req.body const body: VideoImportCreate = req.body
const user = res.locals.oauth.token.User
let videoName: string let videoName: string
let torrentName: string let torrentName: string
@ -100,7 +101,8 @@ async function addTorrentImport (req: express.Request, res: express.Response, to
const videoImportAttributes = { const videoImportAttributes = {
magnetUri, magnetUri,
torrentName, torrentName,
state: VideoImportState.PENDING state: VideoImportState.PENDING,
userId: user.id
} }
const videoImport: VideoImportModel = await insertIntoDB(video, res.locals.videoChannel, tags, videoImportAttributes) const videoImport: VideoImportModel = await insertIntoDB(video, res.locals.videoChannel, tags, videoImportAttributes)
@ -120,6 +122,7 @@ async function addTorrentImport (req: express.Request, res: express.Response, to
async function addYoutubeDLImport (req: express.Request, res: express.Response) { async function addYoutubeDLImport (req: express.Request, res: express.Response) {
const body: VideoImportCreate = req.body const body: VideoImportCreate = req.body
const targetUrl = body.targetUrl const targetUrl = body.targetUrl
const user = res.locals.oauth.token.User
let youtubeDLInfo: YoutubeDLInfo let youtubeDLInfo: YoutubeDLInfo
try { try {
@ -140,7 +143,8 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response)
const tags = body.tags || youtubeDLInfo.tags const tags = body.tags || youtubeDLInfo.tags
const videoImportAttributes = { const videoImportAttributes = {
targetUrl, targetUrl,
state: VideoImportState.PENDING state: VideoImportState.PENDING,
userId: user.id
} }
const videoImport: VideoImportModel = await insertIntoDB(video, res.locals.videoChannel, tags, videoImportAttributes) const videoImport: VideoImportModel = await insertIntoDB(video, res.locals.videoChannel, tags, videoImportAttributes)

View File

@ -15,7 +15,7 @@ let config: IConfig = require('config')
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
const LAST_MIGRATION_VERSION = 245 const LAST_MIGRATION_VERSION = 240
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@ -211,6 +211,9 @@ const CONFIG = {
VIDEOS: { VIDEOS: {
HTTP: { HTTP: {
get ENABLED () { return config.get<boolean>('import.videos.http.enabled') } get ENABLED () { return config.get<boolean>('import.videos.http.enabled') }
},
TORRENT: {
get ENABLED () { return config.get<boolean>('import.videos.torrent.enabled') }
} }
} }
}, },

View File

@ -1,42 +0,0 @@
import * as Sequelize from 'sequelize'
import { Migration } from '../../models/migrations'
import { CONSTRAINTS_FIELDS } from '../index'
async function up (utils: {
transaction: Sequelize.Transaction
queryInterface: Sequelize.QueryInterface
sequelize: Sequelize.Sequelize
}): Promise<any> {
{
const data = {
type: Sequelize.STRING,
allowNull: true,
defaultValue: null
} as Migration.String
await utils.queryInterface.changeColumn('videoImport', 'targetUrl', data)
}
{
const data = {
type: Sequelize.STRING(CONSTRAINTS_FIELDS.VIDEO_IMPORTS.URL.max),
allowNull: true,
defaultValue: null
}
await utils.queryInterface.addColumn('videoImport', 'magnetUri', data)
}
{
const data = {
type: Sequelize.STRING(CONSTRAINTS_FIELDS.VIDEO_IMPORTS.TORRENT_NAME.max),
allowNull: true,
defaultValue: null
}
await utils.queryInterface.addColumn('videoImport', 'torrentName', data)
}
}
function down (options) {
throw new Error('Not implemented.')
}
export { up, down }

View File

@ -114,16 +114,21 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
tempVideoPath = await downloader() tempVideoPath = await downloader()
// Get information about this video // Get information about this video
const { size } = await statPromise(tempVideoPath)
const isAble = await videoImport.User.isAbleToUploadVideo({ size })
if (isAble === false) {
throw new Error('The user video quota is exceeded with this video to import.')
}
const { videoFileResolution } = await getVideoFileResolution(tempVideoPath) const { videoFileResolution } = await getVideoFileResolution(tempVideoPath)
const fps = await getVideoFileFPS(tempVideoPath) const fps = await getVideoFileFPS(tempVideoPath)
const stats = await statPromise(tempVideoPath)
const duration = await getDurationFromVideoFile(tempVideoPath) const duration = await getDurationFromVideoFile(tempVideoPath)
// Create video file object in database // Create video file object in database
const videoFileData = { const videoFileData = {
extname: extname(tempVideoPath), extname: extname(tempVideoPath),
resolution: videoFileResolution, resolution: videoFileResolution,
size: stats.size, size,
fps, fps,
videoId: videoImport.videoId videoId: videoImport.videoId
} }

View File

@ -25,6 +25,7 @@ const customConfigUpdateValidator = [
body('transcoding.resolutions.720p').isBoolean().withMessage('Should have a valid transcoding 720p resolution enabled boolean'), body('transcoding.resolutions.720p').isBoolean().withMessage('Should have a valid transcoding 720p resolution enabled boolean'),
body('transcoding.resolutions.1080p').isBoolean().withMessage('Should have a valid transcoding 1080p resolution enabled boolean'), body('transcoding.resolutions.1080p').isBoolean().withMessage('Should have a valid transcoding 1080p resolution enabled boolean'),
body('import.videos.http.enabled').isBoolean().withMessage('Should have a valid import video http enabled boolean'), body('import.videos.http.enabled').isBoolean().withMessage('Should have a valid import video http enabled boolean'),
body('import.videos.torrent.enabled').isBoolean().withMessage('Should have a valid import video torrent enabled boolean'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => { async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking customConfigUpdateValidator parameters', { parameters: req.body }) logger.debug('Checking customConfigUpdateValidator parameters', { parameters: req.body })

View File

@ -33,21 +33,28 @@ const videoImportAddValidator = getCommonVideoAttributes().concat([
logger.debug('Checking videoImportAddValidator parameters', { parameters: req.body }) logger.debug('Checking videoImportAddValidator parameters', { parameters: req.body })
const user = res.locals.oauth.token.User const user = res.locals.oauth.token.User
const torrentFile = req.files && req.files['torrentfile'] ? req.files['torrentfile'][0] : undefined
if (areValidationErrors(req, res)) return cleanUpReqFiles(req) if (areValidationErrors(req, res)) return cleanUpReqFiles(req)
if (CONFIG.IMPORT.VIDEOS.HTTP.ENABLED !== true) { if (req.body.targetUrl && CONFIG.IMPORT.VIDEOS.HTTP.ENABLED !== true) {
cleanUpReqFiles(req) cleanUpReqFiles(req)
return res.status(409) return res.status(409)
.json({ error: 'Import is not enabled on this instance.' }) .json({ error: 'HTTP import is not enabled on this instance.' })
.end() .end()
} }
if (CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED !== true && (req.body.magnetUri || torrentFile)) {
cleanUpReqFiles(req)
return res.status(409)
.json({ error: 'Torrent/magnet URI import is not enabled on this instance.' })
.end()
}
if (!await isVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req) if (!await isVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req)
// Check we have at least 1 required param // Check we have at least 1 required param
const file = req.files['torrentfile'][0] if (!req.body.targetUrl && !req.body.magnetUri && !torrentFile) {
if (!req.body.targetUrl && !req.body.magnetUri && !file) {
cleanUpReqFiles(req) cleanUpReqFiles(req)
return res.status(400) return res.status(400)

View File

@ -295,7 +295,7 @@ export class UserModel extends Model<UserModel> {
return json return json
} }
isAbleToUploadVideo (videoFile: Express.Multer.File) { isAbleToUploadVideo (videoFile: { size: number }) {
if (this.videoQuota === -1) return Promise.resolve(true) if (this.videoQuota === -1) return Promise.resolve(true)
return UserModel.getOriginalVideoFileTotalFromUser(this) return UserModel.getOriginalVideoFileTotalFromUser(this)

View File

@ -15,34 +15,21 @@ import {
} from 'sequelize-typescript' } from 'sequelize-typescript'
import { CONSTRAINTS_FIELDS, VIDEO_IMPORT_STATES } from '../../initializers' import { CONSTRAINTS_FIELDS, VIDEO_IMPORT_STATES } from '../../initializers'
import { getSort, throwIfNotValid } from '../utils' import { getSort, throwIfNotValid } from '../utils'
import { VideoModel } from './video' import { ScopeNames as VideoModelScopeNames, VideoModel } from './video'
import { isVideoImportStateValid, isVideoImportTargetUrlValid } from '../../helpers/custom-validators/video-imports' import { isVideoImportStateValid, isVideoImportTargetUrlValid } from '../../helpers/custom-validators/video-imports'
import { VideoImport, VideoImportState } from '../../../shared' import { VideoImport, VideoImportState } from '../../../shared'
import { VideoChannelModel } from './video-channel'
import { AccountModel } from '../account/account'
import { TagModel } from './tag'
import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos' import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos'
import { UserModel } from '../account/user'
@DefaultScope({ @DefaultScope({
include: [ include: [
{ {
model: () => VideoModel, model: () => UserModel.unscoped(),
required: false, required: true
include: [ },
{ {
model: () => VideoChannelModel, model: () => VideoModel.scope([ VideoModelScopeNames.WITH_ACCOUNT_DETAILS, VideoModelScopeNames.WITH_TAGS]),
required: true, required: false
include: [
{
model: () => AccountModel,
required: true
}
]
},
{
model: () => TagModel
}
]
} }
] ]
}) })
@ -53,6 +40,9 @@ import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos'
{ {
fields: [ 'videoId' ], fields: [ 'videoId' ],
unique: true unique: true
},
{
fields: [ 'userId' ]
} }
] ]
}) })
@ -91,6 +81,18 @@ export class VideoImportModel extends Model<VideoImportModel> {
@Column(DataType.TEXT) @Column(DataType.TEXT)
error: string error: string
@ForeignKey(() => UserModel)
@Column
userId: number
@BelongsTo(() => UserModel, {
foreignKey: {
allowNull: false
},
onDelete: 'cascade'
})
User: UserModel
@ForeignKey(() => VideoModel) @ForeignKey(() => VideoModel)
@Column @Column
videoId: number videoId: number
@ -116,41 +118,24 @@ export class VideoImportModel extends Model<VideoImportModel> {
return VideoImportModel.findById(id) return VideoImportModel.findById(id)
} }
static listUserVideoImportsForApi (accountId: number, start: number, count: number, sort: string) { static listUserVideoImportsForApi (userId: number, start: number, count: number, sort: string) {
const query = { const query = {
distinct: true, distinct: true,
include: [
{
model: UserModel.unscoped(), // FIXME: Without this, sequelize try to COUNT(DISTINCT(*)) which is an invalid SQL query
required: true
}
],
offset: start, offset: start,
limit: count, limit: count,
order: getSort(sort), order: getSort(sort),
include: [ where: {
{ userId
model: VideoModel, }
required: false,
include: [
{
model: VideoChannelModel,
required: true,
include: [
{
model: AccountModel,
required: true,
where: {
id: accountId
}
}
]
},
{
model: TagModel,
required: false
}
]
}
]
} }
return VideoImportModel.unscoped() return VideoImportModel.findAndCountAll(query)
.findAndCountAll(query)
.then(({ rows, count }) => { .then(({ rows, count }) => {
return { return {
data: rows, data: rows,

View File

@ -65,6 +65,9 @@ describe('Test config API validators', function () {
videos: { videos: {
http: { http: {
enabled: false enabled: false
},
torrent: {
enabled: false
} }
} }
} }

View File

@ -45,6 +45,7 @@ function checkInitialConfig (data: CustomConfig) {
expect(data.transcoding.resolutions['720p']).to.be.true expect(data.transcoding.resolutions['720p']).to.be.true
expect(data.transcoding.resolutions['1080p']).to.be.true expect(data.transcoding.resolutions['1080p']).to.be.true
expect(data.import.videos.http.enabled).to.be.true expect(data.import.videos.http.enabled).to.be.true
expect(data.import.videos.torrent.enabled).to.be.true
} }
function checkUpdatedConfig (data: CustomConfig) { function checkUpdatedConfig (data: CustomConfig) {
@ -72,6 +73,7 @@ function checkUpdatedConfig (data: CustomConfig) {
expect(data.transcoding.resolutions['720p']).to.be.false expect(data.transcoding.resolutions['720p']).to.be.false
expect(data.transcoding.resolutions['1080p']).to.be.false expect(data.transcoding.resolutions['1080p']).to.be.false
expect(data.import.videos.http.enabled).to.be.false expect(data.import.videos.http.enabled).to.be.false
expect(data.import.videos.torrent.enabled).to.be.false
} }
describe('Test config', function () { describe('Test config', function () {
@ -167,6 +169,9 @@ describe('Test config', function () {
videos: { videos: {
http: { http: {
enabled: false enabled: false
},
torrent: {
enabled: false
} }
} }
} }

View File

@ -97,6 +97,9 @@ function updateCustomSubConfig (url: string, token: string, newConfig: any) {
videos: { videos: {
http: { http: {
enabled: false enabled: false
},
torrent: {
enabled: false
} }
} }
} }

View File

@ -60,6 +60,9 @@ export interface CustomConfig {
videos: { videos: {
http: { http: {
enabled: boolean enabled: boolean
},
torrent: {
enabled: boolean
} }
} }
} }

View File

@ -28,6 +28,9 @@ export interface ServerConfig {
http: { http: {
enabled: boolean enabled: boolean
} }
torrent: {
enabled: boolean
}
} }
} }