// Run servers
servers = await createMultipleServers(2)
- serverHLSOnly = await createSingleServer(3, {
- transcoding: {
- enabled: true,
- web_videos: { enabled: false },
- hls: { enabled: true }
- }
- })
+ serverHLSOnly = await createSingleServer(3)
await setAccessTokensToServers([ ...servers, serverHLSOnly ])
await setDefaultChannelAvatar(servers[0])
await doubleFollow(servers[0], servers[1])
await servers[0].config.enableLive({ allowReplay: false, transcoding: false })
+ await serverHLSOnly.config.enableTranscoding({ webVideo: false, hls: true, with0p: true })
{
const user = await servers[0].users.getMyInfo()
const jsonObj = JSON.parse(json)
expect(jsonObj.items.length).to.be.equal(1)
expect(jsonObj.items[0].attachments).to.exist
- expect(jsonObj.items[0].attachments.length).to.be.eq(4)
+ expect(jsonObj.items[0].attachments.length).to.be.eq(6)
- for (let i = 0; i < 4; i++) {
+ for (let i = 0; i < 6; i++) {
expect(jsonObj.items[0].attachments[i].mime_type).to.be.eq('application/x-bittorrent')
expect(jsonObj.items[0].attachments[i].size_in_bytes).to.be.greaterThan(0)
expect(jsonObj.items[0].attachments[i].url).to.exist
await makeRawRequest({ url: imageUrl, expectedStatus: HttpStatusCode.OK_200 })
})
})
+
+ describe('XML feed', function () {
+
+ it('Should correctly have video mime types feed with HLS only', async function () {
+ this.timeout(120000)
+
+ const rss = await serverHLSOnly.feed.getXML({ feed: 'videos', ignoreCache: true })
+ const parser = new XMLParser({ parseAttributeValue: true, ignoreAttributes: false })
+ const xmlDoc = parser.parse(rss)
+
+ for (const media of xmlDoc.rss.channel.item['media:group']['media:content']) {
+ if (media['@_height'] === 0) {
+ expect(media['@_type']).to.equal('audio/mp4')
+ } else {
+ expect(media['@_type']).to.equal('video/mp4')
+ }
+ }
+ })
+ })
})
describe('Video comments feed', function () {
import { Feed } from '@peertube/feed'
import { cacheRouteFactory } from '@server/middlewares/index.js'
import { VideoModel } from '@server/models/video/video.js'
-import { VideoInclude } from '@peertube/peertube-models'
+import { VideoInclude, VideoResolution } from '@peertube/peertube-models'
import { buildNSFWFilter } from '../../helpers/express-utils.js'
-import { MIMETYPES, ROUTE_CACHE_LIFETIME, WEBSERVER } from '../../initializers/constants.js'
+import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../../initializers/constants.js'
import {
asyncMiddleware,
commonVideosFiltersValidator,
videoSubscriptionFeedsValidator
} from '../../middlewares/index.js'
import { buildFeedMetadata, getCommonVideoFeedAttributes, getVideosForFeeds, initFeed, sendFeed } from './shared/index.js'
+import { getVideoFileMimeType } from '@server/lib/video-file.js'
const videoFeedsRouter = express.Router()
const videoFiles = formattedVideoFiles.map(videoFile => {
return {
- type: MIMETYPES.VIDEO.EXT_MIMETYPE[extname(videoFile.fileUrl)],
+ type: getVideoFileMimeType(extname(videoFile.fileUrl), videoFile.resolution.id === VideoResolution.H_NOVIDEO),
medium: 'video',
height: videoFile.resolution.id,
fileSize: videoFile.size,
import { VideoModel } from '../../models/video/video.js'
import { VideoCaptionModel } from '../../models/video/video-caption.js'
import { buildFeedMetadata, getCommonVideoFeedAttributes, getVideosForFeeds, initFeed } from './shared/index.js'
+import { getVideoFileMimeType } from '@server/lib/video-file.js'
const videoPodcastFeedsRouter = express.Router()
// ---------------------------------------------------------------------------
function buildVODWebVideoFile (video: MVideo, videoFile: VideoFile) {
- const isAudio = videoFile.resolution.id === VideoResolution.H_NOVIDEO
- const type = isAudio
- ? MIMETYPES.AUDIO.EXT_MIMETYPE[extname(videoFile.fileUrl)]
- : MIMETYPES.VIDEO.EXT_MIMETYPE[extname(videoFile.fileUrl)]
-
const sources = [
{ uri: videoFile.fileUrl },
{ uri: videoFile.torrentUrl, contentType: 'application/x-bittorrent' }
}
return {
- type,
+ type: getVideoFileMimeType(extname(videoFile.fileUrl), videoFile.resolution.id === VideoResolution.H_NOVIDEO),
title: videoFile.resolution.label,
length: videoFile.size,
bitrate: videoFile.size / video.duration * 8,
'audio/vnd.dlna.adts': '.aac',
'audio/aac': '.aac',
+ // Keep priority for preferred mime type
'audio/m4a': '.m4a',
- 'audio/mp4': '.m4a',
'audio/x-m4a': '.m4a',
+ 'audio/mp4': '.m4a',
'audio/vnd.dolby.dd-raw': '.ac3',
'audio/ac3': '.ac3'
import { lTags } from './object-storage/shared/index.js'
import { generateHLSVideoFilename, generateWebVideoFilename } from './paths.js'
import { VideoPathManager } from './video-path-manager.js'
+import { MIMETYPES } from '@server/initializers/constants.js'
async function buildNewFile (options: {
path: string
return new VideoFileMetadata(metadata)
}
+function getVideoFileMimeType (extname: string, isAudio: boolean) {
+ return isAudio && extname === '.mp4' // We use .mp4 even for audio file only
+ ? MIMETYPES.AUDIO.EXT_MIMETYPE['.m4a']
+ : MIMETYPES.VIDEO.EXT_MIMETYPE[extname]
+}
+
// ---------------------------------------------------------------------------
export {
removeAllWebVideoFiles,
removeWebVideoFile,
- buildFileMetadata
+ buildFileMetadata,
+ getVideoFileMimeType
}
UpdatedAt
} from 'sequelize-typescript'
import {
+ ActivityVideoUrlObject,
CacheFileObject,
FileRedundancyInformation,
StreamingPlaylistRedundancyInformation,
import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc.js'
import { logger } from '../../helpers/logger.js'
import { CONFIG } from '../../initializers/config.js'
-import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../initializers/constants.js'
+import { CONSTRAINTS_FIELDS } from '../../initializers/constants.js'
import { ActorModel } from '../actor/actor.js'
import { ServerModel } from '../server/server.js'
import { getSort, getVideoSort, parseAggregateResult, throwIfNotValid } from '../shared/index.js'
import { VideoFileModel } from '../video/video-file.js'
import { VideoStreamingPlaylistModel } from '../video/video-streaming-playlist.js'
import { VideoModel } from '../video/video.js'
+import { getVideoFileMimeType } from '@server/lib/video-file.js'
export enum ScopeNames {
WITH_VIDEO = 'WITH_VIDEO'
id: this.url,
type: 'CacheFile' as 'CacheFile',
object: this.VideoFile.Video.url,
- expires: this.expiresOn ? this.expiresOn.toISOString() : null,
+
+ expires: this.expiresOn
+ ? this.expiresOn.toISOString()
+ : null,
+
url: {
type: 'Link',
- mediaType: MIMETYPES.VIDEO.EXT_MIMETYPE[this.VideoFile.extname] as any,
+ mediaType: getVideoFileMimeType(this.VideoFile.extname, this.VideoFile.isAudio()),
href: this.fileUrl,
height: this.VideoFile.resolution,
size: this.VideoFile.size,
fps: this.VideoFile.fps
- }
+ } as ActivityVideoUrlObject
}
}
ActivityTagObject,
ActivityTrackerUrlObject,
ActivityUrlObject,
+ ActivityVideoUrlObject,
VideoObject
} from '@peertube/peertube-models'
-import { MIMETYPES, WEBSERVER } from '../../../initializers/constants.js'
+import { WEBSERVER } from '../../../initializers/constants.js'
import {
getLocalVideoChaptersActivityPubUrl,
getLocalVideoCommentsActivityPubUrl,
import { VideoCaptionModel } from '../video-caption.js'
import { sortByResolutionDesc } from './shared/index.js'
import { getCategoryLabel, getLanguageLabel, getLicenceLabel } from './video-api-format.js'
+import { getVideoFileMimeType } from '@server/lib/video-file.js'
export function videoModelToActivityPubObject (video: MVideoAP): VideoObject {
const language = video.language
.sort(sortByResolutionDesc)
for (const file of sortedFiles) {
+ const mimeType = getVideoFileMimeType(file.extname, file.isAudio())
+
urls.push({
type: 'Link',
- mediaType: MIMETYPES.VIDEO.EXT_MIMETYPE[file.extname] as any,
+ mediaType: mimeType,
href: file.getFileUrl(video),
height: file.resolution,
size: file.size,
fps: file.fps
- })
+ } as ActivityVideoUrlObject)
urls.push({
type: 'Link',
- rel: [ 'metadata', MIMETYPES.VIDEO.EXT_MIMETYPE[file.extname] ],
+ rel: [ 'metadata', mimeType ],
mediaType: 'application/json' as 'application/json',
href: getLocalVideoFileMetadataUrl(video, file),
height: file.resolution,