]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Fix feed audio file mimetype develop
authorChocobozzz <me@florianbigard.com>
Tue, 3 Oct 2023 10:20:11 +0000 (12:20 +0200)
committerChocobozzz <me@florianbigard.com>
Tue, 3 Oct 2023 10:20:11 +0000 (12:20 +0200)
packages/tests/src/feeds/feeds.ts
server/server/controllers/feeds/video-feeds.ts
server/server/controllers/feeds/video-podcast-feeds.ts
server/server/initializers/constants.ts
server/server/lib/video-file.ts
server/server/models/redundancy/video-redundancy.ts
server/server/models/video/formatter/video-activity-pub-format.ts

index 7587bb34eddded6026d709e351b0b68687eb024a..ed833ffd169d927da7fd499283c8b825d11651d7 100644 (file)
@@ -46,13 +46,7 @@ describe('Test syndication feeds', () => {
 
     // 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])
@@ -60,6 +54,7 @@ describe('Test syndication feeds', () => {
     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()
@@ -397,9 +392,9 @@ describe('Test syndication feeds', () => {
         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
@@ -450,6 +445,25 @@ describe('Test syndication feeds', () => {
         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 () {
index fc0e6e0f76dc2462ad2b4d88ffc9d2809cd2875f..679d73ffffe2a8a4bdf5e0669b4489226cedbdd5 100644 (file)
@@ -3,9 +3,9 @@ import { extname } from 'path'
 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,
@@ -17,6 +17,7 @@ import {
   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()
 
@@ -137,7 +138,7 @@ function addVideosToFeed (feed: Feed, videos: VideoModel[]) {
 
     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,
index 84d5acadd17bdd0fa8df88fe9c75b093bf3f7b74..ad694affa0ee63e1a64a0d9aefff18e323f490cd 100644 (file)
@@ -15,6 +15,7 @@ import { asyncMiddleware, setFeedPodcastContentType, videoFeedsPodcastValidator
 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()
 
@@ -243,11 +244,6 @@ async function addLivePodcastItem (options: {
 // ---------------------------------------------------------------------------
 
 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' }
@@ -258,7 +254,7 @@ function buildVODWebVideoFile (video: MVideo, videoFile: VideoFile) {
   }
 
   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,
index 38322aecec56f5a198154e3580436b71ebf3bdec..121433caf227454d3c3976e11d586f606545bfb7 100644 (file)
@@ -630,9 +630,10 @@ const MIMETYPES = {
       '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'
index 9d8a6e8fc47194f2bce9eebe26b33dd91a630dc0..ccc5668a24808ea632e40927bb0b6492949cf678 100644 (file)
@@ -8,6 +8,7 @@ import { ffprobePromise, getVideoStreamDimensionsInfo, getVideoStreamFPS, isAudi
 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
@@ -130,6 +131,12 @@ async function buildFileMetadata (path: string, existingProbe?: FfprobeData) {
   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 {
@@ -140,5 +147,6 @@ export {
   removeAllWebVideoFiles,
   removeWebVideoFile,
 
-  buildFileMetadata
+  buildFileMetadata,
+  getVideoFileMimeType
 }
index 26089594d992998a2623ee8afbf1b454e4f48fd5..3a5e7bf338e759b617e031a43f7a30e58e73d554 100644 (file)
@@ -15,6 +15,7 @@ import {
   UpdatedAt
 } from 'sequelize-typescript'
 import {
+  ActivityVideoUrlObject,
   CacheFileObject,
   FileRedundancyInformation,
   StreamingPlaylistRedundancyInformation,
@@ -31,7 +32,7 @@ import { MActor, MVideoForRedundancyAPI, MVideoRedundancy, MVideoRedundancyAP, M
 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'
@@ -40,6 +41,7 @@ import { VideoChannelModel } from '../video/video-channel.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'
@@ -733,15 +735,19 @@ export class VideoRedundancyModel extends Model<Partial<AttributesOnly<VideoRedu
       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
     }
   }
 
index d19bb1880b00777397e90859e42d22ddb442b0ec..7beec741a0c506a4810b68d728286ac449f53955 100644 (file)
@@ -9,9 +9,10 @@ import {
   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,
@@ -23,6 +24,7 @@ import { MStreamingPlaylistFiles, MUserId, MVideo, MVideoAP, MVideoFile } from '
 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
@@ -179,18 +181,20 @@ function buildVideoFileUrls (options: {
     .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,