"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildVideoFromImport = exports.insertFromImportIntoDB = exports.YoutubeDlImportError = exports.buildYoutubeDLImport = void 0;
const tslib_1 = require("tslib");
const fs_extra_1 = require("fs-extra");
const captions_utils_1 = require("../helpers/captions-utils");
const video_captions_1 = require("../helpers/custom-validators/video-captions");
const videos_1 = require("../helpers/custom-validators/videos");
const dns_1 = require("../helpers/dns");
const logger_1 = require("../helpers/logger");
const youtube_dl_1 = require("../helpers/youtube-dl");
const config_1 = require("../initializers/config");
const database_1 = require("../initializers/database");
const hooks_1 = require("./plugins/hooks");
const server_config_manager_1 = require("./server-config-manager");
const video_1 = require("./video");
const video_blacklist_1 = require("./video-blacklist");
const video_2 = require("../models/video/video");
const video_caption_1 = require("../models/video/video-caption");
const video_import_1 = require("../models/video/video-import");
const url_1 = require("./activitypub/url");
const thumbnail_1 = require("./thumbnail");
class YoutubeDlImportError extends Error {
    constructor({ message, code }) {
        super(message);
        this.code = code;
    }
    static fromError(err, code, message) {
        const ytDlErr = new this({ message: message !== null && message !== void 0 ? message : err.message, code });
        ytDlErr.cause = err;
        ytDlErr.stack = err.stack;
        return ytDlErr;
    }
}
exports.YoutubeDlImportError = YoutubeDlImportError;
(function (YoutubeDlImportError) {
    let CODE;
    (function (CODE) {
        CODE[CODE["FETCH_ERROR"] = 0] = "FETCH_ERROR";
        CODE[CODE["NOT_ONLY_UNICAST_URL"] = 1] = "NOT_ONLY_UNICAST_URL";
    })(CODE = YoutubeDlImportError.CODE || (YoutubeDlImportError.CODE = {}));
})(YoutubeDlImportError || (YoutubeDlImportError = {}));
exports.YoutubeDlImportError = YoutubeDlImportError;
function insertFromImportIntoDB(parameters) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        const { video, thumbnailModel, previewModel, videoChannel, tags, videoImportAttributes, user } = parameters;
        const videoImport = 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);
            videoCreated.VideoChannel = videoChannel;
            if (thumbnailModel)
                yield videoCreated.addAndSaveThumbnail(thumbnailModel, t);
            if (previewModel)
                yield videoCreated.addAndSaveThumbnail(previewModel, t);
            yield (0, video_blacklist_1.autoBlacklistVideoIfNeeded)({
                video: videoCreated,
                user,
                notify: false,
                isRemote: false,
                isNew: true,
                transaction: t
            });
            yield (0, video_1.setVideoTags)({ video: videoCreated, tags, transaction: t });
            const videoImport = yield video_import_1.VideoImportModel.create(Object.assign({ videoId: videoCreated.id }, videoImportAttributes), sequelizeOptions);
            videoImport.Video = videoCreated;
            return videoImport;
        }));
        return videoImport;
    });
}
exports.insertFromImportIntoDB = insertFromImportIntoDB;
function buildVideoFromImport({ channelId, importData, importDataOverride, importType }) {
    var _a, _b, _c, _d, _e;
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        let videoData = {
            name: (importDataOverride === null || importDataOverride === void 0 ? void 0 : importDataOverride.name) || importData.name || 'Unknown name',
            remote: false,
            category: (importDataOverride === null || importDataOverride === void 0 ? void 0 : importDataOverride.category) || importData.category,
            licence: (_b = (_a = importDataOverride === null || importDataOverride === void 0 ? void 0 : importDataOverride.licence) !== null && _a !== void 0 ? _a : importData.licence) !== null && _b !== void 0 ? _b : config_1.CONFIG.DEFAULTS.PUBLISH.LICENCE,
            language: (importDataOverride === null || importDataOverride === void 0 ? void 0 : importDataOverride.language) || importData.language,
            commentsEnabled: (_c = importDataOverride === null || importDataOverride === void 0 ? void 0 : importDataOverride.commentsEnabled) !== null && _c !== void 0 ? _c : config_1.CONFIG.DEFAULTS.PUBLISH.COMMENTS_ENABLED,
            downloadEnabled: (_d = importDataOverride === null || importDataOverride === void 0 ? void 0 : importDataOverride.downloadEnabled) !== null && _d !== void 0 ? _d : config_1.CONFIG.DEFAULTS.PUBLISH.DOWNLOAD_ENABLED,
            waitTranscoding: (_e = importDataOverride === null || importDataOverride === void 0 ? void 0 : importDataOverride.waitTranscoding) !== null && _e !== void 0 ? _e : true,
            state: 3,
            nsfw: (importDataOverride === null || importDataOverride === void 0 ? void 0 : importDataOverride.nsfw) || importData.nsfw || false,
            description: (importDataOverride === null || importDataOverride === void 0 ? void 0 : importDataOverride.description) || importData.description,
            support: (importDataOverride === null || importDataOverride === void 0 ? void 0 : importDataOverride.support) || null,
            privacy: (importDataOverride === null || importDataOverride === void 0 ? void 0 : importDataOverride.privacy) || 3,
            duration: 0,
            channelId,
            originallyPublishedAt: (importDataOverride === null || importDataOverride === void 0 ? void 0 : importDataOverride.originallyPublishedAt)
                ? new Date(importDataOverride === null || importDataOverride === void 0 ? void 0 : importDataOverride.originallyPublishedAt)
                : importData.originallyPublishedAtWithoutTime
        };
        videoData = yield hooks_1.Hooks.wrapObject(videoData, importType === 'url'
            ? 'filter:api.video.import-url.video-attribute.result'
            : 'filter:api.video.import-torrent.video-attribute.result');
        const video = new video_2.VideoModel(videoData);
        video.url = (0, url_1.getLocalVideoActivityPubUrl)(video);
        return video;
    });
}
exports.buildVideoFromImport = buildVideoFromImport;
function buildYoutubeDLImport(options) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        const { targetUrl, channel, channelSync, importDataOverride, thumbnailFilePath, previewFilePath, user } = options;
        const youtubeDL = new youtube_dl_1.YoutubeDLWrapper(targetUrl, server_config_manager_1.ServerConfigManager.Instance.getEnabledResolutions('vod'), config_1.CONFIG.TRANSCODING.ALWAYS_TRANSCODE_ORIGINAL_RESOLUTION);
        let youtubeDLInfo;
        try {
            youtubeDLInfo = yield youtubeDL.getInfoForDownload();
        }
        catch (err) {
            throw YoutubeDlImportError.fromError(err, YoutubeDlImportError.CODE.FETCH_ERROR, `Cannot fetch information from import for URL ${targetUrl}`);
        }
        if (!(yield hasUnicastURLsOnly(youtubeDLInfo))) {
            throw new YoutubeDlImportError({
                message: 'Cannot use non unicast IP as targetUrl.',
                code: YoutubeDlImportError.CODE.NOT_ONLY_UNICAST_URL
            });
        }
        const video = yield buildVideoFromImport({
            channelId: channel.id,
            importData: youtubeDLInfo,
            importDataOverride,
            importType: 'url'
        });
        const thumbnailModel = yield forgeThumbnail({
            inputPath: thumbnailFilePath,
            downloadUrl: youtubeDLInfo.thumbnailUrl,
            video,
            type: 1
        });
        const previewModel = yield forgeThumbnail({
            inputPath: previewFilePath,
            downloadUrl: youtubeDLInfo.thumbnailUrl,
            video,
            type: 2
        });
        const videoImport = yield insertFromImportIntoDB({
            video,
            thumbnailModel,
            previewModel,
            videoChannel: channel,
            tags: (importDataOverride === null || importDataOverride === void 0 ? void 0 : importDataOverride.tags) || youtubeDLInfo.tags,
            user,
            videoImportAttributes: {
                targetUrl,
                state: 1,
                userId: user.id,
                videoChannelSyncId: channelSync === null || channelSync === void 0 ? void 0 : channelSync.id
            }
        });
        yield processYoutubeSubtitles(youtubeDL, targetUrl, video.id);
        let fileExt = `.${youtubeDLInfo.ext}`;
        if (!(0, videos_1.isVideoFileExtnameValid)(fileExt))
            fileExt = '.mp4';
        const payload = {
            type: 'youtube-dl',
            videoImportId: videoImport.id,
            fileExt,
            preventException: !!channelSync
        };
        return {
            videoImport,
            job: { type: 'video-import', payload }
        };
    });
}
exports.buildYoutubeDLImport = buildYoutubeDLImport;
function forgeThumbnail({ inputPath, video, downloadUrl, type }) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        if (inputPath) {
            return (0, thumbnail_1.updateVideoMiniatureFromExisting)({
                inputPath,
                video,
                type,
                automaticallyGenerated: false
            });
        }
        else if (downloadUrl) {
            try {
                return yield (0, thumbnail_1.updateVideoMiniatureFromUrl)({ downloadUrl, video, type });
            }
            catch (err) {
                logger_1.logger.warn('Cannot process thumbnail %s from youtubedl.', downloadUrl, { err });
            }
        }
        return null;
    });
}
function processYoutubeSubtitles(youtubeDL, targetUrl, videoId) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        try {
            const subtitles = yield youtubeDL.getSubtitles();
            logger_1.logger.info('Will create %s subtitles from youtube import %s.', subtitles.length, targetUrl);
            for (const subtitle of subtitles) {
                if (!(yield (0, video_captions_1.isVTTFileValid)(subtitle.path))) {
                    yield (0, fs_extra_1.remove)(subtitle.path);
                    continue;
                }
                const videoCaption = new video_caption_1.VideoCaptionModel({
                    videoId,
                    language: subtitle.language,
                    filename: video_caption_1.VideoCaptionModel.generateCaptionName(subtitle.language)
                });
                yield (0, captions_utils_1.moveAndProcessCaptionFile)(subtitle, videoCaption);
                yield database_1.sequelizeTypescript.transaction((t) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                    yield video_caption_1.VideoCaptionModel.insertOrReplaceLanguage(videoCaption, t);
                }));
            }
        }
        catch (err) {
            logger_1.logger.warn('Cannot get video subtitles.', { err });
        }
    });
}
function hasUnicastURLsOnly(youtubeDLInfo) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        const hosts = youtubeDLInfo.urls.map(u => new URL(u).hostname);
        const uniqHosts = new Set(hosts);
        for (const h of uniqHosts) {
            if ((yield (0, dns_1.isResolvingToUnicastOnly)(h)) !== true) {
                return false;
            }
        }
        return true;
    });
}
//# sourceMappingURL=video-pre-import.js.map