openvibers/openvibe.mediahub Commits

Download and Video: their own names and true copy (#73)

OP OpenVibers committed 29 days ago · 01c54275d4 · 8 files, +23 −22

* Download: the site is OpenVibe.Download (not MediaHub), its quota numbers come from the config, and public links are said to exist

* Video: the home says upload, record and the AI edit (and llms.txt no longer says uploads are not open)

Changes

ddiff --git a/server/app.js b/server/app.js
iindex b4ba416..3576804 100644
---- a/server/app.js
++++ b/server/app.js
@@@ -172,7 +172,7 @@ async function createApp(opts = {}) {
// through a proxy, so only loopback can reach it even though nginx proxies this path.
app.use('/internal/events', account.consumer({ send: createSender({ config, fetchImpl }) }));
-- { const legal = require('openvibe-shared/legal'); app.get(legal.PATHS, legal.handler({ id: 'media-hub', service: 'media-hub', host: 'openvibe.download', name: 'OpenVibe.MediaHub', profile: 'ugc' })); }
++ { const legal = require('openvibe-shared/legal'); app.get(legal.PATHS, legal.handler({ id: 'media-hub', service: 'media-hub', host: 'openvibe.download', name: 'OpenVibe.Download', profile: 'ugc' })); }
// ── Static assets (content-hashed ?v= → immutable) ──────
app.use('/shared', require('openvibe-shared/serve').handler());
ddiff --git a/server/http/discovery.js b/server/http/discovery.js
iindex 16fb6e1..bb05287 100644
---- a/server/http/discovery.js
++++ b/server/http/discovery.js
@@@ -13,15 +13,15 @@ const seo = require('openvibe-shared/seo');
const cache = require('openvibe-shared/cache-policy');
const { asyncRouter } = require('./router');
--const SITE_NAME = 'OpenVibe.MediaHub';
--const DESCRIPTION = 'OpenVibe.MediaHub — Your files, shared on your terms.';
++const SITE_NAME = 'OpenVibe.Download';
++const DESCRIPTION = 'OpenVibe.Download — Your files, shared on your terms.';
// /s/ (with the slash) rather than /s, or the rule would also swallow /safety, which is a public page.
const DISALLOW = ['/auth/', '/api/', '/files', '/s/', '/staff'];
const PAGE_TEXT = {
-- '/': ['OpenVibe.MediaHub home', 'OpenVibe.MediaHub: Your files, shared on your terms. A private drive for a signed-in OpenVibe account, with share links that expire, can be revoked and need the downloader to be signed in too.'],
++ '/': ['OpenVibe.Download home', 'OpenVibe.Download: Your files, shared on your terms. A private drive for a signed-in OpenVibe account, with share links that expire, can be revoked and need the downloader to be signed in too.'],
'/safety': ['Safety rules', 'What OpenVibe.Download allows and does not, who can upload and download, how reports work, and who to write to about abuse or copyright.'],
-- '/updates': ['What shipped on OpenVibe.MediaHub', 'This site\'s update log, from the network changelog feed.'],
++ '/updates': ['What shipped on OpenVibe.Download', 'This site\'s update log, from the network changelog feed.'],
};
function dayOf(ts) {
@@@ -67,11 +67,11 @@ function createDiscoveryRoutes(ctx) {
r.get('/llms.txt', (_req, res) => {
res.type('text/plain').set('Cache-Control', TEXT).send(seo.llmsTxt({
name: SITE_NAME,
-- summary: 'OpenVibe.MediaHub: Your files, shared on your terms.',
++ summary: 'OpenVibe.Download: Your files, shared on your terms.',
details: 'Every page is server-rendered and readable without JavaScript.',
sections: [
{ title: 'Start here', links: [
-- { title: 'OpenVibe.MediaHub', url: abs('/'), note: 'Your files, shared on your terms.' },
++ { title: 'OpenVibe.Download', url: abs('/'), note: 'Your files, shared on your terms.' },
{ title: 'Safety rules', url: abs('/safety'), note: 'what is allowed, how reports work, who to write to' },
{ title: 'What shipped on OpenVibe.MediaHub', url: abs('/updates') },
] },
ddiff --git a/server/http/pages.js b/server/http/pages.js
iindex 4687c68..ec2f0de 100644
---- a/server/http/pages.js
++++ b/server/http/pages.js
@@@ -151,7 +151,7 @@ ${notice(p && p.detail ? p.detail : 'You are over the limit for this.', 'warn')}
eyebrow: `${SITE_NAME} · a private drive`,
title: 'Your files,',
accent: 'shared on your terms.',
-- lede: 'A private drive for your files, 25 GB free, with share links you control: for named people, for anyone signed in, or a public download page anyone with the link can use. Uploading needs a signed-in OpenVibe account.',
++ lede: `A private drive for your files, ${rules.formatBytes(config.quotas.bytes)} free, with share links you control: for named people, for anyone signed in, or a public download page anyone with the link can use. Uploading needs a signed-in OpenVibe account.`,
actions: signedIn(req)
? [{ label: 'Your files', href: '/files', primary: true }, { label: 'The safety rules', href: '/safety' }]
: [{ label: 'Sign in with OpenVibe', href: '/auth/login?next=%2Ffiles', primary: true }, { label: 'The safety rules', href: '/safety' }],
@@@ -166,9 +166,9 @@ ${raw(showcase.features({
title: 'What you get',
lede: 'A drive, not a locker: everything here is yours, and everything you hand out can be taken back.',
items: [
-- { icon: 'ov:upload', title: 'Your own drive', text: 'Folders and files, private to you. 2 GB stored and 512 MB in one file, from this service\'s own configuration.' },
++ { icon: 'ov:upload', title: 'Your own drive', text: `Folders and files, private to you: ${rules.formatBytes(config.quotas.bytes)} stored, up to ${rules.formatBytes(config.quotas.maxFileBytes)} in one file.` },
{ icon: 'ov:link', title: 'Share links', text: 'One link per file or folder, expiring in an hour to seven days, revocable at any moment, with its downloads counted.' },
-- { icon: 'ov:account', title: 'Signed in, always', text: 'Nobody opens a share link without an OpenVibe account. No app tokens, no anonymous anything — a shared link still needs a person.' },
++ { icon: 'ov:account', title: 'You choose who opens it', text: 'Named OpenVibe accounts, anyone signed in, or a public download page for anyone with the link. Uploading always needs a signed-in person.' },
{ icon: 'ov:shield', title: 'Reports that act', text: REPORT_NOTE },
{ icon: 'ov:tools', title: 'A drive with an API', text: 'The same actions over /api/v1: list, upload, share, revoke, and see your usage.' },
{ icon: 'ov:db', title: 'Bytes in OpenVibe.Media', text: 'Files are objects in the network\'s own media service; deleting one here asks Media to delete it too.' },
ddiff --git a/server/render/layout.js b/server/render/layout.js
iindex 2ccb0fa..4e2b367 100644
---- a/server/render/layout.js
++++ b/server/render/layout.js
@@@ -19,7 +19,8 @@ const cache = require('openvibe-shared/cache-policy');
const { html, raw, esc } = require('./html');
const NETWORK_URL = 'https://openvibe.network';
--const SITE_NAME = 'OpenVibe.MediaHub';
++// openvibe.download's name (the service is MediaHub; Video and Pics name themselves in their own pages).
++const SITE_NAME = 'OpenVibe.Download';
const TAGLINE = 'Your files, shared on your terms.';
const PUBLIC_DIR = path.join(__dirname, '..', '..', 'public');
// The product's own navigation: one entry per public page it serves.
ddiff --git a/server/video/layout.js b/server/video/layout.js
iindex b6ddcbb..4af18cd 100644
---- a/server/video/layout.js
++++ b/server/video/layout.js
@@@ -19,7 +19,7 @@ const { html, raw, esc, render } = require('../render/html');
const NETWORK_URL = 'https://openvibe.network';
const SITE_NAME = 'OpenVibe.Video';
--const TAGLINE = 'Stream recordings and clips from OpenVibe.Live';
++const TAGLINE = 'Upload, record and watch, with AI captions and chapters';
const PUBLIC_DIR = path.join(__dirname, '..', '..', 'public');
const hashes = new Map();
@@@ -36,7 +36,7 @@ const asset = (rel) => `/${rel}?v=${assetVersion(rel)}`;
* jsonLd (structured data for the page), image (its share image, an https URL), ogType */
function renderPage(o) {
// The front page's title says what the site is (search results show it; at most 60 characters).
-- const title = o.title || (o.path === '/' ? `${SITE_NAME}: recordings and clips from OpenVibe.Live` : SITE_NAME);
++ const title = o.title || (o.path === '/' ? `${SITE_NAME}: upload, record and watch, with AI captions` : SITE_NAME);
const viewer = o.viewer || { kind: 'anonymous' };
const signedIn = viewer.kind === 'user';
const pathNow = o.path || '/';
ddiff --git a/server/video/pages.js b/server/video/pages.js
iindex 6a83d91..d25b017 100644
---- a/server/video/pages.js
++++ b/server/video/pages.js
@@@ -644,8 +644,8 @@ ${kind === 'vod' ? html`<label class="ovv-field"><span>Description</span><textar
description: 'Videos people upload, and stream recordings and clips from OpenVibe.Live with AI chapters and captions.',
body: html`<header class="ovv-hero"><p class="ovv-eyebrow"><i class="fa-solid fa-circle-play" aria-hidden="true"></i> OpenVibe.Video</p>
<h1>Watch, upload, stream.</h1>
--<p class="ovv-lede">Videos people upload here, and the recordings and clips of streams on OpenVibe.Live with AI chapters and captions. Sign in with your OpenVibe account to upload: it plays everywhere, on your channel at openvibe.video/@you.</p>
--<p class="ovv-hero-actions"><a class="ovv-btn ovv-btn--primary" href="/upload"><i class="fa-solid fa-upload" aria-hidden="true"></i> Upload a video</a> <a class="ovv-btn" href="${livePublic}/">Live now on OpenVibe.Live</a> <a class="ovv-btn" href="${livePublic}/docs/go-live-in-your-browser">Go live from your browser</a></p></header>
++<p class="ovv-lede">Upload a video or record your camera or screen right here, and OpenVibe.AI adds captions, chapters and a summary you can use as the description. The recordings and clips of streams on OpenVibe.Live are here too. Everything you post plays on your channel at openvibe.video/@you.</p>
++<p class="ovv-hero-actions"><a class="ovv-btn ovv-btn--primary" href="/upload"><i class="fa-solid fa-upload" aria-hidden="true"></i> Upload a video</a> <a class="ovv-btn" href="/record"><i class="fa-solid fa-video" aria-hidden="true"></i> Record in the browser</a> <a class="ovv-btn" href="${livePublic}/">Live now on OpenVibe.Live</a> <a class="ovv-btn" href="${livePublic}/docs/go-live-in-your-browser">Go live from your browser</a></p></header>
${uploaded.length ? html`<section class="ovv-section" aria-labelledby="h-uploads"><h2 id="h-uploads">Latest uploads</h2>${grid(uploaded.map(uploadCard))}</section>` : ''}
<section class="ovv-section" aria-labelledby="h-vods"><h2 id="h-vods">Latest recordings</h2>${grid(vodItems.map(feedCard), 'No recordings yet.')}</section>
<section class="ovv-section" aria-labelledby="h-clips"><h2 id="h-clips">Latest clips</h2>${grid(clipItems.map(feedCard), 'No clips yet.')}</section>`,
@@@ -1188,8 +1188,8 @@ ${isOwner ? html`<p class="ovv-fine">You can reorder items with the up and down
const links = (items, kind) => items.map((it) => ({ title: it.title || (kind === 'vod' ? 'Recording' : 'Clip'), url: `${base}/${kind === 'vod' ? 'v' : 'c'}/${it.id}` }));
return res.type('text/plain').set('Cache-Control', cache.htmlHeaders({ maxAge: 3600 })).send(seo.llmsTxt({
name: SITE_NAME,
-- summary: 'Public watch pages for stream recordings and clips from OpenVibe.Live.',
-- details: 'AI chapters and captions are rendered on the watch pages. Uploads are not open.',
++ summary: 'Videos people upload or record in the browser, and stream recordings and clips from OpenVibe.Live, each with a watch page.',
++ details: 'AI captions, chapters and a summary are made for uploads (OpenVibe.AI, on OpenVibe\'s own servers) and rendered on the watch pages, as are the AI chapters and captions of stream recordings. Uploading needs a signed-in OpenVibe account; /upload and /record.',
sections: [
{ title: 'Start here', links: [{ title: SITE_NAME, url: `${base}/` }] },
{ title: 'Latest recordings', links: links(vodItems, 'vod') },
ddiff --git a/test/discovery.test.js b/test/discovery.test.js
iindex 496c73c..a199463 100644
---- a/test/discovery.test.js
++++ b/test/discovery.test.js
@@@ -47,7 +47,7 @@ const PRIVATE = ['/auth/', '/api/'];
assert.strictEqual(r.status, 200);
assert.match(r.headers.get('content-type'), /^text\/plain/);
assert.match(r.headers.get('cache-control') || '', /public, max-age=\d+/);
-- assert.ok(r.text.startsWith('# OpenVibe.MediaHub'), 'no title');
++ assert.ok(r.text.startsWith('# OpenVibe.Download'), 'no title');
assert.ok(r.text.includes('(https://openvibe.download/updates)'), 'no updates link');
assert.ok(r.text.includes('(https://openvibe.services)'), 'no pointer to the developer platform');
assert.ok(r.text.includes('https://openvibe.download/sitemap.xml'), 'sitemap not listed');
@@@ -60,7 +60,7 @@ const PRIVATE = ['/auth/', '/api/'];
assert.strictEqual(r.status, 200);
assert.match(r.headers.get('content-type'), /^text\/plain/);
assert.match(r.headers.get('cache-control') || '', /public, max-age=\d+/);
-- assert.ok(r.text.startsWith('# OpenVibe.MediaHub'), 'no title');
++ assert.ok(r.text.startsWith('# OpenVibe.Download'), 'no title');
const sitemap = await t.get('/sitemap.xml');
const locs = [...sitemap.text.matchAll(/<loc>([^<]+)<\/loc>/g)].map((m) => m[1]);
assert.ok(locs.length >= 2, `sitemap too small: ${locs.length}`);
@@@ -78,7 +78,7 @@ const PRIVATE = ['/auth/', '/api/'];
assert.ok(types.includes('WebApplication'), `no primary-type node: ${types}`);
const site = blocks.find((b) => b['@type'] === 'WebSite');
assert.strictEqual(site.url, 'https://openvibe.download');
-- assert.strictEqual(site.name, 'OpenVibe.MediaHub');
++ assert.strictEqual(site.name, 'OpenVibe.Download');
const page = blocks.find((b) => b['@type'] === 'WebPage');
assert.strictEqual(page.url, 'https://openvibe.download/');
});
ddiff --git a/test/layout.test.js b/test/layout.test.js
iindex b77124f..08936ad 100644
---- a/test/layout.test.js
++++ b/test/layout.test.js
@@@ -38,7 +38,7 @@ const BOOST = /<script src="\/shared\/boost\.js\?v=[0-9a-f]{12}" data-main="#mai
assert.strictEqual(r.status, 200);
const head = r.text.slice(0, r.text.indexOf('</head>'));
assert.strictEqual((r.text.match(/<title>/g) || []).length, 1, 'exactly one <title>');
-- assert.ok(head.includes('<title>OpenVibe.MediaHub: Your files, shared on your terms.</title>'), 'home title');
++ assert.ok(head.includes('<title>OpenVibe.Download: Your files, shared on your terms.</title>'), 'home title');
assert.ok(head.includes('<link rel="canonical" href="https://openvibe.download/">'), 'canonical');
assert.ok(head.includes('<meta name="robots" content="index, follow">'), 'robots');
const ld = [...head.matchAll(/<script type="application\/ld\+json">([\s\S]*?)<\/script>/g)].map((m) => JSON.parse(m[1])['@type']);
@@@ -61,7 +61,7 @@ const BOOST = /<script src="\/shared\/boost\.js\?v=[0-9a-f]{12}" data-main="#mai
const r = await t.get('/');
assert.strictEqual(r.status, 200, r.text.slice(0, 300));
assert.match(r.headers.get('content-type'), /^text\/html/);
-- assert.ok(r.text.includes('OpenVibe.MediaHub'), 'the service name is on the page');
++ assert.ok(r.text.includes('OpenVibe.Download'), 'the site name is on the page');
assert.ok(r.text.includes('Your files, shared on your terms.'), 'the tagline is on the page');
const h1 = (/<h1[^>]*>([\s\S]*?)<\/h1>/.exec(r.text) || [])[1] || '';
assert.strictEqual(h1.replace(/<[^>]+>/g, '').replace(/\s+/g, ' ').trim(), 'Your files, shared on your terms.', 'the heading is one sentence');