aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/controllers/api/videos/import.ts
blob: ca7a5f9cac8edabd4365d8215d68f694868b2de9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import * as express from 'express'
import { auditLoggerFactory } from '../../../helpers/audit-logger'
import {
  asyncMiddleware,
  asyncRetryTransactionMiddleware,
  authenticate,
  videoImportAddValidator
} from '../../../middlewares'
import { CONFIG, IMAGE_MIMETYPE_EXT, PREVIEWS_SIZE, sequelizeTypescript, THUMBNAILS_SIZE } from '../../../initializers'
import { getYoutubeDLInfo, YoutubeDLInfo } from '../../../helpers/youtube-dl'
import { createReqFiles } from '../../../helpers/express-utils'
import { logger } from '../../../helpers/logger'
import { VideoImportCreate, VideoImportState, VideoPrivacy, VideoState } from '../../../../shared'
import { VideoModel } from '../../../models/video/video'
import { getVideoActivityPubUrl } from '../../../lib/activitypub'
import { TagModel } from '../../../models/video/tag'
import { VideoImportModel } from '../../../models/video/video-import'
import { JobQueue } from '../../../lib/job-queue/job-queue'
import { processImage } from '../../../helpers/image-utils'
import { join } from 'path'

const auditLogger = auditLoggerFactory('video-imports')
const videoImportsRouter = express.Router()

const reqVideoFileImport = createReqFiles(
  [ 'thumbnailfile', 'previewfile' ],
  IMAGE_MIMETYPE_EXT,
  {
    thumbnailfile: CONFIG.STORAGE.THUMBNAILS_DIR,
    previewfile: CONFIG.STORAGE.PREVIEWS_DIR
  }
)

videoImportsRouter.post('/imports',
  authenticate,
  reqVideoFileImport,
  asyncMiddleware(videoImportAddValidator),
  asyncRetryTransactionMiddleware(addVideoImport)
)

// ---------------------------------------------------------------------------

export {
  videoImportsRouter
}

// ---------------------------------------------------------------------------

async function addVideoImport (req: express.Request, res: express.Response) {
  const body: VideoImportCreate = req.body
  const targetUrl = body.targetUrl

  let youtubeDLInfo: YoutubeDLInfo
  try {
    youtubeDLInfo = await getYoutubeDLInfo(targetUrl)
  } catch (err) {
    logger.info('Cannot fetch information from import for URL %s.', targetUrl, { err })

    return res.status(400).json({
      error: 'Cannot fetch remote information of this URL.'
    }).end()
  }

  // Create video DB object
  const videoData = {
    name: body.name || youtubeDLInfo.name,
    remote: false,
    category: body.category || youtubeDLInfo.category,
    licence: body.licence || youtubeDLInfo.licence,
    language: undefined,
    commentsEnabled: body.commentsEnabled || true,
    waitTranscoding: body.waitTranscoding || false,
    state: VideoState.TO_IMPORT,
    nsfw: body.nsfw || youtubeDLInfo.nsfw || false,
    description: body.description || youtubeDLInfo.description,
    support: body.support || null,
    privacy: body.privacy || VideoPrivacy.PRIVATE,
    duration: 0, // duration will be set by the import job
    channelId: res.locals.videoChannel.id
  }
  const video = new VideoModel(videoData)
  video.url = getVideoActivityPubUrl(video)

  // Process thumbnail file?
  const thumbnailField = req.files['thumbnailfile']
  let downloadThumbnail = true
  if (thumbnailField) {
    const thumbnailPhysicalFile = thumbnailField[ 0 ]
    await processImage(thumbnailPhysicalFile, join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName()), THUMBNAILS_SIZE)
    downloadThumbnail = false
  }

  // Process preview file?
  const previewField = req.files['previewfile']
  let downloadPreview = true
  if (previewField) {
    const previewPhysicalFile = previewField[0]
    await processImage(previewPhysicalFile, join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName()), PREVIEWS_SIZE)
    downloadPreview = false
  }

  const videoImport: VideoImportModel = await sequelizeTypescript.transaction(async t => {
    const sequelizeOptions = { transaction: t }

    // Save video object in database
    const videoCreated = await video.save(sequelizeOptions)
    videoCreated.VideoChannel = res.locals.videoChannel

    // Set tags to the video
    if (youtubeDLInfo.tags !== undefined) {
      const tagInstances = await TagModel.findOrCreateTags(youtubeDLInfo.tags, t)

      await videoCreated.$set('Tags', tagInstances, sequelizeOptions)
      videoCreated.Tags = tagInstances
    }

    // Create video import object in database
    const videoImport = await VideoImportModel.create({
      targetUrl,
      state: VideoImportState.PENDING,
      videoId: videoCreated.id
    }, sequelizeOptions)

    videoImport.Video = videoCreated

    return videoImport
  })

  // Create job to import the video
  const payload = {
    type: 'youtube-dl' as 'youtube-dl',
    videoImportId: videoImport.id,
    thumbnailUrl: youtubeDLInfo.thumbnailUrl,
    downloadThumbnail,
    downloadPreview
  }
  await JobQueue.Instance.createJob({ type: 'video-import', payload })

  return res.json(videoImport.toFormattedJSON())
}