From 08c7aa1ce11b5092e545ae26851554e08543fdd3 Mon Sep 17 00:00:00 2001 From: wukko Date: Sun, 23 Jun 2024 22:13:36 +0600 Subject: [PATCH 1/7] stream: add support for remuxing multiple m3u8 files --- src/modules/processing/matchActionDecider.js | 14 ++++++++++---- src/modules/stream/types.js | 4 ++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/modules/processing/matchActionDecider.js b/src/modules/processing/matchActionDecider.js index f7ed3da9..74f0f8c7 100644 --- a/src/modules/processing/matchActionDecider.js +++ b/src/modules/processing/matchActionDecider.js @@ -24,7 +24,7 @@ export default function(r, host, userFormat, isAudioOnly, lang, isAudioMuted, di else if (r.isGif && toGif) action = "gif"; else if (isAudioMuted) action = "muteVideo"; else if (isAudioOnly) action = "audio"; - else if (r.isM3U8) action = "singleM3U8"; + else if (r.isM3U8) action = "m3u8"; else action = "video"; if (action === "picker" || action === "audio") { @@ -48,13 +48,19 @@ export default function(r, host, userFormat, isAudioOnly, lang, isAudioMuted, di params = { type: "gif" } break; - case "singleM3U8": - params = { type: "remux" } + case "m3u8": + params = { + type: Array.isArray(r.urls) ? "render" : "remux" + } break; case "muteVideo": + let muteType = "mute"; + if (Array.isArray(r.urls) && !r.isM3U8) { + muteType = "bridge"; + } params = { - type: Array.isArray(r.urls) ? "bridge" : "mute", + type: muteType, u: Array.isArray(r.urls) ? r.urls[0] : r.urls, mute: true } diff --git a/src/modules/stream/types.js b/src/modules/stream/types.js index 000b7f7f..af4aa2e5 100644 --- a/src/modules/stream/types.js +++ b/src/modules/stream/types.js @@ -92,6 +92,10 @@ export function streamLiveRender(streamInfo, res) { args = args.concat(ffmpegArgs[format]); + if (hlsExceptions.includes(streamInfo.service)) { + args.push('-bsf:a', 'aac_adtstoasc') + } + if (streamInfo.metadata) { args = args.concat(metadataManager(streamInfo.metadata)) } From eb05c4b938cebc86a0702527f388e7e6395f4405 Mon Sep 17 00:00:00 2001 From: wukko Date: Sun, 23 Jun 2024 22:17:11 +0600 Subject: [PATCH 2/7] stream/internal-hls: transform HLS map when defined in playlist header --- src/modules/stream/internal-hls.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/modules/stream/internal-hls.js b/src/modules/stream/internal-hls.js index 9fa37e6e..3ed7e17b 100644 --- a/src/modules/stream/internal-hls.js +++ b/src/modules/stream/internal-hls.js @@ -23,6 +23,10 @@ function transformObject(streamInfo, hlsObject) { hlsObject.uri = createInternalStream(fullUrl.toString(), streamInfo); + if (hlsObject.map) { + hlsObject.map = transformObject(streamInfo, hlsObject.map); + } + return hlsObject; } From 0432232ea4211c9abc405159e722b5cddd51bf72 Mon Sep 17 00:00:00 2001 From: wukko Date: Sun, 23 Jun 2024 23:02:57 +0600 Subject: [PATCH 3/7] vimeo: use HLS playlists instead of dash manifest --- src/modules/processing/services/vimeo.js | 60 ++++++++++++++++-------- 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/src/modules/processing/services/vimeo.js b/src/modules/processing/services/vimeo.js index 69a36eca..96364dac 100644 --- a/src/modules/processing/services/vimeo.js +++ b/src/modules/processing/services/vimeo.js @@ -1,6 +1,8 @@ import { env } from "../../config.js"; import { cleanString } from '../../sub/utils.js'; +import HLS from "hls-parser"; + const resolutionMatch = { "3840": "2160", "2732": "1440", @@ -33,7 +35,7 @@ export default async function(obj) { url.searchParams.set('h', obj.password); } - let api = await fetch(url) + const api = await fetch(url) .then(r => r.json()) .catch(() => {}); if (!api) return { error: 'ErrorCouldntFetch' }; @@ -43,14 +45,15 @@ export default async function(obj) { if (!obj.isAudioOnly && JSON.stringify(api).includes('"progressive":[{')) downloadType = "progressive"; - let fileMetadata = { + const fileMetadata = { title: cleanString(api.video.title.trim()), artist: cleanString(api.video.owner.name.trim()), } if (downloadType !== "dash") { if (qualityMatch[quality]) quality = qualityMatch[quality]; - let all = api.request.files.progressive.sort((a, b) => Number(b.width) - Number(a.width)); + + const all = api.request.files.progressive.sort((a, b) => Number(b.width) - Number(a.width)); let best = all[0]; let bestQuality = all[0].quality.split('p')[0]; @@ -59,7 +62,7 @@ export default async function(obj) { } if (Number(quality) < Number(bestQuality)) { - best = all.find(i => i.quality.split('p')[0] === quality); + best = all.find(v => v.quality.split('p')[0] === quality); } if (!best) return { error: 'ErrorEmptyDownload' }; @@ -74,26 +77,43 @@ export default async function(obj) { if (api.video.duration > env.durationLimit) return { error: ['ErrorLengthLimit', env.durationLimit / 60] }; - let masterJSONURL = api.request.files.dash.cdns.akfire_interconnect_quic.url; - let masterJSON = await fetch(masterJSONURL).then(r => r.json()).catch(() => {}); + const urlMasterHLS = api.request.files.hls.cdns.akfire_interconnect_quic.url; - if (!masterJSON) return { error: 'ErrorCouldntFetch' }; - if (!masterJSON.video) return { error: 'ErrorEmptyDownload' }; + const masterHLS = await fetch(urlMasterHLS) + .then(r => r.text()) + .catch(() => {}); - let masterJSON_Video = masterJSON.video - .sort((a, b) => Number(b.width) - Number(a.width)) - .filter(a => ["dash", "mp42"].includes(a.format)); + if (!masterHLS) return { error: 'ErrorCouldntFetch' }; - let bestVideo = masterJSON_Video[0]; - if (Number(quality) < Number(resolutionMatch[bestVideo.width])) { - bestVideo = masterJSON_Video.find(i => resolutionMatch[i.width] === quality) + const variants = HLS.parse(masterHLS).variants.sort( + (a, b) => Number(b.bandwidth) - Number(a.bandwidth) + ); + if (!variants) return { error: 'ErrorEmptyDownload' }; + + let bestQuality; + if (Number(quality) < Number(resolutionMatch[variants[0].resolution.width])) { + bestQuality = variants.find(v => + (Number(quality) === Number(resolutionMatch[v.resolution.width])) + ); + } + if (!bestQuality) bestQuality = variants[0]; + + const expandLink = (url) => { + return new URL(url, urlMasterHLS).toString(); + }; + + let urls = expandLink(bestQuality.uri); + + const audioPath = bestQuality?.audio[0]?.uri; + if (audioPath) { + urls = [ + urls, + expandLink(audioPath) + ] } - let masterM3U8 = `${masterJSONURL.split("/sep/")[0]}/sep/video/${bestVideo.id}/master.m3u8`; - const fallbackResolution = bestVideo.height > bestVideo.width ? bestVideo.width : bestVideo.height; - return { - urls: masterM3U8, + urls, isM3U8: true, fileMetadata: fileMetadata, filenameAttributes: { @@ -101,8 +121,8 @@ export default async function(obj) { id: obj.id, title: fileMetadata.title, author: fileMetadata.artist, - resolution: `${bestVideo.width}x${bestVideo.height}`, - qualityLabel: `${resolutionMatch[bestVideo.width] || fallbackResolution}p`, + resolution: `${bestQuality.resolution.width}x${bestQuality.resolution.height}`, + qualityLabel: `${resolutionMatch[bestQuality.resolution.width]}p`, extension: "mp4" } } From cc4abbb3e26491daf211f47b935e7566a095ecb1 Mon Sep 17 00:00:00 2001 From: wukko Date: Sun, 23 Jun 2024 23:15:05 +0600 Subject: [PATCH 4/7] vimeo: remove progressive parsing it's no longer returned by the api --- src/modules/processing/services/vimeo.js | 39 ------------------------ 1 file changed, 39 deletions(-) diff --git a/src/modules/processing/services/vimeo.js b/src/modules/processing/services/vimeo.js index 96364dac..e48317d6 100644 --- a/src/modules/processing/services/vimeo.js +++ b/src/modules/processing/services/vimeo.js @@ -16,16 +16,6 @@ const resolutionMatch = { "426": "240" } -const qualityMatch = { - "2160": "4K", - "1440": "2K", - "480": "540", - - "4K": "2160", - "2K": "1440", - "540": "480" -} - export default async function(obj) { let quality = obj.quality === "max" ? "9000" : obj.quality; if (!quality || obj.isAudioOnly) quality = "9000"; @@ -40,40 +30,11 @@ export default async function(obj) { .catch(() => {}); if (!api) return { error: 'ErrorCouldntFetch' }; - let downloadType = "dash"; - - if (!obj.isAudioOnly && JSON.stringify(api).includes('"progressive":[{')) - downloadType = "progressive"; - const fileMetadata = { title: cleanString(api.video.title.trim()), artist: cleanString(api.video.owner.name.trim()), } - if (downloadType !== "dash") { - if (qualityMatch[quality]) quality = qualityMatch[quality]; - - const all = api.request.files.progressive.sort((a, b) => Number(b.width) - Number(a.width)); - let best = all[0]; - - let bestQuality = all[0].quality.split('p')[0]; - if (qualityMatch[bestQuality]) { - bestQuality = qualityMatch[bestQuality] - } - - if (Number(quality) < Number(bestQuality)) { - best = all.find(v => v.quality.split('p')[0] === quality); - } - - if (!best) return { error: 'ErrorEmptyDownload' }; - - return { - urls: best.url, - audioFilename: `vimeo_${obj.id}_audio`, - filename: `vimeo_${obj.id}_${best.width}x${best.height}.mp4` - } - } - if (api.video.duration > env.durationLimit) return { error: ['ErrorLengthLimit', env.durationLimit / 60] }; From 850877369c05b1f6fb999bc2cdffc4a76e68e543 Mon Sep 17 00:00:00 2001 From: wukko Date: Sun, 23 Jun 2024 23:22:58 +0600 Subject: [PATCH 5/7] vimeo: clean up & fix 144p quality --- src/modules/processing/services/vimeo.js | 31 +++++++++++++----------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/modules/processing/services/vimeo.js b/src/modules/processing/services/vimeo.js index e48317d6..2198bae7 100644 --- a/src/modules/processing/services/vimeo.js +++ b/src/modules/processing/services/vimeo.js @@ -4,21 +4,22 @@ import { cleanString } from '../../sub/utils.js'; import HLS from "hls-parser"; const resolutionMatch = { - "3840": "2160", - "2732": "1440", - "2560": "1440", - "2048": "1080", - "1920": "1080", - "1366": "720", - "1280": "720", - "960": "480", - "640": "360", - "426": "240" + "3840": 2160, + "2732": 1440, + "2560": 1440, + "2048": 1080, + "1920": 1080, + "1366": 720, + "1280": 720, + "960": 480, + "640": 360, + "426": 240 } export default async function(obj) { - let quality = obj.quality === "max" ? "9000" : obj.quality; - if (!quality || obj.isAudioOnly) quality = "9000"; + let quality = obj.quality === "max" ? 9000 : Number(obj.quality); + if (quality < 240) quality = 240; + if (!quality || obj.isAudioOnly) quality = 9000; const url = new URL(`https://player.vimeo.com/video/${obj.id}/config`); if (obj.password) { @@ -52,9 +53,9 @@ export default async function(obj) { if (!variants) return { error: 'ErrorEmptyDownload' }; let bestQuality; - if (Number(quality) < Number(resolutionMatch[variants[0].resolution.width])) { + if (quality < resolutionMatch[variants[0].resolution.width]) { bestQuality = variants.find(v => - (Number(quality) === Number(resolutionMatch[v.resolution.width])) + (quality === resolutionMatch[v.resolution.width]) ); } if (!bestQuality) bestQuality = variants[0]; @@ -71,6 +72,8 @@ export default async function(obj) { urls, expandLink(audioPath) ] + } else if (obj.isAudioOnly) { + return { error: 'ErrorEmptyDownload' }; } return { From de7df94271e23e3cf5f7e3a0bf7400e7a1b4e166 Mon Sep 17 00:00:00 2001 From: wukko Date: Sun, 23 Jun 2024 23:26:43 +0600 Subject: [PATCH 6/7] vimeo: use proper local variable name in expandLink --- src/modules/processing/services/vimeo.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/processing/services/vimeo.js b/src/modules/processing/services/vimeo.js index 2198bae7..a80561f9 100644 --- a/src/modules/processing/services/vimeo.js +++ b/src/modules/processing/services/vimeo.js @@ -60,8 +60,8 @@ export default async function(obj) { } if (!bestQuality) bestQuality = variants[0]; - const expandLink = (url) => { - return new URL(url, urlMasterHLS).toString(); + const expandLink = (path) => { + return new URL(path, urlMasterHLS).toString(); }; let urls = expandLink(bestQuality.uri); From b51bcc2a7cd247b88bd73cf20624ec4023de7eb9 Mon Sep 17 00:00:00 2001 From: wukko Date: Sun, 23 Jun 2024 23:35:29 +0600 Subject: [PATCH 7/7] vimeo: added more checks to avoid exceptions --- src/modules/processing/services/vimeo.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/modules/processing/services/vimeo.js b/src/modules/processing/services/vimeo.js index a80561f9..d79d2e92 100644 --- a/src/modules/processing/services/vimeo.js +++ b/src/modules/processing/services/vimeo.js @@ -29,6 +29,7 @@ export default async function(obj) { const api = await fetch(url) .then(r => r.json()) .catch(() => {}); + if (!api) return { error: 'ErrorCouldntFetch' }; const fileMetadata = { @@ -36,10 +37,12 @@ export default async function(obj) { artist: cleanString(api.video.owner.name.trim()), } - if (api.video.duration > env.durationLimit) + if (api.video?.duration > env.durationLimit) return { error: ['ErrorLengthLimit', env.durationLimit / 60] }; - const urlMasterHLS = api.request.files.hls.cdns.akfire_interconnect_quic.url; + const urlMasterHLS = api.request?.files?.hls?.cdns?.akfire_interconnect_quic?.url; + + if (!urlMasterHLS) return { error: 'ErrorCouldntFetch' } const masterHLS = await fetch(urlMasterHLS) .then(r => r.text()) @@ -47,17 +50,19 @@ export default async function(obj) { if (!masterHLS) return { error: 'ErrorCouldntFetch' }; - const variants = HLS.parse(masterHLS).variants.sort( + const variants = HLS.parse(masterHLS)?.variants?.sort( (a, b) => Number(b.bandwidth) - Number(a.bandwidth) ); - if (!variants) return { error: 'ErrorEmptyDownload' }; + if (!variants || variants.length === 0) return { error: 'ErrorEmptyDownload' }; let bestQuality; - if (quality < resolutionMatch[variants[0].resolution.width]) { + + if (quality < resolutionMatch[variants[0]?.resolution?.width]) { bestQuality = variants.find(v => (quality === resolutionMatch[v.resolution.width]) ); } + if (!bestQuality) bestQuality = variants[0]; const expandLink = (path) => {