]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - shared/utils/videos/videos.ts
Add originallyPublishedAt unit tests
[github/Chocobozzz/PeerTube.git] / shared / utils / videos / videos.ts
CommitLineData
a20399c9
C
1/* tslint:disable:no-unused-expression */
2
3import { expect } from 'chai'
62689b94 4import { existsSync, readdir, readFile } from 'fs-extra'
fdbda9e3 5import * as parseTorrent from 'parse-torrent'
1d791a26 6import { extname, join } from 'path'
c5d31dba 7import * as request from 'supertest'
ac81d1a0
C
8import {
9 buildAbsoluteFixturePath,
62689b94
C
10 getMyUserInformation,
11 immutableAssign,
ac81d1a0
C
12 makeGetRequest,
13 makePutBodyRequest,
14 makeUploadRequest,
15 root,
16 ServerInfo,
17 testImage
18} from '../'
bc22d608 19
d4681c00 20import { VideoDetails, VideoPrivacy } from '../../models/videos'
bc22d608 21import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../server/initializers/constants'
d175a6f7 22import { dateIsValid, webtorrentAdd } from '../miscs/miscs'
0e1dc3e7
C
23
24type VideoAttributes = {
25 name?: string
26 category?: number
27 licence?: number
9d3ef9fe 28 language?: string
0e1dc3e7 29 nsfw?: boolean
47564bbe 30 commentsEnabled?: boolean
7f2cfe3a 31 downloadEnabled?: boolean
2186386c 32 waitTranscoding?: boolean
0e1dc3e7 33 description?: string
7519127b 34 originallyPublishedAt?: string
0e1dc3e7 35 tags?: string[]
5f04dd2f 36 channelId?: number
11474c3c 37 privacy?: VideoPrivacy
0e1dc3e7 38 fixture?: string
ac81d1a0
C
39 thumbnailfile?: string
40 previewfile?: string
2baea0c7
C
41 scheduleUpdate?: {
42 updateAt: string
43 privacy?: VideoPrivacy
44 }
0e1dc3e7
C
45}
46
47function getVideoCategories (url: string) {
48 const path = '/api/v1/videos/categories'
49
eec63bbc
C
50 return makeGetRequest({
51 url,
59651eee
C
52 path,
53 statusCodeExpected: 200
eec63bbc 54 })
0e1dc3e7
C
55}
56
57function getVideoLicences (url: string) {
58 const path = '/api/v1/videos/licences'
59
eec63bbc
C
60 return makeGetRequest({
61 url,
59651eee
C
62 path,
63 statusCodeExpected: 200
eec63bbc 64 })
0e1dc3e7
C
65}
66
67function getVideoLanguages (url: string) {
68 const path = '/api/v1/videos/languages'
69
eec63bbc
C
70 return makeGetRequest({
71 url,
59651eee
C
72 path,
73 statusCodeExpected: 200
eec63bbc 74 })
0e1dc3e7
C
75}
76
11474c3c
C
77function getVideoPrivacies (url: string) {
78 const path = '/api/v1/videos/privacies'
79
eec63bbc
C
80 return makeGetRequest({
81 url,
59651eee
C
82 path,
83 statusCodeExpected: 200
eec63bbc 84 })
11474c3c
C
85}
86
11474c3c 87function getVideo (url: string, id: number | string, expectedStatus = 200) {
0e1dc3e7
C
88 const path = '/api/v1/videos/' + id
89
90 return request(url)
91 .get(path)
92 .set('Accept', 'application/json')
11474c3c
C
93 .expect(expectedStatus)
94}
95
490b595a 96function viewVideo (url: string, id: number | string, expectedStatus = 204, xForwardedFor?: string) {
1f3e9fec
C
97 const path = '/api/v1/videos/' + id + '/views'
98
490b595a 99 const req = request(url)
1f3e9fec
C
100 .post(path)
101 .set('Accept', 'application/json')
490b595a
C
102
103 if (xForwardedFor) {
104 req.set('X-Forwarded-For', xForwardedFor)
105 }
106
107 return req.expect(expectedStatus)
1f3e9fec
C
108}
109
11474c3c
C
110function getVideoWithToken (url: string, token: string, id: number | string, expectedStatus = 200) {
111 const path = '/api/v1/videos/' + id
112
113 return request(url)
114 .get(path)
115 .set('Authorization', 'Bearer ' + token)
116 .set('Accept', 'application/json')
117 .expect(expectedStatus)
0e1dc3e7
C
118}
119
9567011b
C
120function getVideoDescription (url: string, descriptionPath: string) {
121 return request(url)
122 .get(descriptionPath)
123 .set('Accept', 'application/json')
124 .expect(200)
125 .expect('Content-Type', /json/)
126}
127
0e1dc3e7
C
128function getVideosList (url: string) {
129 const path = '/api/v1/videos'
130
131 return request(url)
132 .get(path)
133 .query({ sort: 'name' })
134 .set('Accept', 'application/json')
135 .expect(200)
136 .expect('Content-Type', /json/)
137}
138
d525fc39 139function getVideosListWithToken (url: string, token: string, query: { nsfw?: boolean } = {}) {
0883b324
C
140 const path = '/api/v1/videos'
141
142 return request(url)
143 .get(path)
144 .set('Authorization', 'Bearer ' + token)
d525fc39 145 .query(immutableAssign(query, { sort: 'name' }))
0883b324
C
146 .set('Accept', 'application/json')
147 .expect(200)
148 .expect('Content-Type', /json/)
149}
150
066e94c5
C
151function getLocalVideos (url: string) {
152 const path = '/api/v1/videos'
153
154 return request(url)
155 .get(path)
156 .query({ sort: 'name', filter: 'local' })
157 .set('Accept', 'application/json')
158 .expect(200)
159 .expect('Content-Type', /json/)
160}
161
11474c3c
C
162function getMyVideos (url: string, accessToken: string, start: number, count: number, sort?: string) {
163 const path = '/api/v1/users/me/videos'
164
165 const req = request(url)
166 .get(path)
167 .query({ start: start })
168 .query({ count: count })
169
170 if (sort) req.query({ sort })
171
172 return req.set('Accept', 'application/json')
173 .set('Authorization', 'Bearer ' + accessToken)
174 .expect(200)
175 .expect('Content-Type', /json/)
176}
177
d525fc39
C
178function getAccountVideos (
179 url: string,
180 accessToken: string,
181 accountName: string,
182 start: number,
183 count: number,
184 sort?: string,
185 query: { nsfw?: boolean } = {}
186) {
ad9e39fb 187 const path = '/api/v1/accounts/' + accountName + '/videos'
6b738c7a
C
188
189 return makeGetRequest({
190 url,
191 path,
d525fc39 192 query: immutableAssign(query, {
6b738c7a
C
193 start,
194 count,
195 sort
d525fc39 196 }),
6b738c7a
C
197 token: accessToken,
198 statusCodeExpected: 200
199 })
200}
201
202function getVideoChannelVideos (
203 url: string,
204 accessToken: string,
8a19bee1 205 videoChannelName: string,
6b738c7a
C
206 start: number,
207 count: number,
d525fc39
C
208 sort?: string,
209 query: { nsfw?: boolean } = {}
6b738c7a 210) {
8a19bee1 211 const path = '/api/v1/video-channels/' + videoChannelName + '/videos'
6b738c7a
C
212
213 return makeGetRequest({
214 url,
215 path,
d525fc39 216 query: immutableAssign(query, {
6b738c7a
C
217 start,
218 count,
219 sort
d525fc39 220 }),
6b738c7a
C
221 token: accessToken,
222 statusCodeExpected: 200
223 })
224}
225
0e1dc3e7
C
226function getVideosListPagination (url: string, start: number, count: number, sort?: string) {
227 const path = '/api/v1/videos'
228
229 const req = request(url)
230 .get(path)
231 .query({ start: start })
232 .query({ count: count })
233
234 if (sort) req.query({ sort })
235
236 return req.set('Accept', 'application/json')
237 .expect(200)
238 .expect('Content-Type', /json/)
239}
240
241function getVideosListSort (url: string, sort: string) {
242 const path = '/api/v1/videos'
243
244 return request(url)
245 .get(path)
246 .query({ sort: sort })
247 .set('Accept', 'application/json')
248 .expect(200)
249 .expect('Content-Type', /json/)
250}
251
d525fc39 252function getVideosWithFilters (url: string, query: { tagsAllOf: string[], categoryOneOf: number[] | number }) {
0e1dc3e7
C
253 const path = '/api/v1/videos'
254
255 return request(url)
57c36b27 256 .get(path)
d525fc39 257 .query(query)
f3aaa9a9 258 .set('Accept', 'application/json')
d525fc39 259 .expect(200)
f3aaa9a9 260 .expect('Content-Type', /json/)
0e1dc3e7
C
261}
262
d525fc39 263function removeVideo (url: string, token: string, id: number | string, expectedStatus = 204) {
0883b324 264 const path = '/api/v1/videos'
0e1dc3e7
C
265
266 return request(url)
d525fc39 267 .delete(path + '/' + id)
0e1dc3e7 268 .set('Accept', 'application/json')
d525fc39
C
269 .set('Authorization', 'Bearer ' + token)
270 .expect(expectedStatus)
0e1dc3e7
C
271}
272
25378bc8
C
273async function checkVideoFilesWereRemoved (
274 videoUUID: string,
275 serverNumber: number,
09209296
C
276 directories = [
277 'redundancy',
278 'videos',
279 'thumbnails',
280 'torrents',
281 'previews',
282 'captions',
283 join('playlists', 'hls'),
284 join('redundancy', 'hls')
285 ]
25378bc8 286) {
f05a1c30
C
287 const testDirectory = 'test' + serverNumber
288
25378bc8 289 for (const directory of directories) {
f05a1c30
C
290 const directoryPath = join(root(), testDirectory, directory)
291
292 const directoryExists = existsSync(directoryPath)
09209296 293 if (!directoryExists) continue
f05a1c30 294
62689b94 295 const files = await readdir(directoryPath)
f05a1c30
C
296 for (const file of files) {
297 expect(file).to.not.contain(videoUUID)
298 }
299 }
300}
301
e11f68a3 302async function uploadVideo (url: string, accessToken: string, videoAttributesArg: VideoAttributes, specialStatus = 200) {
e95561cd 303 const path = '/api/v1/videos/upload'
5f04dd2f
C
304 let defaultChannelId = '1'
305
306 try {
307 const res = await getMyUserInformation(url, accessToken)
308 defaultChannelId = res.body.videoChannels[0].id
309 } catch (e) { /* empty */ }
0e1dc3e7 310
ac81d1a0
C
311 // Override default attributes
312 const attributes = Object.assign({
0e1dc3e7
C
313 name: 'my super video',
314 category: 5,
315 licence: 4,
9d3ef9fe 316 language: 'zh',
5f04dd2f 317 channelId: defaultChannelId,
0e1dc3e7 318 nsfw: true,
2186386c 319 waitTranscoding: false,
0e1dc3e7 320 description: 'my super description',
2422c46b 321 support: 'my super support text',
0e1dc3e7 322 tags: [ 'tag' ],
11474c3c 323 privacy: VideoPrivacy.PUBLIC,
47564bbe 324 commentsEnabled: true,
7f2cfe3a 325 downloadEnabled: true,
0e1dc3e7 326 fixture: 'video_short.webm'
ac81d1a0 327 }, videoAttributesArg)
0e1dc3e7
C
328
329 const req = request(url)
330 .post(path)
331 .set('Accept', 'application/json')
332 .set('Authorization', 'Bearer ' + accessToken)
333 .field('name', attributes.name)
0e1dc3e7 334 .field('nsfw', JSON.stringify(attributes.nsfw))
47564bbe 335 .field('commentsEnabled', JSON.stringify(attributes.commentsEnabled))
7f2cfe3a 336 .field('downloadEnabled', JSON.stringify(attributes.downloadEnabled))
2186386c 337 .field('waitTranscoding', JSON.stringify(attributes.waitTranscoding))
11474c3c 338 .field('privacy', attributes.privacy.toString())
5f04dd2f 339 .field('channelId', attributes.channelId)
0e1dc3e7 340
a87d467a
C
341 if (attributes.description !== undefined) {
342 req.field('description', attributes.description)
343 }
8df87ce7
C
344 if (attributes.language !== undefined) {
345 req.field('language', attributes.language.toString())
346 }
a7fea183
C
347 if (attributes.category !== undefined) {
348 req.field('category', attributes.category.toString())
349 }
350 if (attributes.licence !== undefined) {
351 req.field('licence', attributes.licence.toString())
352 }
7519127b
C
353 if (attributes.originallyPublishedAt !== undefined) {
354 req.field('originallyPublishedAt', attributes.originallyPublishedAt)
355 }
8df87ce7 356
2422c46b
C
357 for (let i = 0; i < attributes.tags.length; i++) {
358 req.field('tags[' + i + ']', attributes.tags[i])
359 }
360
ac81d1a0
C
361 if (attributes.thumbnailfile !== undefined) {
362 req.attach('thumbnailfile', buildAbsoluteFixturePath(attributes.thumbnailfile))
363 }
364 if (attributes.previewfile !== undefined) {
365 req.attach('previewfile', buildAbsoluteFixturePath(attributes.previewfile))
0e1dc3e7
C
366 }
367
2baea0c7
C
368 if (attributes.scheduleUpdate) {
369 req.field('scheduleUpdate[updateAt]', attributes.scheduleUpdate.updateAt)
370
371 if (attributes.scheduleUpdate.privacy) {
372 req.field('scheduleUpdate[privacy]', attributes.scheduleUpdate.privacy)
373 }
374 }
375
ac81d1a0 376 return req.attach('videofile', buildAbsoluteFixturePath(attributes.fixture))
0e1dc3e7
C
377 .expect(specialStatus)
378}
379
ac81d1a0 380function updateVideo (url: string, accessToken: string, id: number | string, attributes: VideoAttributes, statusCodeExpected = 204) {
0e1dc3e7
C
381 const path = '/api/v1/videos/' + id
382 const body = {}
383
384 if (attributes.name) body['name'] = attributes.name
385 if (attributes.category) body['category'] = attributes.category
386 if (attributes.licence) body['licence'] = attributes.licence
387 if (attributes.language) body['language'] = attributes.language
47564bbe
C
388 if (attributes.nsfw !== undefined) body['nsfw'] = JSON.stringify(attributes.nsfw)
389 if (attributes.commentsEnabled !== undefined) body['commentsEnabled'] = JSON.stringify(attributes.commentsEnabled)
7f2cfe3a 390 if (attributes.downloadEnabled !== undefined) body['downloadEnabled'] = JSON.stringify(attributes.downloadEnabled)
7519127b 391 if (attributes.originallyPublishedAt !== undefined) body['originallyPublishedAt'] = attributes.originallyPublishedAt
0e1dc3e7
C
392 if (attributes.description) body['description'] = attributes.description
393 if (attributes.tags) body['tags'] = attributes.tags
11474c3c 394 if (attributes.privacy) body['privacy'] = attributes.privacy
0f320037 395 if (attributes.channelId) body['channelId'] = attributes.channelId
2baea0c7 396 if (attributes.scheduleUpdate) body['scheduleUpdate'] = attributes.scheduleUpdate
0e1dc3e7 397
ac81d1a0
C
398 // Upload request
399 if (attributes.thumbnailfile || attributes.previewfile) {
400 const attaches: any = {}
401 if (attributes.thumbnailfile) attaches.thumbnailfile = attributes.thumbnailfile
402 if (attributes.previewfile) attaches.previewfile = attributes.previewfile
403
404 return makeUploadRequest({
405 url,
406 method: 'PUT',
407 path,
408 token: accessToken,
409 fields: body,
410 attaches,
411 statusCodeExpected
412 })
413 }
414
415 return makePutBodyRequest({
416 url,
417 path,
418 fields: body,
419 token: accessToken,
420 statusCodeExpected
421 })
0e1dc3e7
C
422}
423
424function rateVideo (url: string, accessToken: string, id: number, rating: string, specialStatus = 204) {
425 const path = '/api/v1/videos/' + id + '/rate'
426
427 return request(url)
428 .put(path)
429 .set('Accept', 'application/json')
430 .set('Authorization', 'Bearer ' + accessToken)
431 .send({ rating })
432 .expect(specialStatus)
433}
434
14d3270f 435function parseTorrentVideo (server: ServerInfo, videoUUID: string, resolution: number) {
fdbda9e3 436 return new Promise<any>((res, rej) => {
14d3270f 437 const torrentName = videoUUID + '-' + resolution + '.torrent'
2a8c5d0a 438 const torrentPath = join(root(), 'test' + server.serverNumber, 'torrents', torrentName)
fdbda9e3
C
439 readFile(torrentPath, (err, data) => {
440 if (err) return rej(err)
441
442 return res(parseTorrent(data))
443 })
444 })
445}
446
a20399c9
C
447async function completeVideoCheck (
448 url: string,
449 video: any,
450 attributes: {
451 name: string
452 category: number
453 licence: number
9d3ef9fe 454 language: string
a20399c9 455 nsfw: boolean
47564bbe 456 commentsEnabled: boolean
7f2cfe3a 457 downloadEnabled: boolean
a20399c9 458 description: string
53a61317 459 publishedAt?: string
2422c46b 460 support: string
7519127b 461 originallyPublishedAt?: string,
b64c950a
C
462 account: {
463 name: string
464 host: string
465 }
f6eebcb3
C
466 isLocal: boolean
467 tags: string[]
468 privacy: number
469 likes?: number
470 dislikes?: number
471 duration: number
a20399c9 472 channel: {
f6eebcb3
C
473 displayName: string
474 name: string
b1f5b93e 475 description
a20399c9
C
476 isLocal: boolean
477 }
f6eebcb3 478 fixture: string
a20399c9
C
479 files: {
480 resolution: number
481 size: number
ac81d1a0
C
482 }[],
483 thumbnailfile?: string
484 previewfile?: string
a20399c9
C
485 }
486) {
b1f5b93e
C
487 if (!attributes.likes) attributes.likes = 0
488 if (!attributes.dislikes) attributes.dislikes = 0
489
a20399c9 490 expect(video.name).to.equal(attributes.name)
09700934 491 expect(video.category.id).to.equal(attributes.category)
9d3ef9fe 492 expect(video.category.label).to.equal(attributes.category !== null ? VIDEO_CATEGORIES[attributes.category] : 'Misc')
09700934 493 expect(video.licence.id).to.equal(attributes.licence)
9d3ef9fe 494 expect(video.licence.label).to.equal(attributes.licence !== null ? VIDEO_LICENCES[attributes.licence] : 'Unknown')
09700934 495 expect(video.language.id).to.equal(attributes.language)
9d3ef9fe 496 expect(video.language.label).to.equal(attributes.language !== null ? VIDEO_LANGUAGES[attributes.language] : 'Unknown')
2243730c
C
497 expect(video.privacy.id).to.deep.equal(attributes.privacy)
498 expect(video.privacy.label).to.deep.equal(VIDEO_PRIVACIES[attributes.privacy])
a20399c9
C
499 expect(video.nsfw).to.equal(attributes.nsfw)
500 expect(video.description).to.equal(attributes.description)
03e12d7c
C
501 expect(video.account.id).to.be.a('number')
502 expect(video.account.uuid).to.be.a('string')
b64c950a
C
503 expect(video.account.host).to.equal(attributes.account.host)
504 expect(video.account.name).to.equal(attributes.account.name)
f6eebcb3
C
505 expect(video.channel.displayName).to.equal(attributes.channel.displayName)
506 expect(video.channel.name).to.equal(attributes.channel.name)
b1f5b93e
C
507 expect(video.likes).to.equal(attributes.likes)
508 expect(video.dislikes).to.equal(attributes.dislikes)
a20399c9 509 expect(video.isLocal).to.equal(attributes.isLocal)
b1f5b93e 510 expect(video.duration).to.equal(attributes.duration)
a20399c9 511 expect(dateIsValid(video.createdAt)).to.be.true
c49db162 512 expect(dateIsValid(video.publishedAt)).to.be.true
a20399c9
C
513 expect(dateIsValid(video.updatedAt)).to.be.true
514
53a61317 515 if (attributes.publishedAt) {
53a61317
C
516 expect(video.publishedAt).to.equal(attributes.publishedAt)
517 }
518
7519127b
C
519 if (attributes.originallyPublishedAt) {
520 expect(video.originallyPublishedAt).to.equal(attributes.originallyPublishedAt)
521 } else {
522 expect(video.originallyPublishedAt).to.be.null
523 }
524
66b16caf 525 const res = await getVideo(url, video.uuid)
0f320037 526 const videoDetails: VideoDetails = res.body
a20399c9
C
527
528 expect(videoDetails.files).to.have.lengthOf(attributes.files.length)
529 expect(videoDetails.tags).to.deep.equal(attributes.tags)
19a3b914
C
530 expect(videoDetails.account.name).to.equal(attributes.account.name)
531 expect(videoDetails.account.host).to.equal(attributes.account.host)
f6eebcb3
C
532 expect(video.channel.displayName).to.equal(attributes.channel.displayName)
533 expect(video.channel.name).to.equal(attributes.channel.name)
0f320037 534 expect(videoDetails.channel.host).to.equal(attributes.account.host)
a20399c9 535 expect(videoDetails.channel.isLocal).to.equal(attributes.channel.isLocal)
0f320037
C
536 expect(dateIsValid(videoDetails.channel.createdAt.toString())).to.be.true
537 expect(dateIsValid(videoDetails.channel.updatedAt.toString())).to.be.true
538 expect(videoDetails.commentsEnabled).to.equal(attributes.commentsEnabled)
7f2cfe3a 539 expect(videoDetails.downloadEnabled).to.equal(attributes.downloadEnabled)
a20399c9
C
540
541 for (const attributeFile of attributes.files) {
5d00a3d7 542 const file = videoDetails.files.find(f => f.resolution.id === attributeFile.resolution)
a20399c9
C
543 expect(file).not.to.be.undefined
544
b1f5b93e
C
545 let extension = extname(attributes.fixture)
546 // Transcoding enabled on server 2, extension will always be .mp4
b64c950a 547 if (attributes.account.host === 'localhost:9002') extension = '.mp4'
b1f5b93e 548
a20399c9
C
549 const magnetUri = file.magnetUri
550 expect(file.magnetUri).to.have.lengthOf.above(2)
5d00a3d7
C
551 expect(file.torrentUrl).to.equal(`http://${attributes.account.host}/static/torrents/${videoDetails.uuid}-${file.resolution.id}.torrent`)
552 expect(file.fileUrl).to.equal(`http://${attributes.account.host}/static/webseed/${videoDetails.uuid}-${file.resolution.id}${extension}`)
09700934
C
553 expect(file.resolution.id).to.equal(attributeFile.resolution)
554 expect(file.resolution.label).to.equal(attributeFile.resolution + 'p')
b1f5b93e
C
555
556 const minSize = attributeFile.size - ((10 * attributeFile.size) / 100)
557 const maxSize = attributeFile.size + ((10 * attributeFile.size) / 100)
4176e227 558 expect(file.size,
7160878c 559 'File size for resolution ' + file.resolution.label + ' outside confidence interval (' + minSize + '> size <' + maxSize + ')')
4176e227 560 .to.be.above(minSize).and.below(maxSize)
a20399c9 561
ac81d1a0 562 {
7b0956ec 563 await testImage(url, attributes.thumbnailfile || attributes.fixture, videoDetails.thumbnailPath)
ac81d1a0
C
564 }
565
566 if (attributes.previewfile) {
7b0956ec 567 await testImage(url, attributes.previewfile, videoDetails.previewPath)
ac81d1a0 568 }
a20399c9
C
569
570 const torrent = await webtorrentAdd(magnetUri, true)
571 expect(torrent.files).to.be.an('array')
572 expect(torrent.files.length).to.equal(1)
573 expect(torrent.files[0].path).to.exist.and.to.not.equal('')
574 }
575}
576
0e1dc3e7
C
577// ---------------------------------------------------------------------------
578
579export {
9567011b 580 getVideoDescription,
0e1dc3e7
C
581 getVideoCategories,
582 getVideoLicences,
11474c3c 583 getVideoPrivacies,
0e1dc3e7 584 getVideoLanguages,
11474c3c 585 getMyVideos,
6b738c7a
C
586 getAccountVideos,
587 getVideoChannelVideos,
0e1dc3e7 588 getVideo,
11474c3c 589 getVideoWithToken,
0e1dc3e7
C
590 getVideosList,
591 getVideosListPagination,
592 getVideosListSort,
593 removeVideo,
0883b324 594 getVideosListWithToken,
0e1dc3e7 595 uploadVideo,
d525fc39 596 getVideosWithFilters,
0e1dc3e7 597 updateVideo,
fdbda9e3 598 rateVideo,
1f3e9fec 599 viewVideo,
a20399c9 600 parseTorrentVideo,
066e94c5 601 getLocalVideos,
f05a1c30
C
602 completeVideoCheck,
603 checkVideoFilesWereRemoved
0e1dc3e7 604}