131 lines
4.6 KiB
JavaScript
131 lines
4.6 KiB
JavaScript
|
import { spawn } from "child_process";
|
||
|
import ffmpeg from "ffmpeg-static";
|
||
|
import got from "got";
|
||
|
import { genericUserAgent } from "../config.js";
|
||
|
import { msToTime } from "../sub/api-helper.js";
|
||
|
import { internalError } from "../sub/errors.js";
|
||
|
import loc from "../sub/loc.js";
|
||
|
|
||
|
export async function streamDefault(streamInfo, res) {
|
||
|
try {
|
||
|
res.setHeader('Content-disposition', `attachment; filename="${streamInfo.filename}"`);
|
||
|
const stream = got.get(streamInfo.urls, {
|
||
|
headers: {
|
||
|
"user-agent": genericUserAgent
|
||
|
},
|
||
|
isStream: true
|
||
|
});
|
||
|
stream.pipe(res).on('error', (err) => {
|
||
|
internalError(res);
|
||
|
throw Error("File stream pipe error.");
|
||
|
});
|
||
|
stream.on('error', (err) => {
|
||
|
internalError(res);
|
||
|
throw Error("File stream error.")
|
||
|
});
|
||
|
} catch (e) {
|
||
|
internalError(res);
|
||
|
}
|
||
|
}
|
||
|
export async function streamLiveRender(streamInfo, res) {
|
||
|
try {
|
||
|
if (streamInfo.urls.length == 2) {
|
||
|
let headers = {};
|
||
|
if (streamInfo.service == "bilibili") {
|
||
|
headers = { "user-agent": genericUserAgent };
|
||
|
}
|
||
|
const audio = got.get(streamInfo.urls[1], { isStream: true, headers: headers });
|
||
|
const video = got.get(streamInfo.urls[0], { isStream: true, headers: headers });
|
||
|
let format = streamInfo.filename.split('.')[streamInfo.filename.split('.').length - 1], args = [
|
||
|
'-loglevel', '-8',
|
||
|
'-i', 'pipe:3',
|
||
|
'-i', 'pipe:4',
|
||
|
'-map', '0:a',
|
||
|
'-map', '1:v',
|
||
|
'-c:v', 'copy',
|
||
|
'-c:a', 'copy',
|
||
|
];
|
||
|
if (format == 'mp4') {
|
||
|
args.push('-movflags', 'frag_keyframe+empty_moov');
|
||
|
if (streamInfo.service == "youtube") {
|
||
|
args.push('-t', msToTime(streamInfo.time));
|
||
|
}
|
||
|
} else if (format == 'webm') {
|
||
|
args.push('-t', msToTime(streamInfo.time));
|
||
|
}
|
||
|
args.push('-f', format, 'pipe:5');
|
||
|
const ffmpegProcess = spawn(ffmpeg, args, {
|
||
|
windowsHide: true,
|
||
|
stdio: [
|
||
|
'inherit', 'inherit', 'inherit',
|
||
|
'pipe', 'pipe', 'pipe'
|
||
|
],
|
||
|
});
|
||
|
ffmpegProcess.on('error', (err) => {
|
||
|
ffmpegProcess.kill();
|
||
|
internalError(res);
|
||
|
});
|
||
|
audio.on('error', (err) => {
|
||
|
ffmpegProcess.kill();
|
||
|
internalError(res);
|
||
|
});
|
||
|
video.on('error', (err) => {
|
||
|
ffmpegProcess.kill();
|
||
|
internalError(res);
|
||
|
});
|
||
|
res.setHeader('Content-Disposition', `attachment; filename="${streamInfo.filename}"`);
|
||
|
ffmpegProcess.stdio[5].pipe(res);
|
||
|
audio.pipe(ffmpegProcess.stdio[3]).on('error', (err) => {
|
||
|
ffmpegProcess.kill();
|
||
|
internalError(res);
|
||
|
});
|
||
|
video.pipe(ffmpegProcess.stdio[4]).on('error', (err) => {
|
||
|
ffmpegProcess.kill();
|
||
|
internalError(res);
|
||
|
});
|
||
|
} else {
|
||
|
res.status(400).json({ status: "error", text: loc('en', 'apiError', 'corruptedVideo') });
|
||
|
}
|
||
|
} catch (e) {
|
||
|
internalError(res);
|
||
|
}
|
||
|
}
|
||
|
export async function streamAudioOnly(streamInfo, res) {
|
||
|
try {
|
||
|
let headers = {};
|
||
|
if (streamInfo.service == "bilibili") {
|
||
|
headers = { "user-agent": genericUserAgent };
|
||
|
}
|
||
|
const audio = got.get(streamInfo.urls[0], { isStream: true, headers: headers });
|
||
|
const ffmpegProcess = spawn(ffmpeg, [
|
||
|
'-loglevel', '-8',
|
||
|
'-i', 'pipe:3',
|
||
|
'-vn',
|
||
|
'-c:a', 'copy',
|
||
|
'-f', `${streamInfo.filename.split('.')[streamInfo.filename.split('.').length - 1]}`,
|
||
|
'pipe:4',
|
||
|
], {
|
||
|
windowsHide: true,
|
||
|
stdio: [
|
||
|
'inherit', 'inherit', 'inherit',
|
||
|
'pipe', 'pipe'
|
||
|
],
|
||
|
});
|
||
|
ffmpegProcess.on('error', (err) => {
|
||
|
ffmpegProcess.kill();
|
||
|
internalError(res);
|
||
|
});
|
||
|
audio.on('error', (err) => {
|
||
|
ffmpegProcess.kill();
|
||
|
internalError(res);
|
||
|
});
|
||
|
res.setHeader('Content-Disposition', `attachment; filename="${streamInfo.filename}"`);
|
||
|
ffmpegProcess.stdio[4].pipe(res);
|
||
|
audio.pipe(ffmpegProcess.stdio[3]).on('error', (err) => {
|
||
|
ffmpegProcess.kill();
|
||
|
internalError(res);
|
||
|
});
|
||
|
} catch (e) {
|
||
|
internalError(res);
|
||
|
}
|
||
|
}
|