import { join } from 'path'
-import { JobType, VideoRateType, VideoState } from '../../shared/models'
+import { JobType, VideoRateType, VideoResolution, VideoState } from '../../shared/models'
import { ActivityPubActorType } from '../../shared/models/activitypub'
import { FollowState } from '../../shared/models/actors'
import { VideoAbuseState, VideoImportState, VideoPrivacy, VideoTranscodingFPS } from '../../shared/models/videos'
// Do not use barrels, remain constants as independent as possible
-import { isTestInstance, sanitizeHost, sanitizeUrl } from '../helpers/core-utils'
+import { isTestInstance, sanitizeHost, sanitizeUrl, root, parseDurationToMs } from '../helpers/core-utils'
import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type'
import { invert } from 'lodash'
import { CronRepeatOptions, EveryRepeatOptions } from 'bull'
// ---------------------------------------------------------------------------
-const LAST_MIGRATION_VERSION = 360
+const LAST_MIGRATION_VERSION = 450
// ---------------------------------------------------------------------------
-// API version
const API_VERSION = 'v1'
+const PEERTUBE_VERSION = require(join(root(), 'package.json')).version
const PAGINATION = {
COUNT: {
// Sortable columns per schema
const SORTABLE_COLUMNS = {
- USERS: [ 'id', 'username', 'createdAt' ],
+ USERS: [ 'id', 'username', 'videoQuotaUsed', 'createdAt' ],
USER_SUBSCRIPTIONS: [ 'id', 'createdAt' ],
ACCOUNTS: [ 'createdAt' ],
JOBS: [ 'createdAt' ],
VIDEO_COMMENT_THREADS: [ 'createdAt' ],
VIDEO_RATES: [ 'createdAt' ],
BLACKLISTS: [ 'id', 'name', 'duration', 'views', 'likes', 'dislikes', 'uuid', 'createdAt' ],
- FOLLOWERS: [ 'createdAt' ],
- FOLLOWING: [ 'createdAt' ],
+ FOLLOWERS: [ 'createdAt', 'state', 'score' ],
+ FOLLOWING: [ 'createdAt', 'redundancyAllowed', 'state' ],
VIDEOS: [ 'name', 'duration', 'createdAt', 'publishedAt', 'views', 'likes', 'trending' ],
USER_NOTIFICATIONS: [ 'createdAt' ],
- VIDEO_PLAYLISTS: [ 'displayName', 'createdAt', 'updatedAt' ]
+ VIDEO_PLAYLISTS: [ 'displayName', 'createdAt', 'updatedAt' ],
+
+ PLUGINS: [ 'name', 'createdAt', 'updatedAt' ],
+
+ AVAILABLE_PLUGINS: [ 'npmName', 'popularity' ]
}
const OAUTH_LIFETIME = {
const JOB_COMPLETED_LIFETIME = 60000 * 60 * 24 * 2 // 2 days
const VIDEO_IMPORT_TIMEOUT = 1000 * 3600 // 1 hour
-// 1 hour
-let SCHEDULER_INTERVALS_MS = {
+const SCHEDULER_INTERVALS_MS = {
actorFollowScores: 60000 * 60, // 1 hour
removeOldJobs: 60000 * 60, // 1 hour
updateVideos: 60000, // 1 minute
- youtubeDLUpdate: 60000 * 60 * 24 // 1 day
+ youtubeDLUpdate: 60000 * 60 * 24, // 1 day
+ checkPlugins: CONFIG.PLUGINS.INDEX.CHECK_LATEST_VERSIONS_INTERVAL,
+ autoFollowIndexInstances: 60000 * 60 * 24, // 1 day
+ removeOldViews: 60000 * 60 * 24, // 1 day
+ removeOldHistory: 60000 * 60 * 24 // 1 day
+}
+
+const INSTANCES_INDEX = {
+ HOSTS_PATH: '/api/v1/instances/hosts'
}
// ---------------------------------------------------------------------------
-let CONSTRAINTS_FIELDS = {
+const CONSTRAINTS_FIELDS = {
USERS: {
NAME: { min: 1, max: 120 }, // Length
DESCRIPTION: { min: 3, max: 1000 }, // Length
PASSWORD: { min: 6, max: 255 }, // Length
VIDEO_QUOTA: { min: -1 },
VIDEO_QUOTA_DAILY: { min: -1 },
+ VIDEO_LANGUAGES: { max: 500 }, // Array length
BLOCKED_REASON: { min: 3, max: 250 } // Length
},
VIDEO_ABUSES: {
max: 2 * 1024 * 1024 // 2MB
}
},
- EXTNAME: buildVideosExtname(),
+ EXTNAME: [] as string[],
INFO_HASH: { min: 40, max: 40 }, // Length, info hash is 20 bytes length but we represent it in hexadecimal so 20 * 2
DURATION: { min: 0 }, // Number
TAGS: { min: 0, max: 5 }, // Number of total tags
CONTACT_FORM: {
FROM_NAME: { min: 1, max: 120 }, // Length
BODY: { min: 3, max: 5000 } // Length
- }
-}
-
-const RATES_LIMIT = {
- LOGIN: {
- WINDOW_MS: 5 * 60 * 1000, // 5 minutes
- MAX: 15 // 15 attempts
},
- ASK_SEND_EMAIL: {
- WINDOW_MS: 5 * 60 * 1000, // 5 minutes
- MAX: 3 // 3 attempts
+ PLUGINS: {
+ NAME: { min: 1, max: 214 }, // Length
+ DESCRIPTION: { min: 1, max: 20000 } // Length
}
}
KEEP_ORIGIN_FPS_RESOLUTION_MIN: 720 // We keep the original FPS on high resolutions (720 minimum)
}
+const DEFAULT_AUDIO_RESOLUTION = VideoResolution.H_480P
+
const VIDEO_RATE_TYPES: { [ id: string ]: VideoRateType } = {
LIKE: 'like',
DISLIKE: 'dislike'
7: 'Public Domain Dedication'
}
-const VIDEO_LANGUAGES = buildLanguages()
+let VIDEO_LANGUAGES: { [id: string]: string } = {}
const VIDEO_PRIVACIES = {
[ VideoPrivacy.PUBLIC ]: 'Public',
}
const MIMETYPES = {
+ AUDIO: {
+ MIMETYPE_EXT: {
+ 'audio/mpeg': '.mp3',
+ 'audio/mp3': '.mp3',
+ 'application/ogg': '.ogg',
+ 'audio/ogg': '.ogg',
+ 'audio/flac': '.flac'
+ },
+ EXT_MIMETYPE: null as { [ id: string ]: string }
+ },
VIDEO: {
- MIMETYPE_EXT: buildVideoMimetypeExt(),
+ MIMETYPE_EXT: null as { [ id: string ]: string },
EXT_MIMETYPE: null as { [ id: string ]: string }
},
IMAGE: {
VIDEO_CAPTIONS: {
MIMETYPE_EXT: {
'text/vtt': '.vtt',
- 'application/x-subrip': '.srt'
+ 'application/x-subrip': '.srt',
+ 'text/plain': '.srt'
}
},
TORRENT: {
}
}
}
-MIMETYPES.VIDEO.EXT_MIMETYPE = invert(MIMETYPES.VIDEO.MIMETYPE_EXT)
+MIMETYPES.AUDIO.EXT_MIMETYPE = invert(MIMETYPES.AUDIO.MIMETYPE_EXT)
// ---------------------------------------------------------------------------
}
}
+const VIDEO_CHANNELS = {
+ MAX_PER_USER: 20
+}
+
// ---------------------------------------------------------------------------
const SERVER_ACTOR_NAME = 'peertube'
COLLECTION_ITEMS_PER_PAGE: 10,
FETCH_PAGE_LIMIT: 100,
URL_MIME_TYPES: {
- VIDEO: Object.keys(MIMETYPES.VIDEO.MIMETYPE_EXT),
+ VIDEO: [] as string[],
TORRENT: [ 'application/x-bittorrent' ],
MAGNET: [ 'application/x-bittorrent;x-scheme-handler/magnet' ]
},
const ACTIVITY_PUB_ACTOR_TYPES: { [ id: string ]: ActivityPubActorType } = {
GROUP: 'Group',
PERSON: 'Person',
- APPLICATION: 'Application'
+ APPLICATION: 'Application',
+ ORGANIZATION: 'Organization',
+ SERVICE: 'Service'
}
const HTTP_SIGNATURE = {
HEADER_NAME: 'signature',
ALGORITHM: 'rsa-sha256',
- HEADERS_TO_SIGN: [ '(request-target)', 'host', 'date', 'digest' ]
+ HEADERS_TO_SIGN: [ '(request-target)', 'host', 'date', 'digest' ],
+ CLOCK_SKEW_SECONDS: 1800
}
// ---------------------------------------------------------------------------
}
const STATIC_DOWNLOAD_PATHS = {
TORRENTS: '/download/torrents/',
- VIDEOS: '/download/videos/'
+ VIDEOS: '/download/videos/',
+ HLS_VIDEOS: '/download/streaming-playlists/hls/videos/'
+}
+const LAZY_STATIC_PATHS = {
+ AVATARS: '/lazy-static/avatars/',
+ PREVIEWS: '/static/previews/',
+ VIDEO_CAPTIONS: '/static/video-captions/'
}
// Cache control
-let STATIC_MAX_AGE = '2h'
+let STATIC_MAX_AGE = {
+ SERVER: '2h',
+ CLIENT: '30d'
+}
// Videos thumbnail size
const THUMBNAILS_SIZE = {
height: 122
}
const PREVIEWS_SIZE = {
- width: 560,
- height: 315
+ width: 850,
+ height: 480
}
const AVATARS_SIZE = {
width: 120,
}
}
-const CACHE = {
+const LRU_CACHE = {
USER_TOKENS: {
- MAX_SIZE: 10000
+ MAX_SIZE: 1000
+ },
+ AVATAR_STATIC: {
+ MAX_SIZE: 500
}
}
OVERVIEWS_SAMPLE: 1000 * 3600 * 4 // 4 hours
}
+const QUEUE_CONCURRENCY = {
+ AVATAR_PROCESS_IMAGE: 3
+}
+
const REDUNDANCY = {
VIDEOS: {
RANDOMIZED_FACTOR: 5
const ACCEPT_HEADERS = [ 'html', 'application/json' ].concat(ACTIVITY_PUB.POTENTIAL_ACCEPT_HEADERS)
+const ASSETS_PATH = {
+ DEFAULT_AUDIO_BACKGROUND: join(root(), 'dist', 'server', 'assets', 'default-audio-background.jpg')
+}
+
// ---------------------------------------------------------------------------
const CUSTOM_HTML_TAG_COMMENTS = {
// ---------------------------------------------------------------------------
+const PLUGIN_GLOBAL_CSS_FILE_NAME = 'plugins-global.css'
+const PLUGIN_GLOBAL_CSS_PATH = join(CONFIG.STORAGE.TMP_DIR, PLUGIN_GLOBAL_CSS_FILE_NAME)
+
+const DEFAULT_THEME_NAME = 'default'
+const DEFAULT_USER_THEME_NAME = 'instance-default'
+
+// ---------------------------------------------------------------------------
+
// Special constants for a test instance
if (isTestInstance() === true) {
PRIVATE_RSA_KEY_SIZE = 1024
REMOTE_SCHEME.HTTP = 'http'
REMOTE_SCHEME.WS = 'ws'
- STATIC_MAX_AGE = '0'
+ STATIC_MAX_AGE.SERVER = '0'
ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE = 2
ACTIVITY_PUB.ACTOR_REFRESH_INTERVAL = 10 * 1000 // 10 seconds
SCHEDULER_INTERVALS_MS.actorFollowScores = 1000
SCHEDULER_INTERVALS_MS.removeOldJobs = 10000
+ SCHEDULER_INTERVALS_MS.removeOldHistory = 5000
+ SCHEDULER_INTERVALS_MS.removeOldViews = 5000
SCHEDULER_INTERVALS_MS.updateVideos = 5000
+ SCHEDULER_INTERVALS_MS.autoFollowIndexInstances = 5000
REPEAT_JOBS[ 'videos-views' ] = { every: 5000 }
REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR = 1
FILES_CACHE.VIDEO_CAPTIONS.MAX_AGE = 3000
MEMOIZE_TTL.OVERVIEWS_SAMPLE = 1
ROUTE_CACHE_LIFETIME.OVERVIEWS.VIDEOS = '0ms'
-
- RATES_LIMIT.LOGIN.MAX = 20
}
updateWebserverUrls()
+updateWebserverConfig()
registerConfigChangedHandler(() => {
updateWebserverUrls()
export {
WEBSERVER,
API_VERSION,
+ PEERTUBE_VERSION,
+ LAZY_STATIC_PATHS,
HLS_REDUNDANCY_DIRECTORY,
P2P_MEDIA_LOADER_PEER_VERSION,
AVATARS_SIZE,
PREVIEWS_SIZE,
REMOTE_SCHEME,
FOLLOW_STATES,
+ INSTANCES_INDEX,
+ DEFAULT_USER_THEME_NAME,
SERVER_ACTOR_NAME,
+ PLUGIN_GLOBAL_CSS_FILE_NAME,
+ PLUGIN_GLOBAL_CSS_PATH,
PRIVATE_RSA_KEY_SIZE,
ROUTE_CACHE_LIFETIME,
SORTABLE_COLUMNS,
HLS_STREAMING_PLAYLIST_DIRECTORY,
FEEDS,
JOB_TTL,
+ DEFAULT_THEME_NAME,
NSFW_POLICY_TYPES,
STATIC_MAX_AGE,
STATIC_PATHS,
VIDEO_PRIVACIES,
VIDEO_LICENCES,
VIDEO_STATES,
+ QUEUE_CONCURRENCY,
VIDEO_RATE_TYPES,
VIDEO_TRANSCODING_FPS,
FFMPEG_NICE,
VIDEO_ABUSE_STATES,
- CACHE,
+ VIDEO_CHANNELS,
+ LRU_CACHE,
JOB_REQUEST_TIMEOUT,
USER_PASSWORD_RESET_LIFETIME,
MEMOIZE_TTL,
SCHEDULER_INTERVALS_MS,
REPEAT_JOBS,
STATIC_DOWNLOAD_PATHS,
- RATES_LIMIT,
MIMETYPES,
CRAWL_REQUEST_CONCURRENCY,
+ DEFAULT_AUDIO_RESOLUTION,
JOB_COMPLETED_LIFETIME,
HTTP_SIGNATURE,
VIDEO_IMPORT_STATES,
VIDEO_VIEW_LIFETIME,
CONTACT_FORM_LIFETIME,
VIDEO_PLAYLIST_PRIVACIES,
+ ASSETS_PATH,
+ loadLanguages,
buildLanguages
}
'video/mp4': '.mp4'
}
- if (CONFIG.TRANSCODING.ENABLED && CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS) {
- Object.assign(data, {
- 'video/quicktime': '.mov',
- 'video/x-msvideo': '.avi',
- 'video/x-flv': '.flv',
- 'video/x-matroska': '.mkv',
- 'application/octet-stream': '.mkv',
- 'video/avi': '.avi'
- })
+ if (CONFIG.TRANSCODING.ENABLED) {
+ if (CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS) {
+ Object.assign(data, {
+ 'video/quicktime': '.mov',
+ 'video/x-msvideo': '.avi',
+ 'video/x-flv': '.flv',
+ 'video/x-matroska': '.mkv',
+ 'application/octet-stream': '.mkv',
+ 'video/avi': '.avi'
+ })
+ }
+
+ if (CONFIG.TRANSCODING.ALLOW_AUDIO_FILES) {
+ Object.assign(data, MIMETYPES.AUDIO.MIMETYPE_EXT)
+ }
}
return data
}
function updateWebserverConfig () {
- CONSTRAINTS_FIELDS.VIDEOS.EXTNAME = buildVideosExtname()
-
MIMETYPES.VIDEO.MIMETYPE_EXT = buildVideoMimetypeExt()
MIMETYPES.VIDEO.EXT_MIMETYPE = invert(MIMETYPES.VIDEO.MIMETYPE_EXT)
+ ACTIVITY_PUB.URL_MIME_TYPES.VIDEO = Object.keys(MIMETYPES.VIDEO.MIMETYPE_EXT)
+
+ CONSTRAINTS_FIELDS.VIDEOS.EXTNAME = buildVideosExtname()
}
function buildVideosExtname () {
- return CONFIG.TRANSCODING.ENABLED && CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS
- ? [ '.mp4', '.ogv', '.webm', '.mkv', '.mov', '.avi', '.flv' ]
- : [ '.mp4', '.ogv', '.webm' ]
+ return Object.keys(MIMETYPES.VIDEO.EXT_MIMETYPE)
+}
+
+function loadLanguages () {
+ Object.assign(VIDEO_LANGUAGES, buildLanguages())
}
function buildLanguages () {
// Override Occitan label
languages[ 'oc' ] = 'Occitan'
+ languages[ 'el' ] = 'Greek'
return languages
}