Add oembed endpoint
This commit is contained in:
parent
334ddfa471
commit
d8755eed1e
|
@ -27,7 +27,7 @@ export class VideoShareComponent {
|
||||||
|
|
||||||
getVideoIframeCode () {
|
getVideoIframeCode () {
|
||||||
return '<iframe width="560" height="315" ' +
|
return '<iframe width="560" height="315" ' +
|
||||||
'src="' + window.location.origin + '/videos/embed/' + this.video.uuid + '" ' +
|
'src="' + this.video.embedUrl + '" ' +
|
||||||
'frameborder="0" allowfullscreen>' +
|
'frameborder="0" allowfullscreen>' +
|
||||||
'</iframe>'
|
'</iframe>'
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,8 @@ export class Video implements VideoServerModel {
|
||||||
thumbnailUrl: string
|
thumbnailUrl: string
|
||||||
previewPath: string
|
previewPath: string
|
||||||
previewUrl: string
|
previewUrl: string
|
||||||
|
embedPath: string
|
||||||
|
embedUrl: string
|
||||||
views: number
|
views: number
|
||||||
likes: number
|
likes: number
|
||||||
dislikes: number
|
dislikes: number
|
||||||
|
@ -64,6 +66,7 @@ export class Video implements VideoServerModel {
|
||||||
tags: string[],
|
tags: string[],
|
||||||
thumbnailPath: string,
|
thumbnailPath: string,
|
||||||
previewPath: string,
|
previewPath: string,
|
||||||
|
embedPath: string,
|
||||||
views: number,
|
views: number,
|
||||||
likes: number,
|
likes: number,
|
||||||
dislikes: number,
|
dislikes: number,
|
||||||
|
@ -91,6 +94,8 @@ export class Video implements VideoServerModel {
|
||||||
this.thumbnailUrl = API_URL + hash.thumbnailPath
|
this.thumbnailUrl = API_URL + hash.thumbnailPath
|
||||||
this.previewPath = hash.previewPath
|
this.previewPath = hash.previewPath
|
||||||
this.previewUrl = API_URL + hash.previewPath
|
this.previewUrl = API_URL + hash.previewPath
|
||||||
|
this.embedPath = hash.embedPath
|
||||||
|
this.embedUrl = API_URL + hash.embedPath
|
||||||
this.views = hash.views
|
this.views = hash.views
|
||||||
this.likes = hash.likes
|
this.likes = hash.likes
|
||||||
this.dislikes = hash.dislikes
|
this.dislikes = hash.dislikes
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="description" content="PeerTube, a decentralized video streaming platform using P2P (BitTorrent) directly in the web browser" />
|
<meta name="description" content="PeerTube, a decentralized video streaming platform using P2P (BitTorrent) directly in the web browser" />
|
||||||
|
|
||||||
<!-- The following comment is used by the server to prerender OpenGraph tags -->
|
<!-- The following comment is used by the server to prerender OpenGraph and oEmbed tags -->
|
||||||
<!-- open graph tags -->
|
<!-- open graph and oembed tags -->
|
||||||
<!-- Do not remove it! -->
|
<!-- Do not remove it! -->
|
||||||
|
|
||||||
<link rel="icon" type="image/png" href="/client/assets/favicon.png" />
|
<link rel="icon" type="image/png" href="/client/assets/favicon.png" />
|
||||||
|
|
|
@ -26,7 +26,7 @@ if ! which yarn > /dev/null; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if pgrep peertube > /dev/null; then
|
if pgrep peertube > /dev/null; then
|
||||||
echo 'PeerTube is running!'
|
echo 'PeerTube is running, please shut it off before upgrading'
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ db.init(false).then(() => onDatabaseInitDone())
|
||||||
// ----------- PeerTube modules -----------
|
// ----------- PeerTube modules -----------
|
||||||
import { migrate, installApplication } from './server/initializers'
|
import { migrate, installApplication } from './server/initializers'
|
||||||
import { JobScheduler, activateSchedulers, VideosPreviewCache } from './server/lib'
|
import { JobScheduler, activateSchedulers, VideosPreviewCache } from './server/lib'
|
||||||
import { apiRouter, clientsRouter, staticRouter } from './server/controllers'
|
import { apiRouter, clientsRouter, staticRouter, servicesRouter } from './server/controllers'
|
||||||
|
|
||||||
// ----------- Command line -----------
|
// ----------- Command line -----------
|
||||||
|
|
||||||
|
@ -85,6 +85,9 @@ app.use(bodyParser.urlencoded({ extended: false }))
|
||||||
const apiRoute = '/api/' + API_VERSION
|
const apiRoute = '/api/' + API_VERSION
|
||||||
app.use(apiRoute, apiRouter)
|
app.use(apiRoute, apiRouter)
|
||||||
|
|
||||||
|
// Services (oembed...)
|
||||||
|
app.use('/services', servicesRouter)
|
||||||
|
|
||||||
// Client files
|
// Client files
|
||||||
app.use('/', clientsRouter)
|
app.use('/', clientsRouter)
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
CONFIG,
|
CONFIG,
|
||||||
STATIC_PATHS,
|
STATIC_PATHS,
|
||||||
STATIC_MAX_AGE,
|
STATIC_MAX_AGE,
|
||||||
OPENGRAPH_COMMENT
|
OPENGRAPH_AND_OEMBED_COMMENT
|
||||||
} from '../initializers'
|
} from '../initializers'
|
||||||
import { root, readFileBufferPromise } from '../helpers'
|
import { root, readFileBufferPromise } from '../helpers'
|
||||||
import { VideoInstance } from '../models'
|
import { VideoInstance } from '../models'
|
||||||
|
@ -19,7 +19,7 @@ const distPath = join(root(), 'client', 'dist')
|
||||||
const embedPath = join(distPath, 'standalone', 'videos', 'embed.html')
|
const embedPath = join(distPath, 'standalone', 'videos', 'embed.html')
|
||||||
const indexPath = join(distPath, 'index.html')
|
const indexPath = join(distPath, 'index.html')
|
||||||
|
|
||||||
// Special route that add OpenGraph tags
|
// Special route that add OpenGraph and oEmbed tags
|
||||||
// Do not use a template engine for a so little thing
|
// Do not use a template engine for a so little thing
|
||||||
clientsRouter.use('/videos/watch/:id', generateWatchHtmlPage)
|
clientsRouter.use('/videos/watch/:id', generateWatchHtmlPage)
|
||||||
|
|
||||||
|
@ -43,11 +43,11 @@ export {
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
function addOpenGraphTags (htmlStringPage: string, video: VideoInstance) {
|
function addOpenGraphAndOEmbedTags (htmlStringPage: string, video: VideoInstance) {
|
||||||
const previewUrl = CONFIG.WEBSERVER.URL + STATIC_PATHS.PREVIEWS + video.getPreviewName()
|
const previewUrl = CONFIG.WEBSERVER.URL + STATIC_PATHS.PREVIEWS + video.getPreviewName()
|
||||||
const videoUrl = CONFIG.WEBSERVER.URL + '/videos/watch/' + video.id
|
const videoUrl = CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid
|
||||||
|
|
||||||
const metaTags = {
|
const openGraphMetaTags = {
|
||||||
'og:type': 'video',
|
'og:type': 'video',
|
||||||
'og:title': video.name,
|
'og:title': video.name,
|
||||||
'og:image': previewUrl,
|
'og:image': previewUrl,
|
||||||
|
@ -65,14 +65,26 @@ function addOpenGraphTags (htmlStringPage: string, video: VideoInstance) {
|
||||||
'twitter:image': previewUrl
|
'twitter:image': previewUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
let tagsString = ''
|
const oembedLinkTags = [
|
||||||
Object.keys(metaTags).forEach(tagName => {
|
{
|
||||||
const tagValue = metaTags[tagName]
|
type: 'application/json+oembed',
|
||||||
|
href: CONFIG.WEBSERVER.URL + '/services/oembed?url=' + encodeURIComponent(videoUrl),
|
||||||
|
title: video.name
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
tagsString += '<meta property="' + tagName + '" content="' + tagValue + '" />'
|
let tagsString = ''
|
||||||
|
Object.keys(openGraphMetaTags).forEach(tagName => {
|
||||||
|
const tagValue = openGraphMetaTags[tagName]
|
||||||
|
|
||||||
|
tagsString += `<meta property="${tagName}" content="${tagValue}" />`
|
||||||
})
|
})
|
||||||
|
|
||||||
return htmlStringPage.replace(OPENGRAPH_COMMENT, tagsString)
|
for (const oembedLinkTag of oembedLinkTags) {
|
||||||
|
tagsString += `<link rel="alternate" type="${oembedLinkTag.type}" href="${oembedLinkTag.href}" title="${oembedLinkTag.title}" />`
|
||||||
|
}
|
||||||
|
|
||||||
|
return htmlStringPage.replace(OPENGRAPH_AND_OEMBED_COMMENT, tagsString)
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateWatchHtmlPage (req: express.Request, res: express.Response, next: express.NextFunction) {
|
function generateWatchHtmlPage (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
|
@ -101,7 +113,7 @@ function generateWatchHtmlPage (req: express.Request, res: express.Response, nex
|
||||||
// Let Angular application handle errors
|
// Let Angular application handle errors
|
||||||
if (!video) return res.sendFile(indexPath)
|
if (!video) return res.sendFile(indexPath)
|
||||||
|
|
||||||
const htmlStringPageWithTags = addOpenGraphTags(html, video)
|
const htmlStringPageWithTags = addOpenGraphAndOEmbedTags(html, video)
|
||||||
res.set('Content-Type', 'text/html; charset=UTF-8').send(htmlStringPageWithTags)
|
res.set('Content-Type', 'text/html; charset=UTF-8').send(htmlStringPageWithTags)
|
||||||
})
|
})
|
||||||
.catch(err => next(err))
|
.catch(err => next(err))
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
export * from './static'
|
export * from './static'
|
||||||
export * from './client'
|
export * from './client'
|
||||||
|
export * from './services'
|
||||||
export * from './api'
|
export * from './api'
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
import * as express from 'express'
|
||||||
|
|
||||||
|
import { CONFIG, THUMBNAILS_SIZE } from '../initializers'
|
||||||
|
import { oembedValidator } from '../middlewares'
|
||||||
|
import { VideoInstance } from '../models'
|
||||||
|
|
||||||
|
const servicesRouter = express.Router()
|
||||||
|
|
||||||
|
servicesRouter.use('/oembed', oembedValidator, generateOEmbed)
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export {
|
||||||
|
servicesRouter
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function generateOEmbed (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
|
const video = res.locals.video as VideoInstance
|
||||||
|
const webserverUrl = CONFIG.WEBSERVER.URL
|
||||||
|
const maxHeight = parseInt(req.query.maxheight, 10)
|
||||||
|
const maxWidth = parseInt(req.query.maxwidth, 10)
|
||||||
|
|
||||||
|
const embedUrl = webserverUrl + video.getEmbedPath()
|
||||||
|
let thumbnailUrl = webserverUrl + video.getThumbnailPath()
|
||||||
|
let embedWidth = 560
|
||||||
|
let embedHeight = 315
|
||||||
|
|
||||||
|
if (maxHeight < embedHeight) embedHeight = maxHeight
|
||||||
|
if (maxWidth < embedWidth) embedWidth = maxWidth
|
||||||
|
|
||||||
|
// Our thumbnail is too big for the consumer
|
||||||
|
if (
|
||||||
|
(maxHeight !== undefined && maxHeight < THUMBNAILS_SIZE.height) ||
|
||||||
|
(maxWidth !== undefined && maxWidth < THUMBNAILS_SIZE.width)
|
||||||
|
) {
|
||||||
|
thumbnailUrl = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const html = `<iframe width="${embedWidth}" height="${embedHeight}" src="${embedUrl}" frameborder="0" allowfullscreen></iframe>`
|
||||||
|
|
||||||
|
const json: any = {
|
||||||
|
type: 'video',
|
||||||
|
version: '1.0',
|
||||||
|
html,
|
||||||
|
width: embedWidth,
|
||||||
|
height: embedHeight,
|
||||||
|
title: video.name,
|
||||||
|
author_name: video.Author.name,
|
||||||
|
provider_name: 'PeerTube',
|
||||||
|
provider_url: webserverUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thumbnailUrl !== undefined) {
|
||||||
|
json.thumbnail_url = thumbnailUrl
|
||||||
|
json.thumbnail_width = THUMBNAILS_SIZE.width
|
||||||
|
json.thumbnail_height = THUMBNAILS_SIZE.height
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.json(json)
|
||||||
|
}
|
|
@ -295,8 +295,14 @@ const STATIC_PATHS = {
|
||||||
let STATIC_MAX_AGE = '30d'
|
let STATIC_MAX_AGE = '30d'
|
||||||
|
|
||||||
// Videos thumbnail size
|
// Videos thumbnail size
|
||||||
const THUMBNAILS_SIZE = '200x110'
|
const THUMBNAILS_SIZE = {
|
||||||
const PREVIEWS_SIZE = '640x480'
|
width: 200,
|
||||||
|
height: 110
|
||||||
|
}
|
||||||
|
const PREVIEWS_SIZE = {
|
||||||
|
width: 640,
|
||||||
|
height: 480
|
||||||
|
}
|
||||||
|
|
||||||
// Sub folders of cache directory
|
// Sub folders of cache directory
|
||||||
const CACHE = {
|
const CACHE = {
|
||||||
|
@ -314,7 +320,7 @@ const USER_ROLES: { [ id: string ]: UserRole } = {
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const OPENGRAPH_COMMENT = '<!-- open graph tags -->'
|
const OPENGRAPH_AND_OEMBED_COMMENT = '<!-- open graph and oembed tags -->'
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -344,7 +350,7 @@ export {
|
||||||
JOBS_FETCHING_INTERVAL,
|
JOBS_FETCHING_INTERVAL,
|
||||||
LAST_MIGRATION_VERSION,
|
LAST_MIGRATION_VERSION,
|
||||||
OAUTH_LIFETIME,
|
OAUTH_LIFETIME,
|
||||||
OPENGRAPH_COMMENT,
|
OPENGRAPH_AND_OEMBED_COMMENT,
|
||||||
PAGINATION_COUNT_DEFAULT,
|
PAGINATION_COUNT_DEFAULT,
|
||||||
PODS_SCORE,
|
PODS_SCORE,
|
||||||
PREVIEWS_SIZE,
|
PREVIEWS_SIZE,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
export * from './oembed'
|
||||||
export * from './remote'
|
export * from './remote'
|
||||||
export * from './pagination'
|
export * from './pagination'
|
||||||
export * from './pods'
|
export * from './pods'
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
import { query } from 'express-validator/check'
|
||||||
|
import * as express from 'express'
|
||||||
|
import { join } from 'path'
|
||||||
|
|
||||||
|
import { checkErrors } from './utils'
|
||||||
|
import { CONFIG } from '../../initializers'
|
||||||
|
import { logger } from '../../helpers'
|
||||||
|
import { checkVideoExists, isVideoIdOrUUIDValid } from '../../helpers/custom-validators/videos'
|
||||||
|
import { isTestInstance } from '../../helpers/core-utils'
|
||||||
|
|
||||||
|
const urlShouldStartWith = CONFIG.WEBSERVER.SCHEME + '://' + join(CONFIG.WEBSERVER.HOST, 'videos', 'watch') + '/'
|
||||||
|
const videoWatchRegex = new RegExp('([^/]+)$')
|
||||||
|
const isURLOptions = {
|
||||||
|
require_host: true,
|
||||||
|
require_tld: true
|
||||||
|
}
|
||||||
|
|
||||||
|
// We validate 'localhost', so we don't have the top level domain
|
||||||
|
if (isTestInstance()) {
|
||||||
|
isURLOptions.require_tld = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const oembedValidator = [
|
||||||
|
query('url').isURL(isURLOptions).withMessage('Should have a valid url'),
|
||||||
|
query('maxwidth').optional().isInt().withMessage('Should have a valid max width'),
|
||||||
|
query('maxheight').optional().isInt().withMessage('Should have a valid max height'),
|
||||||
|
query('format').optional().isIn([ 'xml', 'json' ]).withMessage('Should have a valid format'),
|
||||||
|
|
||||||
|
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
|
logger.debug('Checking oembed parameters', { parameters: req.query })
|
||||||
|
|
||||||
|
checkErrors(req, res, () => {
|
||||||
|
if (req.query.format !== undefined && req.query.format !== 'json') {
|
||||||
|
return res.status(501)
|
||||||
|
.json({ error: 'Requested format is not implemented on server.' })
|
||||||
|
.end()
|
||||||
|
}
|
||||||
|
|
||||||
|
const startIsOk = req.query.url.startsWith(urlShouldStartWith)
|
||||||
|
const matches = videoWatchRegex.exec(req.query.url)
|
||||||
|
if (startIsOk === false || matches === null) {
|
||||||
|
return res.status(400)
|
||||||
|
.json({ error: 'Invalid url.' })
|
||||||
|
.end()
|
||||||
|
}
|
||||||
|
|
||||||
|
const videoId = matches[1]
|
||||||
|
if (isVideoIdOrUUIDValid(videoId) === false) {
|
||||||
|
return res.status(400)
|
||||||
|
.json({ error: 'Invalid video id.' })
|
||||||
|
.end()
|
||||||
|
}
|
||||||
|
|
||||||
|
checkVideoExists(videoId, res, next)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export {
|
||||||
|
oembedValidator
|
||||||
|
}
|
|
@ -32,6 +32,9 @@ export namespace VideoMethods {
|
||||||
export type OptimizeOriginalVideofile = (this: VideoInstance) => Promise<void>
|
export type OptimizeOriginalVideofile = (this: VideoInstance) => Promise<void>
|
||||||
export type TranscodeOriginalVideofile = (this: VideoInstance, resolution: number) => Promise<void>
|
export type TranscodeOriginalVideofile = (this: VideoInstance, resolution: number) => Promise<void>
|
||||||
export type GetOriginalFileHeight = (this: VideoInstance) => Promise<number>
|
export type GetOriginalFileHeight = (this: VideoInstance) => Promise<number>
|
||||||
|
export type GetEmbedPath = (this: VideoInstance) => string
|
||||||
|
export type GetThumbnailPath = (this: VideoInstance) => string
|
||||||
|
export type GetPreviewPath = (this: VideoInstance) => string
|
||||||
|
|
||||||
// Return thumbnail name
|
// Return thumbnail name
|
||||||
export type GenerateThumbnailFromData = (video: VideoInstance, thumbnailData: string) => Promise<string>
|
export type GenerateThumbnailFromData = (video: VideoInstance, thumbnailData: string) => Promise<string>
|
||||||
|
@ -107,7 +110,9 @@ export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.In
|
||||||
getOriginalFile: VideoMethods.GetOriginalFile
|
getOriginalFile: VideoMethods.GetOriginalFile
|
||||||
generateMagnetUri: VideoMethods.GenerateMagnetUri
|
generateMagnetUri: VideoMethods.GenerateMagnetUri
|
||||||
getPreviewName: VideoMethods.GetPreviewName
|
getPreviewName: VideoMethods.GetPreviewName
|
||||||
|
getPreviewPath: VideoMethods.GetPreviewPath
|
||||||
getThumbnailName: VideoMethods.GetThumbnailName
|
getThumbnailName: VideoMethods.GetThumbnailName
|
||||||
|
getThumbnailPath: VideoMethods.GetThumbnailPath
|
||||||
getTorrentFileName: VideoMethods.GetTorrentFileName
|
getTorrentFileName: VideoMethods.GetTorrentFileName
|
||||||
getVideoFilename: VideoMethods.GetVideoFilename
|
getVideoFilename: VideoMethods.GetVideoFilename
|
||||||
getVideoFilePath: VideoMethods.GetVideoFilePath
|
getVideoFilePath: VideoMethods.GetVideoFilePath
|
||||||
|
@ -122,6 +127,7 @@ export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.In
|
||||||
optimizeOriginalVideofile: VideoMethods.OptimizeOriginalVideofile
|
optimizeOriginalVideofile: VideoMethods.OptimizeOriginalVideofile
|
||||||
transcodeOriginalVideofile: VideoMethods.TranscodeOriginalVideofile
|
transcodeOriginalVideofile: VideoMethods.TranscodeOriginalVideofile
|
||||||
getOriginalFileHeight: VideoMethods.GetOriginalFileHeight
|
getOriginalFileHeight: VideoMethods.GetOriginalFileHeight
|
||||||
|
getEmbedPath: VideoMethods.GetEmbedPath
|
||||||
|
|
||||||
setTags: Sequelize.HasManySetAssociationsMixin<TagAttributes, string>
|
setTags: Sequelize.HasManySetAssociationsMixin<TagAttributes, string>
|
||||||
addVideoFile: Sequelize.HasManyAddAssociationMixin<VideoFileAttributes, string>
|
addVideoFile: Sequelize.HasManyAddAssociationMixin<VideoFileAttributes, string>
|
||||||
|
|
|
@ -54,7 +54,9 @@ let getOriginalFile: VideoMethods.GetOriginalFile
|
||||||
let generateMagnetUri: VideoMethods.GenerateMagnetUri
|
let generateMagnetUri: VideoMethods.GenerateMagnetUri
|
||||||
let getVideoFilename: VideoMethods.GetVideoFilename
|
let getVideoFilename: VideoMethods.GetVideoFilename
|
||||||
let getThumbnailName: VideoMethods.GetThumbnailName
|
let getThumbnailName: VideoMethods.GetThumbnailName
|
||||||
|
let getThumbnailPath: VideoMethods.GetThumbnailPath
|
||||||
let getPreviewName: VideoMethods.GetPreviewName
|
let getPreviewName: VideoMethods.GetPreviewName
|
||||||
|
let getPreviewPath: VideoMethods.GetPreviewPath
|
||||||
let getTorrentFileName: VideoMethods.GetTorrentFileName
|
let getTorrentFileName: VideoMethods.GetTorrentFileName
|
||||||
let isOwned: VideoMethods.IsOwned
|
let isOwned: VideoMethods.IsOwned
|
||||||
let toFormattedJSON: VideoMethods.ToFormattedJSON
|
let toFormattedJSON: VideoMethods.ToFormattedJSON
|
||||||
|
@ -67,6 +69,7 @@ let createThumbnail: VideoMethods.CreateThumbnail
|
||||||
let getVideoFilePath: VideoMethods.GetVideoFilePath
|
let getVideoFilePath: VideoMethods.GetVideoFilePath
|
||||||
let createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash
|
let createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash
|
||||||
let getOriginalFileHeight: VideoMethods.GetOriginalFileHeight
|
let getOriginalFileHeight: VideoMethods.GetOriginalFileHeight
|
||||||
|
let getEmbedPath: VideoMethods.GetEmbedPath
|
||||||
|
|
||||||
let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData
|
let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData
|
||||||
let list: VideoMethods.List
|
let list: VideoMethods.List
|
||||||
|
@ -252,7 +255,9 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
|
||||||
createTorrentAndSetInfoHash,
|
createTorrentAndSetInfoHash,
|
||||||
generateMagnetUri,
|
generateMagnetUri,
|
||||||
getPreviewName,
|
getPreviewName,
|
||||||
|
getPreviewPath,
|
||||||
getThumbnailName,
|
getThumbnailName,
|
||||||
|
getThumbnailPath,
|
||||||
getTorrentFileName,
|
getTorrentFileName,
|
||||||
getVideoFilename,
|
getVideoFilename,
|
||||||
getVideoFilePath,
|
getVideoFilePath,
|
||||||
|
@ -267,7 +272,8 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
|
||||||
toUpdateRemoteJSON,
|
toUpdateRemoteJSON,
|
||||||
optimizeOriginalVideofile,
|
optimizeOriginalVideofile,
|
||||||
transcodeOriginalVideofile,
|
transcodeOriginalVideofile,
|
||||||
getOriginalFileHeight
|
getOriginalFileHeight,
|
||||||
|
getEmbedPath
|
||||||
]
|
]
|
||||||
addMethodsToModel(Video, classMethods, instanceMethods)
|
addMethodsToModel(Video, classMethods, instanceMethods)
|
||||||
|
|
||||||
|
@ -375,11 +381,13 @@ createPreview = function (this: VideoInstance, videoFile: VideoFileInstance) {
|
||||||
}
|
}
|
||||||
|
|
||||||
createThumbnail = function (this: VideoInstance, videoFile: VideoFileInstance) {
|
createThumbnail = function (this: VideoInstance, videoFile: VideoFileInstance) {
|
||||||
|
const imageSize = THUMBNAILS_SIZE.width + 'x' + THUMBNAILS_SIZE.height
|
||||||
|
|
||||||
return generateImageFromVideoFile(
|
return generateImageFromVideoFile(
|
||||||
this.getVideoFilePath(videoFile),
|
this.getVideoFilePath(videoFile),
|
||||||
CONFIG.STORAGE.THUMBNAILS_DIR,
|
CONFIG.STORAGE.THUMBNAILS_DIR,
|
||||||
this.getThumbnailName(),
|
this.getThumbnailName(),
|
||||||
THUMBNAILS_SIZE
|
imageSize
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -438,6 +446,18 @@ generateMagnetUri = function (this: VideoInstance, videoFile: VideoFileInstance)
|
||||||
return magnetUtil.encode(magnetHash)
|
return magnetUtil.encode(magnetHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getEmbedPath = function (this: VideoInstance) {
|
||||||
|
return '/videos/embed/' + this.uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
toFormattedJSON = function (this: VideoInstance) {
|
||||||
let podHost
|
let podHost
|
||||||
|
|
||||||
|
@ -480,8 +500,9 @@ toFormattedJSON = function (this: VideoInstance) {
|
||||||
likes: this.likes,
|
likes: this.likes,
|
||||||
dislikes: this.dislikes,
|
dislikes: this.dislikes,
|
||||||
tags: map<TagInstance, string>(this.Tags, 'name'),
|
tags: map<TagInstance, string>(this.Tags, 'name'),
|
||||||
thumbnailPath: join(STATIC_PATHS.THUMBNAILS, this.getThumbnailName()),
|
thumbnailPath: this.getThumbnailPath(),
|
||||||
previewPath: join(STATIC_PATHS.PREVIEWS, this.getPreviewName()),
|
previewPath: this.getPreviewPath(),
|
||||||
|
embedPath: this.getEmbedPath(),
|
||||||
createdAt: this.createdAt,
|
createdAt: this.createdAt,
|
||||||
updatedAt: this.updatedAt,
|
updatedAt: this.updatedAt,
|
||||||
files: []
|
files: []
|
||||||
|
|
|
@ -3,6 +3,7 @@ import './pods'
|
||||||
import './remotes'
|
import './remotes'
|
||||||
import './users'
|
import './users'
|
||||||
import './request-schedulers'
|
import './request-schedulers'
|
||||||
|
import './services'
|
||||||
import './videos'
|
import './videos'
|
||||||
import './video-abuses'
|
import './video-abuses'
|
||||||
import './video-blacklist'
|
import './video-blacklist'
|
||||||
|
|
|
@ -0,0 +1,159 @@
|
||||||
|
/* tslint:disable:no-unused-expression */
|
||||||
|
|
||||||
|
import * as request from 'supertest'
|
||||||
|
import 'mocha'
|
||||||
|
|
||||||
|
import {
|
||||||
|
flushTests,
|
||||||
|
runServer,
|
||||||
|
setAccessTokensToServers,
|
||||||
|
killallServers
|
||||||
|
} from '../../utils'
|
||||||
|
import { getVideosList, uploadVideo } from '../../utils/videos'
|
||||||
|
|
||||||
|
describe('Test services API validators', function () {
|
||||||
|
let server
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
this.timeout(60000)
|
||||||
|
|
||||||
|
await flushTests()
|
||||||
|
|
||||||
|
server = await runServer(1)
|
||||||
|
await setAccessTokensToServers([ server ])
|
||||||
|
|
||||||
|
const videoAttributes = {
|
||||||
|
name: 'my super name'
|
||||||
|
}
|
||||||
|
await uploadVideo(server.url, server.accessToken, videoAttributes)
|
||||||
|
|
||||||
|
const res = await getVideosList(server.url)
|
||||||
|
server.video = res.body.data[0]
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Test oEmbed API validators', function () {
|
||||||
|
const path = '/services/oembed'
|
||||||
|
|
||||||
|
it('Should fail with an invalid url', async function () {
|
||||||
|
const embedUrl = 'hello.com'
|
||||||
|
|
||||||
|
await request(server.url)
|
||||||
|
.get(path)
|
||||||
|
.query({ url: embedUrl })
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.set('Authorization', 'Bearer ' + server.accessToken)
|
||||||
|
.expect(400)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should fail with an invalid host', async function () {
|
||||||
|
const embedUrl = 'http://hello.com/videos/watch/' + server.video.uuid
|
||||||
|
|
||||||
|
await request(server.url)
|
||||||
|
.get(path)
|
||||||
|
.query({ url: embedUrl })
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.set('Authorization', 'Bearer ' + server.accessToken)
|
||||||
|
.expect(400)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should fail with an invalid video id', async function () {
|
||||||
|
const embedUrl = 'http://localhost:9001/videos/watch/blabla'
|
||||||
|
|
||||||
|
await request(server.url)
|
||||||
|
.get(path)
|
||||||
|
.query({ url: embedUrl })
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.set('Authorization', 'Bearer ' + server.accessToken)
|
||||||
|
.expect(400)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should fail with an unknown video', async function () {
|
||||||
|
const embedUrl = 'http://localhost:9001/videos/watch/88fc0165-d1f0-4a35-a51a-3b47f668689c'
|
||||||
|
|
||||||
|
await request(server.url)
|
||||||
|
.get(path)
|
||||||
|
.query({ url: embedUrl })
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.set('Authorization', 'Bearer ' + server.accessToken)
|
||||||
|
.expect(404)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should fail with an invalid path', async function () {
|
||||||
|
const embedUrl = 'http://localhost:9001/videos/watchs/' + server.video.uuid
|
||||||
|
|
||||||
|
await request(server.url)
|
||||||
|
.get(path)
|
||||||
|
.query({ url: embedUrl })
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.set('Authorization', 'Bearer ' + server.accessToken)
|
||||||
|
.expect(400)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should fail with an invalid max height', async function () {
|
||||||
|
const embedUrl = 'http://localhost:9001/videos/watch/' + server.video.uuid
|
||||||
|
|
||||||
|
await request(server.url)
|
||||||
|
.get(path)
|
||||||
|
.query({
|
||||||
|
url: embedUrl,
|
||||||
|
maxheight: 'hello'
|
||||||
|
})
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.set('Authorization', 'Bearer ' + server.accessToken)
|
||||||
|
.expect(400)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should fail with an invalid max width', async function () {
|
||||||
|
const embedUrl = 'http://localhost:9001/videos/watch/' + server.video.uuid
|
||||||
|
|
||||||
|
await request(server.url)
|
||||||
|
.get(path)
|
||||||
|
.query({
|
||||||
|
url: embedUrl,
|
||||||
|
maxwidth: 'hello'
|
||||||
|
})
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.set('Authorization', 'Bearer ' + server.accessToken)
|
||||||
|
.expect(400)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should fail with an invalid format', async function () {
|
||||||
|
const embedUrl = 'http://localhost:9001/videos/watch/' + server.video.uuid
|
||||||
|
|
||||||
|
await request(server.url)
|
||||||
|
.get(path)
|
||||||
|
.query({
|
||||||
|
url: embedUrl,
|
||||||
|
format: 'blabla'
|
||||||
|
})
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.set('Authorization', 'Bearer ' + server.accessToken)
|
||||||
|
.expect(400)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should fail with a non supported format', async function () {
|
||||||
|
const embedUrl = 'http://localhost:9001/videos/watch/' + server.video.uuid
|
||||||
|
|
||||||
|
await request(server.url)
|
||||||
|
.get(path)
|
||||||
|
.query({
|
||||||
|
url: embedUrl,
|
||||||
|
format: 'xml'
|
||||||
|
})
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.set('Authorization', 'Bearer ' + server.accessToken)
|
||||||
|
.expect(501)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
after(async function () {
|
||||||
|
killallServers([ server ])
|
||||||
|
|
||||||
|
// Keep the logs if the test failed
|
||||||
|
if (this['ok']) {
|
||||||
|
await flushTests()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
|
@ -8,6 +8,7 @@ import './video-abuse'
|
||||||
import './video-blacklist'
|
import './video-blacklist'
|
||||||
import './video-blacklist-management'
|
import './video-blacklist-management'
|
||||||
import './multiple-pods'
|
import './multiple-pods'
|
||||||
|
import './services'
|
||||||
import './request-schedulers'
|
import './request-schedulers'
|
||||||
import './friends-advanced'
|
import './friends-advanced'
|
||||||
import './video-transcoder'
|
import './video-transcoder'
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
/* tslint:disable:no-unused-expression */
|
||||||
|
|
||||||
|
import 'mocha'
|
||||||
|
import * as chai from 'chai'
|
||||||
|
const expect = chai.expect
|
||||||
|
|
||||||
|
import {
|
||||||
|
ServerInfo,
|
||||||
|
flushTests,
|
||||||
|
uploadVideo,
|
||||||
|
getVideosList,
|
||||||
|
setAccessTokensToServers,
|
||||||
|
killallServers,
|
||||||
|
getOEmbed
|
||||||
|
} from '../utils'
|
||||||
|
import { runServer } from '../utils/servers'
|
||||||
|
|
||||||
|
describe('Test services', function () {
|
||||||
|
let server: ServerInfo = null
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
this.timeout(120000)
|
||||||
|
|
||||||
|
await flushTests()
|
||||||
|
|
||||||
|
server = await runServer(1)
|
||||||
|
|
||||||
|
await setAccessTokensToServers([ server ])
|
||||||
|
|
||||||
|
const videoAttributes = {
|
||||||
|
name: 'my super name'
|
||||||
|
}
|
||||||
|
await uploadVideo(server.url, server.accessToken, videoAttributes)
|
||||||
|
|
||||||
|
const res = await getVideosList(server.url)
|
||||||
|
server.video = res.body.data[0]
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should have a valid oEmbed response', async function () {
|
||||||
|
const oembedUrl = 'http://localhost:9001/videos/watch/' + server.video.uuid
|
||||||
|
|
||||||
|
const res = await getOEmbed(server.url, oembedUrl)
|
||||||
|
const expectedHtml = `<iframe width="560" height="315" src="http://localhost:9001/videos/embed/${server.video.uuid}" ` +
|
||||||
|
'frameborder="0" allowfullscreen></iframe>'
|
||||||
|
const expectedThumbnailUrl = 'http://localhost:9001/static/thumbnails/' + server.video.uuid + '.jpg'
|
||||||
|
|
||||||
|
expect(res.body.html).to.equal(expectedHtml)
|
||||||
|
expect(res.body.title).to.equal(server.video.name)
|
||||||
|
expect(res.body.author_name).to.equal(server.video.author)
|
||||||
|
expect(res.body.height).to.equal(315)
|
||||||
|
expect(res.body.width).to.equal(560)
|
||||||
|
expect(res.body.thumbnail_url).to.equal(expectedThumbnailUrl)
|
||||||
|
expect(res.body.thumbnail_width).to.equal(200)
|
||||||
|
expect(res.body.thumbnail_height).to.equal(110)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should have a valid oEmbed response with small max height query', async function () {
|
||||||
|
const oembedUrl = 'http://localhost:9001/videos/watch/' + server.video.uuid
|
||||||
|
const format = 'json'
|
||||||
|
const maxHeight = 50
|
||||||
|
const maxWidth = 50
|
||||||
|
|
||||||
|
const res = await getOEmbed(server.url, oembedUrl, format, maxHeight, maxWidth)
|
||||||
|
const expectedHtml = `<iframe width="50" height="50" src="http://localhost:9001/videos/embed/${server.video.uuid}" ` +
|
||||||
|
'frameborder="0" allowfullscreen></iframe>'
|
||||||
|
|
||||||
|
expect(res.body.html).to.equal(expectedHtml)
|
||||||
|
expect(res.body.title).to.equal(server.video.name)
|
||||||
|
expect(res.body.author_name).to.equal(server.video.author)
|
||||||
|
expect(res.body.height).to.equal(50)
|
||||||
|
expect(res.body.width).to.equal(50)
|
||||||
|
expect(res.body).to.not.have.property('thumbnail_url')
|
||||||
|
expect(res.body).to.not.have.property('thumbnail_width')
|
||||||
|
expect(res.body).to.not.have.property('thumbnail_height')
|
||||||
|
})
|
||||||
|
|
||||||
|
after(async function () {
|
||||||
|
killallServers([ server ])
|
||||||
|
|
||||||
|
// Keep the logs if the test failed
|
||||||
|
if (this['ok']) {
|
||||||
|
await flushTests()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
|
@ -39,7 +39,7 @@ describe('Test a client controllers', function () {
|
||||||
server.video = videos[0]
|
server.video = videos[0]
|
||||||
})
|
})
|
||||||
|
|
||||||
it('It should have valid Open Graph tags on the watch page with video id', async function () {
|
it('Should have valid Open Graph tags on the watch page with video id', async function () {
|
||||||
const res = await request(server.url)
|
const res = await request(server.url)
|
||||||
.get('/videos/watch/' + server.video.id)
|
.get('/videos/watch/' + server.video.id)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
@ -48,7 +48,7 @@ describe('Test a client controllers', function () {
|
||||||
expect(res.text).to.contain('<meta property="og:description" content="my super description for pod 1" />')
|
expect(res.text).to.contain('<meta property="og:description" content="my super description for pod 1" />')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('It should have valid Open Graph tags on the watch page with video uuid', async function () {
|
it('Should have valid Open Graph tags on the watch page with video uuid', async function () {
|
||||||
const res = await request(server.url)
|
const res = await request(server.url)
|
||||||
.get('/videos/watch/' + server.video.uuid)
|
.get('/videos/watch/' + server.video.uuid)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
@ -57,6 +57,19 @@ describe('Test a client controllers', function () {
|
||||||
expect(res.text).to.contain('<meta property="og:description" content="my super description for pod 1" />')
|
expect(res.text).to.contain('<meta property="og:description" content="my super description for pod 1" />')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should have valid oEmbed discovery tags', async function () {
|
||||||
|
const path = '/videos/watch/' + server.video.uuid
|
||||||
|
const res = await request(server.url)
|
||||||
|
.get(path)
|
||||||
|
.expect(200)
|
||||||
|
|
||||||
|
const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:9001/services/oembed?' +
|
||||||
|
`url=http%3A%2F%2Flocalhost%3A9001%2Fvideos%2Fwatch%2F${server.video.uuid}" ` +
|
||||||
|
`title="${server.video.name}" />`
|
||||||
|
|
||||||
|
expect(res.text).to.contain(expectedLink)
|
||||||
|
})
|
||||||
|
|
||||||
after(async function () {
|
after(async function () {
|
||||||
process.kill(-server.app.pid)
|
process.kill(-server.app.pid)
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ export * from './pods'
|
||||||
export * from './request-schedulers'
|
export * from './request-schedulers'
|
||||||
export * from './requests'
|
export * from './requests'
|
||||||
export * from './servers'
|
export * from './servers'
|
||||||
|
export * from './services'
|
||||||
export * from './users'
|
export * from './users'
|
||||||
export * from './video-abuses'
|
export * from './video-abuses'
|
||||||
export * from './video-blacklist'
|
export * from './video-blacklist'
|
||||||
|
|
|
@ -23,6 +23,8 @@ interface ServerInfo {
|
||||||
video?: {
|
video?: {
|
||||||
id: number
|
id: number
|
||||||
uuid: string
|
uuid: string
|
||||||
|
name: string
|
||||||
|
author: string
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteVideo?: {
|
remoteVideo?: {
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
import * as request from 'supertest'
|
||||||
|
|
||||||
|
function getOEmbed (url: string, oembedUrl: string, format?: string, maxHeight?: number, maxWidth?: number) {
|
||||||
|
const path = '/services/oembed'
|
||||||
|
const query = {
|
||||||
|
url: oembedUrl,
|
||||||
|
format,
|
||||||
|
maxheight: maxHeight,
|
||||||
|
maxwidth: maxWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
return request(url)
|
||||||
|
.get(path)
|
||||||
|
.query(query)
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.expect(200)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export {
|
||||||
|
getOEmbed
|
||||||
|
}
|
|
@ -25,6 +25,7 @@ export interface Video {
|
||||||
tags: string[]
|
tags: string[]
|
||||||
thumbnailPath: string
|
thumbnailPath: string
|
||||||
previewPath: string
|
previewPath: string
|
||||||
|
embedPath: string
|
||||||
views: number
|
views: number
|
||||||
likes: number
|
likes: number
|
||||||
dislikes: number
|
dislikes: number
|
||||||
|
|
Loading…
Reference in New Issue