import { SortMeta } from 'primeng/api'
import { switchMap } from 'rxjs/operators'
-import { buildVideoEmbedLink, buildVideoOrPlaylistEmbed, decorateVideoLink } from 'src/assets/player/utils'
+import { buildVideoOrPlaylistEmbed } from 'src/assets/player/utils'
import { environment } from 'src/environments/environment'
import { Component, OnInit } from '@angular/core'
import { DomSanitizer } from '@angular/platform-browser'
import { AdvancedInputFilter } from '@app/shared/shared-forms'
import { DropdownAction, Video, VideoService } from '@app/shared/shared-main'
import { VideoBlockService } from '@app/shared/shared-moderation'
+import { buildVideoEmbedLink, decorateVideoLink } from '@shared/core-utils'
import { VideoBlacklist, VideoBlacklistType } from '@shared/models'
@Component({
import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service'
import { ComponentPagination, ConfirmService, hasMoreItems, Notifier } from '@app/core'
import { PluginService } from '@app/core/plugins/plugin.service'
-import { compareSemVer } from '@shared/core-utils/miscs/miscs'
+import { compareSemVer } from '@shared/core-utils'
import { PeerTubePlugin, PluginType } from '@shared/models'
@Component({
import { Video, VideoCaptionService, VideoDetails, VideoService } from '@app/shared/shared-main'
import { SubscribeButtonComponent } from '@app/shared/shared-user-subscription'
import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-playlist'
+import { timeToInt } from '@shared/core-utils'
import {
HTMLServerConfig,
HttpStatusCode,
PlayerMode,
videojs
} from '../../../assets/player/peertube-player-manager'
-import { timeToInt } from '../../../assets/player/utils'
import { environment } from '../../../environments/environment'
import { VideoWatchPlaylistComponent } from './shared'
import * as MarkdownIt from 'markdown-it'
-import { buildVideoLink, decorateVideoLink } from 'src/assets/player/utils'
import { Injectable } from '@angular/core'
+import { buildVideoLink, decorateVideoLink } from '@shared/core-utils'
import {
COMPLETE_RULES,
ENHANCED_RULES,
import * as debug from 'debug'
import truncate from 'lodash-es/truncate'
import { SortMeta } from 'primeng/api'
-import { buildVideoEmbedLink, buildVideoOrPlaylistEmbed, decorateVideoLink } from 'src/assets/player/utils'
+import { buildVideoOrPlaylistEmbed } from 'src/assets/player/utils'
import { environment } from 'src/environments/environment'
import { Component, Input, OnInit, ViewChild } from '@angular/core'
import { DomSanitizer } from '@angular/platform-browser'
import { Account, Actor, DropdownAction, Video, VideoService } from '@app/shared/shared-main'
import { AbuseService, BlocklistService, VideoBlockService } from '@app/shared/shared-moderation'
import { VideoCommentService } from '@app/shared/shared-video-comment'
+import { buildVideoEmbedLink, decorateVideoLink } from '@shared/core-utils'
import { AbuseState, AdminAbuse } from '@shared/models'
import { AdvancedInputFilter } from '../shared-forms'
import { AbuseMessageModalComponent } from './abuse-message-modal.component'
-import { buildPlaylistEmbedLink, buildVideoEmbedLink, buildVideoOrPlaylistEmbed } from 'src/assets/player/utils'
+import { buildVideoOrPlaylistEmbed } from 'src/assets/player/utils'
import { environment } from 'src/environments/environment'
import { Component, ElementRef, Input, OnInit } from '@angular/core'
+import { buildPlaylistEmbedLink, buildVideoEmbedLink } from '@shared/core-utils'
import { CustomMarkupComponent } from './shared'
@Component({
import { ChangeDetectorRef, Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core'
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
-import { secondsToTime, timeToInt } from '../../../assets/player/utils'
+import { secondsToTime, timeToInt } from '@shared/core-utils'
@Component({
selector: 'my-timestamp-input',
import { User } from '@app/core/users/user.model'
import { durationToString, getAbsoluteAPIUrl, getAbsoluteEmbedUrl } from '@app/helpers'
import { Actor } from '@app/shared/shared-main/account/actor.model'
+import { buildVideoWatchPath } from '@shared/core-utils'
import { peertubeTranslate } from '@shared/core-utils/i18n'
import {
ActorImage,
pluginData?: any
static buildWatchUrl (video: Partial<Pick<Video, 'uuid' | 'shortUUID'>>) {
- return '/w/' + (video.shortUUID || video.uuid)
+ return buildVideoWatchPath({ shortUUID: video.shortUUID || video.uuid })
}
static buildUpdateUrl (video: Pick<Video, 'uuid'>) {
import { mapValues, pickBy } from 'lodash-es'
-import { buildVideoOrPlaylistEmbed, decorateVideoLink } from 'src/assets/player/utils'
+import { buildVideoOrPlaylistEmbed } from 'src/assets/player/utils'
import { Component, Input, OnInit, ViewChild } from '@angular/core'
import { DomSanitizer, SafeHtml } from '@angular/platform-browser'
import { Notifier } from '@app/core'
import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
+import { decorateVideoLink } from '@shared/core-utils'
import { abusePredefinedReasonsMap } from '@shared/core-utils/abuse'
import { AbusePredefinedReasonsString } from '@shared/models'
import { Video } from '../../shared-main'
import { VideoDetails } from '@app/shared/shared-main'
import { VideoPlaylist } from '@app/shared/shared-video-playlist'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
+import { buildPlaylistLink, buildVideoLink, decoratePlaylistLink, decorateVideoLink } from '@shared/core-utils'
import { VideoCaption } from '@shared/models'
-import {
- buildPlaylistLink,
- buildVideoLink,
- buildVideoOrPlaylistEmbed,
- decoratePlaylistLink,
- decorateVideoLink
-} from '../../../assets/player/utils'
+import { buildVideoOrPlaylistEmbed } from '../../../assets/player/utils'
type Customizations = {
startAtCheckbox: boolean
} from '@app/core'
import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook'
import { GlobalIconName } from '@app/shared/shared-icons'
-import { isLastMonth, isLastWeek, isThisMonth, isToday, isYesterday } from '@shared/core-utils/miscs/date'
+import { isLastMonth, isLastWeek, isThisMonth, isToday, isYesterday } from '@shared/core-utils'
import { HTMLServerConfig, UserRight, VideoFilter, VideoSortField } from '@shared/models'
import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type'
import { Syndication, Video } from '../shared-main'
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core'
import { AuthService, DisableForReuseHook, Notifier } from '@app/core'
import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
+import { secondsToTime } from '@shared/core-utils'
import {
Video,
VideoExistInPlaylist,
VideoPlaylistElementUpdate,
VideoPlaylistPrivacy
} from '@shared/models'
-import { secondsToTime } from '../../../assets/player/utils'
import { VIDEO_PLAYLIST_DISPLAY_NAME_VALIDATOR } from '../form-validators/video-playlist-validators'
import { CachedPlaylist, VideoPlaylistService } from './video-playlist.service'
import { AuthService, Notifier, ServerService } from '@app/core'
import { Video } from '@app/shared/shared-main'
import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap'
+import { secondsToTime } from '@shared/core-utils'
import { HTMLServerConfig, VideoPlaylistElementType, VideoPlaylistElementUpdate } from '@shared/models'
-import { secondsToTime } from '../../../assets/player/utils'
import { VideoPlaylistElement } from './video-playlist-element.model'
import { VideoPlaylist } from './video-playlist.model'
import { VideoPlaylistService } from './video-playlist.service'
import { getAbsoluteAPIUrl, getAbsoluteEmbedUrl } from '@app/helpers'
import { Actor } from '@app/shared/shared-main'
+import { buildPlaylistWatchPath } from '@shared/core-utils'
import { peertubeTranslate } from '@shared/core-utils/i18n'
import {
AccountSummary,
videoChannelBy?: string
static buildWatchUrl (playlist: Pick<VideoPlaylist, 'uuid' | 'shortUUID'>) {
- return '/w/p/' + (playlist.shortUUID || playlist.uuid)
+ return buildPlaylistWatchPath({ shortUUID: playlist.shortUUID || playlist.uuid })
}
constructor (hash: ServerVideoPlaylist, translations: {}) {
import { Events, Segment } from 'p2p-media-loader-core'
import { Engine, initHlsJsPlayer, initVideoJsContribHlsJsPlayer } from 'p2p-media-loader-hlsjs'
import videojs from 'video.js'
+import { timeToInt } from '@shared/core-utils'
import { P2PMediaLoaderPluginOptions, PlayerNetworkInfo } from '../peertube-videojs-typings'
-import { timeToInt } from '../utils'
import { registerConfigPlugin, registerSourceHandler } from './hls-plugin'
registerConfigPlugin(videojs)
import './playlist/playlist-plugin'
import videojs from 'video.js'
import { PluginsManager } from '@root-helpers/plugins-manager'
+import { buildVideoLink, decorateVideoLink } from '@shared/core-utils'
import { isDefaultLocale } from '@shared/core-utils/i18n'
import { VideoFile } from '@shared/models'
import { copyToClipboard } from '../../root-helpers/utils'
VideoJSPluginOptions
} from './peertube-videojs-typings'
import { TranslationsManager } from './translations-manager'
-import { buildVideoLink, buildVideoOrPlaylistEmbed, decorateVideoLink, getRtcConfig, isIOS, isSafari } from './utils'
+import { buildVideoOrPlaylistEmbed, getRtcConfig, isIOS, isSafari } from './utils'
// Change 'Playback Rate' to 'Speed' (smaller for our settings menu)
(videojs.getComponent('PlaybackRateMenuButton') as any).prototype.controlText_ = 'Speed'
-import videojs from 'video.js'
import './videojs-components/settings-menu-button'
-import {
- PeerTubePluginOptions,
- ResolutionUpdateData,
- UserWatching,
- VideoJSCaption
-} from './peertube-videojs-typings'
-import { isMobile, timeToInt } from './utils'
+import videojs from 'video.js'
+import { timeToInt } from '@shared/core-utils'
import {
getStoredLastSubtitle,
getStoredMute,
saveVideoWatchHistory,
saveVolumeInStore
} from './peertube-player-local-storage'
+import { PeerTubePluginOptions, ResolutionUpdateData, UserWatching, VideoJSCaption } from './peertube-videojs-typings'
+import { isMobile } from './utils'
const Plugin = videojs.getPlugin('plugin')
import videojs from 'video.js'
+import { secondsToTime } from '@shared/core-utils'
import { VideoPlaylistElement } from '@shared/models'
import { PlaylistItemOptions } from '../peertube-videojs-typings'
-import { secondsToTime } from '../utils'
const Component = videojs.getComponent('Component')
import videojs from 'video.js'
+import { secondsToTime } from '@shared/core-utils'
import { PlayerNetworkInfo as EventPlayerNetworkInfo } from '../peertube-videojs-typings'
-import { bytes, secondsToTime } from '../utils'
+import { bytes } from '../utils'
interface StatsCardOptions extends videojs.ComponentOptions {
videoUUID: string
-import { Video, VideoFile, VideoPlaylist } from '@shared/models'
import { escapeHTML } from '@shared/core-utils/renderer'
+import { VideoFile } from '@shared/models'
function toTitleCase (str: string) {
return str.charAt(0).toUpperCase() + str.slice(1)
return /iPhone|iPad|iPod|Android/i.test(navigator.userAgent)
}
-function buildPlaylistLink (playlist: Pick<VideoPlaylist, 'shortUUID'>, base?: string) {
- return (base ?? window.location.origin) + '/w/p/' + playlist.shortUUID
-}
-
-function buildVideoLink (video: Pick<Video, 'shortUUID'>, base?: string) {
- return (base ?? window.location.origin) + '/w/' + video.shortUUID
-}
-
-function buildPlaylistEmbedLink (playlist: Pick<VideoPlaylist, 'uuid'>, base?: string) {
- return (base ?? window.location.origin) + '/video-playlists/embed/' + playlist.uuid
-}
-
-function buildVideoEmbedLink (video: Pick<Video, 'uuid'>, base?: string) {
- return (base ?? window.location.origin) + '/videos/embed/' + video.uuid
-}
-
-function decorateVideoLink (options: {
- url: string
-
- startTime?: number
- stopTime?: number
-
- subtitle?: string
-
- loop?: boolean
- autoplay?: boolean
- muted?: boolean
-
- // Embed options
- title?: boolean
- warningTitle?: boolean
- controls?: boolean
- peertubeLink?: boolean
-}) {
- const { url } = options
-
- const params = generateParams(window.location.search)
-
- if (options.startTime !== undefined && options.startTime !== null) {
- const startTimeInt = Math.floor(options.startTime)
- params.set('start', secondsToTime(startTimeInt))
- }
-
- if (options.stopTime) {
- const stopTimeInt = Math.floor(options.stopTime)
- params.set('stop', secondsToTime(stopTimeInt))
- }
-
- if (options.subtitle) params.set('subtitle', options.subtitle)
-
- if (options.loop === true) params.set('loop', '1')
- if (options.autoplay === true) params.set('autoplay', '1')
- if (options.muted === true) params.set('muted', '1')
- if (options.title === false) params.set('title', '0')
- if (options.warningTitle === false) params.set('warningTitle', '0')
- if (options.controls === false) params.set('controls', '0')
- if (options.peertubeLink === false) params.set('peertubeLink', '0')
-
- return buildUrl(url, params)
-}
-
-function decoratePlaylistLink (options: {
- url: string
-
- playlistPosition?: number
-}) {
- const { url } = options
-
- const params = generateParams(window.location.search)
-
- if (options.playlistPosition) params.set('playlistPosition', '' + options.playlistPosition)
-
- return buildUrl(url, params)
-}
-
-function buildUrl (url: string, params: URLSearchParams) {
- let hasParams = false
- params.forEach(() => hasParams = true)
-
- if (hasParams) return url + '?' + params.toString()
-
- return url
-}
-
-function generateParams (url: string) {
- const params = new URLSearchParams(window.location.search)
- // Unused parameters in embed
- params.delete('videoId')
- params.delete('resume')
-
- return params
-}
-
-function timeToInt (time: number | string) {
- if (!time) return 0
- if (typeof time === 'number') return time
-
- const reg = /^((\d+)[h:])?((\d+)[m:])?((\d+)s?)?$/
- const matches = time.match(reg)
-
- if (!matches) return 0
-
- const hours = parseInt(matches[2] || '0', 10)
- const minutes = parseInt(matches[4] || '0', 10)
- const seconds = parseInt(matches[6] || '0', 10)
-
- return hours * 3600 + minutes * 60 + seconds
-}
-
-function secondsToTime (seconds: number, full = false, symbol?: string) {
- let time = ''
-
- if (seconds === 0 && !full) return '0s'
-
- const hourSymbol = (symbol || 'h')
- const minuteSymbol = (symbol || 'm')
- const secondsSymbol = full ? '' : 's'
-
- const hours = Math.floor(seconds / 3600)
- if (hours >= 1) time = hours + hourSymbol
- else if (full) time = '0' + hourSymbol
-
- seconds %= 3600
- const minutes = Math.floor(seconds / 60)
- if (minutes >= 1 && minutes < 10 && full) time += '0' + minutes + minuteSymbol
- else if (minutes >= 1) time += minutes + minuteSymbol
- else if (full) time += '00' + minuteSymbol
-
- seconds %= 60
- if (seconds >= 1 && seconds < 10 && full) time += '0' + seconds + secondsSymbol
- else if (seconds >= 1) time += seconds + secondsSymbol
- else if (full) time += '00'
-
- return time
-}
-
function buildVideoOrPlaylistEmbed (embedUrl: string, embedTitle: string) {
const title = escapeHTML(embedTitle)
+
return '<iframe width="560" height="315" ' +
'sandbox="allow-same-origin allow-scripts allow-popups" ' +
'title="' + title + '" ' +
export {
getRtcConfig,
toTitleCase,
- timeToInt,
- secondsToTime,
isWebRTCDisabled,
- buildPlaylistLink,
- buildVideoLink,
- decorateVideoLink,
- decoratePlaylistLink,
- buildPlaylistEmbedLink,
- buildVideoEmbedLink,
-
buildVideoOrPlaylistEmbed,
videoFileMaxByResolution,
videoFileMinByResolution,
import videojs from 'video.js'
+import { buildVideoLink, decorateVideoLink } from '@shared/core-utils'
import { PeerTubeLinkButtonOptions } from '../peertube-videojs-typings'
-import { buildVideoLink, decorateVideoLink } from '../utils'
const Button = videojs.getComponent('Button')
class PeerTubeLinkButton extends Button {
import videojs from 'video.js'
import * as WebTorrent from 'webtorrent'
-import { renderVideo } from './video-renderer'
-import { LoadedQualityData, PlayerNetworkInfo, WebtorrentPluginOptions } from '../peertube-videojs-typings'
-import { getRtcConfig, timeToInt, videoFileMaxByResolution, videoFileMinByResolution, isIOS, isSafari } from '../utils'
-import { PeertubeChunkStore } from './peertube-chunk-store'
+import { timeToInt } from '@shared/core-utils'
+import { VideoFile } from '@shared/models'
import {
getAverageBandwidthInStore,
getStoredMute,
getStoredVolume,
saveAverageBandwidth
} from '../peertube-player-local-storage'
-import { VideoFile } from '@shared/models'
+import { LoadedQualityData, PlayerNetworkInfo, WebtorrentPluginOptions } from '../peertube-videojs-typings'
+import { getRtcConfig, isIOS, videoFileMaxByResolution, videoFileMinByResolution } from '../utils'
+import { PeertubeChunkStore } from './peertube-chunk-store'
+import { renderVideo } from './video-renderer'
const CacheChunkStore = require('cache-chunk-store')
import { join } from 'path'
import { createInterface } from 'readline'
import * as winston from 'winston'
-import { labelFormatter } from '../server/helpers/logger'
+import { labelFormatter, mtimeSortFilesDesc } from '../server/helpers/logger'
import { CONFIG } from '../server/initializers/config'
-import { mtimeSortFilesDesc } from '../shared/core-utils/logs/logs'
import { inspect } from 'util'
import { format as sqlFormat } from 'sql-formatter'
import * as express from 'express'
import { readdir, readFile } from 'fs-extra'
import { join } from 'path'
-import { logger } from '@server/helpers/logger'
-import { mtimeSortFilesDesc } from '../../../../shared/core-utils/logs/logs'
+import { logger, mtimeSortFilesDesc } from '@server/helpers/logger'
import { LogLevel } from '../../../../shared/models/server/log-level.type'
import { UserRight } from '../../../../shared/models/users'
import { CONFIG } from '../../../initializers/config'
import * as express from 'express'
-import { asyncMiddleware } from '../middlewares'
-import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants'
+import { truncate } from 'lodash'
import { SitemapStream, streamToPromise } from 'sitemap'
+import { buildNSFWFilter } from '../helpers/express-utils'
+import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants'
+import { asyncMiddleware } from '../middlewares'
+import { cacheRoute } from '../middlewares/cache/cache'
+import { AccountModel } from '../models/account/account'
import { VideoModel } from '../models/video/video'
import { VideoChannelModel } from '../models/video/video-channel'
-import { AccountModel } from '../models/account/account'
-import { cacheRoute } from '../middlewares/cache/cache'
-import { buildNSFWFilter } from '../helpers/express-utils'
-import { truncate } from 'lodash'
const botsRouter = express.Router()
})
return data.map(v => ({
- url: WEBSERVER.URL + '/w/' + v.uuid,
+ url: WEBSERVER.URL + v.getWatchStaticPath(),
video: [
{
title: v.name,
// Sitemap description should be < 2000 characters
description: truncate(v.description || v.name, { length: 2000, omission: '...' }),
- player_loc: WEBSERVER.URL + '/videos/embed/' + v.uuid,
+ player_loc: WEBSERVER.URL + v.getEmbedStaticPath(),
thumbnail_loc: WEBSERVER.URL + v.getMiniatureStaticPath()
}
]
feed.addItem({
title: video.name,
id: video.url,
- link: WEBSERVER.URL + '/w/' + video.uuid,
+ link: WEBSERVER.URL + video.getWatchStaticPath(),
description: video.getTruncatedDescription(),
content: video.description,
author: [
// Thanks http://tostring.it/2014/06/23/advanced-logging-with-nodejs/
-import { mkdirpSync } from 'fs-extra'
+import { mkdirpSync, stat } from 'fs-extra'
import { omit } from 'lodash'
import * as path from 'path'
import { format as sqlFormat } from 'sql-formatter'
}
}
+async function mtimeSortFilesDesc (files: string[], basePath: string) {
+ const promises = []
+ const out: { file: string, mtime: number }[] = []
+
+ for (const file of files) {
+ const p = stat(basePath + '/' + file)
+ .then(stats => {
+ if (stats.isFile()) out.push({ file, mtime: stats.mtime.getTime() })
+ })
+
+ promises.push(p)
+ }
+
+ await Promise.all(promises)
+
+ out.sort((a, b) => b.mtime - a.mtime)
+
+ return out
+}
+
// ---------------------------------------------------------------------------
export {
labelFormatter,
consoleLoggerFormat,
jsonLoggerFormat,
+ mtimeSortFilesDesc,
logger,
loggerTagsFactory,
bunyanLogger
import { randomBytes } from 'crypto'
import { invert } from 'lodash'
import { join } from 'path'
-import { randomInt } from '../../shared/core-utils/miscs/miscs'
+import { randomInt } from '../../shared/core-utils/common/miscs'
import {
AbuseState,
JobType,
+import { chunk } from 'lodash'
+import { compareSemVer } from '@shared/core-utils'
import { logger } from '../../helpers/logger'
-import { AbstractScheduler } from './abstract-scheduler'
-import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants'
import { CONFIG } from '../../initializers/config'
+import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants'
import { PluginModel } from '../../models/server/plugin'
-import { chunk } from 'lodash'
-import { getLatestPluginsVersion } from '../plugins/plugin-index'
-import { compareSemVer } from '../../../shared/core-utils/miscs/miscs'
import { Notifier } from '../notifier'
+import { getLatestPluginsVersion } from '../plugins/plugin-index'
+import { AbstractScheduler } from './abstract-scheduler'
export class PluginsCheckScheduler extends AbstractScheduler {
import { setAsUpdated } from '@server/helpers/database-utils'
import { buildUUID, uuidToShort } from '@server/helpers/uuid'
import { MAccountId, MChannelId } from '@server/types/models'
-import { AttributesOnly } from '@shared/core-utils'
+import { AttributesOnly, buildPlaylistEmbedPath, buildPlaylistLink, buildPlaylistWatchPath } from '@shared/core-utils'
import { ActivityIconObject } from '../../../shared/models/activitypub/objects'
import { PlaylistObject } from '../../../shared/models/activitypub/objects/playlist-object'
import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model'
return join(STATIC_PATHS.THUMBNAILS, this.Thumbnail.filename)
}
- getWatchUrl () {
- return WEBSERVER.URL + '/w/p/' + this.uuid
+ getWatchStaticPath () {
+ return buildPlaylistWatchPath({ shortUUID: uuidToShort(this.uuid) })
}
getEmbedStaticPath () {
- return '/video-playlists/embed/' + this.uuid
+ return buildPlaylistEmbedPath(this)
}
static async getStats () {
} from 'sequelize-typescript'
import { setAsUpdated } from '@server/helpers/database-utils'
import { buildNSFWFilter } from '@server/helpers/express-utils'
+import { shortToUUID } from '@server/helpers/uuid'
import { getPrivaciesForFederation, isPrivacyForFederation, isStateForFederation } from '@server/helpers/video'
import { LiveManager } from '@server/lib/live/live-manager'
import { getHLSDirectory, getVideoFilePath } from '@server/lib/video-paths'
import { getServerActor } from '@server/models/application/application'
import { ModelCache } from '@server/models/model-cache'
-import { AttributesOnly } from '@shared/core-utils'
+import { AttributesOnly, buildVideoEmbedPath, buildVideoWatchPath } from '@shared/core-utils'
import { VideoFile } from '@shared/models/videos/video-file.model'
import { ResultList, UserRight, VideoPrivacy, VideoState } from '../../../shared'
import { VideoObject } from '../../../shared/models/activitypub/objects'
}
getWatchStaticPath () {
- return '/w/' + this.uuid
+ return buildVideoWatchPath({ shortUUID: shortToUUID(this.uuid) })
}
getEmbedStaticPath () {
- return '/videos/embed/' + this.uuid
+ return buildVideoEmbedPath(this)
}
getMiniatureStaticPath () {
return getDaysDifferences(now, d) <= 7
}
+function timeToInt (time: number | string) {
+ if (!time) return 0
+ if (typeof time === 'number') return time
+
+ const reg = /^((\d+)[h:])?((\d+)[m:])?((\d+)s?)?$/
+ const matches = time.match(reg)
+
+ if (!matches) return 0
+
+ const hours = parseInt(matches[2] || '0', 10)
+ const minutes = parseInt(matches[4] || '0', 10)
+ const seconds = parseInt(matches[6] || '0', 10)
+
+ return hours * 3600 + minutes * 60 + seconds
+}
+
+function secondsToTime (seconds: number, full = false, symbol?: string) {
+ let time = ''
+
+ if (seconds === 0 && !full) return '0s'
+
+ const hourSymbol = (symbol || 'h')
+ const minuteSymbol = (symbol || 'm')
+ const secondsSymbol = full ? '' : 's'
+
+ const hours = Math.floor(seconds / 3600)
+ if (hours >= 1) time = hours + hourSymbol
+ else if (full) time = '0' + hourSymbol
+
+ seconds %= 3600
+ const minutes = Math.floor(seconds / 60)
+ if (minutes >= 1 && minutes < 10 && full) time += '0' + minutes + minuteSymbol
+ else if (minutes >= 1) time += minutes + minuteSymbol
+ else if (full) time += '00' + minuteSymbol
+
+ seconds %= 60
+ if (seconds >= 1 && seconds < 10 && full) time += '0' + seconds + secondsSymbol
+ else if (seconds >= 1) time += seconds + secondsSymbol
+ else if (full) time += '00'
+
+ return time
+}
+
// ---------------------------------------------------------------------------
export {
isThisMonth,
isToday,
isLastMonth,
- isLastWeek
+ isLastWeek,
+ timeToInt,
+ secondsToTime
}
// ---------------------------------------------------------------------------
export * from './date'
export * from './miscs'
export * from './regexp'
+export * from './promises'
export * from './types'
-export * from './url
+export * from './url'
return segmentsA.length - segmentsB.length
}
-function isPromise (value: any) {
- return value && typeof value.then === 'function'
-}
-
-function isCatchable (value: any) {
- return value && typeof value.catch === 'function'
-}
-
function sortObjectComparator (key: string, order: 'asc' | 'desc') {
return (a: any, b: any) => {
if (a[key] < b[key]) {
export {
randomInt,
compareSemVer,
- isPromise,
- isCatchable,
sortObjectComparator
}
--- /dev/null
+function isPromise (value: any) {
+ return value && typeof value.then === 'function'
+}
+
+function isCatchable (value: any) {
+ return value && typeof value.catch === 'function'
+}
+
+export {
+ isPromise,
+ isCatchable
+}
+import { Video, VideoPlaylist } from '../../models'
+import { secondsToTime } from './date'
+
+function buildPlaylistLink (playlist: Pick<VideoPlaylist, 'shortUUID'>, base?: string) {
+ return (base ?? window.location.origin) + buildPlaylistWatchPath(playlist)
+}
+
+function buildPlaylistWatchPath (playlist: Pick<VideoPlaylist, 'shortUUID'>) {
+ return '/w/p/' + playlist.shortUUID
+}
+
+function buildVideoWatchPath (video: Pick<Video, 'shortUUID'>) {
+ return '/w/' + video.shortUUID
+}
+
+function buildVideoLink (video: Pick<Video, 'shortUUID'>, base?: string) {
+ return (base ?? window.location.origin) + buildVideoWatchPath(video)
+}
+
+function buildPlaylistEmbedPath (playlist: Pick<VideoPlaylist, 'uuid'>) {
+ return '/video-playlists/embed/' + playlist.uuid
+}
+
+function buildPlaylistEmbedLink (playlist: Pick<VideoPlaylist, 'uuid'>, base?: string) {
+ return (base ?? window.location.origin) + buildPlaylistEmbedPath(playlist)
+}
+
+function buildVideoEmbedPath (video: Pick<Video, 'uuid'>) {
+ return '/videos/embed/' + video.uuid
+}
+
+function buildVideoEmbedLink (video: Pick<Video, 'uuid'>, base?: string) {
+ return (base ?? window.location.origin) + buildVideoEmbedPath(video)
+}
+
+function decorateVideoLink (options: {
+ url: string
+
+ startTime?: number
+ stopTime?: number
+
+ subtitle?: string
+
+ loop?: boolean
+ autoplay?: boolean
+ muted?: boolean
+
+ // Embed options
+ title?: boolean
+ warningTitle?: boolean
+ controls?: boolean
+ peertubeLink?: boolean
+}) {
+ const { url } = options
+
+ const params = generateParams(window.location.search)
+
+ if (options.startTime !== undefined && options.startTime !== null) {
+ const startTimeInt = Math.floor(options.startTime)
+ params.set('start', secondsToTime(startTimeInt))
+ }
+
+ if (options.stopTime) {
+ const stopTimeInt = Math.floor(options.stopTime)
+ params.set('stop', secondsToTime(stopTimeInt))
+ }
+
+ if (options.subtitle) params.set('subtitle', options.subtitle)
+
+ if (options.loop === true) params.set('loop', '1')
+ if (options.autoplay === true) params.set('autoplay', '1')
+ if (options.muted === true) params.set('muted', '1')
+ if (options.title === false) params.set('title', '0')
+ if (options.warningTitle === false) params.set('warningTitle', '0')
+ if (options.controls === false) params.set('controls', '0')
+ if (options.peertubeLink === false) params.set('peertubeLink', '0')
+
+ return buildUrl(url, params)
+}
+
+function decoratePlaylistLink (options: {
+ url: string
+
+ playlistPosition?: number
+}) {
+ const { url } = options
+
+ const params = generateParams(window.location.search)
+
+ if (options.playlistPosition) params.set('playlistPosition', '' + options.playlistPosition)
+
+ return buildUrl(url, params)
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+ buildPlaylistLink,
+ buildVideoLink,
+
+ buildVideoWatchPath,
+ buildPlaylistWatchPath,
+
+ buildPlaylistEmbedPath,
+ buildVideoEmbedPath,
+
+ buildPlaylistEmbedLink,
+ buildVideoEmbedLink,
+
+ decorateVideoLink,
+ decoratePlaylistLink
+}
+
+function buildUrl (url: string, params: URLSearchParams) {
+ let hasParams = false
+ params.forEach(() => { hasParams = true })
+
+ if (hasParams) return url + '?' + params.toString()
+
+ return url
+}
+
+function generateParams (url: string) {
+ const params = new URLSearchParams(window.location.search)
+ // Unused parameters in embed
+ params.delete('videoId')
+ params.delete('resume')
+
+ return params
+}
export * from './abuse'
export * from './common'
export * from './i18n'
-export * from './logs'
export * from './plugins'
export * from './renderer'
export * from './users'
+++ /dev/null
-export * from './logs'
+++ /dev/null
-import { stat } from 'fs-extra'
-
-async function mtimeSortFilesDesc (files: string[], basePath: string) {
- const promises = []
- const out: { file: string, mtime: number }[] = []
-
- for (const file of files) {
- const p = stat(basePath + '/' + file)
- .then(stats => {
- if (stats.isFile()) out.push({ file, mtime: stats.mtime.getTime() })
- })
-
- promises.push(p)
- }
-
- await Promise.all(promises)
-
- out.sort((a, b) => b.mtime - a.mtime)
-
- return out
-}
-
-export {
- mtimeSortFilesDesc
-}
import { HookType } from '../../models/plugins/hook-type.enum'
-import { isCatchable, isPromise } from '../miscs/miscs'
+import { isCatchable, isPromise } from '../common/promises'
function getHookType (hookName: string) {
if (hookName.startsWith('filter:')) return HookType.FILTER
import { copy } from 'fs-extra'
import { join } from 'path'
import { root } from '@server/helpers/core-utils'
-import { randomInt } from '../../core-utils/miscs/miscs'
+import { randomInt } from '@shared/core-utils'
import { Video, VideoChannel, VideoCreateResult, VideoDetails } from '../../models/videos'
import { BulkCommand } from '../bulk'
import { CLICommand } from '../cli'