import { VideoCaptionService } from '@app/shared/video-caption'
import { VideoImportService } from '@app/shared/video-import'
import { scrollToTop } from '@app/shared/misc/utils'
+import { switchMap, map } from 'rxjs/operators'
@Component({
selector: 'my-video-import-url',
this.loadingBar.start()
- this.videoImportService.importVideoUrl(this.targetUrl, videoUpdate).subscribe(
- res => {
- this.loadingBar.complete()
- this.firstStepDone.emit(res.video.name)
- this.isImportingVideo = false
- this.hasImportedVideo = true
-
- this.video = new VideoEdit(Object.assign(res.video, {
- commentsEnabled: videoUpdate.commentsEnabled,
- downloadEnabled: videoUpdate.downloadEnabled,
- support: null,
- thumbnailUrl: null,
- previewUrl: null
- }))
-
- this.hydrateFormFromVideo()
- },
-
- err => {
- this.loadingBar.complete()
- this.isImportingVideo = false
- this.firstStepError.emit()
- this.notifier.error(err.message)
- }
- )
+ this.videoImportService
+ .importVideoUrl(this.targetUrl, videoUpdate)
+ .pipe(
+ switchMap(res => {
+ return this.videoCaptionService
+ .listCaptions(res.video.id)
+ .pipe(
+ map(result => ({ video: res.video, videoCaptions: result.data }))
+ )
+ })
+ )
+ .subscribe(
+ ({ video, videoCaptions }) => {
+ this.loadingBar.complete()
+ this.firstStepDone.emit(video.name)
+ this.isImportingVideo = false
+ this.hasImportedVideo = true
+
+ this.video = new VideoEdit(Object.assign(video, {
+ commentsEnabled: videoUpdate.commentsEnabled,
+ downloadEnabled: videoUpdate.downloadEnabled,
+ support: null,
+ thumbnailUrl: null,
+ previewUrl: null
+ }))
+
+ this.videoCaptions = videoCaptions
+
+ this.hydrateFormFromVideo()
+ },
+
+ err => {
+ this.loadingBar.complete()
+ this.isImportingVideo = false
+ this.firstStepError.emit()
+ this.notifier.error(err.message)
+ }
+ )
}
updateSecondStep () {
import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger'
import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares'
import { MIMETYPES } from '../../../initializers/constants'
-import { getYoutubeDLInfo, YoutubeDLInfo } from '../../../helpers/youtube-dl'
+import { getYoutubeDLInfo, YoutubeDLInfo, getYoutubeDLSubs } 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 { VideoCaptionModel } from '../../../models/video/video-caption'
+import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils'
import { getVideoActivityPubUrl } from '../../../lib/activitypub'
import { TagModel } from '../../../models/video/tag'
import { VideoImportModel } from '../../../models/video/video-import'
MThumbnail,
MUser,
MVideoAccountDefault,
+ MVideoCaptionVideo,
MVideoTag,
MVideoThumbnailAccountDefault,
MVideoWithBlacklistLight
const targetUrl = body.targetUrl
const user = res.locals.oauth.token.User
+ // Get video infos
let youtubeDLInfo: YoutubeDLInfo
try {
youtubeDLInfo = await getYoutubeDLInfo(targetUrl)
user
})
+
+ // Get video subtitles
+ try {
+ const subtitles = await getYoutubeDLSubs(targetUrl)
+
+ for (const subtitle of subtitles) {
+ const videoCaption = new VideoCaptionModel({
+ videoId: video.id,
+ language: subtitle.language
+ }) as MVideoCaptionVideo
+ videoCaption.Video = video
+
+ // Move physical file
+ await moveAndProcessCaptionFile(subtitle, videoCaption)
+
+ await sequelizeTypescript.transaction(async t => {
+ await VideoCaptionModel.insertOrReplaceLanguage(video.id, subtitle.language, null, t)
+ })
+ }
+ } catch (err) {
+ logger.warn('Cannot get video subtitles.', { err })
+ }
+
// Create job to import the video
const payload = {
type: 'youtube-dl' as 'youtube-dl',
originallyPublishedAt?: Date
}
+export type YoutubeDLSubs = {
+ language: string,
+ filename: string,
+ path: string
+}[]
+
const processOptions = {
maxBuffer: 1024 * 1024 * 10 // 10MB
}
})
}
+function getYoutubeDLSubs (url: string, opts?: object): Promise<YoutubeDLSubs> {
+ return new Promise<YoutubeDLSubs>((res, rej) => {
+ const cwd = CONFIG.STORAGE.TMP_DIR
+ const options = opts || { all: true, format: 'vtt', cwd }
+
+ safeGetYoutubeDL()
+ .then(youtubeDL => {
+ youtubeDL.getSubs(url, options, (err, files) => {
+ if (err) return rej(err)
+
+ const subtitles = files.reduce((acc, filename) => {
+ const matched = filename.match(/\.([a-z]{2})\.(vtt|ttml)/i)
+
+ if (matched[1]) {
+ return [...acc, {
+ language: matched[1],
+ path: join(cwd, filename),
+ filename
+ }]
+ }
+ }, [])
+
+ return res(subtitles)
+ })
+ })
+ .catch(err => rej(err))
+ })
+}
+
function downloadYoutubeDLVideo (url: string, extension: string, timeout: number) {
const path = generateVideoImportTmpPath(url, extension)
let timer
export {
updateYoutubeDLBinary,
downloadYoutubeDLVideo,
+ getYoutubeDLSubs,
getYoutubeDLInfo,
safeGetYoutubeDL,
buildOriginallyPublishedAt