import { ServerConfig } from '../../../../../shared'
import { About } from '../../../../../shared/models/server/about.model'
import { environment } from '../../../environments/environment'
+import { VideoConstant, VideoPrivacy } from '../../../../../shared/models/videos'
@Injectable()
export class ServerService {
videoQuota: -1
}
}
- private videoCategories: Array<{ id: number, label: string }> = []
- private videoLicences: Array<{ id: number, label: string }> = []
- private videoLanguages: Array<{ id: number, label: string }> = []
- private videoPrivacies: Array<{ id: number, label: string }> = []
+ private videoCategories: Array<VideoConstant<number>> = []
+ private videoLicences: Array<VideoConstant<number>> = []
+ private videoLanguages: Array<VideoConstant<string>> = []
+ private videoPrivacies: Array<VideoConstant<VideoPrivacy>> = []
constructor (private http: HttpClient) {
this.loadConfigLocally()
private loadVideoAttributeEnum (
attributeName: 'categories' | 'licences' | 'languages' | 'privacies',
- hashToPopulate: { id: number, label: string }[],
+ hashToPopulate: VideoConstant<number | string>[],
notifier: ReplaySubject<boolean>,
sort = false
) {
Object.keys(data)
.forEach(dataKey => {
hashToPopulate.push({
- id: parseInt(dataKey, 10),
+ id: dataKey,
label: data[dataKey]
})
})
export class VideoEdit {
category: number
licence: number
- language: number
+ language: string
description: string
name: string
tags: string[]
publishedAt: Date
category: VideoConstant<number>
licence: VideoConstant<number>
- language: VideoConstant<number>
+ language: VideoConstant<string>
privacy: VideoConstant<VideoPrivacy>
description: string
duration: number
"express-rate-limit": "^2.11.0",
"express-validator": "^5.0.0",
"fluent-ffmpeg": "^2.1.0",
+ "iso-639-3": "^1.0.1",
"js-yaml": "^3.5.4",
"jsonld": "^1.0.1",
"jsonld-signatures": "https://github.com/Chocobozzz/jsonld-signatures#rsa2017",
isActivityPubVideoDurationValid(video.duration) &&
isUUIDValid(video.uuid) &&
setValidRemoteTags(video) &&
- (!video.category || isRemoteIdentifierValid(video.category)) &&
- (!video.licence || isRemoteIdentifierValid(video.licence)) &&
- (!video.language || isRemoteIdentifierValid(video.language)) &&
+ (!video.category || isRemoteNumberIdentifierValid(video.category)) &&
+ (!video.licence || isRemoteNumberIdentifierValid(video.licence)) &&
+ (!video.language || isRemoteStringIdentifierValid(video.language)) &&
isVideoViewsValid(video.views) &&
isBooleanValid(video.sensitive) &&
isBooleanValid(video.commentsEnabled) &&
isVideoTorrentCreateActivityValid,
isVideoTorrentUpdateActivityValid,
isVideoTorrentDeleteActivityValid,
+ isRemoteStringIdentifierValid,
isVideoFlagValid,
isVideoTorrentObjectValid
}
return true
}
-function isRemoteIdentifierValid (data: any) {
+function isRemoteNumberIdentifierValid (data: any) {
return validator.isInt(data.identifier, { min: 0 })
}
+function isRemoteStringIdentifierValid (data: any) {
+ return typeof data.identifier === 'string'
+}
+
function isRemoteVideoContentValid (mediaType: string, content: string) {
return mediaType === 'text/markdown' && isVideoTruncatedDescriptionValid(content)
}
const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS
const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES
-function isVideoCategoryValid (value: number) {
+function isVideoCategoryValid (value: any) {
return value === null || VIDEO_CATEGORIES[value] !== undefined
}
-function isVideoLicenceValid (value: number) {
+function isVideoLicenceValid (value: any) {
return value === null || VIDEO_LICENCES[value] !== undefined
}
-function isVideoLanguageValid (value: number) {
- return value === null || VIDEO_LANGUAGES[value] !== undefined
+function isVideoLanguageValid (value: any) {
+ return value === null ||
+ (typeof value === 'string' && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.LANGUAGE))
}
function isVideoDurationValid (value: string) {
errorFilter: err => {
const willRetry = (err.name === 'SequelizeDatabaseError')
- logger.debug('Maybe retrying the transaction function.', { willRetry })
+ logger.debug('Maybe retrying the transaction function.', { willRetry, err })
return willRetry
}
}, func, (err, data) => err ? rej(err) : res(data))
// ---------------------------------------------------------------------------
-const LAST_MIGRATION_VERSION = 205
+const LAST_MIGRATION_VERSION = 210
// ---------------------------------------------------------------------------
},
VIDEOS: {
NAME: { min: 3, max: 120 }, // Length
+ LANGUAGE: { min: 1, max: 10 }, // Length
TRUNCATED_DESCRIPTION: { min: 3, max: 250 }, // Length
DESCRIPTION: { min: 3, max: 10000 }, // Length
SUPPORT: { min: 3, max: 300 }, // Length
7: 'Public Domain Dedication'
}
-// See https://en.wikipedia.org/wiki/List_of_languages_by_number_of_native_speakers#Nationalencyklopedin
-const VIDEO_LANGUAGES = {
- 1: 'English',
- 2: 'Spanish',
- 3: 'Mandarin',
- 4: 'Hindi',
- 5: 'Arabic',
- 6: 'Portuguese',
- 7: 'Bengali',
- 8: 'Russian',
- 9: 'Japanese',
- 10: 'Punjabi',
- 11: 'German',
- 12: 'Korean',
- 13: 'French',
- 14: 'Italian',
- 1000: 'Sign Language',
- 1001: 'American Sign Language',
- 1002: 'Arab Sign Language',
- 1003: 'British Sign Language',
- 1004: 'Brazilian Sign Language',
- 1005: 'Chinese Sign Language',
- 1006: 'Czech Sign Language',
- 1007: 'Danish Sign Language',
- 1008: 'French Sign Language',
- 1009: 'German Sign Language',
- 1010: 'Indo-Pakistani Sign Language',
- 1011: 'Japanese Sign Language',
- 1012: 'South African Sign Language',
- 1013: 'Swedish Sign Language',
- 1014: 'Russian Sign Language'
-}
+const VIDEO_LANGUAGES = buildLanguages()
const VIDEO_PRIVACIES = {
[VideoPrivacy.PUBLIC]: 'Public',
CONFIG.WEBSERVER.HOST = sanitizeHost(CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT, REMOTE_SCHEME.HTTP)
}
+function buildLanguages () {
+ const iso639 = require('iso-639-3')
+
+ const languages: { [ id: string ]: string } = {}
+
+ const signLanguages = [
+ 'sgn', // Sign languages (macro language)
+ 'ase', // American
+ 'sdl', // Arabian
+ 'bfi', // British
+ 'bzs', // Brazilian
+ 'csl', // Chinese
+ 'cse', // Czech
+ 'dsl', // Danish
+ 'fsl', // French
+ 'gsg', // German
+ 'pks', // Pakistan
+ 'jsl', // Japanese
+ 'sfs', // South African
+ 'swl', // Swedish
+ 'rsl' // Russian
+ ]
+
+ // Only add ISO639-1 languages and some sign languages (ISO639-3)
+ iso639
+ .filter(l => {
+ return (l.iso6391 !== null && l.type === 'living') ||
+ signLanguages.indexOf(l.iso6393) !== -1
+ })
+ .forEach(l => languages[l.iso6391 || l.iso6393] = l.name)
+
+ return languages
+}
+
export function reloadConfig () {
function directory () {
--- /dev/null
+import * as Sequelize from 'sequelize'
+import { CONSTRAINTS_FIELDS } from '../index'
+
+async function up (utils: {
+ transaction: Sequelize.Transaction,
+ queryInterface: Sequelize.QueryInterface,
+ sequelize: Sequelize.Sequelize
+}): Promise<void> {
+
+ {
+ await utils.queryInterface.renameColumn('video', 'language', 'oldLanguage')
+ }
+
+ {
+ const data = {
+ type: Sequelize.STRING(CONSTRAINTS_FIELDS.VIDEOS.LANGUAGE.max),
+ allowNull: true,
+ defaultValue: null
+ }
+ await utils.queryInterface.addColumn('video', 'language', data)
+ }
+
+ {
+ const languages = [
+ {
+ oldLanguage: 1,
+ newLanguage: 'en'
+ },
+ {
+ oldLanguage: 2,
+ newLanguage: 'es'
+ },
+ {
+ oldLanguage: 3,
+ newLanguage: 'zh'
+ },
+ {
+ oldLanguage: 4,
+ newLanguage: 'hi'
+ },
+ {
+ oldLanguage: 5,
+ newLanguage: 'ar'
+ },
+ {
+ oldLanguage: 6,
+ newLanguage: 'pt'
+ },
+ {
+ oldLanguage: 7,
+ newLanguage: 'bn'
+ },
+ {
+ oldLanguage: 8,
+ newLanguage: 'ru'
+ },
+ {
+ oldLanguage: 9,
+ newLanguage: 'ja'
+ },
+ {
+ oldLanguage: 10,
+ newLanguage: 'pa'
+ },
+ {
+ oldLanguage: 11,
+ newLanguage: 'de'
+ },
+ {
+ oldLanguage: 12,
+ newLanguage: 'ko'
+ },
+ {
+ oldLanguage: 13,
+ newLanguage: 'fr'
+ },
+ {
+ oldLanguage: 14,
+ newLanguage: 'it'
+ },
+ {
+ oldLanguage: 1000,
+ newLanguage: 'sgn'
+ },
+ {
+ oldLanguage: 1001,
+ newLanguage: 'ase'
+ },
+ {
+ oldLanguage: 1002,
+ newLanguage: 'sdl'
+ },
+ {
+ oldLanguage: 1003,
+ newLanguage: 'bfi'
+ },
+ {
+ oldLanguage: 1004,
+ newLanguage: 'bzs'
+ },
+ {
+ oldLanguage: 1005,
+ newLanguage: 'csl'
+ },
+ {
+ oldLanguage: 1006,
+ newLanguage: 'cse'
+ },
+ {
+ oldLanguage: 1007,
+ newLanguage: 'dsl'
+ },
+ {
+ oldLanguage: 1008,
+ newLanguage: 'fsl'
+ },
+ {
+ oldLanguage: 1009,
+ newLanguage: 'gsg'
+ },
+ {
+ oldLanguage: 1010,
+ newLanguage: 'pks'
+ },
+ {
+ oldLanguage: 1011,
+ newLanguage: 'jsl'
+ },
+ {
+ oldLanguage: 1012,
+ newLanguage: 'sfs'
+ },
+ {
+ oldLanguage: 1013,
+ newLanguage: 'swl'
+ },
+ {
+ oldLanguage: 1014,
+ newLanguage: 'rsl'
+ }
+ ]
+
+ for (const language of languages) {
+ const query = 'UPDATE "video" SET "language" = \'' + language.newLanguage + '\' WHERE "oldLanguage" = ' + language.oldLanguage
+ await utils.sequelize.query(query)
+ }
+ }
+
+ {
+ await utils.queryInterface.removeColumn('video', 'oldLanguage')
+ }
+
+}
+
+function down (options) {
+ throw new Error('Not implemented.')
+}
+
+export {
+ up,
+ down
+}
videoObject: VideoTorrentObject,
to: string[] = []) {
const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPrivacy.PUBLIC : VideoPrivacy.UNLISTED
-
const duration = videoObject.duration.replace(/[^\d]+/, '')
- let language = null
+
+ let language: string = null
if (videoObject.language) {
- language = parseInt(videoObject.language.identifier, 10)
+ language = videoObject.language.identifier
}
- let category = null
+ let category: number = null
if (videoObject.category) {
category = parseInt(videoObject.category.identifier, 10)
}
- let licence = null
+ let licence: number = null
if (videoObject.licence) {
licence = parseInt(videoObject.licence.identifier, 10)
}
@AllowNull(true)
@Default(null)
@Is('VideoLanguage', value => throwIfNotValid(value, isVideoLanguageValid, 'language'))
- @Column
- language: number
+ @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.LANGUAGE.max))
+ language: string
@AllowNull(false)
@Is('VideoPrivacy', value => throwIfNotValid(value, isVideoPrivacyValid, 'privacy'))
return licenceLabel
}
- private static getLanguageLabel (id: number) {
+ private static getLanguageLabel (id: string) {
let languageLabel = VIDEO_LANGUAGES[id]
+ console.log(VIDEO_LANGUAGES)
+ console.log(id)
if (!languageLabel) languageLabel = 'Unknown'
return languageLabel
let language
if (this.language) {
language = {
- identifier: this.language + '',
+ identifier: this.language,
name: VideoModel.getLanguageLabel(this.language)
}
}
name: 'my super name',
category: 5,
licence: 1,
- language: 6,
+ language: 'pt',
nsfw: false,
commentsEnabled: true,
description: 'my super description',
})
it('Should fail with a bad language', async function () {
- const fields = immutableAssign(baseCorrectParams, { language: 125 })
+ const fields = immutableAssign(baseCorrectParams, { language: 'a'.repeat(15) })
const attaches = baseCorrectAttaches
await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
name: 'my super name',
category: 5,
licence: 2,
- language: 6,
+ language: 'pt',
nsfw: false,
commentsEnabled: false,
description: 'my super description',
})
it('Should fail with a bad language', async function () {
- const fields = immutableAssign(baseCorrectParams, { language: 125 })
+ const fields = immutableAssign(baseCorrectParams, { language: 'a'.repeat(15) })
await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields })
})
name: 'server3-4',
category: 2,
licence: 6,
- language: 3,
+ language: 'zh',
nsfw: true,
description: 'my super description',
support: 'my super support text',
name: 'my super name for server 1',
category: 5,
licence: 4,
- language: 9,
+ language: 'ja',
nsfw: true,
privacy: VideoPrivacy.PUBLIC,
description: 'my super description for server 1',
name: 'my super name for server 1',
category: 5,
licence: 4,
- language: 9,
+ language: 'ja',
nsfw: true,
description: 'my super description for server 1',
support: 'my super support text for server 1',
name: 'my super name for server 1',
category: 5,
licence: 4,
- language: 9,
+ language: 'ja',
nsfw: true,
description: 'my super description for server 1',
support: 'my super support text for server 1',
name: 'my super name for server 1',
category: 5,
licence: 4,
- language: 9,
+ language: 'ja',
nsfw: true,
description: 'my super description for server 1',
support: 'my super support text for server 1',
name: 'my super name for server 2',
category: 4,
licence: 3,
- language: 11,
+ language: 'de',
nsfw: true,
description: 'my super description for server 2',
support: 'my super support text for server 2',
name: 'my super name for server 2',
category: 4,
licence: 3,
- language: 11,
+ language: 'de',
nsfw: true,
description: 'my super description for server 2',
support: 'my super support text for server 2',
name: 'my super name for server 3',
category: 6,
licence: 5,
- language: 11,
+ language: 'de',
nsfw: true,
description: 'my super description for server 3',
support: 'my super support text for server 3',
name: 'my super name for server 3-2',
category: 7,
licence: 6,
- language: 12,
+ language: 'ko',
nsfw: false,
description: 'my super description for server 3-2',
support: 'my super support text for server 3-2',
name: 'my super name for server 3',
category: 6,
licence: 5,
- language: 11,
+ language: 'de',
nsfw: true,
description: 'my super description for server 3',
support: 'my super support text for server 3',
name: 'my super name for server 3-2',
category: 7,
licence: 6,
- language: 12,
+ language: 'ko',
nsfw: false,
description: 'my super description for server 3-2',
support: 'my super support text for server 3-2',
name: 'my super video updated',
category: 10,
licence: 7,
- language: 13,
+ language: 'fr',
nsfw: true,
description: 'my super description updated',
support: 'my super support text updated',
name: 'my super video updated',
category: 10,
licence: 7,
- language: 13,
+ language: 'fr',
nsfw: true,
description: 'my super description updated',
support: 'my super support text updated',
name: 'my super name',
category: 2,
licence: 6,
- language: 3,
+ language: 'zh',
nsfw: true,
description: 'my super description',
support: 'my super support text',
name: 'my super video updated',
category: 4,
licence: 2,
- language: 5,
+ language: 'ar',
nsfw: false,
description: 'my super description updated',
support: 'my super support text updated',
const languages = res.body
expect(Object.keys(languages)).to.have.length.above(5)
- expect(languages[3]).to.equal('Mandarin')
+ expect(languages['ru']).to.equal('Russian')
})
it('Should list video privacies', async function () {
// expect(video.categoryLabel).to.equal('Films')
// expect(video.licence).to.equal(6)
// expect(video.licenceLabel).to.equal('Attribution - Non Commercial - No Derivatives')
- // expect(video.language).to.equal(3)
- // expect(video.languageLabel).to.equal('Mandarin')
+ // expect(video.language).to.equal('zh')
+ // expect(video.languageLabel).to.equal('Chinese')
// expect(video.nsfw).to.be.ok
// expect(video.description).to.equal('my super description')
// expect(video.account.name).to.equal('root')
description: video + ' description',
category: 2,
licence: 1,
- language: 1,
+ language: 'en',
nsfw: true,
tags: [ 'tag1', 'tag2', 'tag3' ],
fixture: video
name: 'my super video updated',
category: 4,
licence: 2,
- language: 5,
+ language: 'ar',
nsfw: false,
description: 'my super description updated',
commentsEnabled: false,
category: 4,
nsfw: false,
licence: 2,
- language: 1,
+ language: 'en',
description: Date.now() + ' description',
tags: [ Date.now().toString().substring(0, 5) + 't1', Date.now().toString().substring(0, 5) + 't2' ],
fixture: 'video_short1.webm'
name?: string
category?: number
licence?: number
- language?: number
+ language?: string
nsfw?: boolean
commentsEnabled?: boolean
description?: string
name: 'my super video',
category: 5,
licence: 4,
- language: 3,
+ language: 'zh',
channelId: defaultChannelId,
nsfw: true,
description: 'my super description',
name: string
category: number
licence: number
- language: number
+ language: string
nsfw: boolean
commentsEnabled: boolean
description: string
expect(video.name).to.equal(attributes.name)
expect(video.category.id).to.equal(attributes.category)
- expect(video.category.label).to.equal(VIDEO_CATEGORIES[attributes.category] || 'Misc')
+ expect(video.category.label).to.equal(attributes.category !== null ? VIDEO_CATEGORIES[attributes.category] : 'Misc')
expect(video.licence.id).to.equal(attributes.licence)
- expect(video.licence.label).to.equal(VIDEO_LICENCES[attributes.licence] || 'Unknown')
+ expect(video.licence.label).to.equal(attributes.licence !== null ? VIDEO_LICENCES[attributes.licence] : 'Unknown')
expect(video.language.id).to.equal(attributes.language)
- expect(video.language.label).to.equal(VIDEO_LANGUAGES[attributes.language] || 'Unknown')
+ expect(video.language.label).to.equal(attributes.language !== null ? VIDEO_LANGUAGES[attributes.language] : 'Unknown')
expect(video.privacy.id).to.deep.equal(attributes.privacy)
expect(video.privacy.label).to.deep.equal(VIDEO_PRIVACIES[attributes.privacy])
expect(video.nsfw).to.equal(attributes.nsfw)
.option('-U, --username <username>', 'Username')
.option('-p, --password <token>', 'Password')
.option('-t, --target-url <targetUrl>', 'Video target URL')
- .option('-l, --language <languageCode>', 'Language code')
+ .option('-l, --language <languageCode>', 'Language ISO 639 code (fr or en...)')
.option('-v, --verbose', 'Verbose mode')
.parse(process.argv)
})
}
-function processVideo (info: any, languageCode: number) {
+function processVideo (info: any, languageCode: string) {
return new Promise(async res => {
if (program['verbose']) console.log('Fetching object.', info)
})
}
-async function uploadVideoOnPeerTube (videoInfo: any, videoPath: string, language?: number) {
+async function uploadVideoOnPeerTube (videoInfo: any, videoPath: string, language?: string) {
const category = await getCategory(videoInfo.categories)
const licence = getLicence(videoInfo.license)
let tags = []
.option('-c, --category <category number>', 'Category number')
.option('-m, --comments-enabled', 'Enable comments')
.option('-l, --licence <licence number>', 'Licence number')
- .option('-L, --language <language number>', 'Language number')
+ .option('-L, --language <language code>', 'Language ISO 639 code (fr or en...)')
.option('-d, --video-description <description>', 'Video description')
.option('-t, --tags <tags>', 'Video tags', list)
.option('-b, --thumbnail <thumbnailPath>', 'Thumbnail path')
export interface VideoCreate {
category?: number
licence?: number
- language?: number
+ language?: string
description?: string
support?: string
channelId: number
name?: string
category?: number
licence?: number
- language?: number
+ language?: string
description?: string
support?: string
privacy?: VideoPrivacy
import { VideoPrivacy } from './video-privacy.enum'
export interface VideoConstant <T> {
- id: number
+ id: T
label: string
}
publishedAt: Date | string
category: VideoConstant<number>
licence: VideoConstant<number>
- language: VideoConstant<number>
+ language: VideoConstant<string>
privacy: VideoConstant<VideoPrivacy>
description: string
duration: number
get:
tags:
- Feeds
- consumes:
- - application/json
produces:
+ - application/atom+xml
+ - application/rss+xml
- application/json
parameters:
- name: format
responses:
'200':
description: successful operation
- content:
- application/json:
- application/xml:
/jobs:
get:
security:
description: 'Video licence'
- name: language
in: formData
- type: number
+ type: string
description: 'Video language'
- name: description
in: formData
description: 'Video licence'
- name: language
in: formData
- type: number
+ type: string
description: 'Video language'
- name: description
in: formData
'204':
description: successful operation
definitions:
- VideoConstant:
+ VideoConstantNumber:
properties:
id:
type: number
label:
type: string
+ VideoConstantString:
+ properties:
+ id:
+ type: string
+ label:
+ type: string
VideoPrivacy:
type: string
enum: [Public, Unlisted, Private]
updatedAt:
type: string
category:
- $ref: "#/definitions/VideoConstant"
+ $ref: "#/definitions/VideoConstantNumber"
licence:
- $ref: "#/definitions/VideoConstant"
+ $ref: "#/definitions/VideoConstantNumber"
language:
- $ref: "#/definitions/VideoConstant"
+ $ref: "#/definitions/VideoConstantString"
privacy:
$ref: "#/definitions/VideoPrivacy"
description:
version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
+iso-639-3@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/iso-639-3/-/iso-639-3-1.0.1.tgz#ebdf945e1e691bc8225e41e4520fe6a51083e647"
+
isobject@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"