From cd1d699886d5cff3c8a23ba220c0e2fd2d0ce290 Mon Sep 17 00:00:00 2001 From: Blobadoodle Date: Sat, 19 Aug 2023 17:42:10 +0100 Subject: [PATCH 1/4] feat: streamable support --- src/modules/processing/match.js | 8 ++++ src/modules/processing/matchActionDecider.js | 1 + src/modules/processing/services/streamable.js | 21 +++++++++++ src/modules/processing/servicesConfig.json | 5 +++ .../processing/servicesPatternTesters.js | 4 +- src/test/tests.json | 37 +++++++++++++++++++ 6 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 src/modules/processing/services/streamable.js diff --git a/src/modules/processing/match.js b/src/modules/processing/match.js index 70a31a21..0552f2da 100644 --- a/src/modules/processing/match.js +++ b/src/modules/processing/match.js @@ -18,6 +18,7 @@ import soundcloud from "./services/soundcloud.js"; import instagram from "./services/instagram.js"; import vine from "./services/vine.js"; import pinterest from "./services/pinterest.js"; +import streamable from "./services/streamable.js"; export default async function (host, patternMatch, url, lang, obj) { try { @@ -114,6 +115,13 @@ export default async function (host, patternMatch, url, lang, obj) { case "pinterest": r = await pinterest({ id: patternMatch["id"] }); break; + case "streamable": + r = await streamable({ + id: patternMatch["id"], + quality: obj.vQuality, + isAudioOnly: isAudioOnly, + }); + break; default: return apiJSON(0, { t: errorUnsupported(lang) }); } diff --git a/src/modules/processing/matchActionDecider.js b/src/modules/processing/matchActionDecider.js index 6d1b46b5..e46991fa 100644 --- a/src/modules/processing/matchActionDecider.js +++ b/src/modules/processing/matchActionDecider.js @@ -56,6 +56,7 @@ export default function(r, host, audioFormat, isAudioOnly, lang, isAudioMuted) { case "tumblr": case "twitter": case "pinterest": + case "streamable": responseType = 1; break; } diff --git a/src/modules/processing/services/streamable.js b/src/modules/processing/services/streamable.js new file mode 100644 index 00000000..9e5da11a --- /dev/null +++ b/src/modules/processing/services/streamable.js @@ -0,0 +1,21 @@ +export default async function(obj) { + const video = await fetch(`https://api.streamable.com/videos/${obj.id}`) + .then((r) => { + if (r.status === 404) + return undefined; + else + return r.json(); + }).catch(() => { return false }); + + if (video === undefined) return { error: 'ErrorEmptyDownload' } ; + else if (!video) return { error: 'ErrorCouldntFetch' }; // not sure if this is the correct error here, should it be this or ErrorCantConnectToServiceAPI + + let best; + if (obj.isAudioOnly || obj.quality === "max" || obj.quality >= "720") // audio seems to be compressed on the mp4-mobile version so isAudiOnly only usese the higest quality + best = video.files.mp4 ?? video.files['mp4-mobile']; + else + best = video.files['mp4-mobile'] ?? video.files.mp4; + + if (best) return { urls: best.url, filename: `streamable_${obj.id}_${best.width}x${best.height}.mp4`, audioFilename: `streamable_${obj.id}_audio` }; // video filename isnt actually used since its redirected but who cares + else return { error: 'ErrorEmptyDownload' } +} diff --git a/src/modules/processing/servicesConfig.json b/src/modules/processing/servicesConfig.json index a9219354..69dad6ab 100644 --- a/src/modules/processing/servicesConfig.json +++ b/src/modules/processing/servicesConfig.json @@ -67,6 +67,11 @@ "alias": "pinterest videos & stories", "patterns": ["pin/:id"], "enabled": true + }, + "streamable": { + "alias": "streamable videos", + "patterns": [":id", "o/:id"], + "enabled": true } } } diff --git a/src/modules/processing/servicesPatternTesters.js b/src/modules/processing/servicesPatternTesters.js index c336afcc..5c759acf 100644 --- a/src/modules/processing/servicesPatternTesters.js +++ b/src/modules/processing/servicesPatternTesters.js @@ -30,5 +30,7 @@ export const testers = { "vine": (patternMatch) => (patternMatch["id"] && patternMatch["id"].length <= 12), - "pinterest": (patternMatch) => (patternMatch["id"] && patternMatch["id"].length <= 128) + "pinterest": (patternMatch) => (patternMatch["id"] && patternMatch["id"].length <= 128), + + "streamable": (patternMatch) => (patternMatch["id"] && patternMatch["id"].length == 6) } diff --git a/src/test/tests.json b/src/test/tests.json index bfac3bec..c777f9e4 100644 --- a/src/test/tests.json +++ b/src/test/tests.json @@ -983,5 +983,42 @@ "code": 200, "status": "redirect" } + }], + "streamable": [{ + "name": "regular video", + "url": "https://streamable.com/03r3c2", + "params": {}, + "expected": { + "code": 200, + "status": "redirect" + } + }, { + "name": "regular video (isAudioOnly)", + "url": "https://streamable.com/03r3c2", + "params": { + "isAudioOnly": true + }, + "expected": { + "code": 200, + "status": "stream" + } + }, { + "name": "regular video (isAudioMuted)", + "url": "https://streamable.com/03r3c2", + "params": { + "isAudioMuted": true + }, + "expected": { + "code": 200, + "status": "stream" + } + }, { + "name": "inexistent video", + "url": "https://streamable.com/XXXXXX", + "params": {}, + "expected": { + "code": 400, + "status": "error" + } }] } \ No newline at end of file From df894864ce9ca8f07940c2e4cabf79903790b243 Mon Sep 17 00:00:00 2001 From: Blobadoodle Date: Sat, 19 Aug 2023 18:34:43 +0100 Subject: [PATCH 2/4] useless comments --- src/modules/processing/services/streamable.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/processing/services/streamable.js b/src/modules/processing/services/streamable.js index 9e5da11a..7a70d4bb 100644 --- a/src/modules/processing/services/streamable.js +++ b/src/modules/processing/services/streamable.js @@ -8,14 +8,14 @@ export default async function(obj) { }).catch(() => { return false }); if (video === undefined) return { error: 'ErrorEmptyDownload' } ; - else if (!video) return { error: 'ErrorCouldntFetch' }; // not sure if this is the correct error here, should it be this or ErrorCantConnectToServiceAPI + else if (!video) return { error: 'ErrorCouldntFetch' }; let best; - if (obj.isAudioOnly || obj.quality === "max" || obj.quality >= "720") // audio seems to be compressed on the mp4-mobile version so isAudiOnly only usese the higest quality + if (obj.isAudioOnly || obj.quality === "max" || obj.quality >= "720") // audio seems to be compressed on the mp4-mobile version so isAudioOnly only uses the higest quality best = video.files.mp4 ?? video.files['mp4-mobile']; else best = video.files['mp4-mobile'] ?? video.files.mp4; - if (best) return { urls: best.url, filename: `streamable_${obj.id}_${best.width}x${best.height}.mp4`, audioFilename: `streamable_${obj.id}_audio` }; // video filename isnt actually used since its redirected but who cares + if (best) return { urls: best.url, filename: `streamable_${obj.id}_${best.width}x${best.height}.mp4`, audioFilename: `streamable_${obj.id}_audio` }; else return { error: 'ErrorEmptyDownload' } } From 9af60e1e6ba4095b56532be6b51e5143773e9b2f Mon Sep 17 00:00:00 2001 From: Blobadoodle Date: Sat, 19 Aug 2023 18:58:01 +0100 Subject: [PATCH 3/4] === --- src/modules/processing/servicesPatternTesters.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/processing/servicesPatternTesters.js b/src/modules/processing/servicesPatternTesters.js index 5c759acf..cd4d4d61 100644 --- a/src/modules/processing/servicesPatternTesters.js +++ b/src/modules/processing/servicesPatternTesters.js @@ -32,5 +32,5 @@ export const testers = { "pinterest": (patternMatch) => (patternMatch["id"] && patternMatch["id"].length <= 128), - "streamable": (patternMatch) => (patternMatch["id"] && patternMatch["id"].length == 6) + "streamable": (patternMatch) => (patternMatch["id"] && patternMatch["id"].length === 6) } From 890b7c8924945fff26f5d7953450a5eebc1f941f Mon Sep 17 00:00:00 2001 From: wukko Date: Sun, 20 Aug 2023 15:18:21 +0600 Subject: [PATCH 4/4] streamable: clean up + more patterns --- src/modules/processing/services/streamable.js | 32 +++++++++---------- src/modules/processing/servicesConfig.json | 4 +-- src/test/tests.json | 8 +++++ 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/modules/processing/services/streamable.js b/src/modules/processing/services/streamable.js index 7a70d4bb..29b6dece 100644 --- a/src/modules/processing/services/streamable.js +++ b/src/modules/processing/services/streamable.js @@ -1,21 +1,19 @@ export default async function(obj) { - const video = await fetch(`https://api.streamable.com/videos/${obj.id}`) - .then((r) => { - if (r.status === 404) - return undefined; - else - return r.json(); - }).catch(() => { return false }); + let video = await fetch(`https://api.streamable.com/videos/${obj.id}`).then((r) => { return r.status === 200 ? r.json() : false }).catch(() => { return false }); + if (!video) return { error: 'ErrorEmptyDownload' }; - if (video === undefined) return { error: 'ErrorEmptyDownload' } ; - else if (!video) return { error: 'ErrorCouldntFetch' }; + let best = video.files['mp4-mobile']; + if (video.files.mp4 && (obj.isAudioOnly || obj.quality === "max" || obj.quality >= Number("720"))) { + best = video.files.mp4; + } - let best; - if (obj.isAudioOnly || obj.quality === "max" || obj.quality >= "720") // audio seems to be compressed on the mp4-mobile version so isAudioOnly only uses the higest quality - best = video.files.mp4 ?? video.files['mp4-mobile']; - else - best = video.files['mp4-mobile'] ?? video.files.mp4; - - if (best) return { urls: best.url, filename: `streamable_${obj.id}_${best.width}x${best.height}.mp4`, audioFilename: `streamable_${obj.id}_audio` }; - else return { error: 'ErrorEmptyDownload' } + if (best) return { + urls: best.url, + filename: `streamable_${obj.id}_${best.width}x${best.height}.mp4`, + audioFilename: `streamable_${obj.id}_audio`, + metadata: { + title: video.title + } + } + return { error: 'ErrorEmptyDownload' } } diff --git a/src/modules/processing/servicesConfig.json b/src/modules/processing/servicesConfig.json index 69dad6ab..aadc3926 100644 --- a/src/modules/processing/servicesConfig.json +++ b/src/modules/processing/servicesConfig.json @@ -2,7 +2,7 @@ "audioIgnore": ["vk"], "config": { "bilibili": { - "alias": "bilibili (.com only)", + "alias": "bilibili.com videos", "patterns": ["video/:id"], "enabled": true }, @@ -70,7 +70,7 @@ }, "streamable": { "alias": "streamable videos", - "patterns": [":id", "o/:id"], + "patterns": [":id", "o/:id", "e/:id", "s/:id"], "enabled": true } } diff --git a/src/test/tests.json b/src/test/tests.json index c777f9e4..e72421df 100644 --- a/src/test/tests.json +++ b/src/test/tests.json @@ -992,6 +992,14 @@ "code": 200, "status": "redirect" } + }, { + "name": "embedded link", + "url": "https://streamable.com/e/rsmo56", + "params": {}, + "expected": { + "code": 200, + "status": "redirect" + } }, { "name": "regular video (isAudioOnly)", "url": "https://streamable.com/03r3c2",