Initial commit
This commit is contained in:
65
server_nm3u8dl/Dockerfile
Normal file
65
server_nm3u8dl/Dockerfile
Normal file
@@ -0,0 +1,65 @@
|
||||
#Use image Ubuntu
|
||||
FROM ubuntu:jammy
|
||||
|
||||
#Update and install dependencies
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y wget
|
||||
RUN apt-get install -y xz-utils
|
||||
RUN apt-get install -y libicu-dev
|
||||
RUN apt-get install -y ffmpeg
|
||||
|
||||
#Install Node.js y npm
|
||||
RUN apt-get update && apt-get install -y curl && \
|
||||
curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && \
|
||||
apt-get install -y nodejs
|
||||
|
||||
#Install dependencies project
|
||||
COPY package.json .
|
||||
RUN npm install
|
||||
|
||||
#Working directory
|
||||
WORKDIR /app
|
||||
|
||||
#Copy server.js to container
|
||||
COPY ./server.js .
|
||||
#COPY ./mp4decrypt /usr/local/bin/mp4decrypt
|
||||
|
||||
#Install FFMPEG ( ERROR DOWNLOAD )
|
||||
#RUN wget https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz \
|
||||
#&& tar -xf ffmpeg-release-amd64-static.tar.xz \
|
||||
#&& cd ffmpeg-6.1-amd64-static/ \
|
||||
#&& mv ffmpeg /usr/bin/ \
|
||||
#&& mv ffprobe /usr/bin/
|
||||
|
||||
#Copy ffmpeg-release-amd64-static.tar.xz to container
|
||||
#COPY ./lib/ffmpeg-release-amd64-static.tar.xz /tmp/
|
||||
|
||||
#Install FFMPEG
|
||||
#RUN tar -xf /tmp/ffmpeg-release-amd64-static.tar.xz -C /tmp/ \
|
||||
# && mv /tmp/ffmpeg-*/ffmpeg /usr/bin/ \
|
||||
# && mv /tmp/ffmpeg-*/ffprobe /usr/bin/ \
|
||||
# && rm -rf /tmp/ffmpeg-*
|
||||
|
||||
#Download and config N_m3u8DL-RE
|
||||
RUN wget https://github.com/nilaoda/N_m3u8DL-RE/releases/download/v0.2.0-beta/N_m3u8DL-RE_Beta_linux-x64_20230628.tar.gz \
|
||||
&& tar xf N_m3u8DL-RE_Beta_linux-x64_20230628.tar.gz --strip-components 1 \
|
||||
&& chmod +x N_m3u8DL-RE \
|
||||
&& mv N_m3u8DL-RE /usr/local/bin
|
||||
|
||||
#Download and config shaka-packager
|
||||
RUN wget https://github.com/shaka-project/shaka-packager/releases/download/v2.6.1/packager-linux-x64 \
|
||||
&& chmod +x packager-linux-x64 \
|
||||
&& mv packager-linux-x64 /usr/local/bin
|
||||
|
||||
#Create temp directory and grant permission
|
||||
RUN mkdir /tmp/ramdisk \
|
||||
&& chmod 777 /tmp/ramdisk
|
||||
|
||||
#Config ENV RE_LIVE_PIPE_OPTIONS
|
||||
ENV RE_LIVE_PIPE_OPTIONS="-c copy -f mpegts pipe:1"
|
||||
|
||||
#Expose internal port server
|
||||
EXPOSE 8080
|
||||
|
||||
#run server
|
||||
CMD ["sh", "-c", "node server.js"]
|
||||
BIN
server_nm3u8dl/lib/ffmpeg-release-amd64-static.tar.xz
Normal file
BIN
server_nm3u8dl/lib/ffmpeg-release-amd64-static.tar.xz
Normal file
Binary file not shown.
BIN
server_nm3u8dl/mp4decrypt
Executable file
BIN
server_nm3u8dl/mp4decrypt
Executable file
Binary file not shown.
18
server_nm3u8dl/package.json
Normal file
18
server_nm3u8dl/package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "ubuntu-n_m3u8dl",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "server.js",
|
||||
"directories": {
|
||||
"lib": "lib"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node server.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "",
|
||||
"dependencies": {
|
||||
"express": "^4.18.2"
|
||||
}
|
||||
}
|
||||
201
server_nm3u8dl/server.js
Normal file
201
server_nm3u8dl/server.js
Normal file
@@ -0,0 +1,201 @@
|
||||
const express = require('express');
|
||||
const { spawn } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const app = express();
|
||||
const port = 8080;
|
||||
const tmp_folder = '/tmp/ramdisk/';
|
||||
|
||||
let channelsPath = path.resolve(__dirname, 'channels.json');
|
||||
let channels = JSON.parse(fs.readFileSync(channelsPath, 'utf8'));
|
||||
|
||||
const activeStreams = {}; // Store active stream processes with unique IDs
|
||||
|
||||
fs.watch(channelsPath, (eventType, filename) => {
|
||||
if (eventType === 'change') {
|
||||
console.log(`channels.json was updated, reloading...`);
|
||||
try {
|
||||
channels = JSON.parse(fs.readFileSync(channelsPath, 'utf8'));
|
||||
} catch (error) {
|
||||
console.log(error.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function startStreamlink(channel, clientID) {
|
||||
const useragent = channel.useragent;
|
||||
const authorization = channel.authorization;
|
||||
const referer = channel.referer;
|
||||
const proxy = channel.proxy;
|
||||
const key1 = channel.key1;
|
||||
const key2 = channel.key2;
|
||||
const key3 = channel.key3;
|
||||
const key4 = channel.key4;
|
||||
const key5 = channel.key5;
|
||||
const audios = channel.audios;
|
||||
const resolution = channel.resolution;
|
||||
const delay = channel.delay;
|
||||
|
||||
let args = [
|
||||
channel.url,
|
||||
'--use-shaka-packager',
|
||||
// '--log-level', 'INFO',
|
||||
'--no-log',
|
||||
// '--live-real-time-merge', 'true',
|
||||
// '--mp4-real-time-decryption', 'true',
|
||||
// '--live-wait-time', '1',
|
||||
'--thread-count', '3',
|
||||
'--concurrent-download',
|
||||
'--live-pipe-mux',
|
||||
'--live-keep-segments', 'false',
|
||||
'--check-segments-count', 'false',
|
||||
// '--live-take-count', '10',
|
||||
'--tmp-dir', tmp_folder + `${channel.name}_${clientID}`, // Unique folder for each client
|
||||
'--del-after-done', 'true'
|
||||
];
|
||||
|
||||
if (useragent) {
|
||||
args.splice(1, 0, '--header', `User-Agent: ${useragent}`);
|
||||
} else {
|
||||
args.splice(1, 0, '--header', `User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299`);
|
||||
}
|
||||
if (authorization) {
|
||||
args.splice(1, 0, '--header', `Authorization: ${authorization}`);
|
||||
}
|
||||
if (referer) {
|
||||
args.splice(1, 0, '--header', `Referer: ${referer}`);
|
||||
}
|
||||
if (proxy) {
|
||||
args.splice(1, 0, '--custom-proxy', proxy);
|
||||
}
|
||||
if (key5) {
|
||||
args.splice(1, 0, '--key', key5);
|
||||
}
|
||||
if (key4) {
|
||||
args.splice(1, 0, '--key', key4);
|
||||
}
|
||||
if (key3) {
|
||||
args.splice(1, 0, '--key', key3);
|
||||
}
|
||||
if (key2) {
|
||||
args.splice(1, 0, '--key', key2);
|
||||
}
|
||||
if (key1) {
|
||||
args.splice(1, 0, '--key', key1);
|
||||
}
|
||||
if (audios) {
|
||||
args.splice(1, 0, '-sa', audios);
|
||||
} else {
|
||||
args.splice(1, 0, '-sa', 'best2');
|
||||
}
|
||||
if (resolution) {
|
||||
args.splice(1, 0, '-sv', 'res=' + resolution);
|
||||
} else {
|
||||
args.splice(1, 0, '-sv', 'best');
|
||||
}
|
||||
|
||||
if (delay) {
|
||||
args.splice(1, 0, '--live-pipe-options', '-itsoffset ' + delay + ' -re -loglevel error {INPUTS} -c copy -f mpegts -fflags +genpts -shortest pipe:1');
|
||||
}
|
||||
|
||||
console.log(`Args: ${args}`);
|
||||
|
||||
const streamlinkProcess = spawn('N_m3u8DL-RE', args);
|
||||
|
||||
const processID = `${channel.name}_${clientID}`;
|
||||
activeStreams[processID] = streamlinkProcess;
|
||||
|
||||
return streamlinkProcess;
|
||||
}
|
||||
|
||||
app.get('/stream/:channelName', async (req, res) => {
|
||||
const channelName = req.params.channelName;
|
||||
const clientID = req.ip;
|
||||
|
||||
const channel = channels[channelName];
|
||||
console.log(`Received request for /stream/${channelName} from Client IP: ${clientID}`);
|
||||
channels[channelName]['name'] = channelName;
|
||||
|
||||
if (!channel) {
|
||||
res.status(404).send('Channel not found');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Starting Streamlink command for channel: ${channelName} (Client IP: ${clientID})`);
|
||||
|
||||
try {
|
||||
let streamlinkProcess = await startStreamlink(channel, clientID);
|
||||
|
||||
res.setHeader('Content-Type', 'video/MP2T');
|
||||
streamlinkProcess.stdout.pipe(res);
|
||||
|
||||
streamlinkProcess.stderr.on('data', data => {
|
||||
console.error(`Streamlink: ${data}`);
|
||||
});
|
||||
|
||||
streamlinkProcess.on('close', code => {
|
||||
console.log(`Streamlink process exited with code ${code} for Client IP: ${clientID}`);
|
||||
res.end();
|
||||
const processID = `${channelName}_${clientID}`;
|
||||
delete activeStreams[processID];
|
||||
const tempFolderPath = tmp_folder + `${channel.name}_${clientID}`;
|
||||
if (fs.existsSync(tempFolderPath)) {
|
||||
fs.rmSync(tempFolderPath, { recursive: true });
|
||||
}
|
||||
logActiveConnections();
|
||||
});
|
||||
|
||||
res.on('close', () => {
|
||||
console.log(`Response closed, killing channel ${channel.name} for Client IP: ${clientID}`);
|
||||
const processID = `${channelName}_${clientID}`;
|
||||
if (activeStreams[processID]) {
|
||||
activeStreams[processID].kill();
|
||||
delete activeStreams[processID];
|
||||
}
|
||||
const tempFolderPath = tmp_folder + `${channel.name}_${clientID}`;
|
||||
if (fs.existsSync(tempFolderPath)) {
|
||||
fs.rmSync(tempFolderPath, { recursive: true });
|
||||
}
|
||||
logActiveConnections();
|
||||
});
|
||||
|
||||
req.on('abort', () => {
|
||||
console.log(`Request aborted, killing Streamlink process for Client IP: ${clientID}`);
|
||||
const processID = `${channelName}_${clientID}`;
|
||||
if (activeStreams[processID]) {
|
||||
activeStreams[processID].kill();
|
||||
delete activeStreams[processID];
|
||||
}
|
||||
const tempFolderPath = tmp_folder + `${channel.name}_${clientID}`;
|
||||
if (fs.existsSync(tempFolderPath)) {
|
||||
fs.rmSync(tempFolderPath, { recursive: true });
|
||||
}
|
||||
logActiveConnections();
|
||||
});
|
||||
|
||||
logActiveConnections();
|
||||
} catch (error) {
|
||||
console.error(`Streamlink failed: ${error}`);
|
||||
res.status(500).send('Streamlink failed');
|
||||
}
|
||||
});
|
||||
|
||||
function logActiveConnections() {
|
||||
const activeConnections = Object.keys(activeStreams).length;
|
||||
console.log(`Active connections: ${activeConnections}`);
|
||||
}
|
||||
|
||||
app.listen(port, "0.0.0.0", () => {
|
||||
console.log(`Server listening on port ${port}`);
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
for (const processID in activeStreams) {
|
||||
if (activeStreams.hasOwnProperty(processID)) {
|
||||
activeStreams[processID].kill();
|
||||
delete activeStreams[processID];
|
||||
}
|
||||
}
|
||||
process.exit();
|
||||
});
|
||||
104
server_nm3u8dl/server.js.old
Normal file
104
server_nm3u8dl/server.js.old
Normal file
@@ -0,0 +1,104 @@
|
||||
const express = require('express');
|
||||
const { spawn } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
let channelsPath = path.resolve(__dirname, 'channels.json');
|
||||
let channels = JSON.parse(fs.readFileSync(channelsPath, 'utf8'));
|
||||
|
||||
fs.watch(channelsPath, (eventType, filename) => {
|
||||
if (eventType === 'change') {
|
||||
console.log(`channels.json was updated, reloading...`);
|
||||
channels = JSON.parse(fs.readFileSync(channelsPath, 'utf8'));
|
||||
}
|
||||
});
|
||||
|
||||
const app = express();
|
||||
const port = 8080;
|
||||
const tmp_folder = '/tmp/ramdisk/mpegts'
|
||||
|
||||
async function startStreamlink(channel) {
|
||||
const useragent = channel.useragent;
|
||||
const authorization = channel.authorization;
|
||||
const proxy = channel.proxy;
|
||||
const key1 = channel.key1;
|
||||
const key2 = channel.key2;
|
||||
const key3 = channel.key3;
|
||||
let resolution = channel.resolution;
|
||||
resolution = 'res=' + resolution;
|
||||
|
||||
let args = [
|
||||
channel.url,
|
||||
'--header', `User-Agent:${useragent}`,
|
||||
'--use-shaka-packager',
|
||||
'--log-level', 'INFO',
|
||||
'--live-real-time-merge',
|
||||
'--live-pipe-mux',
|
||||
'--live-keep-segments', 'false',
|
||||
'-sv', 'best',
|
||||
'-sa', 'best2',
|
||||
'--tmp-dir', tmp_folder,
|
||||
'--del-after-done', 'true'
|
||||
];
|
||||
if (authorization) {
|
||||
args.splice(2, 0, '--header', `Authorization=${authorization}`);
|
||||
}
|
||||
if (proxy) {
|
||||
args.splice(2, 0, '--custom-proxy', proxy);
|
||||
}
|
||||
if (key1) {
|
||||
args.splice(4, 0, '--key', key1);
|
||||
}
|
||||
if (key2) {
|
||||
args.splice(6, 0, '--key', key2);
|
||||
}
|
||||
if (key3) {
|
||||
args.splice(6, 0, '--key', key3);
|
||||
}
|
||||
return spawn('N_m3u8DL-RE', args);
|
||||
}
|
||||
|
||||
app.get('/stream/:channelName', async (req, res) => {
|
||||
const channelName = req.params.channelName;
|
||||
console.log(`Received request for /stream/${channelName}`);
|
||||
|
||||
const channel = channels[channelName];
|
||||
|
||||
if (!channel) {
|
||||
res.status(404).send('Channel not found');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Starting Streamlink command for channel: ${channelName}`);
|
||||
|
||||
try {
|
||||
let streamlinkProcess = await startStreamlink(channel);
|
||||
res.setHeader('Content-Type', 'video/MP2T');
|
||||
streamlinkProcess.stdout.pipe(res);
|
||||
streamlinkProcess.stderr.on('data', data => {
|
||||
console.error(`Streamlink: ${data}`);
|
||||
});
|
||||
streamlinkProcess.on('close', code => {
|
||||
console.log(`Streamlink process exited with code ${code}`);
|
||||
res.end();
|
||||
});
|
||||
res.on('close', () => {
|
||||
console.log(`Response closed, killing Streamlink process`);
|
||||
streamlinkProcess.kill();
|
||||
if (fs.existsSync(tmp_folder)) {
|
||||
fs.rmSync(tmp_folder, { recursive: true });
|
||||
}
|
||||
});
|
||||
req.on('abort', () => {
|
||||
console.log(`Request aborted, killing Streamlink process`);
|
||||
streamlinkProcess.kill();
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Streamlink failed: ${error}`);
|
||||
res.status(500).send('Streamlink failed');
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Server listening on port ${port}`);
|
||||
});
|
||||
Reference in New Issue
Block a user