Fix feed audio file mimetype
This commit is contained in:
parent
5cea8f9567
commit
1611721c9b
|
@ -46,13 +46,7 @@ describe('Test syndication feeds', () => {
|
||||||
|
|
||||||
// Run servers
|
// Run servers
|
||||||
servers = await createMultipleServers(2)
|
servers = await createMultipleServers(2)
|
||||||
serverHLSOnly = await createSingleServer(3, {
|
serverHLSOnly = await createSingleServer(3)
|
||||||
transcoding: {
|
|
||||||
enabled: true,
|
|
||||||
web_videos: { enabled: false },
|
|
||||||
hls: { enabled: true }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
await setAccessTokensToServers([ ...servers, serverHLSOnly ])
|
await setAccessTokensToServers([ ...servers, serverHLSOnly ])
|
||||||
await setDefaultChannelAvatar(servers[0])
|
await setDefaultChannelAvatar(servers[0])
|
||||||
|
@ -60,6 +54,7 @@ describe('Test syndication feeds', () => {
|
||||||
await doubleFollow(servers[0], servers[1])
|
await doubleFollow(servers[0], servers[1])
|
||||||
|
|
||||||
await servers[0].config.enableLive({ allowReplay: false, transcoding: false })
|
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 user = await servers[0].users.getMyInfo()
|
||||||
|
@ -397,9 +392,9 @@ describe('Test syndication feeds', () => {
|
||||||
const jsonObj = JSON.parse(json)
|
const jsonObj = JSON.parse(json)
|
||||||
expect(jsonObj.items.length).to.be.equal(1)
|
expect(jsonObj.items.length).to.be.equal(1)
|
||||||
expect(jsonObj.items[0].attachments).to.exist
|
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].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].size_in_bytes).to.be.greaterThan(0)
|
||||||
expect(jsonObj.items[0].attachments[i].url).to.exist
|
expect(jsonObj.items[0].attachments[i].url).to.exist
|
||||||
|
@ -450,6 +445,25 @@ describe('Test syndication feeds', () => {
|
||||||
await makeRawRequest({ url: imageUrl, expectedStatus: HttpStatusCode.OK_200 })
|
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 () {
|
describe('Video comments feed', function () {
|
||||||
|
|
|
@ -3,9 +3,9 @@ import { extname } from 'path'
|
||||||
import { Feed } from '@peertube/feed'
|
import { Feed } from '@peertube/feed'
|
||||||
import { cacheRouteFactory } from '@server/middlewares/index.js'
|
import { cacheRouteFactory } from '@server/middlewares/index.js'
|
||||||
import { VideoModel } from '@server/models/video/video.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 { 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 {
|
import {
|
||||||
asyncMiddleware,
|
asyncMiddleware,
|
||||||
commonVideosFiltersValidator,
|
commonVideosFiltersValidator,
|
||||||
|
@ -17,6 +17,7 @@ import {
|
||||||
videoSubscriptionFeedsValidator
|
videoSubscriptionFeedsValidator
|
||||||
} from '../../middlewares/index.js'
|
} from '../../middlewares/index.js'
|
||||||
import { buildFeedMetadata, getCommonVideoFeedAttributes, getVideosForFeeds, initFeed, sendFeed } from './shared/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 videoFeedsRouter = express.Router()
|
||||||
|
|
||||||
|
@ -137,7 +138,7 @@ function addVideosToFeed (feed: Feed, videos: VideoModel[]) {
|
||||||
|
|
||||||
const videoFiles = formattedVideoFiles.map(videoFile => {
|
const videoFiles = formattedVideoFiles.map(videoFile => {
|
||||||
return {
|
return {
|
||||||
type: MIMETYPES.VIDEO.EXT_MIMETYPE[extname(videoFile.fileUrl)],
|
type: getVideoFileMimeType(extname(videoFile.fileUrl), videoFile.resolution.id === VideoResolution.H_NOVIDEO),
|
||||||
medium: 'video',
|
medium: 'video',
|
||||||
height: videoFile.resolution.id,
|
height: videoFile.resolution.id,
|
||||||
fileSize: videoFile.size,
|
fileSize: videoFile.size,
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { asyncMiddleware, setFeedPodcastContentType, videoFeedsPodcastValidator
|
||||||
import { VideoModel } from '../../models/video/video.js'
|
import { VideoModel } from '../../models/video/video.js'
|
||||||
import { VideoCaptionModel } from '../../models/video/video-caption.js'
|
import { VideoCaptionModel } from '../../models/video/video-caption.js'
|
||||||
import { buildFeedMetadata, getCommonVideoFeedAttributes, getVideosForFeeds, initFeed } from './shared/index.js'
|
import { buildFeedMetadata, getCommonVideoFeedAttributes, getVideosForFeeds, initFeed } from './shared/index.js'
|
||||||
|
import { getVideoFileMimeType } from '@server/lib/video-file.js'
|
||||||
|
|
||||||
const videoPodcastFeedsRouter = express.Router()
|
const videoPodcastFeedsRouter = express.Router()
|
||||||
|
|
||||||
|
@ -243,11 +244,6 @@ async function addLivePodcastItem (options: {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
function buildVODWebVideoFile (video: MVideo, videoFile: VideoFile) {
|
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 = [
|
const sources = [
|
||||||
{ uri: videoFile.fileUrl },
|
{ uri: videoFile.fileUrl },
|
||||||
{ uri: videoFile.torrentUrl, contentType: 'application/x-bittorrent' }
|
{ uri: videoFile.torrentUrl, contentType: 'application/x-bittorrent' }
|
||||||
|
@ -258,7 +254,7 @@ function buildVODWebVideoFile (video: MVideo, videoFile: VideoFile) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type,
|
type: getVideoFileMimeType(extname(videoFile.fileUrl), videoFile.resolution.id === VideoResolution.H_NOVIDEO),
|
||||||
title: videoFile.resolution.label,
|
title: videoFile.resolution.label,
|
||||||
length: videoFile.size,
|
length: videoFile.size,
|
||||||
bitrate: videoFile.size / video.duration * 8,
|
bitrate: videoFile.size / video.duration * 8,
|
||||||
|
|
|
@ -630,9 +630,10 @@ const MIMETYPES = {
|
||||||
'audio/vnd.dlna.adts': '.aac',
|
'audio/vnd.dlna.adts': '.aac',
|
||||||
'audio/aac': '.aac',
|
'audio/aac': '.aac',
|
||||||
|
|
||||||
|
// Keep priority for preferred mime type
|
||||||
'audio/m4a': '.m4a',
|
'audio/m4a': '.m4a',
|
||||||
'audio/mp4': '.m4a',
|
|
||||||
'audio/x-m4a': '.m4a',
|
'audio/x-m4a': '.m4a',
|
||||||
|
'audio/mp4': '.m4a',
|
||||||
|
|
||||||
'audio/vnd.dolby.dd-raw': '.ac3',
|
'audio/vnd.dolby.dd-raw': '.ac3',
|
||||||
'audio/ac3': '.ac3'
|
'audio/ac3': '.ac3'
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { ffprobePromise, getVideoStreamDimensionsInfo, getVideoStreamFPS, isAudi
|
||||||
import { lTags } from './object-storage/shared/index.js'
|
import { lTags } from './object-storage/shared/index.js'
|
||||||
import { generateHLSVideoFilename, generateWebVideoFilename } from './paths.js'
|
import { generateHLSVideoFilename, generateWebVideoFilename } from './paths.js'
|
||||||
import { VideoPathManager } from './video-path-manager.js'
|
import { VideoPathManager } from './video-path-manager.js'
|
||||||
|
import { MIMETYPES } from '@server/initializers/constants.js'
|
||||||
|
|
||||||
async function buildNewFile (options: {
|
async function buildNewFile (options: {
|
||||||
path: string
|
path: string
|
||||||
|
@ -130,6 +131,12 @@ async function buildFileMetadata (path: string, existingProbe?: FfprobeData) {
|
||||||
return new VideoFileMetadata(metadata)
|
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 {
|
export {
|
||||||
|
@ -140,5 +147,6 @@ export {
|
||||||
removeAllWebVideoFiles,
|
removeAllWebVideoFiles,
|
||||||
removeWebVideoFile,
|
removeWebVideoFile,
|
||||||
|
|
||||||
buildFileMetadata
|
buildFileMetadata,
|
||||||
|
getVideoFileMimeType
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import {
|
||||||
UpdatedAt
|
UpdatedAt
|
||||||
} from 'sequelize-typescript'
|
} from 'sequelize-typescript'
|
||||||
import {
|
import {
|
||||||
|
ActivityVideoUrlObject,
|
||||||
CacheFileObject,
|
CacheFileObject,
|
||||||
FileRedundancyInformation,
|
FileRedundancyInformation,
|
||||||
StreamingPlaylistRedundancyInformation,
|
StreamingPlaylistRedundancyInformation,
|
||||||
|
@ -31,7 +32,7 @@ import { MActor, MVideoForRedundancyAPI, MVideoRedundancy, MVideoRedundancyAP, M
|
||||||
import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc.js'
|
import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc.js'
|
||||||
import { logger } from '../../helpers/logger.js'
|
import { logger } from '../../helpers/logger.js'
|
||||||
import { CONFIG } from '../../initializers/config.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 { ActorModel } from '../actor/actor.js'
|
||||||
import { ServerModel } from '../server/server.js'
|
import { ServerModel } from '../server/server.js'
|
||||||
import { getSort, getVideoSort, parseAggregateResult, throwIfNotValid } from '../shared/index.js'
|
import { getSort, getVideoSort, parseAggregateResult, throwIfNotValid } from '../shared/index.js'
|
||||||
|
@ -40,6 +41,7 @@ import { VideoChannelModel } from '../video/video-channel.js'
|
||||||
import { VideoFileModel } from '../video/video-file.js'
|
import { VideoFileModel } from '../video/video-file.js'
|
||||||
import { VideoStreamingPlaylistModel } from '../video/video-streaming-playlist.js'
|
import { VideoStreamingPlaylistModel } from '../video/video-streaming-playlist.js'
|
||||||
import { VideoModel } from '../video/video.js'
|
import { VideoModel } from '../video/video.js'
|
||||||
|
import { getVideoFileMimeType } from '@server/lib/video-file.js'
|
||||||
|
|
||||||
export enum ScopeNames {
|
export enum ScopeNames {
|
||||||
WITH_VIDEO = 'WITH_VIDEO'
|
WITH_VIDEO = 'WITH_VIDEO'
|
||||||
|
@ -733,15 +735,19 @@ export class VideoRedundancyModel extends Model<Partial<AttributesOnly<VideoRedu
|
||||||
id: this.url,
|
id: this.url,
|
||||||
type: 'CacheFile' as 'CacheFile',
|
type: 'CacheFile' as 'CacheFile',
|
||||||
object: this.VideoFile.Video.url,
|
object: this.VideoFile.Video.url,
|
||||||
expires: this.expiresOn ? this.expiresOn.toISOString() : null,
|
|
||||||
|
expires: this.expiresOn
|
||||||
|
? this.expiresOn.toISOString()
|
||||||
|
: null,
|
||||||
|
|
||||||
url: {
|
url: {
|
||||||
type: 'Link',
|
type: 'Link',
|
||||||
mediaType: MIMETYPES.VIDEO.EXT_MIMETYPE[this.VideoFile.extname] as any,
|
mediaType: getVideoFileMimeType(this.VideoFile.extname, this.VideoFile.isAudio()),
|
||||||
href: this.fileUrl,
|
href: this.fileUrl,
|
||||||
height: this.VideoFile.resolution,
|
height: this.VideoFile.resolution,
|
||||||
size: this.VideoFile.size,
|
size: this.VideoFile.size,
|
||||||
fps: this.VideoFile.fps
|
fps: this.VideoFile.fps
|
||||||
}
|
} as ActivityVideoUrlObject
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,9 +9,10 @@ import {
|
||||||
ActivityTagObject,
|
ActivityTagObject,
|
||||||
ActivityTrackerUrlObject,
|
ActivityTrackerUrlObject,
|
||||||
ActivityUrlObject,
|
ActivityUrlObject,
|
||||||
|
ActivityVideoUrlObject,
|
||||||
VideoObject
|
VideoObject
|
||||||
} from '@peertube/peertube-models'
|
} from '@peertube/peertube-models'
|
||||||
import { MIMETYPES, WEBSERVER } from '../../../initializers/constants.js'
|
import { WEBSERVER } from '../../../initializers/constants.js'
|
||||||
import {
|
import {
|
||||||
getLocalVideoChaptersActivityPubUrl,
|
getLocalVideoChaptersActivityPubUrl,
|
||||||
getLocalVideoCommentsActivityPubUrl,
|
getLocalVideoCommentsActivityPubUrl,
|
||||||
|
@ -23,6 +24,7 @@ import { MStreamingPlaylistFiles, MUserId, MVideo, MVideoAP, MVideoFile } from '
|
||||||
import { VideoCaptionModel } from '../video-caption.js'
|
import { VideoCaptionModel } from '../video-caption.js'
|
||||||
import { sortByResolutionDesc } from './shared/index.js'
|
import { sortByResolutionDesc } from './shared/index.js'
|
||||||
import { getCategoryLabel, getLanguageLabel, getLicenceLabel } from './video-api-format.js'
|
import { getCategoryLabel, getLanguageLabel, getLicenceLabel } from './video-api-format.js'
|
||||||
|
import { getVideoFileMimeType } from '@server/lib/video-file.js'
|
||||||
|
|
||||||
export function videoModelToActivityPubObject (video: MVideoAP): VideoObject {
|
export function videoModelToActivityPubObject (video: MVideoAP): VideoObject {
|
||||||
const language = video.language
|
const language = video.language
|
||||||
|
@ -179,18 +181,20 @@ function buildVideoFileUrls (options: {
|
||||||
.sort(sortByResolutionDesc)
|
.sort(sortByResolutionDesc)
|
||||||
|
|
||||||
for (const file of sortedFiles) {
|
for (const file of sortedFiles) {
|
||||||
|
const mimeType = getVideoFileMimeType(file.extname, file.isAudio())
|
||||||
|
|
||||||
urls.push({
|
urls.push({
|
||||||
type: 'Link',
|
type: 'Link',
|
||||||
mediaType: MIMETYPES.VIDEO.EXT_MIMETYPE[file.extname] as any,
|
mediaType: mimeType,
|
||||||
href: file.getFileUrl(video),
|
href: file.getFileUrl(video),
|
||||||
height: file.resolution,
|
height: file.resolution,
|
||||||
size: file.size,
|
size: file.size,
|
||||||
fps: file.fps
|
fps: file.fps
|
||||||
})
|
} as ActivityVideoUrlObject)
|
||||||
|
|
||||||
urls.push({
|
urls.push({
|
||||||
type: 'Link',
|
type: 'Link',
|
||||||
rel: [ 'metadata', MIMETYPES.VIDEO.EXT_MIMETYPE[file.extname] ],
|
rel: [ 'metadata', mimeType ],
|
||||||
mediaType: 'application/json' as 'application/json',
|
mediaType: 'application/json' as 'application/json',
|
||||||
href: getLocalVideoFileMetadataUrl(video, file),
|
href: getLocalVideoFileMetadataUrl(video, file),
|
||||||
height: file.resolution,
|
height: file.resolution,
|
||||||
|
|
Loading…
Reference in New Issue