diff options
-rw-r--r-- | client/src/app/videos/+video-watch/video-watch.component.html | 4 | ||||
-rw-r--r-- | client/src/app/videos/+video-watch/video-watch.component.ts | 4 | ||||
-rw-r--r-- | server/controllers/api/videos/import.ts | 21 | ||||
-rw-r--r-- | server/helpers/youtube-dl.ts | 2 | ||||
-rw-r--r-- | server/lib/job-queue/handlers/video-import.ts | 34 | ||||
-rw-r--r-- | server/middlewares/validators/video-imports.ts | 29 | ||||
-rw-r--r-- | server/models/video/video.ts | 6 |
7 files changed, 42 insertions, 58 deletions
diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html index f39b5a94a..5a132112d 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.html +++ b/client/src/app/videos/+video-watch/video-watch.component.html | |||
@@ -8,6 +8,10 @@ | |||
8 | </div> | 8 | </div> |
9 | </div> | 9 | </div> |
10 | 10 | ||
11 | <div i18n class="alert alert-warning" *ngIf="isVideoToImport()"> | ||
12 | The video is being imported, it will be available when the import is finished. | ||
13 | </div> | ||
14 | |||
11 | <div i18n class="alert alert-warning" *ngIf="isVideoToTranscode()"> | 15 | <div i18n class="alert alert-warning" *ngIf="isVideoToTranscode()"> |
12 | The video is being transcoded, it may not work properly. | 16 | The video is being transcoded, it may not work properly. |
13 | </div> | 17 | </div> |
diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts index 995fb8e2b..04bcc6cd1 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.ts +++ b/client/src/app/videos/+video-watch/video-watch.component.ts | |||
@@ -289,6 +289,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
289 | return this.video && this.video.state.id === VideoState.TO_TRANSCODE | 289 | return this.video && this.video.state.id === VideoState.TO_TRANSCODE |
290 | } | 290 | } |
291 | 291 | ||
292 | isVideoToImport () { | ||
293 | return this.video && this.video.state.id === VideoState.TO_IMPORT | ||
294 | } | ||
295 | |||
292 | hasVideoScheduledPublication () { | 296 | hasVideoScheduledPublication () { |
293 | return this.video && this.video.scheduledUpdate !== undefined | 297 | return this.video && this.video.scheduledUpdate !== undefined |
294 | } | 298 | } |
diff --git a/server/controllers/api/videos/import.ts b/server/controllers/api/videos/import.ts index 680d8665f..ca7a5f9ca 100644 --- a/server/controllers/api/videos/import.ts +++ b/server/controllers/api/videos/import.ts | |||
@@ -4,8 +4,7 @@ import { | |||
4 | asyncMiddleware, | 4 | asyncMiddleware, |
5 | asyncRetryTransactionMiddleware, | 5 | asyncRetryTransactionMiddleware, |
6 | authenticate, | 6 | authenticate, |
7 | videoImportAddValidator, | 7 | videoImportAddValidator |
8 | videoImportDeleteValidator | ||
9 | } from '../../../middlewares' | 8 | } from '../../../middlewares' |
10 | import { CONFIG, IMAGE_MIMETYPE_EXT, PREVIEWS_SIZE, sequelizeTypescript, THUMBNAILS_SIZE } from '../../../initializers' | 9 | import { CONFIG, IMAGE_MIMETYPE_EXT, PREVIEWS_SIZE, sequelizeTypescript, THUMBNAILS_SIZE } from '../../../initializers' |
11 | import { getYoutubeDLInfo, YoutubeDLInfo } from '../../../helpers/youtube-dl' | 10 | import { getYoutubeDLInfo, YoutubeDLInfo } from '../../../helpers/youtube-dl' |
@@ -39,12 +38,6 @@ videoImportsRouter.post('/imports', | |||
39 | asyncRetryTransactionMiddleware(addVideoImport) | 38 | asyncRetryTransactionMiddleware(addVideoImport) |
40 | ) | 39 | ) |
41 | 40 | ||
42 | videoImportsRouter.delete('/imports/:id', | ||
43 | authenticate, | ||
44 | asyncMiddleware(videoImportDeleteValidator), | ||
45 | asyncRetryTransactionMiddleware(deleteVideoImport) | ||
46 | ) | ||
47 | |||
48 | // --------------------------------------------------------------------------- | 41 | // --------------------------------------------------------------------------- |
49 | 42 | ||
50 | export { | 43 | export { |
@@ -145,15 +138,3 @@ async function addVideoImport (req: express.Request, res: express.Response) { | |||
145 | 138 | ||
146 | return res.json(videoImport.toFormattedJSON()) | 139 | return res.json(videoImport.toFormattedJSON()) |
147 | } | 140 | } |
148 | |||
149 | async function deleteVideoImport (req: express.Request, res: express.Response) { | ||
150 | await sequelizeTypescript.transaction(async t => { | ||
151 | const videoImport = res.locals.videoImport | ||
152 | const video = videoImport.Video | ||
153 | |||
154 | await videoImport.destroy({ transaction: t }) | ||
155 | await video.destroy({ transaction: t }) | ||
156 | }) | ||
157 | |||
158 | return res.status(204).end() | ||
159 | } | ||
diff --git a/server/helpers/youtube-dl.ts b/server/helpers/youtube-dl.ts index 43156bb22..ff1fbf59f 100644 --- a/server/helpers/youtube-dl.ts +++ b/server/helpers/youtube-dl.ts | |||
@@ -30,7 +30,7 @@ function getYoutubeDLInfo (url: string): Promise<YoutubeDLInfo> { | |||
30 | } | 30 | } |
31 | 31 | ||
32 | function downloadYoutubeDLVideo (url: string) { | 32 | function downloadYoutubeDLVideo (url: string) { |
33 | const hash = crypto.createHash('sha256').update(url).digest('base64') | 33 | const hash = crypto.createHash('sha256').update(url).digest('hex') |
34 | const path = join(CONFIG.STORAGE.VIDEOS_DIR, hash + '-import.mp4') | 34 | const path = join(CONFIG.STORAGE.VIDEOS_DIR, hash + '-import.mp4') |
35 | 35 | ||
36 | logger.info('Importing video %s', url) | 36 | logger.info('Importing video %s', url) |
diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts index 4f2faab7d..3b9d08d3b 100644 --- a/server/lib/job-queue/handlers/video-import.ts +++ b/server/lib/job-queue/handlers/video-import.ts | |||
@@ -12,6 +12,7 @@ import { doRequestAndSaveToFile } from '../../../helpers/requests' | |||
12 | import { VideoState } from '../../../../shared' | 12 | import { VideoState } from '../../../../shared' |
13 | import { JobQueue } from '../index' | 13 | import { JobQueue } from '../index' |
14 | import { federateVideoIfNeeded } from '../../activitypub' | 14 | import { federateVideoIfNeeded } from '../../activitypub' |
15 | import { VideoModel } from '../../../models/video/video' | ||
15 | 16 | ||
16 | export type VideoImportPayload = { | 17 | export type VideoImportPayload = { |
17 | type: 'youtube-dl' | 18 | type: 'youtube-dl' |
@@ -26,9 +27,13 @@ async function processVideoImport (job: Bull.Job) { | |||
26 | logger.info('Processing video import in job %d.', job.id) | 27 | logger.info('Processing video import in job %d.', job.id) |
27 | 28 | ||
28 | const videoImport = await VideoImportModel.loadAndPopulateVideo(payload.videoImportId) | 29 | const videoImport = await VideoImportModel.loadAndPopulateVideo(payload.videoImportId) |
29 | if (!videoImport) throw new Error('Cannot import video %s: the video import entry does not exist anymore.') | 30 | if (!videoImport || !videoImport.Video) { |
31 | throw new Error('Cannot import video %s: the video import or video linked to this import does not exist anymore.') | ||
32 | } | ||
30 | 33 | ||
31 | let tempVideoPath: string | 34 | let tempVideoPath: string |
35 | let videoDestFile: string | ||
36 | let videoFile: VideoFileModel | ||
32 | try { | 37 | try { |
33 | // Download video from youtubeDL | 38 | // Download video from youtubeDL |
34 | tempVideoPath = await downloadYoutubeDLVideo(videoImport.targetUrl) | 39 | tempVideoPath = await downloadYoutubeDLVideo(videoImport.targetUrl) |
@@ -47,11 +52,14 @@ async function processVideoImport (job: Bull.Job) { | |||
47 | fps, | 52 | fps, |
48 | videoId: videoImport.videoId | 53 | videoId: videoImport.videoId |
49 | } | 54 | } |
50 | const videoFile = new VideoFileModel(videoFileData) | 55 | videoFile = new VideoFileModel(videoFileData) |
56 | // Import if the import fails, to clean files | ||
57 | videoImport.Video.VideoFiles = [ videoFile ] | ||
51 | 58 | ||
52 | // Move file | 59 | // Move file |
53 | const destination = join(CONFIG.STORAGE.VIDEOS_DIR, videoImport.Video.getVideoFilename(videoFile)) | 60 | videoDestFile = join(CONFIG.STORAGE.VIDEOS_DIR, videoImport.Video.getVideoFilename(videoFile)) |
54 | await renamePromise(tempVideoPath, destination) | 61 | await renamePromise(tempVideoPath, videoDestFile) |
62 | tempVideoPath = null // This path is not used anymore | ||
55 | 63 | ||
56 | // Process thumbnail | 64 | // Process thumbnail |
57 | if (payload.downloadThumbnail) { | 65 | if (payload.downloadThumbnail) { |
@@ -77,15 +85,21 @@ async function processVideoImport (job: Bull.Job) { | |||
77 | await videoImport.Video.createTorrentAndSetInfoHash(videoFile) | 85 | await videoImport.Video.createTorrentAndSetInfoHash(videoFile) |
78 | 86 | ||
79 | const videoImportUpdated: VideoImportModel = await sequelizeTypescript.transaction(async t => { | 87 | const videoImportUpdated: VideoImportModel = await sequelizeTypescript.transaction(async t => { |
80 | await videoFile.save({ transaction: t }) | 88 | // Refresh video |
89 | const video = await VideoModel.load(videoImport.videoId, t) | ||
90 | if (!video) throw new Error('Video linked to import ' + videoImport.videoId + ' does not exist anymore.') | ||
91 | videoImport.Video = video | ||
92 | |||
93 | const videoFileCreated = await videoFile.save({ transaction: t }) | ||
94 | video.VideoFiles = [ videoFileCreated ] | ||
81 | 95 | ||
82 | // Update video DB object | 96 | // Update video DB object |
83 | videoImport.Video.duration = duration | 97 | video.duration = duration |
84 | videoImport.Video.state = CONFIG.TRANSCODING.ENABLED ? VideoState.TO_TRANSCODE : VideoState.PUBLISHED | 98 | video.state = CONFIG.TRANSCODING.ENABLED ? VideoState.TO_TRANSCODE : VideoState.PUBLISHED |
85 | const videoUpdated = await videoImport.Video.save({ transaction: t }) | 99 | const videoUpdated = await video.save({ transaction: t }) |
86 | 100 | ||
87 | // Now we can federate the video | 101 | // Now we can federate the video |
88 | await federateVideoIfNeeded(videoImport.Video, true, t) | 102 | await federateVideoIfNeeded(video, true, t) |
89 | 103 | ||
90 | // Update video import object | 104 | // Update video import object |
91 | videoImport.state = VideoImportState.SUCCESS | 105 | videoImport.state = VideoImportState.SUCCESS |
@@ -112,7 +126,7 @@ async function processVideoImport (job: Bull.Job) { | |||
112 | try { | 126 | try { |
113 | if (tempVideoPath) await unlinkPromise(tempVideoPath) | 127 | if (tempVideoPath) await unlinkPromise(tempVideoPath) |
114 | } catch (errUnlink) { | 128 | } catch (errUnlink) { |
115 | logger.error('Cannot cleanup files after a video import error.', { err: errUnlink }) | 129 | logger.warn('Cannot cleanup files after a video import error.', { err: errUnlink }) |
116 | } | 130 | } |
117 | 131 | ||
118 | videoImport.error = err.message | 132 | videoImport.error = err.message |
diff --git a/server/middlewares/validators/video-imports.ts b/server/middlewares/validators/video-imports.ts index 0dedcf803..e0a552976 100644 --- a/server/middlewares/validators/video-imports.ts +++ b/server/middlewares/validators/video-imports.ts | |||
@@ -1,14 +1,12 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body, param } from 'express-validator/check' | 2 | import { body } from 'express-validator/check' |
3 | import { isIdValid } from '../../helpers/custom-validators/misc' | 3 | import { isIdValid } from '../../helpers/custom-validators/misc' |
4 | import { logger } from '../../helpers/logger' | 4 | import { logger } from '../../helpers/logger' |
5 | import { areValidationErrors } from './utils' | 5 | import { areValidationErrors } from './utils' |
6 | import { getCommonVideoAttributes } from './videos' | 6 | import { getCommonVideoAttributes } from './videos' |
7 | import { isVideoImportTargetUrlValid, isVideoImportExist } from '../../helpers/custom-validators/video-imports' | 7 | import { isVideoImportTargetUrlValid } from '../../helpers/custom-validators/video-imports' |
8 | import { cleanUpReqFiles } from '../../helpers/utils' | 8 | import { cleanUpReqFiles } from '../../helpers/utils' |
9 | import { isVideoChannelOfAccountExist, isVideoNameValid, checkUserCanManageVideo } from '../../helpers/custom-validators/videos' | 9 | import { isVideoChannelOfAccountExist, isVideoNameValid } from '../../helpers/custom-validators/videos' |
10 | import { VideoImportModel } from '../../models/video/video-import' | ||
11 | import { UserRight } from '../../../shared' | ||
12 | 10 | ||
13 | const videoImportAddValidator = getCommonVideoAttributes().concat([ | 11 | const videoImportAddValidator = getCommonVideoAttributes().concat([ |
14 | body('targetUrl').custom(isVideoImportTargetUrlValid).withMessage('Should have a valid video import target URL'), | 12 | body('targetUrl').custom(isVideoImportTargetUrlValid).withMessage('Should have a valid video import target URL'), |
@@ -31,29 +29,10 @@ const videoImportAddValidator = getCommonVideoAttributes().concat([ | |||
31 | } | 29 | } |
32 | ]) | 30 | ]) |
33 | 31 | ||
34 | const videoImportDeleteValidator = [ | ||
35 | param('id').custom(isIdValid).not().isEmpty().withMessage('Should have a valid id'), | ||
36 | |||
37 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
38 | logger.debug('Checking videoImportDeleteValidator parameters', { parameters: req.body }) | ||
39 | |||
40 | if (areValidationErrors(req, res)) return | ||
41 | if (!await isVideoImportExist(req.params.id, res)) return | ||
42 | |||
43 | const user = res.locals.oauth.token.User | ||
44 | const videoImport: VideoImportModel = res.locals.videoImport | ||
45 | |||
46 | if (!await checkUserCanManageVideo(user, videoImport.Video, UserRight.UPDATE_ANY_VIDEO, res)) return | ||
47 | |||
48 | return next() | ||
49 | } | ||
50 | ] | ||
51 | |||
52 | // --------------------------------------------------------------------------- | 32 | // --------------------------------------------------------------------------- |
53 | 33 | ||
54 | export { | 34 | export { |
55 | videoImportAddValidator, | 35 | videoImportAddValidator |
56 | videoImportDeleteValidator | ||
57 | } | 36 | } |
58 | 37 | ||
59 | // --------------------------------------------------------------------------- | 38 | // --------------------------------------------------------------------------- |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index f32010014..67711b102 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -957,8 +957,10 @@ export class VideoModel extends Model<VideoModel> { | |||
957 | }) | 957 | }) |
958 | } | 958 | } |
959 | 959 | ||
960 | static load (id: number) { | 960 | static load (id: number, t?: Sequelize.Transaction) { |
961 | return VideoModel.findById(id) | 961 | const options = t ? { transaction: t } : undefined |
962 | |||
963 | return VideoModel.findById(id, options) | ||
962 | } | 964 | } |
963 | 965 | ||
964 | static loadByUrlAndPopulateAccount (url: string, t?: Sequelize.Transaction) { | 966 | static loadByUrlAndPopulateAccount (url: string, t?: Sequelize.Transaction) { |