diff options
author | Chocobozzz <me@florianbigard.com> | 2020-08-24 16:11:37 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2020-08-24 16:11:37 +0200 |
commit | 97816649b793bdd0f3df64631ae0ef7cf3d7461c (patch) | |
tree | abd56aac43d2d79dc4840d2ed6385449e1563286 | |
parent | 74055dc882e484b217f398917a3cc24bf2ea8cbe (diff) | |
download | PeerTube-97816649b793bdd0f3df64631ae0ef7cf3d7461c.tar.gz PeerTube-97816649b793bdd0f3df64631ae0ef7cf3d7461c.tar.zst PeerTube-97816649b793bdd0f3df64631ae0ef7cf3d7461c.zip |
Fix RSS feed when HLS only is enabled
-rw-r--r-- | server/models/video/video-format-utils.ts | 6 | ||||
-rw-r--r-- | server/models/video/video-query-builder.ts | 32 | ||||
-rw-r--r-- | server/models/video/video.ts | 56 | ||||
-rw-r--r-- | server/tests/feeds/feeds.ts | 36 |
4 files changed, 117 insertions, 13 deletions
diff --git a/server/models/video/video-format-utils.ts b/server/models/video/video-format-utils.ts index 7a17c839f..ad512fc7f 100644 --- a/server/models/video/video-format-utils.ts +++ b/server/models/video/video-format-utils.ts | |||
@@ -59,7 +59,11 @@ function videoModelToFormattedJSON (video: MVideoFormattable, options?: VideoFor | |||
59 | label: VideoModel.getPrivacyLabel(video.privacy) | 59 | label: VideoModel.getPrivacyLabel(video.privacy) |
60 | }, | 60 | }, |
61 | nsfw: video.nsfw, | 61 | nsfw: video.nsfw, |
62 | description: options && options.completeDescription === true ? video.description : video.getTruncatedDescription(), | 62 | |
63 | description: options && options.completeDescription === true | ||
64 | ? video.description | ||
65 | : video.getTruncatedDescription(), | ||
66 | |||
63 | isLocal: video.isOwned(), | 67 | isLocal: video.isOwned(), |
64 | duration: video.duration, | 68 | duration: video.duration, |
65 | views: video.views, | 69 | views: video.views, |
diff --git a/server/models/video/video-query-builder.ts b/server/models/video/video-query-builder.ts index 466890364..b14bb16d6 100644 --- a/server/models/video/video-query-builder.ts +++ b/server/models/video/video-query-builder.ts | |||
@@ -156,7 +156,16 @@ function buildListQuery (model: typeof Model, options: BuildVideosQueryOptions) | |||
156 | } | 156 | } |
157 | 157 | ||
158 | if (options.withFiles === true) { | 158 | if (options.withFiles === true) { |
159 | and.push('EXISTS (SELECT 1 FROM "videoFile" WHERE "videoFile"."videoId" = "video"."id")') | 159 | and.push( |
160 | '(' + | ||
161 | ' EXISTS (SELECT 1 FROM "videoFile" WHERE "videoFile"."videoId" = "video"."id") ' + | ||
162 | ' OR EXISTS (' + | ||
163 | ' SELECT 1 FROM "videoStreamingPlaylist" ' + | ||
164 | ' INNER JOIN "videoFile" ON "videoFile"."videoStreamingPlaylistId" = "videoStreamingPlaylist"."id" ' + | ||
165 | ' WHERE "videoStreamingPlaylist"."videoId" = "video"."id"' + | ||
166 | ' )' + | ||
167 | ')' | ||
168 | ) | ||
160 | } | 169 | } |
161 | 170 | ||
162 | if (options.tagsOneOf) { | 171 | if (options.tagsOneOf) { |
@@ -443,7 +452,13 @@ function wrapForAPIResults (baseQuery: string, replacements: any, options: Build | |||
443 | ] | 452 | ] |
444 | 453 | ||
445 | if (options.withFiles) { | 454 | if (options.withFiles) { |
446 | joins.push('INNER JOIN "videoFile" AS "VideoFiles" ON "VideoFiles"."videoId" = "video"."id"') | 455 | joins.push('LEFT JOIN "videoFile" AS "VideoFiles" ON "VideoFiles"."videoId" = "video"."id"') |
456 | |||
457 | joins.push('LEFT JOIN "videoStreamingPlaylist" AS "VideoStreamingPlaylists" ON "VideoStreamingPlaylists"."videoId" = "video"."id"') | ||
458 | joins.push( | ||
459 | 'LEFT JOIN "videoFile" AS "VideoStreamingPlaylists->VideoFiles" ' + | ||
460 | 'ON "VideoStreamingPlaylists->VideoFiles"."videoStreamingPlaylistId" = "VideoStreamingPlaylists"."id"' | ||
461 | ) | ||
447 | 462 | ||
448 | Object.assign(attributes, { | 463 | Object.assign(attributes, { |
449 | '"VideoFiles"."id"': '"VideoFiles.id"', | 464 | '"VideoFiles"."id"': '"VideoFiles.id"', |
@@ -454,7 +469,18 @@ function wrapForAPIResults (baseQuery: string, replacements: any, options: Build | |||
454 | '"VideoFiles"."extname"': '"VideoFiles.extname"', | 469 | '"VideoFiles"."extname"': '"VideoFiles.extname"', |
455 | '"VideoFiles"."infoHash"': '"VideoFiles.infoHash"', | 470 | '"VideoFiles"."infoHash"': '"VideoFiles.infoHash"', |
456 | '"VideoFiles"."fps"': '"VideoFiles.fps"', | 471 | '"VideoFiles"."fps"': '"VideoFiles.fps"', |
457 | '"VideoFiles"."videoId"': '"VideoFiles.videoId"' | 472 | '"VideoFiles"."videoId"': '"VideoFiles.videoId"', |
473 | |||
474 | '"VideoStreamingPlaylists"."id"': '"VideoStreamingPlaylists.id"', | ||
475 | '"VideoStreamingPlaylists->VideoFiles"."id"': '"VideoStreamingPlaylists.VideoFiles.id"', | ||
476 | '"VideoStreamingPlaylists->VideoFiles"."createdAt"': '"VideoStreamingPlaylists.VideoFiles.createdAt"', | ||
477 | '"VideoStreamingPlaylists->VideoFiles"."updatedAt"': '"VideoStreamingPlaylists.VideoFiles.updatedAt"', | ||
478 | '"VideoStreamingPlaylists->VideoFiles"."resolution"': '"VideoStreamingPlaylists.VideoFiles.resolution"', | ||
479 | '"VideoStreamingPlaylists->VideoFiles"."size"': '"VideoStreamingPlaylists.VideoFiles.size"', | ||
480 | '"VideoStreamingPlaylists->VideoFiles"."extname"': '"VideoStreamingPlaylists.VideoFiles.extname"', | ||
481 | '"VideoStreamingPlaylists->VideoFiles"."infoHash"': '"VideoStreamingPlaylists.VideoFiles.infoHash"', | ||
482 | '"VideoStreamingPlaylists->VideoFiles"."fps"': '"VideoStreamingPlaylists.VideoFiles.fps"', | ||
483 | '"VideoStreamingPlaylists->VideoFiles"."videoId"': '"VideoStreamingPlaylists.VideoFiles.videoId"' | ||
458 | }) | 484 | }) |
459 | } | 485 | } |
460 | 486 | ||
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 43609587c..1eded0d56 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -95,7 +95,7 @@ import { | |||
95 | MVideoWithRights | 95 | MVideoWithRights |
96 | } from '../../types/models' | 96 | } from '../../types/models' |
97 | import { MThumbnail } from '../../types/models/video/thumbnail' | 97 | import { MThumbnail } from '../../types/models/video/thumbnail' |
98 | import { MVideoFile, MVideoFileStreamingPlaylistVideo } from '../../types/models/video/video-file' | 98 | import { MVideoFile, MVideoFileStreamingPlaylistVideo, MVideoFileRedundanciesOpt } from '../../types/models/video/video-file' |
99 | import { VideoAbuseModel } from '../abuse/video-abuse' | 99 | import { VideoAbuseModel } from '../abuse/video-abuse' |
100 | import { AccountModel } from '../account/account' | 100 | import { AccountModel } from '../account/account' |
101 | import { AccountVideoRateModel } from '../account/account-video-rate' | 101 | import { AccountVideoRateModel } from '../account/account-video-rate' |
@@ -127,6 +127,7 @@ import { VideoShareModel } from './video-share' | |||
127 | import { VideoStreamingPlaylistModel } from './video-streaming-playlist' | 127 | import { VideoStreamingPlaylistModel } from './video-streaming-playlist' |
128 | import { VideoTagModel } from './video-tag' | 128 | import { VideoTagModel } from './video-tag' |
129 | import { VideoViewModel } from './video-view' | 129 | import { VideoViewModel } from './video-view' |
130 | import { stream } from 'winston' | ||
130 | 131 | ||
131 | export enum ScopeNames { | 132 | export enum ScopeNames { |
132 | AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS', | 133 | AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS', |
@@ -1472,11 +1473,13 @@ export class VideoModel extends Model<VideoModel> { | |||
1472 | } | 1473 | } |
1473 | 1474 | ||
1474 | private static buildAPIResult (rows: any[]) { | 1475 | private static buildAPIResult (rows: any[]) { |
1475 | const memo: { [ id: number ]: VideoModel } = {} | 1476 | const videosMemo: { [ id: number ]: VideoModel } = {} |
1477 | const videoStreamingPlaylistMemo: { [ id: number ]: VideoStreamingPlaylistModel } = {} | ||
1476 | 1478 | ||
1477 | const thumbnailsDone = new Set<number>() | 1479 | const thumbnailsDone = new Set<number>() |
1478 | const historyDone = new Set<number>() | 1480 | const historyDone = new Set<number>() |
1479 | const videoFilesDone = new Set<number>() | 1481 | const videoFilesDone = new Set<number>() |
1482 | const videoStreamingPlaylistsDone = new Set<number>() | ||
1480 | 1483 | ||
1481 | const videos: VideoModel[] = [] | 1484 | const videos: VideoModel[] = [] |
1482 | 1485 | ||
@@ -1484,6 +1487,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1484 | const actorKeys = [ 'id', 'preferredUsername', 'url', 'serverId', 'avatarId' ] | 1487 | const actorKeys = [ 'id', 'preferredUsername', 'url', 'serverId', 'avatarId' ] |
1485 | const serverKeys = [ 'id', 'host' ] | 1488 | const serverKeys = [ 'id', 'host' ] |
1486 | const videoFileKeys = [ 'id', 'createdAt', 'updatedAt', 'resolution', 'size', 'extname', 'infoHash', 'fps', 'videoId' ] | 1489 | const videoFileKeys = [ 'id', 'createdAt', 'updatedAt', 'resolution', 'size', 'extname', 'infoHash', 'fps', 'videoId' ] |
1490 | const videoStreamingPlaylistKeys = [ 'id' ] | ||
1487 | const videoKeys = [ | 1491 | const videoKeys = [ |
1488 | 'id', | 1492 | 'id', |
1489 | 'uuid', | 1493 | 'uuid', |
@@ -1529,7 +1533,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1529 | } | 1533 | } |
1530 | 1534 | ||
1531 | for (const row of rows) { | 1535 | for (const row of rows) { |
1532 | if (!memo[row.id]) { | 1536 | if (!videosMemo[row.id]) { |
1533 | // Build Channel | 1537 | // Build Channel |
1534 | const channel = row.VideoChannel | 1538 | const channel = row.VideoChannel |
1535 | const channelModel = new VideoChannelModel(pick(channel, [ 'id', 'name', 'description', 'actorId' ])) | 1539 | const channelModel = new VideoChannelModel(pick(channel, [ 'id', 'name', 'description', 'actorId' ])) |
@@ -1547,13 +1551,14 @@ export class VideoModel extends Model<VideoModel> { | |||
1547 | videoModel.UserVideoHistories = [] | 1551 | videoModel.UserVideoHistories = [] |
1548 | videoModel.Thumbnails = [] | 1552 | videoModel.Thumbnails = [] |
1549 | videoModel.VideoFiles = [] | 1553 | videoModel.VideoFiles = [] |
1554 | videoModel.VideoStreamingPlaylists = [] | ||
1550 | 1555 | ||
1551 | memo[row.id] = videoModel | 1556 | videosMemo[row.id] = videoModel |
1552 | // Don't take object value to have a sorted array | 1557 | // Don't take object value to have a sorted array |
1553 | videos.push(videoModel) | 1558 | videos.push(videoModel) |
1554 | } | 1559 | } |
1555 | 1560 | ||
1556 | const videoModel = memo[row.id] | 1561 | const videoModel = videosMemo[row.id] |
1557 | 1562 | ||
1558 | if (row.userVideoHistory?.id && !historyDone.has(row.userVideoHistory.id)) { | 1563 | if (row.userVideoHistory?.id && !historyDone.has(row.userVideoHistory.id)) { |
1559 | const historyModel = new UserVideoHistoryModel(pick(row.userVideoHistory, [ 'id', 'currentTime' ])) | 1564 | const historyModel = new UserVideoHistoryModel(pick(row.userVideoHistory, [ 'id', 'currentTime' ])) |
@@ -1575,6 +1580,31 @@ export class VideoModel extends Model<VideoModel> { | |||
1575 | 1580 | ||
1576 | videoFilesDone.add(row.VideoFiles.id) | 1581 | videoFilesDone.add(row.VideoFiles.id) |
1577 | } | 1582 | } |
1583 | |||
1584 | if (row.VideoFiles?.id && !videoFilesDone.has(row.VideoFiles.id)) { | ||
1585 | const videoFileModel = new VideoFileModel(pick(row.VideoFiles, videoFileKeys)) | ||
1586 | videoModel.VideoFiles.push(videoFileModel) | ||
1587 | |||
1588 | videoFilesDone.add(row.VideoFiles.id) | ||
1589 | } | ||
1590 | |||
1591 | if (row.VideoStreamingPlaylists?.id && !videoStreamingPlaylistMemo[row.VideoStreamingPlaylists.id]) { | ||
1592 | const streamingPlaylist = new VideoStreamingPlaylistModel(pick(row.VideoStreamingPlaylists, videoStreamingPlaylistKeys)) | ||
1593 | streamingPlaylist.VideoFiles = [] | ||
1594 | |||
1595 | videoModel.VideoStreamingPlaylists.push(streamingPlaylist) | ||
1596 | |||
1597 | videoStreamingPlaylistMemo[streamingPlaylist.id] = streamingPlaylist | ||
1598 | } | ||
1599 | |||
1600 | if (row.VideoStreamingPlaylists?.VideoFiles?.id && !videoFilesDone.has(row.VideoStreamingPlaylists.VideoFiles.id)) { | ||
1601 | const streamingPlaylist = videoStreamingPlaylistMemo[row.VideoStreamingPlaylists.id] | ||
1602 | |||
1603 | const videoFileModel = new VideoFileModel(pick(row.VideoStreamingPlaylists.VideoFiles, videoFileKeys)) | ||
1604 | streamingPlaylist.VideoFiles.push(videoFileModel) | ||
1605 | |||
1606 | videoFilesDone.add(row.VideoStreamingPlaylists.VideoFiles.id) | ||
1607 | } | ||
1578 | } | 1608 | } |
1579 | 1609 | ||
1580 | return videos | 1610 | return videos |
@@ -1717,7 +1747,21 @@ export class VideoModel extends Model<VideoModel> { | |||
1717 | 1747 | ||
1718 | getFormattedVideoFilesJSON (): VideoFile[] { | 1748 | getFormattedVideoFilesJSON (): VideoFile[] { |
1719 | const { baseUrlHttp, baseUrlWs } = this.getBaseUrls() | 1749 | const { baseUrlHttp, baseUrlWs } = this.getBaseUrls() |
1720 | return videoFilesModelToFormattedJSON(this, baseUrlHttp, baseUrlWs, this.VideoFiles) | 1750 | let files: MVideoFileRedundanciesOpt[] = [] |
1751 | |||
1752 | logger.info('coucou', { files }) | ||
1753 | |||
1754 | if (Array.isArray(this.VideoFiles)) { | ||
1755 | files = files.concat(this.VideoFiles) | ||
1756 | } | ||
1757 | |||
1758 | for (const p of (this.VideoStreamingPlaylists || [])) { | ||
1759 | files = files.concat(p.VideoFiles || []) | ||
1760 | } | ||
1761 | |||
1762 | logger.info('coucou', { files, video: this.VideoStreamingPlaylists }) | ||
1763 | |||
1764 | return videoFilesModelToFormattedJSON(this, baseUrlHttp, baseUrlWs, files) | ||
1721 | } | 1765 | } |
1722 | 1766 | ||
1723 | toActivityPubObject (this: MVideoAP): VideoTorrentObject { | 1767 | toActivityPubObject (this: MVideoAP): VideoTorrentObject { |
diff --git a/server/tests/feeds/feeds.ts b/server/tests/feeds/feeds.ts index ba961cdba..0ff690f34 100644 --- a/server/tests/feeds/feeds.ts +++ b/server/tests/feeds/feeds.ts | |||
@@ -21,7 +21,8 @@ import { | |||
21 | setAccessTokensToServers, | 21 | setAccessTokensToServers, |
22 | uploadVideo, | 22 | uploadVideo, |
23 | uploadVideoAndGetId, | 23 | uploadVideoAndGetId, |
24 | userLogin | 24 | userLogin, |
25 | flushAndRunServer | ||
25 | } from '../../../shared/extra-utils' | 26 | } from '../../../shared/extra-utils' |
26 | import { waitJobs } from '../../../shared/extra-utils/server/jobs' | 27 | import { waitJobs } from '../../../shared/extra-utils/server/jobs' |
27 | import { addVideoCommentThread } from '../../../shared/extra-utils/videos/video-comments' | 28 | import { addVideoCommentThread } from '../../../shared/extra-utils/videos/video-comments' |
@@ -34,6 +35,7 @@ const expect = chai.expect | |||
34 | 35 | ||
35 | describe('Test syndication feeds', () => { | 36 | describe('Test syndication feeds', () => { |
36 | let servers: ServerInfo[] = [] | 37 | let servers: ServerInfo[] = [] |
38 | let serverHLSOnly: ServerInfo | ||
37 | let userAccessToken: string | 39 | let userAccessToken: string |
38 | let rootAccountId: number | 40 | let rootAccountId: number |
39 | let rootChannelId: number | 41 | let rootChannelId: number |
@@ -45,8 +47,15 @@ describe('Test syndication feeds', () => { | |||
45 | 47 | ||
46 | // Run servers | 48 | // Run servers |
47 | servers = await flushAndRunMultipleServers(2) | 49 | servers = await flushAndRunMultipleServers(2) |
50 | serverHLSOnly = await flushAndRunServer(3, { | ||
51 | transcoding: { | ||
52 | enabled: true, | ||
53 | webtorrent: { enabled: false }, | ||
54 | hls: { enabled: true } | ||
55 | } | ||
56 | }) | ||
48 | 57 | ||
49 | await setAccessTokensToServers(servers) | 58 | await setAccessTokensToServers([ ...servers, serverHLSOnly ]) |
50 | await doubleFollow(servers[0], servers[1]) | 59 | await doubleFollow(servers[0], servers[1]) |
51 | 60 | ||
52 | { | 61 | { |
@@ -116,6 +125,7 @@ describe('Test syndication feeds', () => { | |||
116 | }) | 125 | }) |
117 | 126 | ||
118 | describe('Videos feed', function () { | 127 | describe('Videos feed', function () { |
128 | |||
119 | it('Should contain a valid enclosure (covers RSS 2.0 endpoint)', async function () { | 129 | it('Should contain a valid enclosure (covers RSS 2.0 endpoint)', async function () { |
120 | for (const server of servers) { | 130 | for (const server of servers) { |
121 | const rss = await getXMLfeed(server.url, 'videos') | 131 | const rss = await getXMLfeed(server.url, 'videos') |
@@ -208,6 +218,26 @@ describe('Test syndication feeds', () => { | |||
208 | } | 218 | } |
209 | } | 219 | } |
210 | }) | 220 | }) |
221 | |||
222 | it('Should correctly have videos feed with HLS only', async function () { | ||
223 | this.timeout(120000) | ||
224 | |||
225 | await uploadVideo(serverHLSOnly.url, serverHLSOnly.accessToken, { name: 'hls only video' }) | ||
226 | |||
227 | await waitJobs([ serverHLSOnly ]) | ||
228 | |||
229 | const json = await getJSONfeed(serverHLSOnly.url, 'videos') | ||
230 | const jsonObj = JSON.parse(json.text) | ||
231 | expect(jsonObj.items.length).to.be.equal(1) | ||
232 | expect(jsonObj.items[0].attachments).to.exist | ||
233 | expect(jsonObj.items[0].attachments.length).to.be.eq(4) | ||
234 | |||
235 | for (let i = 0; i < 4; i++) { | ||
236 | expect(jsonObj.items[0].attachments[i].mime_type).to.be.eq('application/x-bittorrent') | ||
237 | expect(jsonObj.items[0].attachments[i].size_in_bytes).to.be.greaterThan(0) | ||
238 | expect(jsonObj.items[0].attachments[i].url).to.exist | ||
239 | } | ||
240 | }) | ||
211 | }) | 241 | }) |
212 | 242 | ||
213 | describe('Video comments feed', function () { | 243 | describe('Video comments feed', function () { |
@@ -260,6 +290,6 @@ describe('Test syndication feeds', () => { | |||
260 | }) | 290 | }) |
261 | 291 | ||
262 | after(async function () { | 292 | after(async function () { |
263 | await cleanupTests(servers) | 293 | await cleanupTests([ ...servers, serverHLSOnly ]) |
264 | }) | 294 | }) |
265 | }) | 295 | }) |