mirror of
https://github.com/wukko/cobalt.git
synced 2025-01-27 21:26:18 +01:00
Merge branch 'imputnet:main' into main
This commit is contained in:
commit
81c5f29b3a
10 changed files with 86 additions and 27 deletions
6
.github/test.sh
vendored
6
.github/test.sh
vendored
|
@ -14,7 +14,7 @@ waitport() {
|
||||||
test_api() {
|
test_api() {
|
||||||
waitport 3000
|
waitport 3000
|
||||||
curl -m 3 http://localhost:3000/
|
curl -m 3 http://localhost:3000/
|
||||||
API_RESPONSE=$(curl -m 3 http://localhost:3000/ \
|
API_RESPONSE=$(curl -m 10 http://localhost:3000/ \
|
||||||
-X POST \
|
-X POST \
|
||||||
-H "Accept: application/json" \
|
-H "Accept: application/json" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
|
@ -24,7 +24,7 @@ test_api() {
|
||||||
STATUS=$(echo "$API_RESPONSE" | jq -r .status)
|
STATUS=$(echo "$API_RESPONSE" | jq -r .status)
|
||||||
STREAM_URL=$(echo "$API_RESPONSE" | jq -r .url)
|
STREAM_URL=$(echo "$API_RESPONSE" | jq -r .url)
|
||||||
[ "$STATUS" = tunnel ] || exit 1;
|
[ "$STATUS" = tunnel ] || exit 1;
|
||||||
S=$(curl -I -m 3 "$STREAM_URL")
|
S=$(curl -I -m 10 "$STREAM_URL")
|
||||||
|
|
||||||
CONTENT_LENGTH=$(echo "$S" \
|
CONTENT_LENGTH=$(echo "$S" \
|
||||||
| grep -i content-length \
|
| grep -i content-length \
|
||||||
|
@ -64,4 +64,4 @@ else
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
wait || exit $?
|
wait || exit $?
|
||||||
|
|
|
@ -2,11 +2,19 @@ export default (f, style, isAudioOnly, isAudioMuted) => {
|
||||||
let filename = '';
|
let filename = '';
|
||||||
|
|
||||||
let infoBase = [f.service, f.id];
|
let infoBase = [f.service, f.id];
|
||||||
let classicTags = [...infoBase, f.resolution];
|
let classicTags = [...infoBase];
|
||||||
let basicTags = [f.qualityLabel];
|
let basicTags = [];
|
||||||
|
|
||||||
const title = `${f.title} - ${f.author}`;
|
const title = `${f.title} - ${f.author}`;
|
||||||
|
|
||||||
|
if (f.resolution) {
|
||||||
|
classicTags.push(f.resolution);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (f.qualityLabel) {
|
||||||
|
basicTags.push(f.qualityLabel);
|
||||||
|
}
|
||||||
|
|
||||||
if (f.youtubeFormat) {
|
if (f.youtubeFormat) {
|
||||||
classicTags.push(f.youtubeFormat);
|
classicTags.push(f.youtubeFormat);
|
||||||
basicTags.push(f.youtubeFormat);
|
basicTags.push(f.youtubeFormat);
|
||||||
|
|
|
@ -148,6 +148,7 @@ export default function({ r, host, audioFormat, isAudioOnly, isAudioMuted, disab
|
||||||
case "streamable":
|
case "streamable":
|
||||||
case "snapchat":
|
case "snapchat":
|
||||||
case "loom":
|
case "loom":
|
||||||
|
case "twitch":
|
||||||
responseType = "redirect";
|
responseType = "redirect";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@ import HLS from "hls-parser";
|
||||||
import { cobaltUserAgent } from "../../config.js";
|
import { cobaltUserAgent } from "../../config.js";
|
||||||
import { createStream } from "../../stream/manage.js";
|
import { createStream } from "../../stream/manage.js";
|
||||||
|
|
||||||
const extractVideo = async ({ getPost, filename }) => {
|
const extractVideo = async ({ media, filename }) => {
|
||||||
const urlMasterHLS = getPost?.thread?.post?.embed?.playlist;
|
const urlMasterHLS = media?.playlist;
|
||||||
if (!urlMasterHLS) return { error: "fetch.empty" };
|
if (!urlMasterHLS) return { error: "fetch.empty" };
|
||||||
if (!urlMasterHLS.startsWith("https://video.bsky.app/")) return { error: "fetch.empty" };
|
if (!urlMasterHLS.startsWith("https://video.bsky.app/")) return { error: "fetch.empty" };
|
||||||
|
|
||||||
|
@ -77,14 +77,37 @@ export default async function ({ user, post, alwaysProxy }) {
|
||||||
}
|
}
|
||||||
}).then(r => r.json()).catch(() => {});
|
}).then(r => r.json()).catch(() => {});
|
||||||
|
|
||||||
if (!getPost || getPost?.error) return { error: "fetch.empty" };
|
if (!getPost) return { error: "fetch.empty" };
|
||||||
|
|
||||||
|
if (getPost.error) {
|
||||||
|
switch (getPost.error) {
|
||||||
|
case "NotFound":
|
||||||
|
case "InternalServerError":
|
||||||
|
return { error: "content.post.unavailable" };
|
||||||
|
case "InvalidRequest":
|
||||||
|
return { error: "link.unsupported" };
|
||||||
|
default:
|
||||||
|
return { error: "fetch.empty" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const embedType = getPost?.thread?.post?.embed?.$type;
|
const embedType = getPost?.thread?.post?.embed?.$type;
|
||||||
const filename = `bluesky_${user}_${post}`;
|
const filename = `bluesky_${user}_${post}`;
|
||||||
|
|
||||||
if (embedType === "app.bsky.embed.video#view") {
|
if (embedType === "app.bsky.embed.video#view") {
|
||||||
return extractVideo({ getPost, filename });
|
return extractVideo({
|
||||||
|
media: getPost.thread?.post?.embed,
|
||||||
|
filename,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (embedType === "app.bsky.embed.recordWithMedia#view") {
|
||||||
|
return extractVideo({
|
||||||
|
media: getPost.thread?.post?.embed?.media,
|
||||||
|
filename,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (embedType === "app.bsky.embed.images#view") {
|
if (embedType === "app.bsky.embed.images#view") {
|
||||||
return extractImages({ getPost, filename, alwaysProxy });
|
return extractImages({ getPost, filename, alwaysProxy });
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,8 @@ const codecMatch = {
|
||||||
},
|
},
|
||||||
av1: {
|
av1: {
|
||||||
videoCodec: "av01",
|
videoCodec: "av01",
|
||||||
audioCodec: "mp4a",
|
audioCodec: "opus",
|
||||||
container: "mp4"
|
container: "webm"
|
||||||
},
|
},
|
||||||
vp9: {
|
vp9: {
|
||||||
videoCodec: "vp9",
|
videoCodec: "vp9",
|
||||||
|
|
|
@ -1113,7 +1113,7 @@
|
||||||
"params": {},
|
"params": {},
|
||||||
"expected": {
|
"expected": {
|
||||||
"code": 200,
|
"code": 200,
|
||||||
"status": "tunnel"
|
"status": "redirect"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1401,7 +1401,16 @@
|
||||||
"bsky": [
|
"bsky": [
|
||||||
{
|
{
|
||||||
"name": "horizontal video",
|
"name": "horizontal video",
|
||||||
"url": "https://bsky.app/profile/samuel.bsky.team/post/3l2udah76ch2c",
|
"url": "https://bsky.app/profile/haileyok.com/post/3l3giwtwp222m",
|
||||||
|
"params": {},
|
||||||
|
"expected": {
|
||||||
|
"code": 200,
|
||||||
|
"status": "tunnel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "horizontal video, recordWithMedia",
|
||||||
|
"url": "https://bsky.app/profile/juicysteak117.gay/post/3l3wonhk23g2i",
|
||||||
"params": {},
|
"params": {},
|
||||||
"expected": {
|
"expected": {
|
||||||
"code": 200,
|
"code": 200,
|
||||||
|
@ -1410,7 +1419,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "vertical video",
|
"name": "vertical video",
|
||||||
"url": "https://bsky.app/profile/samuel.bsky.team/post/3l2uftgmitr2p",
|
"url": "https://bsky.app/profile/haileyok.com/post/3l3jhpomhjk2m",
|
||||||
"params": {},
|
"params": {},
|
||||||
"expected": {
|
"expected": {
|
||||||
"code": 200,
|
"code": 200,
|
||||||
|
@ -1419,7 +1428,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "vertical video (muted)",
|
"name": "vertical video (muted)",
|
||||||
"url": "https://bsky.app/profile/samuel.bsky.team/post/3l2uftgmitr2p",
|
"url": "https://bsky.app/profile/haileyok.com/post/3l3jhpomhjk2m",
|
||||||
"params": {
|
"params": {
|
||||||
"downloadMode": "mute"
|
"downloadMode": "mute"
|
||||||
},
|
},
|
||||||
|
@ -1430,7 +1439,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "vertical video (audio)",
|
"name": "vertical video (audio)",
|
||||||
"url": "https://bsky.app/profile/samuel.bsky.team/post/3l2uftgmitr2p",
|
"url": "https://bsky.app/profile/haileyok.com/post/3l3jhpomhjk2m",
|
||||||
"params": {
|
"params": {
|
||||||
"downloadMode": "audio"
|
"downloadMode": "audio"
|
||||||
},
|
},
|
||||||
|
@ -1456,6 +1465,15 @@
|
||||||
"code": 200,
|
"code": 200,
|
||||||
"status": "picker"
|
"status": "picker"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "deleted post/invalid user",
|
||||||
|
"url": "https://bsky.app/profile/notreal.bsky.team/post/3l2udah76ch2c",
|
||||||
|
"params": {},
|
||||||
|
"expected": {
|
||||||
|
"code": 400,
|
||||||
|
"status": "error"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -32,13 +32,19 @@ cobalt package will update automatically thanks to watchtower.
|
||||||
|
|
||||||
it's highly recommended to use a reverse proxy (such as nginx) if you want your instance to face the public internet. look up tutorials online.
|
it's highly recommended to use a reverse proxy (such as nginx) if you want your instance to face the public internet. look up tutorials online.
|
||||||
|
|
||||||
## using regular node.js (useful for local development)
|
## run cobalt api outside of docker (useful for local development)
|
||||||
setup script installs all needed `npm` dependencies, but you have to install `node.js` *(version 18 or above)* and `git` yourself.
|
requirements:
|
||||||
|
- node.js >= 18
|
||||||
|
- git
|
||||||
|
- pnpm
|
||||||
|
|
||||||
1. clone the repo: `git clone https://github.com/imputnet/cobalt`.
|
1. clone the repo: `git clone https://github.com/imputnet/cobalt`.
|
||||||
2. run setup script and follow instructions: `npm run setup`. you need to host api and web instances separately, so pick whichever applies.
|
2. go to api/src directory: `cd cobalt/api/src`.
|
||||||
3. run cobalt via `npm start`.
|
3. install dependencies: `pnpm install`.
|
||||||
4. done.
|
4. create `.env` file in the same directory.
|
||||||
|
5. add needed environment variables to `.env` file. only `API_URL` is required to run cobalt.
|
||||||
|
- if you don't know what api url to use for local development, use `http://localhost:9000/`.
|
||||||
|
6. run cobalt: `pnpm start`.
|
||||||
|
|
||||||
### ubuntu 22.04 workaround
|
### ubuntu 22.04 workaround
|
||||||
`nscd` needs to be installed and running so that the `ffmpeg-static` binary can resolve DNS ([#101](https://github.com/imputnet/cobalt/issues/101#issuecomment-1494822258)):
|
`nscd` needs to be installed and running so that the `ffmpeg-static` binary can resolve DNS ([#101](https://github.com/imputnet/cobalt/issues/101#issuecomment-1494822258)):
|
||||||
|
@ -72,4 +78,4 @@ sudo service nscd start
|
||||||
setting a `FREEBIND_CIDR` allows cobalt to pick a random IP for every download and use it for all
|
setting a `FREEBIND_CIDR` allows cobalt to pick a random IP for every download and use it for all
|
||||||
requests it makes for that particular download. to use freebind in cobalt, you need to follow its [setup instructions](https://github.com/imputnet/freebind.js?tab=readme-ov-file#setup) first. if you configure this option while running cobalt
|
requests it makes for that particular download. to use freebind in cobalt, you need to follow its [setup instructions](https://github.com/imputnet/freebind.js?tab=readme-ov-file#setup) first. if you configure this option while running cobalt
|
||||||
in a docker container, you also need to set the `API_LISTEN_ADDRESS` env to `127.0.0.1`, and set
|
in a docker container, you also need to set the `API_LISTEN_ADDRESS` env to `127.0.0.1`, and set
|
||||||
`network_mode` for the container to `host`.
|
`network_mode` for the container to `host`.
|
||||||
|
|
|
@ -30,9 +30,6 @@
|
||||||
"video.quality.description": "if preferred video quality isn't available, next best is picked instead.",
|
"video.quality.description": "if preferred video quality isn't available, next best is picked instead.",
|
||||||
|
|
||||||
"video.youtube.codec": "youtube video codec and container",
|
"video.youtube.codec": "youtube video codec and container",
|
||||||
"video.youtube.codec.h264": "h264 (mp4)",
|
|
||||||
"video.youtube.codec.av1": "av1 (mp4)",
|
|
||||||
"video.youtube.codec.vp9": "vp9 (webm)",
|
|
||||||
"video.youtube.codec.description": "h264: best compatibility, average bitrate. max quality is 1080p. \nav1: best quality, efficiency, and bitrate. supports 8k & HDR. \nvp9: same quality & bitrate as av1, but file is approximately two times bigger. supports 4k & HDR.\n\nav1 and vp9 aren't as widely supported as h264.",
|
"video.youtube.codec.description": "h264: best compatibility, average bitrate. max quality is 1080p. \nav1: best quality, efficiency, and bitrate. supports 8k & HDR. \nvp9: same quality & bitrate as av1, but file is approximately two times bigger. supports 4k & HDR.\n\nav1 and vp9 aren't as widely supported as h264.",
|
||||||
|
|
||||||
"video.twitter.gif": "twitter/x",
|
"video.twitter.gif": "twitter/x",
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
<h3>leading privacy</h3>
|
<h3>leading privacy</h3>
|
||||||
<p>
|
<p>
|
||||||
all requests to backend are anonymous and all tunnels are encrypted.
|
all requests to backend are anonymous and all tunnels are encrypted.
|
||||||
we have a strict zero log policy and don't track <i>anything at all</i>.
|
we have a strict zero log policy and don't track <i>anything</i> about individual people.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
to avoid caching or storing downloaded files, cobalt processes them on-fly, sending processed pieces directly to client.
|
to avoid caching or storing downloaded files, cobalt processes them on-fly, sending processed pieces directly to client.
|
||||||
|
|
|
@ -8,6 +8,12 @@
|
||||||
import Switcher from "$components/buttons/Switcher.svelte";
|
import Switcher from "$components/buttons/Switcher.svelte";
|
||||||
import SettingsButton from "$components/buttons/SettingsButton.svelte";
|
import SettingsButton from "$components/buttons/SettingsButton.svelte";
|
||||||
import SettingsToggle from "$components/buttons/SettingsToggle.svelte";
|
import SettingsToggle from "$components/buttons/SettingsToggle.svelte";
|
||||||
|
|
||||||
|
const codecTitles = {
|
||||||
|
h264: "h264 (mp4)",
|
||||||
|
av1: "av1 (webm)",
|
||||||
|
vp9: "vp9 (webm)",
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SettingsCategory
|
<SettingsCategory
|
||||||
|
@ -41,7 +47,7 @@
|
||||||
settingId="youtubeVideoCodec"
|
settingId="youtubeVideoCodec"
|
||||||
settingValue={value}
|
settingValue={value}
|
||||||
>
|
>
|
||||||
{$t(`settings.video.youtube.codec.${value}`)}
|
{codecTitles[value]}
|
||||||
</SettingsButton>
|
</SettingsButton>
|
||||||
{/each}
|
{/each}
|
||||||
</Switcher>
|
</Switcher>
|
||||||
|
|
Loading…
Reference in a new issue