"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.uploadRouter = void 0;
const tslib_1 = require("tslib");
const express_1 = tslib_1.__importDefault(require("express"));
const fs_extra_1 = require("fs-extra");
const path_1 = require("path");
const upload_1 = require("../../../helpers/upload");
const url_1 = require("../../../lib/activitypub/url");
const job_queue_1 = require("../../../lib/job-queue");
const paths_1 = require("../../../lib/paths");
const redis_1 = require("../../../lib/redis");
const uploadx_1 = require("../../../lib/uploadx");
const video_1 = require("../../../lib/video");
const video_path_manager_1 = require("../../../lib/video-path-manager");
const video_state_1 = require("../../../lib/video-state");
const doc_1 = require("../../../middlewares/doc");
const video_source_1 = require("../../../models/video/video-source");
const core_utils_1 = require("../../../../shared/core-utils");
const extra_utils_1 = require("../../../../shared/extra-utils");
const audit_logger_1 = require("../../../helpers/audit-logger");
const express_utils_1 = require("../../../helpers/express-utils");
const ffmpeg_1 = require("../../../helpers/ffmpeg");
const logger_1 = require("../../../helpers/logger");
const constants_1 = require("../../../initializers/constants");
const database_1 = require("../../../initializers/database");
const hooks_1 = require("../../../lib/plugins/hooks");
const thumbnail_1 = require("../../../lib/thumbnail");
const video_blacklist_1 = require("../../../lib/video-blacklist");
const middlewares_1 = require("../../../middlewares");
const schedule_video_update_1 = require("../../../models/video/schedule-video-update");
const video_2 = require("../../../models/video/video");
const video_file_1 = require("../../../models/video/video-file");
const lTags = (0, logger_1.loggerTagsFactory)('api', 'video');
const auditLogger = (0, audit_logger_1.auditLoggerFactory)('videos');
const uploadRouter = express_1.default.Router();
exports.uploadRouter = uploadRouter;
const reqVideoFileAdd = (0, express_utils_1.createReqFiles)(['videofile', 'thumbnailfile', 'previewfile'], Object.assign(Object.assign({}, constants_1.MIMETYPES.VIDEO.MIMETYPE_EXT), constants_1.MIMETYPES.IMAGE.MIMETYPE_EXT));
const reqVideoFileAddResumable = (0, express_utils_1.createReqFiles)(['thumbnailfile', 'previewfile'], constants_1.MIMETYPES.IMAGE.MIMETYPE_EXT, (0, upload_1.getResumableUploadPath)());
uploadRouter.post('/upload', (0, doc_1.openapiOperationDoc)({ operationId: 'uploadLegacy' }), middlewares_1.authenticate, reqVideoFileAdd, (0, middlewares_1.asyncMiddleware)(middlewares_1.videosAddLegacyValidator), (0, middlewares_1.asyncRetryTransactionMiddleware)(addVideoLegacy));
uploadRouter.post('/upload-resumable', (0, doc_1.openapiOperationDoc)({ operationId: 'uploadResumableInit' }), middlewares_1.authenticate, reqVideoFileAddResumable, (0, middlewares_1.asyncMiddleware)(middlewares_1.videosAddResumableInitValidator), uploadx_1.uploadx.upload);
uploadRouter.delete('/upload-resumable', middlewares_1.authenticate, (0, middlewares_1.asyncMiddleware)(deleteUploadResumableCache), uploadx_1.uploadx.upload);
uploadRouter.put('/upload-resumable', (0, doc_1.openapiOperationDoc)({ operationId: 'uploadResumable' }), middlewares_1.authenticate, uploadx_1.uploadx.upload, (0, middlewares_1.asyncMiddleware)(middlewares_1.videosAddResumableValidator), (0, middlewares_1.asyncMiddleware)(addVideoResumable));
function addVideoLegacy(req, res) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        req.setTimeout(1000 * 60 * 10, () => {
            logger_1.logger.error('Video upload has timed out.');
            return res.fail({
                status: 408,
                message: 'Video upload has timed out.'
            });
        });
        const videoPhysicalFile = req.files['videofile'][0];
        const videoInfo = req.body;
        const files = req.files;
        const response = yield addVideo({ req, res, videoPhysicalFile, videoInfo, files });
        return res.json(response);
    });
}
function addVideoResumable(req, res) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        const videoPhysicalFile = res.locals.videoFileResumable;
        const videoInfo = videoPhysicalFile.metadata;
        const files = { previewfile: videoInfo.previewfile };
        const response = yield addVideo({ req, res, videoPhysicalFile, videoInfo, files });
        yield redis_1.Redis.Instance.setUploadSession(req.query.upload_id, response);
        return res.json(response);
    });
}
function addVideo(options) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        const { req, res, videoPhysicalFile, videoInfo, files } = options;
        const videoChannel = res.locals.videoChannel;
        const user = res.locals.oauth.token.User;
        let videoData = (0, video_1.buildLocalVideoFromReq)(videoInfo, videoChannel.id);
        videoData = yield hooks_1.Hooks.wrapObject(videoData, 'filter:api.video.upload.video-attribute.result');
        videoData.state = (0, video_state_1.buildNextVideoState)();
        videoData.duration = videoPhysicalFile.duration;
        const video = new video_2.VideoModel(videoData);
        video.VideoChannel = videoChannel;
        video.url = (0, url_1.getLocalVideoActivityPubUrl)(video);
        const videoFile = yield buildNewFile(videoPhysicalFile);
        const originalFilename = videoPhysicalFile.originalname;
        const destination = video_path_manager_1.VideoPathManager.Instance.getFSVideoFileOutputPath(video, videoFile);
        yield (0, fs_extra_1.move)(videoPhysicalFile.path, destination);
        videoPhysicalFile.filename = (0, path_1.basename)(destination);
        videoPhysicalFile.path = destination;
        const [thumbnailModel, previewModel] = yield (0, video_1.buildVideoThumbnailsFromReq)({
            video,
            files,
            fallback: type => (0, thumbnail_1.generateVideoMiniature)({ video, videoFile, type })
        });
        const { videoCreated } = yield database_1.sequelizeTypescript.transaction((t) => tslib_1.__awaiter(this, void 0, void 0, function* () {
            const sequelizeOptions = { transaction: t };
            const videoCreated = yield video.save(sequelizeOptions);
            yield videoCreated.addAndSaveThumbnail(thumbnailModel, t);
            yield videoCreated.addAndSaveThumbnail(previewModel, t);
            videoCreated.VideoChannel = res.locals.videoChannel;
            videoFile.videoId = video.id;
            yield videoFile.save(sequelizeOptions);
            video.VideoFiles = [videoFile];
            yield video_source_1.VideoSourceModel.create({
                filename: originalFilename,
                videoId: video.id
            }, { transaction: t });
            yield (0, video_1.setVideoTags)({ video, tags: videoInfo.tags, transaction: t });
            if (videoInfo.scheduleUpdate) {
                yield schedule_video_update_1.ScheduleVideoUpdateModel.create({
                    videoId: video.id,
                    updateAt: new Date(videoInfo.scheduleUpdate.updateAt),
                    privacy: videoInfo.scheduleUpdate.privacy || null
                }, sequelizeOptions);
            }
            yield (0, video_blacklist_1.autoBlacklistVideoIfNeeded)({
                video,
                user,
                isRemote: false,
                isNew: true,
                transaction: t
            });
            auditLogger.create((0, audit_logger_1.getAuditIdFromRes)(res), new audit_logger_1.VideoAuditView(videoCreated.toFormattedDetailsJSON()));
            logger_1.logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid, lTags(videoCreated.uuid));
            return { videoCreated };
        }));
        yield videoCreated.VideoChannel.setAsUpdated();
        addVideoJobsAfterUpload(videoCreated, videoFile, user)
            .catch(err => logger_1.logger.error('Cannot build new video jobs of %s.', videoCreated.uuid, Object.assign({ err }, lTags(videoCreated.uuid))));
        hooks_1.Hooks.runAction('action:api.video.uploaded', { video: videoCreated, req, res });
        return {
            video: {
                id: videoCreated.id,
                shortUUID: (0, extra_utils_1.uuidToShort)(videoCreated.uuid),
                uuid: videoCreated.uuid
            }
        };
    });
}
function buildNewFile(videoPhysicalFile) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        const videoFile = new video_file_1.VideoFileModel({
            extname: (0, core_utils_1.getLowercaseExtension)(videoPhysicalFile.filename),
            size: videoPhysicalFile.size,
            videoStreamingPlaylistId: null,
            metadata: yield (0, ffmpeg_1.buildFileMetadata)(videoPhysicalFile.path)
        });
        const probe = yield (0, ffmpeg_1.ffprobePromise)(videoPhysicalFile.path);
        if (yield (0, extra_utils_1.isAudioFile)(videoPhysicalFile.path, probe)) {
            videoFile.resolution = 0;
        }
        else {
            videoFile.fps = yield (0, ffmpeg_1.getVideoStreamFPS)(videoPhysicalFile.path, probe);
            videoFile.resolution = (yield (0, ffmpeg_1.getVideoStreamDimensionsInfo)(videoPhysicalFile.path, probe)).resolution;
        }
        videoFile.filename = (0, paths_1.generateWebTorrentVideoFilename)(videoFile.resolution, videoFile.extname);
        return videoFile;
    });
}
function addVideoJobsAfterUpload(video, videoFile, user) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        return job_queue_1.JobQueue.Instance.createSequentialJobFlow({
            type: 'manage-video-torrent',
            payload: {
                videoId: video.id,
                videoFileId: videoFile.id,
                action: 'create'
            }
        }, {
            type: 'notify',
            payload: {
                action: 'new-video',
                videoUUID: video.uuid
            }
        }, {
            type: 'federate-video',
            payload: {
                videoUUID: video.uuid,
                isNewVideo: true
            }
        }, video.state === 6
            ? yield (0, video_1.buildMoveToObjectStorageJob)({ video, previousVideoState: undefined })
            : undefined, video.state === 2
            ? yield (0, video_1.buildOptimizeOrMergeAudioJob)({ video, videoFile, user })
            : undefined);
    });
}
function deleteUploadResumableCache(req, res, next) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        yield redis_1.Redis.Instance.deleteUploadSession(req.query.upload_id);
        return next();
    });
}
//# sourceMappingURL=upload.js.map