]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - client/src/app/shared/shared-main/video/video.service.ts
Remove unnecessary env
[github/Chocobozzz/PeerTube.git] / client / src / app / shared / shared-main / video / video.service.ts
CommitLineData
33f6dce1
C
1import { SortMeta } from 'primeng/api'
2import { from, Observable } from 'rxjs'
3import { catchError, concatMap, map, switchMap, toArray } from 'rxjs/operators'
29510651 4import { HttpClient, HttpParams, HttpRequest } from '@angular/common/http'
202f6b6c 5import { Injectable } from '@angular/core'
33f6dce1 6import { ComponentPaginationLight, RestExtractor, RestPagination, RestService, ServerService, UserService } from '@app/core'
67ed6552 7import { objectToFormData } from '@app/helpers'
8cd7faaa 8import {
dd24f1bb 9 BooleanBothQuery,
67ed6552
C
10 FeedFormat,
11 NSFWPolicyType,
12 ResultList,
8cd7faaa 13 UserVideoRate,
5c6d985f 14 UserVideoRateType,
8cd7faaa 15 UserVideoRateUpdate,
67ed6552 16 Video as VideoServerModel,
978c87e7 17 VideoChannel as VideoChannelServerModel,
8cd7faaa 18 VideoConstant,
67ed6552 19 VideoDetails as VideoDetailsServerModel,
66357162 20 VideoFileMetadata,
2760b454 21 VideoInclude,
8cd7faaa 22 VideoPrivacy,
67ed6552 23 VideoSortField,
5beb89f2 24 VideoUpdate
67ed6552
C
25} from '@shared/models'
26import { environment } from '../../../../environments/environment'
b4c3c51d
C
27import { Account } from '../account/account.model'
28import { AccountService } from '../account/account.service'
67ed6552 29import { VideoChannel, VideoChannelService } from '../video-channel'
404b54e1
C
30import { VideoDetails } from './video-details.model'
31import { VideoEdit } from './video-edit.model'
202f6b6c 32import { Video } from './video.model'
dc8bc31b 33
dd24f1bb 34export type CommonVideoParams = {
33f6dce1
C
35 videoPagination?: ComponentPaginationLight
36 sort: VideoSortField | SortMeta
2760b454
C
37 include?: VideoInclude
38 isLocal?: boolean
dd24f1bb
C
39 categoryOneOf?: number[]
40 languageOneOf?: string[]
41 isLive?: boolean
42 skipCount?: boolean
2760b454 43
dd24f1bb
C
44 // FIXME: remove?
45 nsfwPolicy?: NSFWPolicyType
46 nsfw?: BooleanBothQuery
7f5f4152
BJ
47}
48
dc8bc31b 49@Injectable()
dd24f1bb 50export class VideoService {
40e87e9e
C
51 static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/'
52 static BASE_FEEDS_URL = environment.apiUrl + '/feeds/videos.'
5beb89f2 53 static BASE_SUBSCRIPTION_FEEDS_URL = environment.apiUrl + '/feeds/subscriptions.'
dc8bc31b 54
df98563e 55 constructor (
d592e0a9 56 private authHttp: HttpClient,
de59c48f 57 private restExtractor: RestExtractor,
7ce44a74 58 private restService: RestService,
5beb89f2 59 private serverService: ServerService
4fd8aa32
C
60 ) {}
61
8cac1b64
C
62 getVideoViewUrl (uuid: string) {
63 return VideoService.BASE_VIDEO_URL + uuid + '/views'
64 }
65
6e46de09
C
66 getUserWatchingVideoUrl (uuid: string) {
67 return VideoService.BASE_VIDEO_URL + uuid + '/watching'
68 }
69
93cae479 70 getVideo (options: { videoId: string }): Observable<VideoDetails> {
ba430d75 71 return this.serverService.getServerLocale()
db400f44 72 .pipe(
7ce44a74 73 switchMap(translations => {
93cae479 74 return this.authHttp.get<VideoDetailsServerModel>(VideoService.BASE_VIDEO_URL + options.videoId)
74b7c6d4 75 .pipe(map(videoHash => ({ videoHash, translations })))
7ce44a74
C
76 }),
77 map(({ videoHash, translations }) => new VideoDetails(videoHash, translations)),
e4f0e92e 78 catchError(err => this.restExtractor.handleError(err))
db400f44 79 )
4fd8aa32 80 }
dc8bc31b 81
404b54e1 82 updateVideo (video: VideoEdit) {
360329cc
C
83 const language = video.language || null
84 const licence = video.licence || null
85 const category = video.category || null
86 const description = video.description || null
87 const support = video.support || null
e94fc297 88 const scheduleUpdate = video.scheduleUpdate || null
1e74f19a 89 const originallyPublishedAt = video.originallyPublishedAt || null
c24ac1c1 90
4771e000 91 const body: VideoUpdate = {
d8e689b8 92 name: video.name,
cadb46d8
C
93 category,
94 licence,
c24ac1c1 95 language,
3bf1ec2e 96 support,
cadb46d8 97 description,
0f320037 98 channelId: video.channelId,
fd45e8f4 99 privacy: video.privacy,
4771e000 100 tags: video.tags,
47564bbe 101 nsfw: video.nsfw,
2186386c 102 waitTranscoding: video.waitTranscoding,
6de36768 103 commentsEnabled: video.commentsEnabled,
7f2cfe3a 104 downloadEnabled: video.downloadEnabled,
6de36768 105 thumbnailfile: video.thumbnailfile,
bbe0f064 106 previewfile: video.previewfile,
7294aab0 107 pluginData: video.pluginData,
1e74f19a 108 scheduleUpdate,
109 originallyPublishedAt
df98563e 110 }
c24ac1c1 111
6de36768
C
112 const data = objectToFormData(body)
113
114 return this.authHttp.put(VideoService.BASE_VIDEO_URL + video.id, data)
db400f44
C
115 .pipe(
116 map(this.restExtractor.extractDataBool),
e4f0e92e 117 catchError(err => this.restExtractor.handleError(err))
db400f44 118 )
d8e689b8
C
119 }
120
db7af09b 121 uploadVideo (video: FormData) {
334ddfa4 122 const req = new HttpRequest('POST', VideoService.BASE_VIDEO_URL + 'upload', video, { reportProgress: true })
bfb3a98f 123
fd45e8f4 124 return this.authHttp
2186386c 125 .request<{ video: { id: number, uuid: string } }>(req)
e4f0e92e 126 .pipe(catchError(err => this.restExtractor.handleError(err)))
bfb3a98f
C
127 }
128
978c87e7
C
129 getMyVideos (options: {
130 videoPagination: ComponentPaginationLight
131 sort: VideoSortField
132 userChannels?: VideoChannelServerModel[]
133 search?: string
134 }): Observable<ResultList<Video>> {
135 const { videoPagination, sort, userChannels = [], search } = options
136
4beda9e1 137 const pagination = this.restService.componentToRestPagination(videoPagination)
cf20596c 138
d592e0a9
C
139 let params = new HttpParams()
140 params = this.restService.addRestGetParams(params, pagination, sort)
1fd61899
C
141
142 if (search) {
143 const filters = this.restService.parseQueryStringFilter(search, {
144 isLive: {
145 prefix: 'isLive:',
1a7d0887 146 isBoolean: true
978c87e7
C
147 },
148 channelId: {
149 prefix: 'channel:',
150 handler: (name: string) => {
151 const channel = userChannels.find(c => c.name === name)
152
153 if (channel) return channel.id
154
155 return undefined
156 }
1fd61899
C
157 }
158 })
159
160 params = this.restService.addObjectParams(params, filters)
161 }
dc8bc31b 162
7ce44a74 163 return this.authHttp
bf64ed41 164 .get<ResultList<Video>>(UserService.BASE_USERS_URL + 'me/videos', { params })
db400f44 165 .pipe(
7ce44a74 166 switchMap(res => this.extractVideos(res)),
e4f0e92e 167 catchError(err => this.restExtractor.handleError(err))
db400f44 168 )
dc8bc31b
C
169 }
170
dd24f1bb 171 getAccountVideos (parameters: CommonVideoParams & {
9df52d66 172 account: Pick<Account, 'nameWithHost'>
37024082 173 search?: string
0aa52e17 174 }): Observable<ResultList<Video>> {
dd24f1bb 175 const { account, search } = parameters
0626e7af
C
176
177 let params = new HttpParams()
dd24f1bb 178 params = this.buildCommonVideosParams({ params, ...parameters })
0aa52e17 179
dd24f1bb 180 if (search) params = params.set('search', search)
37024082 181
0626e7af 182 return this.authHttp
7ce44a74 183 .get<ResultList<Video>>(AccountService.BASE_ACCOUNT_URL + account.nameWithHost + '/videos', { params })
db400f44 184 .pipe(
7ce44a74 185 switchMap(res => this.extractVideos(res)),
e4f0e92e 186 catchError(err => this.restExtractor.handleError(err))
db400f44 187 )
0626e7af
C
188 }
189
dd24f1bb 190 getVideoChannelVideos (parameters: CommonVideoParams & {
9df52d66 191 videoChannel: Pick<VideoChannel, 'nameWithHost'>
0aa52e17 192 }): Observable<ResultList<Video>> {
dd24f1bb 193 const { videoChannel } = parameters
170726f5
C
194
195 let params = new HttpParams()
dd24f1bb 196 params = this.buildCommonVideosParams({ params, ...parameters })
0aa52e17 197
170726f5 198 return this.authHttp
f5b0af50 199 .get<ResultList<Video>>(VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannel.nameWithHost + '/videos', { params })
db400f44 200 .pipe(
7ce44a74 201 switchMap(res => this.extractVideos(res)),
22a16e36
C
202 catchError(err => this.restExtractor.handleError(err))
203 )
204 }
205
33f6dce1 206 getAdminVideos (
2760b454 207 parameters: CommonVideoParams & { pagination: RestPagination, search?: string }
33f6dce1
C
208 ): Observable<ResultList<Video>> {
209 const { pagination, search } = parameters
210
3c10840f
C
211 const include = VideoInclude.BLACKLISTED |
212 VideoInclude.BLOCKED_OWNER |
213 VideoInclude.HIDDEN_PRIVACY |
214 VideoInclude.NOT_PUBLISHED_STATE |
215 VideoInclude.FILES
2760b454 216
33f6dce1 217 let params = new HttpParams()
2760b454 218 params = this.buildCommonVideosParams({ params, include, ...parameters })
33f6dce1
C
219
220 params = params.set('start', pagination.start.toString())
221 .set('count', pagination.count.toString())
222
223 if (search) {
224 params = this.buildAdminParamsFromSearch(search, params)
225 }
226
33f6dce1
C
227 return this.authHttp
228 .get<ResultList<Video>>(VideoService.BASE_VIDEO_URL, { params })
229 .pipe(
230 switchMap(res => this.extractVideos(res)),
231 catchError(err => this.restExtractor.handleError(err))
232 )
233 }
234
dd24f1bb 235 getVideos (parameters: CommonVideoParams): Observable<ResultList<Video>> {
fd45e8f4 236 let params = new HttpParams()
dd24f1bb 237 params = this.buildCommonVideosParams({ params, ...parameters })
5c20a455 238
fd45e8f4 239 return this.authHttp
7ce44a74 240 .get<ResultList<Video>>(VideoService.BASE_VIDEO_URL, { params })
db400f44 241 .pipe(
7ce44a74 242 switchMap(res => this.extractVideos(res)),
e4f0e92e 243 catchError(err => this.restExtractor.handleError(err))
db400f44 244 )
fd45e8f4
C
245 }
246
5beb89f2 247 buildBaseFeedUrls (params: HttpParams, base = VideoService.BASE_FEEDS_URL) {
cc1561f9
C
248 const feeds = [
249 {
39ba2e8e 250 format: FeedFormat.RSS,
2d011d94 251 label: 'media rss 2.0',
5beb89f2 252 url: base + FeedFormat.RSS.toLowerCase()
cc1561f9
C
253 },
254 {
39ba2e8e 255 format: FeedFormat.ATOM,
cc1561f9 256 label: 'atom 1.0',
5beb89f2 257 url: base + FeedFormat.ATOM.toLowerCase()
cc1561f9
C
258 },
259 {
39ba2e8e 260 format: FeedFormat.JSON,
cc1561f9 261 label: 'json 1.0',
5beb89f2 262 url: base + FeedFormat.JSON.toLowerCase()
cc1561f9
C
263 }
264 ]
265
7b87d2d5
C
266 if (params && params.keys().length !== 0) {
267 for (const feed of feeds) {
268 feed.url += '?' + params.toString()
269 }
270 }
271
cc1561f9 272 return feeds
244e76a5
RK
273 }
274
2760b454 275 getVideoFeedUrls (sort: VideoSortField, isLocal: boolean, categoryOneOf?: number[]) {
7b87d2d5 276 let params = this.restService.addRestGetParams(new HttpParams(), undefined, sort)
244e76a5 277
2760b454 278 if (isLocal) params = params.set('isLocal', isLocal)
cc1561f9 279
5c20a455
C
280 if (categoryOneOf) {
281 for (const c of categoryOneOf) {
282 params = params.append('categoryOneOf[]', c + '')
283 }
284 }
61b909b9 285
7b87d2d5 286 return this.buildBaseFeedUrls(params)
244e76a5
RK
287 }
288
cc1561f9 289 getAccountFeedUrls (accountId: number) {
244e76a5 290 let params = this.restService.addRestGetParams(new HttpParams())
244e76a5 291 params = params.set('accountId', accountId.toString())
cc1561f9 292
7b87d2d5 293 return this.buildBaseFeedUrls(params)
244e76a5
RK
294 }
295
170726f5
C
296 getVideoChannelFeedUrls (videoChannelId: number) {
297 let params = this.restService.addRestGetParams(new HttpParams())
298 params = params.set('videoChannelId', videoChannelId.toString())
299
300 return this.buildBaseFeedUrls(params)
301 }
302
5beb89f2 303 getVideoSubscriptionFeedUrls (accountId: number, feedToken: string) {
afff310e
RK
304 let params = this.restService.addRestGetParams(new HttpParams())
305 params = params.set('accountId', accountId.toString())
afff310e
RK
306 params = params.set('token', feedToken)
307
5beb89f2 308 return this.buildBaseFeedUrls(params, VideoService.BASE_SUBSCRIPTION_FEEDS_URL)
afff310e
RK
309 }
310
8319d6ae
RK
311 getVideoFileMetadata (metadataUrl: string) {
312 return this.authHttp
583eb04b 313 .get<VideoFileMetadata>(metadataUrl)
8319d6ae
RK
314 .pipe(
315 catchError(err => this.restExtractor.handleError(err))
316 )
317 }
318
33f6dce1
C
319 removeVideo (idArg: number | number[]) {
320 const ids = Array.isArray(idArg) ? idArg : [ idArg ]
321
322 return from(ids)
323 .pipe(
324 concatMap(id => this.authHttp.delete(VideoService.BASE_VIDEO_URL + id)),
325 toArray(),
326 catchError(err => this.restExtractor.handleError(err))
327 )
4fd8aa32
C
328 }
329
2de96f4d
C
330 loadCompleteDescription (descriptionPath: string) {
331 return this.authHttp
c199c427 332 .get<{ description: string }>(environment.apiUrl + descriptionPath)
db400f44 333 .pipe(
c199c427 334 map(res => res.description),
e4f0e92e 335 catchError(err => this.restExtractor.handleError(err))
db400f44 336 )
d38b8281
C
337 }
338
0a6658fd 339 setVideoLike (id: number) {
df98563e 340 return this.setVideoRate(id, 'like')
d38b8281
C
341 }
342
0a6658fd 343 setVideoDislike (id: number) {
df98563e 344 return this.setVideoRate(id, 'dislike')
d38b8281
C
345 }
346
57a49263
BB
347 unsetVideoLike (id: number) {
348 return this.setVideoRate(id, 'none')
349 }
350
5fcbd898 351 getUserVideoRating (id: number) {
334ddfa4 352 const url = UserService.BASE_USERS_URL + 'me/videos/' + id + '/rating'
d38b8281 353
5fcbd898 354 return this.authHttp.get<UserVideoRate>(url)
e4f0e92e 355 .pipe(catchError(err => this.restExtractor.handleError(err)))
d38b8281
C
356 }
357
57c36b27 358 extractVideos (result: ResultList<VideoServerModel>) {
ba430d75 359 return this.serverService.getServerLocale()
2186386c
C
360 .pipe(
361 map(translations => {
362 const videosJson = result.data
363 const totalVideos = result.total
364 const videos: Video[] = []
365
366 for (const videoJson of videosJson) {
367 videos.push(new Video(videoJson, translations))
368 }
369
93cae479 370 return { total: totalVideos, data: videos }
2186386c
C
371 })
372 )
501bc6c2 373 }
57c36b27 374
b980bcff 375 explainedPrivacyLabels (serverPrivacies: VideoConstant<VideoPrivacy>[], defaultPrivacyId = VideoPrivacy.PUBLIC) {
d91714ca
C
376 const descriptions = {
377 [VideoPrivacy.PRIVATE]: $localize`Only I can see this video`,
378 [VideoPrivacy.UNLISTED]: $localize`Only shareable via a private link`,
379 [VideoPrivacy.PUBLIC]: $localize`Anyone can see this video`,
380 [VideoPrivacy.INTERNAL]: $localize`Only users of this instance can see this video`
381 }
382
383 const videoPrivacies = serverPrivacies.map(p => {
384 return {
385 ...p,
386
387 description: descriptions[p.id]
22a73cb8 388 }
d91714ca 389 })
8cd7faaa 390
29510651 391 return {
d91714ca
C
392 videoPrivacies,
393 defaultPrivacyId: serverPrivacies.find(p => p.id === defaultPrivacyId)?.id || serverPrivacies[0].id
29510651 394 }
8cd7faaa
C
395 }
396
a3f45a2a
C
397 getHighestAvailablePrivacy (serverPrivacies: VideoConstant<VideoPrivacy>[]) {
398 const order = [ VideoPrivacy.PRIVATE, VideoPrivacy.INTERNAL, VideoPrivacy.UNLISTED, VideoPrivacy.PUBLIC ]
399
400 for (const privacy of order) {
401 if (serverPrivacies.find(p => p.id === privacy)) {
402 return privacy
403 }
404 }
405
406 throw new Error('No highest privacy available')
407 }
408
5c20a455
C
409 nsfwPolicyToParam (nsfwPolicy: NSFWPolicyType) {
410 return nsfwPolicy === 'do_not_list'
411 ? 'false'
412 : 'both'
413 }
414
5c6d985f 415 private setVideoRate (id: number, rateType: UserVideoRateType) {
57c36b27
C
416 const url = VideoService.BASE_VIDEO_URL + id + '/rate'
417 const body: UserVideoRateUpdate = {
418 rating: rateType
419 }
420
421 return this.authHttp
422 .put(url, body)
423 .pipe(
424 map(this.restExtractor.extractDataBool),
425 catchError(err => this.restExtractor.handleError(err))
426 )
427 }
dd24f1bb
C
428
429 private buildCommonVideosParams (options: CommonVideoParams & { params: HttpParams }) {
33f6dce1
C
430 const {
431 params,
432 videoPagination,
433 sort,
2760b454
C
434 isLocal,
435 include,
33f6dce1
C
436 categoryOneOf,
437 languageOneOf,
438 skipCount,
439 nsfwPolicy,
440 isLive,
441 nsfw
442 } = options
443
444 const pagination = videoPagination
445 ? this.restService.componentToRestPagination(videoPagination)
446 : undefined
dd24f1bb 447
dd24f1bb
C
448 let newParams = this.restService.addRestGetParams(params, pagination, sort)
449
dd24f1bb
C
450 if (skipCount) newParams = newParams.set('skipCount', skipCount + '')
451
2760b454
C
452 if (isLocal) newParams = newParams.set('isLocal', isLocal)
453 if (include) newParams = newParams.set('include', include)
dd24f1bb
C
454 if (isLive) newParams = newParams.set('isLive', isLive)
455 if (nsfw) newParams = newParams.set('nsfw', nsfw)
456 if (nsfwPolicy) newParams = newParams.set('nsfw', this.nsfwPolicyToParam(nsfwPolicy))
457 if (languageOneOf) newParams = this.restService.addArrayParams(newParams, 'languageOneOf', languageOneOf)
458 if (categoryOneOf) newParams = this.restService.addArrayParams(newParams, 'categoryOneOf', categoryOneOf)
459
460 return newParams
461 }
33f6dce1
C
462
463 private buildAdminParamsFromSearch (search: string, params: HttpParams) {
464 const filters = this.restService.parseQueryStringFilter(search, {
2760b454
C
465 isLocal: {
466 prefix: 'isLocal:',
467 isBoolean: true
33f6dce1
C
468 }
469 })
470
471 return this.restService.addObjectParams(params, filters)
472 }
dc8bc31b 473}