@mathrunet/masamune
Version:
Manages packages for the server portion (NodeJS) of the Masamune framework.
169 lines • 8.64 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const functions = __importStar(require("firebase-functions/v2"));
const admin = __importStar(require("firebase-admin"));
const path = __importStar(require("path"));
const os = __importStar(require("os"));
const fs = __importStar(require("fs"));
const fluent_ffmpeg_1 = __importDefault(require("fluent-ffmpeg"));
/**
* Converts videos uploaded to storage to HLS format.
*
* ストレージにアップロードされた動画をHLS形式に変換します。
*/
module.exports = (regions, options, data) => {
var _a, _b;
return functions.storage.onObjectFinalized({
timeoutSeconds: 540,
region: (_a = options.region) !== null && _a !== void 0 ? _a : regions[0],
memory: "2GiB",
minInstances: options.minInstances,
concurrency: options.concurrency,
maxInstances: options.maxInstances,
serviceAccount: (_b = options === null || options === void 0 ? void 0 : options.serviceAccount) !== null && _b !== void 0 ? _b : undefined,
}, (event) => __awaiter(void 0, void 0, void 0, function* () {
try {
const storage = admin.storage();
// ファイルがアップロードされたバケット
const fileBucket = event.data.bucket;
// ファイルのパス
const filePath = event.data.name;
// ファイルのディレクトリ
const fileDir = path.dirname(filePath);
// ファイルのMIMEタイプ
const contentType = event.data.contentType;
// --- トリガー条件のチェック ---
// 1. ファイルパスが存在しない場合は終了
if (!filePath) {
console.log("File path is not available.");
return;
}
// 2. ファイルの拡張子がm3u8かtsの場合は、処理済みファイルなので無限ループを避けるため終了
if (path.extname(filePath) === ".m3u8" || path.extname(filePath) === ".ts") {
console.log(`File ${filePath} is already processed. Exiting.`);
return;
}
// 3. 動画ファイルでない場合は終了 (例: 'video/mp4')
if (!contentType || !contentType.startsWith("video/")) {
console.log(`File ${filePath} is not a video. Exiting.`);
return;
}
console.log(`New video uploaded: ${filePath}. Starting HLS conversion.`);
const bucket = storage.bucket(fileBucket);
const sourceFile = bucket.file(filePath);
const fileName = path.basename(filePath);
const fileNameWithoutExt = path.parse(fileName).name;
// --- ファイルのダウンロード ---
// 一時ディレクトリを作成
const tempFilePath = path.join(os.tmpdir(), fileName);
yield sourceFile.download({ destination: tempFilePath });
console.log(`Downloaded ${fileName} to ${tempFilePath}.`);
// HLS ファイルの出力先となる一時ディレクトリを作成
const tempHlsDir = path.join(os.tmpdir(), "hls");
if (!fs.existsSync(tempHlsDir)) {
fs.mkdirSync(tempHlsDir);
}
// --- FFmpegによるエンコード処理 ---
yield new Promise((resolve, reject) => {
(0, fluent_ffmpeg_1.default)(tempFilePath)
// ビデオコーデック(H.264エンコーダー使用、リサイズのため再エンコードが必要)
.addOption("-c:v", "libx264")
// オーディオコーデックもコピー
.addOption("-c:a", "copy")
// 動画サイズを最大1920pxに制限(アスペクト比維持)
.addOption("-vf", "scale=1920:1920:force_original_aspect_ratio=decrease")
// エンコード品質を設定(CRF値、低いほど高品質)
.addOption("-crf", "23")
// エンコードプリセット(速度と品質のバランス)
.addOption("-preset", "medium")
// HLSセグメントの長さを10秒に設定
.addOption("-hls_time", "10")
// HLSプレイリストのタイプをVOD (Video On Demand) に設定
.addOption("-hls_playlist_type", "vod")
// HLSセグメントのファイル名のフォーマット
.addOption("-hls_segment_filename", `${tempHlsDir}/${fileNameWithoutExt}%04d.ts`)
// 出力するマスタープレイリストファイル
.output(`${tempHlsDir}/${fileNameWithoutExt}.m3u8`)
.on("end", () => {
console.log("FFmpeg processing finished successfully.");
resolve();
})
.on("error", (err) => {
console.error("Error during FFmpeg processing:", err);
reject(err);
})
.run();
});
// --- HLSファイルのアップロード ---
const outputDir = fileDir;
const files = fs.readdirSync(tempHlsDir);
const uploadPromises = files.map((file) => {
const localPath = path.join(tempHlsDir, file);
const remotePath = `${outputDir}/${file}`;
return bucket.upload(localPath, {
destination: remotePath,
public: true, // 公開アクセス可能にする場合
});
});
yield Promise.all(uploadPromises);
console.log(`Uploaded HLS files to ${outputDir}.`);
// --- 一時ファイルのクリーンアップ ---
fs.unlinkSync(tempFilePath);
fs.rmSync(tempHlsDir, { recursive: true, force: true });
console.log("Cleaned up temporary files.");
// (オプション) 元のMP4ファイルを削除する場合
yield sourceFile.delete();
console.log(`Deleted original file: ${filePath}`);
}
catch (err) {
console.error(err);
throw err;
}
}));
};
//# sourceMappingURL=hls.js.map