diff options
Diffstat (limited to 'server/models/video/video.ts')
-rw-r--r-- | server/models/video/video.ts | 419 |
1 files changed, 22 insertions, 397 deletions
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index b7d3f184f..ce856aed2 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import { map, maxBy } from 'lodash' | 2 | import { maxBy } from 'lodash' |
3 | import * as magnetUtil from 'magnet-uri' | 3 | import * as magnetUtil from 'magnet-uri' |
4 | import * as parseTorrent from 'parse-torrent' | 4 | import * as parseTorrent from 'parse-torrent' |
5 | import { extname, join } from 'path' | 5 | import { join } from 'path' |
6 | import * as Sequelize from 'sequelize' | 6 | import * as Sequelize from 'sequelize' |
7 | import { | 7 | import { |
8 | AllowNull, | 8 | AllowNull, |
@@ -27,7 +27,7 @@ import { | |||
27 | Table, | 27 | Table, |
28 | UpdatedAt | 28 | UpdatedAt |
29 | } from 'sequelize-typescript' | 29 | } from 'sequelize-typescript' |
30 | import { ActivityUrlObject, VideoPrivacy, VideoResolution, VideoState } from '../../../shared' | 30 | import { VideoPrivacy, VideoState } from '../../../shared' |
31 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' | 31 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' |
32 | import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos' | 32 | import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos' |
33 | import { VideoFilter } from '../../../shared/models/videos/video-query.type' | 33 | import { VideoFilter } from '../../../shared/models/videos/video-query.type' |
@@ -45,7 +45,7 @@ import { | |||
45 | isVideoStateValid, | 45 | isVideoStateValid, |
46 | isVideoSupportValid | 46 | isVideoSupportValid |
47 | } from '../../helpers/custom-validators/videos' | 47 | } from '../../helpers/custom-validators/videos' |
48 | import { generateImageFromVideoFile, getVideoFileFPS, getVideoFileResolution, transcode } from '../../helpers/ffmpeg-utils' | 48 | import { generateImageFromVideoFile, getVideoFileResolution } from '../../helpers/ffmpeg-utils' |
49 | import { logger } from '../../helpers/logger' | 49 | import { logger } from '../../helpers/logger' |
50 | import { getServerActor } from '../../helpers/utils' | 50 | import { getServerActor } from '../../helpers/utils' |
51 | import { | 51 | import { |
@@ -59,18 +59,11 @@ import { | |||
59 | STATIC_PATHS, | 59 | STATIC_PATHS, |
60 | THUMBNAILS_SIZE, | 60 | THUMBNAILS_SIZE, |
61 | VIDEO_CATEGORIES, | 61 | VIDEO_CATEGORIES, |
62 | VIDEO_EXT_MIMETYPE, | ||
63 | VIDEO_LANGUAGES, | 62 | VIDEO_LANGUAGES, |
64 | VIDEO_LICENCES, | 63 | VIDEO_LICENCES, |
65 | VIDEO_PRIVACIES, | 64 | VIDEO_PRIVACIES, |
66 | VIDEO_STATES | 65 | VIDEO_STATES |
67 | } from '../../initializers' | 66 | } from '../../initializers' |
68 | import { | ||
69 | getVideoCommentsActivityPubUrl, | ||
70 | getVideoDislikesActivityPubUrl, | ||
71 | getVideoLikesActivityPubUrl, | ||
72 | getVideoSharesActivityPubUrl | ||
73 | } from '../../lib/activitypub' | ||
74 | import { sendDeleteVideo } from '../../lib/activitypub/send' | 67 | import { sendDeleteVideo } from '../../lib/activitypub/send' |
75 | import { AccountModel } from '../account/account' | 68 | import { AccountModel } from '../account/account' |
76 | import { AccountVideoRateModel } from '../account/account-video-rate' | 69 | import { AccountVideoRateModel } from '../account/account-video-rate' |
@@ -88,9 +81,16 @@ import { VideoTagModel } from './video-tag' | |||
88 | import { ScheduleVideoUpdateModel } from './schedule-video-update' | 81 | import { ScheduleVideoUpdateModel } from './schedule-video-update' |
89 | import { VideoCaptionModel } from './video-caption' | 82 | import { VideoCaptionModel } from './video-caption' |
90 | import { VideoBlacklistModel } from './video-blacklist' | 83 | import { VideoBlacklistModel } from './video-blacklist' |
91 | import { copy, remove, rename, stat, writeFile } from 'fs-extra' | 84 | import { remove, writeFile } from 'fs-extra' |
92 | import { VideoViewModel } from './video-views' | 85 | import { VideoViewModel } from './video-views' |
93 | import { VideoRedundancyModel } from '../redundancy/video-redundancy' | 86 | import { VideoRedundancyModel } from '../redundancy/video-redundancy' |
87 | import { | ||
88 | videoFilesModelToFormattedJSON, | ||
89 | VideoFormattingJSONOptions, | ||
90 | videoModelToActivityPubObject, | ||
91 | videoModelToFormattedDetailsJSON, | ||
92 | videoModelToFormattedJSON | ||
93 | } from './video-format-utils' | ||
94 | 94 | ||
95 | // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation | 95 | // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation |
96 | const indexes: Sequelize.DefineIndexesOptions[] = [ | 96 | const indexes: Sequelize.DefineIndexesOptions[] = [ |
@@ -1257,23 +1257,23 @@ export class VideoModel extends Model<VideoModel> { | |||
1257 | } | 1257 | } |
1258 | } | 1258 | } |
1259 | 1259 | ||
1260 | private static getCategoryLabel (id: number) { | 1260 | static getCategoryLabel (id: number) { |
1261 | return VIDEO_CATEGORIES[ id ] || 'Misc' | 1261 | return VIDEO_CATEGORIES[ id ] || 'Misc' |
1262 | } | 1262 | } |
1263 | 1263 | ||
1264 | private static getLicenceLabel (id: number) { | 1264 | static getLicenceLabel (id: number) { |
1265 | return VIDEO_LICENCES[ id ] || 'Unknown' | 1265 | return VIDEO_LICENCES[ id ] || 'Unknown' |
1266 | } | 1266 | } |
1267 | 1267 | ||
1268 | private static getLanguageLabel (id: string) { | 1268 | static getLanguageLabel (id: string) { |
1269 | return VIDEO_LANGUAGES[ id ] || 'Unknown' | 1269 | return VIDEO_LANGUAGES[ id ] || 'Unknown' |
1270 | } | 1270 | } |
1271 | 1271 | ||
1272 | private static getPrivacyLabel (id: number) { | 1272 | static getPrivacyLabel (id: number) { |
1273 | return VIDEO_PRIVACIES[ id ] || 'Unknown' | 1273 | return VIDEO_PRIVACIES[ id ] || 'Unknown' |
1274 | } | 1274 | } |
1275 | 1275 | ||
1276 | private static getStateLabel (id: number) { | 1276 | static getStateLabel (id: number) { |
1277 | return VIDEO_STATES[ id ] || 'Unknown' | 1277 | return VIDEO_STATES[ id ] || 'Unknown' |
1278 | } | 1278 | } |
1279 | 1279 | ||
@@ -1369,273 +1369,20 @@ export class VideoModel extends Model<VideoModel> { | |||
1369 | return join(STATIC_PATHS.PREVIEWS, this.getPreviewName()) | 1369 | return join(STATIC_PATHS.PREVIEWS, this.getPreviewName()) |
1370 | } | 1370 | } |
1371 | 1371 | ||
1372 | toFormattedJSON (options?: { | 1372 | toFormattedJSON (options?: VideoFormattingJSONOptions): Video { |
1373 | additionalAttributes: { | 1373 | return videoModelToFormattedJSON(this, options) |
1374 | state?: boolean, | ||
1375 | waitTranscoding?: boolean, | ||
1376 | scheduledUpdate?: boolean, | ||
1377 | blacklistInfo?: boolean | ||
1378 | } | ||
1379 | }): Video { | ||
1380 | const formattedAccount = this.VideoChannel.Account.toFormattedJSON() | ||
1381 | const formattedVideoChannel = this.VideoChannel.toFormattedJSON() | ||
1382 | |||
1383 | const videoObject: Video = { | ||
1384 | id: this.id, | ||
1385 | uuid: this.uuid, | ||
1386 | name: this.name, | ||
1387 | category: { | ||
1388 | id: this.category, | ||
1389 | label: VideoModel.getCategoryLabel(this.category) | ||
1390 | }, | ||
1391 | licence: { | ||
1392 | id: this.licence, | ||
1393 | label: VideoModel.getLicenceLabel(this.licence) | ||
1394 | }, | ||
1395 | language: { | ||
1396 | id: this.language, | ||
1397 | label: VideoModel.getLanguageLabel(this.language) | ||
1398 | }, | ||
1399 | privacy: { | ||
1400 | id: this.privacy, | ||
1401 | label: VideoModel.getPrivacyLabel(this.privacy) | ||
1402 | }, | ||
1403 | nsfw: this.nsfw, | ||
1404 | description: this.getTruncatedDescription(), | ||
1405 | isLocal: this.isOwned(), | ||
1406 | duration: this.duration, | ||
1407 | views: this.views, | ||
1408 | likes: this.likes, | ||
1409 | dislikes: this.dislikes, | ||
1410 | thumbnailPath: this.getThumbnailStaticPath(), | ||
1411 | previewPath: this.getPreviewStaticPath(), | ||
1412 | embedPath: this.getEmbedStaticPath(), | ||
1413 | createdAt: this.createdAt, | ||
1414 | updatedAt: this.updatedAt, | ||
1415 | publishedAt: this.publishedAt, | ||
1416 | account: { | ||
1417 | id: formattedAccount.id, | ||
1418 | uuid: formattedAccount.uuid, | ||
1419 | name: formattedAccount.name, | ||
1420 | displayName: formattedAccount.displayName, | ||
1421 | url: formattedAccount.url, | ||
1422 | host: formattedAccount.host, | ||
1423 | avatar: formattedAccount.avatar | ||
1424 | }, | ||
1425 | channel: { | ||
1426 | id: formattedVideoChannel.id, | ||
1427 | uuid: formattedVideoChannel.uuid, | ||
1428 | name: formattedVideoChannel.name, | ||
1429 | displayName: formattedVideoChannel.displayName, | ||
1430 | url: formattedVideoChannel.url, | ||
1431 | host: formattedVideoChannel.host, | ||
1432 | avatar: formattedVideoChannel.avatar | ||
1433 | } | ||
1434 | } | ||
1435 | |||
1436 | if (options) { | ||
1437 | if (options.additionalAttributes.state === true) { | ||
1438 | videoObject.state = { | ||
1439 | id: this.state, | ||
1440 | label: VideoModel.getStateLabel(this.state) | ||
1441 | } | ||
1442 | } | ||
1443 | |||
1444 | if (options.additionalAttributes.waitTranscoding === true) { | ||
1445 | videoObject.waitTranscoding = this.waitTranscoding | ||
1446 | } | ||
1447 | |||
1448 | if (options.additionalAttributes.scheduledUpdate === true && this.ScheduleVideoUpdate) { | ||
1449 | videoObject.scheduledUpdate = { | ||
1450 | updateAt: this.ScheduleVideoUpdate.updateAt, | ||
1451 | privacy: this.ScheduleVideoUpdate.privacy || undefined | ||
1452 | } | ||
1453 | } | ||
1454 | |||
1455 | if (options.additionalAttributes.blacklistInfo === true) { | ||
1456 | videoObject.blacklisted = !!this.VideoBlacklist | ||
1457 | videoObject.blacklistedReason = this.VideoBlacklist ? this.VideoBlacklist.reason : null | ||
1458 | } | ||
1459 | } | ||
1460 | |||
1461 | return videoObject | ||
1462 | } | 1374 | } |
1463 | 1375 | ||
1464 | toFormattedDetailsJSON (): VideoDetails { | 1376 | toFormattedDetailsJSON (): VideoDetails { |
1465 | const formattedJson = this.toFormattedJSON({ | 1377 | return videoModelToFormattedDetailsJSON(this) |
1466 | additionalAttributes: { | ||
1467 | scheduledUpdate: true, | ||
1468 | blacklistInfo: true | ||
1469 | } | ||
1470 | }) | ||
1471 | |||
1472 | const detailsJson = { | ||
1473 | support: this.support, | ||
1474 | descriptionPath: this.getDescriptionPath(), | ||
1475 | channel: this.VideoChannel.toFormattedJSON(), | ||
1476 | account: this.VideoChannel.Account.toFormattedJSON(), | ||
1477 | tags: map(this.Tags, 'name'), | ||
1478 | commentsEnabled: this.commentsEnabled, | ||
1479 | waitTranscoding: this.waitTranscoding, | ||
1480 | state: { | ||
1481 | id: this.state, | ||
1482 | label: VideoModel.getStateLabel(this.state) | ||
1483 | }, | ||
1484 | files: [] | ||
1485 | } | ||
1486 | |||
1487 | // Format and sort video files | ||
1488 | detailsJson.files = this.getFormattedVideoFilesJSON() | ||
1489 | |||
1490 | return Object.assign(formattedJson, detailsJson) | ||
1491 | } | 1378 | } |
1492 | 1379 | ||
1493 | getFormattedVideoFilesJSON (): VideoFile[] { | 1380 | getFormattedVideoFilesJSON (): VideoFile[] { |
1494 | const { baseUrlHttp, baseUrlWs } = this.getBaseUrls() | 1381 | return videoFilesModelToFormattedJSON(this, this.VideoFiles) |
1495 | |||
1496 | return this.VideoFiles | ||
1497 | .map(videoFile => { | ||
1498 | let resolutionLabel = videoFile.resolution + 'p' | ||
1499 | |||
1500 | return { | ||
1501 | resolution: { | ||
1502 | id: videoFile.resolution, | ||
1503 | label: resolutionLabel | ||
1504 | }, | ||
1505 | magnetUri: this.generateMagnetUri(videoFile, baseUrlHttp, baseUrlWs), | ||
1506 | size: videoFile.size, | ||
1507 | fps: videoFile.fps, | ||
1508 | torrentUrl: this.getTorrentUrl(videoFile, baseUrlHttp), | ||
1509 | torrentDownloadUrl: this.getTorrentDownloadUrl(videoFile, baseUrlHttp), | ||
1510 | fileUrl: this.getVideoFileUrl(videoFile, baseUrlHttp), | ||
1511 | fileDownloadUrl: this.getVideoFileDownloadUrl(videoFile, baseUrlHttp) | ||
1512 | } as VideoFile | ||
1513 | }) | ||
1514 | .sort((a, b) => { | ||
1515 | if (a.resolution.id < b.resolution.id) return 1 | ||
1516 | if (a.resolution.id === b.resolution.id) return 0 | ||
1517 | return -1 | ||
1518 | }) | ||
1519 | } | 1382 | } |
1520 | 1383 | ||
1521 | toActivityPubObject (): VideoTorrentObject { | 1384 | toActivityPubObject (): VideoTorrentObject { |
1522 | const { baseUrlHttp, baseUrlWs } = this.getBaseUrls() | 1385 | return videoModelToActivityPubObject(this) |
1523 | if (!this.Tags) this.Tags = [] | ||
1524 | |||
1525 | const tag = this.Tags.map(t => ({ | ||
1526 | type: 'Hashtag' as 'Hashtag', | ||
1527 | name: t.name | ||
1528 | })) | ||
1529 | |||
1530 | let language | ||
1531 | if (this.language) { | ||
1532 | language = { | ||
1533 | identifier: this.language, | ||
1534 | name: VideoModel.getLanguageLabel(this.language) | ||
1535 | } | ||
1536 | } | ||
1537 | |||
1538 | let category | ||
1539 | if (this.category) { | ||
1540 | category = { | ||
1541 | identifier: this.category + '', | ||
1542 | name: VideoModel.getCategoryLabel(this.category) | ||
1543 | } | ||
1544 | } | ||
1545 | |||
1546 | let licence | ||
1547 | if (this.licence) { | ||
1548 | licence = { | ||
1549 | identifier: this.licence + '', | ||
1550 | name: VideoModel.getLicenceLabel(this.licence) | ||
1551 | } | ||
1552 | } | ||
1553 | |||
1554 | const url: ActivityUrlObject[] = [] | ||
1555 | for (const file of this.VideoFiles) { | ||
1556 | url.push({ | ||
1557 | type: 'Link', | ||
1558 | mimeType: VIDEO_EXT_MIMETYPE[ file.extname ] as any, | ||
1559 | href: this.getVideoFileUrl(file, baseUrlHttp), | ||
1560 | height: file.resolution, | ||
1561 | size: file.size, | ||
1562 | fps: file.fps | ||
1563 | }) | ||
1564 | |||
1565 | url.push({ | ||
1566 | type: 'Link', | ||
1567 | mimeType: 'application/x-bittorrent' as 'application/x-bittorrent', | ||
1568 | href: this.getTorrentUrl(file, baseUrlHttp), | ||
1569 | height: file.resolution | ||
1570 | }) | ||
1571 | |||
1572 | url.push({ | ||
1573 | type: 'Link', | ||
1574 | mimeType: 'application/x-bittorrent;x-scheme-handler/magnet' as 'application/x-bittorrent;x-scheme-handler/magnet', | ||
1575 | href: this.generateMagnetUri(file, baseUrlHttp, baseUrlWs), | ||
1576 | height: file.resolution | ||
1577 | }) | ||
1578 | } | ||
1579 | |||
1580 | // Add video url too | ||
1581 | url.push({ | ||
1582 | type: 'Link', | ||
1583 | mimeType: 'text/html', | ||
1584 | href: CONFIG.WEBSERVER.URL + '/videos/watch/' + this.uuid | ||
1585 | }) | ||
1586 | |||
1587 | const subtitleLanguage = [] | ||
1588 | for (const caption of this.VideoCaptions) { | ||
1589 | subtitleLanguage.push({ | ||
1590 | identifier: caption.language, | ||
1591 | name: VideoCaptionModel.getLanguageLabel(caption.language) | ||
1592 | }) | ||
1593 | } | ||
1594 | |||
1595 | return { | ||
1596 | type: 'Video' as 'Video', | ||
1597 | id: this.url, | ||
1598 | name: this.name, | ||
1599 | duration: this.getActivityStreamDuration(), | ||
1600 | uuid: this.uuid, | ||
1601 | tag, | ||
1602 | category, | ||
1603 | licence, | ||
1604 | language, | ||
1605 | views: this.views, | ||
1606 | sensitive: this.nsfw, | ||
1607 | waitTranscoding: this.waitTranscoding, | ||
1608 | state: this.state, | ||
1609 | commentsEnabled: this.commentsEnabled, | ||
1610 | published: this.publishedAt.toISOString(), | ||
1611 | updated: this.updatedAt.toISOString(), | ||
1612 | mediaType: 'text/markdown', | ||
1613 | content: this.getTruncatedDescription(), | ||
1614 | support: this.support, | ||
1615 | subtitleLanguage, | ||
1616 | icon: { | ||
1617 | type: 'Image', | ||
1618 | url: this.getThumbnailUrl(baseUrlHttp), | ||
1619 | mediaType: 'image/jpeg', | ||
1620 | width: THUMBNAILS_SIZE.width, | ||
1621 | height: THUMBNAILS_SIZE.height | ||
1622 | }, | ||
1623 | url, | ||
1624 | likes: getVideoLikesActivityPubUrl(this), | ||
1625 | dislikes: getVideoDislikesActivityPubUrl(this), | ||
1626 | shares: getVideoSharesActivityPubUrl(this), | ||
1627 | comments: getVideoCommentsActivityPubUrl(this), | ||
1628 | attributedTo: [ | ||
1629 | { | ||
1630 | type: 'Person', | ||
1631 | id: this.VideoChannel.Account.Actor.url | ||
1632 | }, | ||
1633 | { | ||
1634 | type: 'Group', | ||
1635 | id: this.VideoChannel.Actor.url | ||
1636 | } | ||
1637 | ] | ||
1638 | } | ||
1639 | } | 1386 | } |
1640 | 1387 | ||
1641 | getTruncatedDescription () { | 1388 | getTruncatedDescription () { |
@@ -1645,123 +1392,6 @@ export class VideoModel extends Model<VideoModel> { | |||
1645 | return peertubeTruncate(this.description, maxLength) | 1392 | return peertubeTruncate(this.description, maxLength) |
1646 | } | 1393 | } |
1647 | 1394 | ||
1648 | async optimizeOriginalVideofile () { | ||
1649 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR | ||
1650 | const newExtname = '.mp4' | ||
1651 | const inputVideoFile = this.getOriginalFile() | ||
1652 | const videoInputPath = join(videosDirectory, this.getVideoFilename(inputVideoFile)) | ||
1653 | const videoTranscodedPath = join(videosDirectory, this.id + '-transcoded' + newExtname) | ||
1654 | |||
1655 | const transcodeOptions = { | ||
1656 | inputPath: videoInputPath, | ||
1657 | outputPath: videoTranscodedPath | ||
1658 | } | ||
1659 | |||
1660 | // Could be very long! | ||
1661 | await transcode(transcodeOptions) | ||
1662 | |||
1663 | try { | ||
1664 | await remove(videoInputPath) | ||
1665 | |||
1666 | // Important to do this before getVideoFilename() to take in account the new file extension | ||
1667 | inputVideoFile.set('extname', newExtname) | ||
1668 | |||
1669 | const videoOutputPath = this.getVideoFilePath(inputVideoFile) | ||
1670 | await rename(videoTranscodedPath, videoOutputPath) | ||
1671 | const stats = await stat(videoOutputPath) | ||
1672 | const fps = await getVideoFileFPS(videoOutputPath) | ||
1673 | |||
1674 | inputVideoFile.set('size', stats.size) | ||
1675 | inputVideoFile.set('fps', fps) | ||
1676 | |||
1677 | await this.createTorrentAndSetInfoHash(inputVideoFile) | ||
1678 | await inputVideoFile.save() | ||
1679 | |||
1680 | } catch (err) { | ||
1681 | // Auto destruction... | ||
1682 | this.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', { err })) | ||
1683 | |||
1684 | throw err | ||
1685 | } | ||
1686 | } | ||
1687 | |||
1688 | async transcodeOriginalVideofile (resolution: VideoResolution, isPortraitMode: boolean) { | ||
1689 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR | ||
1690 | const extname = '.mp4' | ||
1691 | |||
1692 | // We are sure it's x264 in mp4 because optimizeOriginalVideofile was already executed | ||
1693 | const videoInputPath = join(videosDirectory, this.getVideoFilename(this.getOriginalFile())) | ||
1694 | |||
1695 | const newVideoFile = new VideoFileModel({ | ||
1696 | resolution, | ||
1697 | extname, | ||
1698 | size: 0, | ||
1699 | videoId: this.id | ||
1700 | }) | ||
1701 | const videoOutputPath = join(videosDirectory, this.getVideoFilename(newVideoFile)) | ||
1702 | |||
1703 | const transcodeOptions = { | ||
1704 | inputPath: videoInputPath, | ||
1705 | outputPath: videoOutputPath, | ||
1706 | resolution, | ||
1707 | isPortraitMode | ||
1708 | } | ||
1709 | |||
1710 | await transcode(transcodeOptions) | ||
1711 | |||
1712 | const stats = await stat(videoOutputPath) | ||
1713 | const fps = await getVideoFileFPS(videoOutputPath) | ||
1714 | |||
1715 | newVideoFile.set('size', stats.size) | ||
1716 | newVideoFile.set('fps', fps) | ||
1717 | |||
1718 | await this.createTorrentAndSetInfoHash(newVideoFile) | ||
1719 | |||
1720 | await newVideoFile.save() | ||
1721 | |||
1722 | this.VideoFiles.push(newVideoFile) | ||
1723 | } | ||
1724 | |||
1725 | async importVideoFile (inputFilePath: string) { | ||
1726 | const { videoFileResolution } = await getVideoFileResolution(inputFilePath) | ||
1727 | const { size } = await stat(inputFilePath) | ||
1728 | const fps = await getVideoFileFPS(inputFilePath) | ||
1729 | |||
1730 | let updatedVideoFile = new VideoFileModel({ | ||
1731 | resolution: videoFileResolution, | ||
1732 | extname: extname(inputFilePath), | ||
1733 | size, | ||
1734 | fps, | ||
1735 | videoId: this.id | ||
1736 | }) | ||
1737 | |||
1738 | const currentVideoFile = this.VideoFiles.find(videoFile => videoFile.resolution === updatedVideoFile.resolution) | ||
1739 | |||
1740 | if (currentVideoFile) { | ||
1741 | // Remove old file and old torrent | ||
1742 | await this.removeFile(currentVideoFile) | ||
1743 | await this.removeTorrent(currentVideoFile) | ||
1744 | // Remove the old video file from the array | ||
1745 | this.VideoFiles = this.VideoFiles.filter(f => f !== currentVideoFile) | ||
1746 | |||
1747 | // Update the database | ||
1748 | currentVideoFile.set('extname', updatedVideoFile.extname) | ||
1749 | currentVideoFile.set('size', updatedVideoFile.size) | ||
1750 | currentVideoFile.set('fps', updatedVideoFile.fps) | ||
1751 | |||
1752 | updatedVideoFile = currentVideoFile | ||
1753 | } | ||
1754 | |||
1755 | const outputPath = this.getVideoFilePath(updatedVideoFile) | ||
1756 | await copy(inputFilePath, outputPath) | ||
1757 | |||
1758 | await this.createTorrentAndSetInfoHash(updatedVideoFile) | ||
1759 | |||
1760 | await updatedVideoFile.save() | ||
1761 | |||
1762 | this.VideoFiles.push(updatedVideoFile) | ||
1763 | } | ||
1764 | |||
1765 | getOriginalFileResolution () { | 1395 | getOriginalFileResolution () { |
1766 | const originalFilePath = this.getVideoFilePath(this.getOriginalFile()) | 1396 | const originalFilePath = this.getVideoFilePath(this.getOriginalFile()) |
1767 | 1397 | ||
@@ -1796,11 +1426,6 @@ export class VideoModel extends Model<VideoModel> { | |||
1796 | .catch(err => logger.warn('Cannot delete torrent %s.', torrentPath, { err })) | 1426 | .catch(err => logger.warn('Cannot delete torrent %s.', torrentPath, { err })) |
1797 | } | 1427 | } |
1798 | 1428 | ||
1799 | getActivityStreamDuration () { | ||
1800 | // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration | ||
1801 | return 'PT' + this.duration + 'S' | ||
1802 | } | ||
1803 | |||
1804 | isOutdated () { | 1429 | isOutdated () { |
1805 | if (this.isOwned()) return false | 1430 | if (this.isOwned()) return false |
1806 | 1431 | ||