Enlarge watermark badge for preview/encode parity and document video title fields.
Fix double-scaled badge preview, bump default watermark to 42% frame width, and update OSS docs/API for title vs songTitle separation and shared typography.
This commit is contained in:
@@ -87,12 +87,16 @@ function watermarkOverlayStyle(
|
|||||||
position: WatermarkPosition,
|
position: WatermarkPosition,
|
||||||
offsetX: number,
|
offsetX: number,
|
||||||
offsetY: number,
|
offsetY: number,
|
||||||
|
sizedBox = false,
|
||||||
): CSSProperties {
|
): CSSProperties {
|
||||||
const base: CSSProperties = {
|
const base: CSSProperties = {
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
maxWidth: "32%",
|
maxWidth: `${WATERMARK_WIDTH_FRACTION * 100}%`,
|
||||||
pointerEvents: "none",
|
pointerEvents: "none",
|
||||||
zIndex: 5,
|
zIndex: 5,
|
||||||
|
...(sizedBox
|
||||||
|
? { width: `${WATERMARK_WIDTH_FRACTION * 100}%` }
|
||||||
|
: null),
|
||||||
};
|
};
|
||||||
const ox = `${offsetX}px`;
|
const ox = `${offsetX}px`;
|
||||||
const oy = `${offsetY}px`;
|
const oy = `${offsetY}px`;
|
||||||
@@ -347,8 +351,14 @@ export function LayoutStudio({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const wmOverlay = useMemo(
|
const wmOverlay = useMemo(
|
||||||
() => watermarkOverlayStyle(W.position, W.offsetX, W.offsetY),
|
() =>
|
||||||
[W.position, W.offsetX, W.offsetY],
|
watermarkOverlayStyle(
|
||||||
|
W.position,
|
||||||
|
W.offsetX,
|
||||||
|
W.offsetY,
|
||||||
|
W.mode === "default" || W.mode === "logo",
|
||||||
|
),
|
||||||
|
[W.position, W.offsetX, W.offsetY, W.mode],
|
||||||
);
|
);
|
||||||
|
|
||||||
const textFontStyle = useMemo(
|
const textFontStyle = useMemo(
|
||||||
@@ -569,16 +579,14 @@ export function LayoutStudio({
|
|||||||
<img
|
<img
|
||||||
src="/api/branding/watermark"
|
src="/api/branding/watermark"
|
||||||
alt=""
|
alt=""
|
||||||
className="h-auto object-contain"
|
className="h-auto w-full object-contain"
|
||||||
style={{ width: `${WATERMARK_WIDTH_FRACTION * 100}%` }}
|
|
||||||
/>
|
/>
|
||||||
) : W.mode === "logo" && logoPreviewUrl ? (
|
) : W.mode === "logo" && logoPreviewUrl ? (
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
// eslint-disable-next-line @next/next/no-img-element
|
||||||
<img
|
<img
|
||||||
src={logoPreviewUrl}
|
src={logoPreviewUrl}
|
||||||
alt=""
|
alt=""
|
||||||
className="h-auto object-contain"
|
className="h-auto w-full object-contain"
|
||||||
style={{ width: `${WATERMARK_WIDTH_FRACTION * 100}%` }}
|
|
||||||
/>
|
/>
|
||||||
) : W.mode === "text" ? (
|
) : W.mode === "text" ? (
|
||||||
<span
|
<span
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ import {
|
|||||||
buildArtTrackFilterComplex,
|
buildArtTrackFilterComplex,
|
||||||
type LayoutSettings,
|
type LayoutSettings,
|
||||||
} from "../layout";
|
} from "../layout";
|
||||||
|
import {
|
||||||
|
WATERMARK_WIDTH_FRACTION,
|
||||||
|
watermarkFontSizeForWidth,
|
||||||
|
} from "../preview-typography";
|
||||||
import { getWatermarkPath } from "../storage";
|
import { getWatermarkPath } from "../storage";
|
||||||
import {
|
import {
|
||||||
buildDrawtextFilter,
|
buildDrawtextFilter,
|
||||||
@@ -113,8 +117,8 @@ export async function encodeVideo(options: {
|
|||||||
|
|
||||||
const settings = normalizeWatermarkSettings(options.watermark, options.includeWatermark);
|
const settings = normalizeWatermarkSettings(options.watermark, options.includeWatermark);
|
||||||
const layout = options.layout?.template ? options.layout : null;
|
const layout = options.layout?.template ? options.layout : null;
|
||||||
const watermarkWidth = Math.max(1, Math.round(res.width * 0.32));
|
const watermarkWidth = Math.max(1, Math.round(res.width * WATERMARK_WIDTH_FRACTION));
|
||||||
const fontSize = Math.max(16, Math.round(res.width * 0.018));
|
const fontSize = watermarkFontSizeForWidth(res.width);
|
||||||
const fontfile = await resolveFontfileEscaped(settings);
|
const fontfile = await resolveFontfileEscaped(settings);
|
||||||
|
|
||||||
const args = ["-y", "-loop", "1", "-r", "1", "-i", options.imagePath, "-i", options.audioPath];
|
const args = ["-y", "-loop", "1", "-r", "1", "-i", options.imagePath, "-i", options.audioPath];
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export function artistFontSizeForWidth(width: number): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function watermarkFontSizeForWidth(width: number): number {
|
export function watermarkFontSizeForWidth(width: number): number {
|
||||||
return Math.max(16, Math.round(width * 0.018));
|
return Math.max(22, Math.round(width * 0.028));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Scale encode-resolution px to preview container px. */
|
/** Scale encode-resolution px to preview container px. */
|
||||||
@@ -43,4 +43,4 @@ export function curatedFontApiUrl(key: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Watermark overlay width as fraction of frame (matches encode.ts). */
|
/** Watermark overlay width as fraction of frame (matches encode.ts). */
|
||||||
export const WATERMARK_WIDTH_FRACTION = 0.32;
|
export const WATERMARK_WIDTH_FRACTION = 0.42;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ function testFontSizesMatchLayout() {
|
|||||||
const width = 1280;
|
const width = 1280;
|
||||||
assert.equal(titleFontSizeForWidth(width), Math.max(22, Math.round(width * 0.032)));
|
assert.equal(titleFontSizeForWidth(width), Math.max(22, Math.round(width * 0.032)));
|
||||||
assert.equal(artistFontSizeForWidth(width), Math.max(16, Math.round(width * 0.02)));
|
assert.equal(artistFontSizeForWidth(width), Math.max(16, Math.round(width * 0.02)));
|
||||||
assert.equal(watermarkFontSizeForWidth(width), Math.max(16, Math.round(width * 0.018)));
|
assert.equal(watermarkFontSizeForWidth(width), Math.max(22, Math.round(width * 0.028)));
|
||||||
}
|
}
|
||||||
|
|
||||||
function testPreviewScaling() {
|
function testPreviewScaling() {
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ curl -X POST "$BASE_URL/api/v1/upload" \
|
|||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Optional: custom watermark font (.ttf / .otf, max 10 MB)
|
# Optional: custom font (.ttf / .otf, max 10 MB) for art-track / text watermark
|
||||||
curl -X POST "$BASE_URL/api/v1/upload" \
|
curl -X POST "$BASE_URL/api/v1/upload" \
|
||||||
-H "Authorization: Bearer $API_KEY" \
|
-H "Authorization: Bearer $API_KEY" \
|
||||||
-F "file=@Brand.ttf" \
|
-F "file=@Brand.ttf" \
|
||||||
@@ -93,7 +93,7 @@ curl -X POST "$BASE_URL/api/v1/upload" \
|
|||||||
| `size` | Bytes |
|
| `size` | Bytes |
|
||||||
| `audioTags` | Present for MP3 when tags are readable; otherwise `null`. Fields may be omitted when missing in the file |
|
| `audioTags` | Present for MP3 when tags are readable; otherwise `null`. Fields may be omitted when missing in the file |
|
||||||
|
|
||||||
Upload the shared cover once (`type=image`), each audio (`type=audio`), optionally a PNG logo (`type=logo`), and optionally a custom font (`type=font`) for text watermarks.
|
Upload the shared cover once (`type=image`), each audio (`type=audio`), optionally a PNG logo (`type=logo`), and optionally a custom font (`type=font`) for art-track typography and/or text watermarks.
|
||||||
|
|
||||||
## Create job from paths (recommended)
|
## Create job from paths (recommended)
|
||||||
|
|
||||||
@@ -111,7 +111,8 @@ curl -X POST "$BASE_URL/api/v1/jobs" \
|
|||||||
"audioPath": "/uploads/.../track.mp3",
|
"audioPath": "/uploads/.../track.mp3",
|
||||||
"audioFilename": "track.mp3",
|
"audioFilename": "track.mp3",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"title": "My Track",
|
"title": "My Artist - My Track (Official Audio)",
|
||||||
|
"songTitle": "My Track",
|
||||||
"artist": "My Artist",
|
"artist": "My Artist",
|
||||||
"description": "",
|
"description": "",
|
||||||
"tags": "electronic",
|
"tags": "electronic",
|
||||||
@@ -134,8 +135,7 @@ curl -X POST "$BASE_URL/api/v1/jobs" \
|
|||||||
"textOffsetY": 0
|
"textOffsetY": 0
|
||||||
},
|
},
|
||||||
"watermark": {
|
"watermark": {
|
||||||
"mode": "text",
|
"mode": "default",
|
||||||
"text": "My Label",
|
|
||||||
"fontKey": "montserrat",
|
"fontKey": "montserrat",
|
||||||
"position": "bottom-right",
|
"position": "bottom-right",
|
||||||
"offsetX": 24,
|
"offsetX": 24,
|
||||||
@@ -164,8 +164,9 @@ curl -X POST "$BASE_URL/api/v1/jobs" \
|
|||||||
|
|
||||||
| Field | Type | Notes |
|
| Field | Type | Notes |
|
||||||
|-------|------|--------|
|
|-------|------|--------|
|
||||||
| `title` | string | Video title |
|
| `title` | string | **YouTube** video title. If empty, falls back to ``${artist} - ${songTitle}`` when those are set |
|
||||||
| `artist` | string \| null | On-video artist line (max **80**) |
|
| `songTitle` | string \| null | On-video song / track title for art-track layouts (max **120**) |
|
||||||
|
| `artist` | string \| null | On-video artist line for art-track layouts (max **80**) |
|
||||||
| `description` | string | YouTube description |
|
| `description` | string | YouTube description |
|
||||||
| `tags` | string | Comma-separated (quoted tags supported) |
|
| `tags` | string | Comma-separated (quoted tags supported) |
|
||||||
| `privacy` | string | `PUBLIC` \| `PRIVATE` \| `UNLISTED` |
|
| `privacy` | string | `PUBLIC` \| `PRIVATE` \| `UNLISTED` |
|
||||||
@@ -224,11 +225,15 @@ Official reference: [YouTube Data API — VideoCategories](https://developers.go
|
|||||||
|
|
||||||
`watermark.position`: `top-left` | `top-right` | `bottom-left` | `bottom-right` | `center`.
|
`watermark.position`: `top-left` | `top-right` | `bottom-left` | `bottom-right` | `center`.
|
||||||
|
|
||||||
`watermark.mode`: `none` | `default` | `text` | `logo` (logo requires prior `type=logo` upload; set `logoPath`).
|
`watermark.mode`: `none` | `default` | `text` | `logo`
|
||||||
|
|
||||||
|
- `default` — built-in Songs2VID badge PNG (`assets/watermark.png`), scaled to ~**42%** of frame width (matches Layout Studio preview)
|
||||||
|
- `logo` — requires prior `type=logo` upload; set `logoPath` (same width scaling)
|
||||||
|
- `text` — custom string (max **80**); set `text`
|
||||||
|
|
||||||
`watermark.offsetX` / `offsetY`: `0`–`200` (default `20`) — pixels from the chosen anchor.
|
`watermark.offsetX` / `offsetY`: `0`–`200` (default `20`) — pixels from the chosen anchor.
|
||||||
|
|
||||||
`watermark.fontKey` (text mode): `system` | `inter` | `montserrat` | `roboto` | `oswald` | `playfair` | `custom`. For `custom`, upload with `type=font` first and set `fontPath` to the returned path. Text max length: **80**.
|
`watermark.fontKey`: `system` | `inter` | `montserrat` | `roboto` | `oswald` | `playfair` | `custom`. Styles **art-track song title / artist** and **text watermarks** (same `.ttf` files in preview and FFmpeg). For `custom`, upload with `type=font` first and set `fontPath`. `system` maps to Arial in the dashboard preview.
|
||||||
|
|
||||||
For a full walkthrough of composition controls, see [Video editing](../video-editing.md).
|
For a full walkthrough of composition controls, see [Video editing](../video-editing.md).
|
||||||
|
|
||||||
@@ -254,7 +259,7 @@ Optional fine-tuning (clamped; camelCase or snake_case):
|
|||||||
| `textOffsetX` / `text_offset_x` | −120–120 | 0 | Shift text block horizontally |
|
| `textOffsetX` / `text_offset_x` | −120–120 | 0 | Shift text block horizontally |
|
||||||
| `textOffsetY` / `text_offset_y` | −120–120 | 0 | Shift text block vertically |
|
| `textOffsetY` / `text_offset_y` | −120–120 | 0 | Shift text block vertically |
|
||||||
|
|
||||||
Also set `metadata.artist` (max 80) for the on-video artist line.
|
Also set `metadata.songTitle` (max 120) and `metadata.artist` (max 80) for the on-video text lines. `metadata.title` remains the YouTube title (see [Video editing — Video title, song title, and artist](../video-editing.md#video-title-song-title-and-artist)).
|
||||||
|
|
||||||
Omit `layout.template` (or use classic letterbox) when you only want a black-padded cover. Free-form cover coordinates (`x`, `y`, `coverX`, …) and layout-level `offsetX`/`offsetY` are **rejected** (use `textOffsetX`/`textOffsetY` instead; watermark offsets stay under `watermark`).
|
Omit `layout.template` (or use classic letterbox) when you only want a black-padded cover. Free-form cover coordinates (`x`, `y`, `coverX`, …) and layout-level `offsetX`/`offsetY` are **rejected** (use `textOffsetX`/`textOffsetY` instead; watermark offsets stay under `watermark`).
|
||||||
|
|
||||||
@@ -321,7 +326,7 @@ curl -X POST "$BASE_URL/api/v1/jobs/batch" \
|
|||||||
|
|
||||||
Optional `metadata` JSON supports `defaults` applied to every item and per-item overrides in `items`. Item order should match the order of `audio` files.
|
Optional `metadata` JSON supports `defaults` applied to every item and per-item overrides in `items`. Item order should match the order of `audio` files.
|
||||||
|
|
||||||
When item metadata is omitted, batch defaults include privacy `PUBLIC`, resolution `1920x1080`, and watermark off unless overridden in `defaults`.
|
When item metadata is omitted, batch defaults include privacy `PUBLIC`, resolution `1920x1080`, and watermark off unless overridden in `defaults`. Per-item `title` is the YouTube title (falls back from the audio filename when omitted). Pass `songTitle` and `artist` in `items[]` or `defaults` when using art-track layouts.
|
||||||
|
|
||||||
**Multipart tips**
|
**Multipart tips**
|
||||||
|
|
||||||
|
|||||||
@@ -102,6 +102,6 @@ For public HTTPS, OAuth redirect URIs, and worker checklist, see [Production not
|
|||||||
| `npm run dev` | Next.js dev server only |
|
| `npm run dev` | Next.js dev server only |
|
||||||
| `npm run worker` | Background job processor only |
|
| `npm run worker` | Background job processor only |
|
||||||
| `npm run docs:dev` | Documentation site only (port 3001) |
|
| `npm run docs:dev` | Documentation site only (port 3001) |
|
||||||
|
| `npm run docs:build` | Static docs build (for Caddy / nginx) |
|
||||||
| `npm run db:push` | Push Prisma schema to the database |
|
| `npm run db:push` | Push Prisma schema to the database |
|
||||||
| `npm run build` | Production build |
|
| `npm run build` | Production build |
|
||||||
| `npm run docs:build` | Build the documentation site |
|
|
||||||
|
|||||||
@@ -12,9 +12,10 @@ This documentation is for **self-hosted / open-source** deployments (`S2VID_EDIT
|
|||||||
## What you can do
|
## What you can do
|
||||||
|
|
||||||
- Combine one image with multiple tracks in a batch
|
- Combine one image with multiple tracks in a batch
|
||||||
- Set per-video metadata (title, artist, description, tags, privacy, resolution)
|
- Set per-video metadata (YouTube **video title**, on-video **song title** / **artist**, description, tags, privacy, resolution)
|
||||||
- Art-track layouts with blur backgrounds and fine-tuning (padding, title/artist gap, text offsets)
|
- Art-track layouts with blur backgrounds and fine-tuning (padding, title/artist gap, text offsets)
|
||||||
- Custom watermarks: text or logo, positions/offsets, curated or uploaded fonts
|
- Typography: curated or uploaded `.ttf` / `.otf` fonts shared by Layout Studio preview and FFmpeg
|
||||||
|
- Custom watermarks: Songs2VID badge, text, or logo — positions/offsets match the live preview
|
||||||
- Unique cover image per track
|
- Unique cover image per track
|
||||||
- Add uploads to YouTube playlists
|
- Add uploads to YouTube playlists
|
||||||
- Use the REST API for automation
|
- Use the REST API for automation
|
||||||
|
|||||||
@@ -26,14 +26,21 @@ Templates are enum-based. Free-form cover coordinates (`x`, `y`, `coverX`, …)
|
|||||||
|
|
||||||
Pick a template in the UI under **Composition**, or set `metadata.layout.template` in the API.
|
Pick a template in the UI under **Composition**, or set `metadata.layout.template` in the API.
|
||||||
|
|
||||||
## Title and artist
|
## Video title, song title, and artist
|
||||||
|
|
||||||
Each track can have:
|
YouTube metadata and on-video layout text are separate fields:
|
||||||
|
|
||||||
- **Title** — used on the video and as the YouTube title (often prefilled from the filename)
|
| Field | UI label | Purpose | Limits |
|
||||||
- **Artist** — drawn under the title on art-track layouts (max 80 characters)
|
|-------|----------|---------|--------|
|
||||||
|
| `title` | **Video title** | YouTube upload title only | Required unless fallback applies (below) |
|
||||||
|
| `songTitle` | **Song title** | Track title burned into art-track layouts | Max **120** characters |
|
||||||
|
| `artist` | **Artist** | Artist line under the song title on art-track layouts | Max **80** characters |
|
||||||
|
|
||||||
Artist is especially useful when ID3 tags or your API payload include it.
|
- On the dashboard, video title is always editable. Song title and artist control the Layout Studio preview and the encoded frame.
|
||||||
|
- If **video title** is left empty, Songs2VID falls back to ``${artist} - ${songTitle}`` (or whichever of those is present). Self-hosted editions always have this unlocked.
|
||||||
|
- ID3 tags (when readable) typically prefill song title / artist and a combined video title such as `Artist - Song`.
|
||||||
|
|
||||||
|
Classic letterbox mode does not burn song title or artist into the frame; they still matter for the YouTube title fallback when video title is omitted.
|
||||||
|
|
||||||
## Blur background
|
## Blur background
|
||||||
|
|
||||||
@@ -68,38 +75,42 @@ Modes:
|
|||||||
| Mode | Effect |
|
| Mode | Effect |
|
||||||
|------|--------|
|
|------|--------|
|
||||||
| `none` | No watermark |
|
| `none` | No watermark |
|
||||||
| `default` | Built-in Songs2VID branding |
|
| `default` | Built-in **Songs2VID badge** PNG (`assets/watermark.png`), same asset in Layout Studio preview and FFmpeg output (~42% of frame width) |
|
||||||
| `text` | Custom text with optional typography |
|
| `text` | Custom text (max **80** characters) |
|
||||||
| `logo` | Custom PNG (upload with `type=logo`, then set `logoPath`) |
|
| `logo` | Custom PNG (upload with `type=logo`, then set `logoPath`; same ~42% width scaling) |
|
||||||
|
|
||||||
### Position and offset
|
### Position and offset
|
||||||
|
|
||||||
- **Position**: `top-left` · `top-right` · `bottom-left` · `bottom-right` · `center`
|
- **Position**: `top-left` · `top-right` · `bottom-left` · `bottom-right` · `center`
|
||||||
- **Offsets** (`offsetX` / `offsetY`): `0`–`200` px from the chosen anchor (default `20`)
|
- **Offsets** (`offsetX` / `offsetY`): `0`–`200` px from the chosen anchor (default `20`)
|
||||||
|
|
||||||
### Typography (text mode)
|
### Typography
|
||||||
|
|
||||||
|
Font choice applies to **art-track song title / artist** and to **text watermarks**. Layout Studio loads the same `.ttf` files FFmpeg uses, so preview and encoded video stay aligned.
|
||||||
|
|
||||||
Curated fonts (bundled under `assets/fonts` after fetch):
|
Curated fonts (bundled under `assets/fonts` after fetch):
|
||||||
|
|
||||||
- `system` — FFmpeg default
|
- `system` — Arial / Helvetica in preview; FFmpeg system default when encoding
|
||||||
- `inter` · `montserrat` · `roboto` · `oswald` · `playfair`
|
- `inter` · `montserrat` · `roboto` · `oswald` · `playfair`
|
||||||
- `custom` — your `.ttf` / `.otf` (max 10 MB; upload with `type=font`, set `fontKey: "custom"` and `fontPath`)
|
- `custom` — your `.ttf` / `.otf` (max 10 MB; upload with `type=font`, set `fontKey: "custom"` and `fontPath`)
|
||||||
|
|
||||||
|
Set `watermark.fontKey` (and `fontPath` for custom) even when watermark mode is `default` or `none` if you only need the font for art-track text.
|
||||||
|
|
||||||
Refresh curated files if missing:
|
Refresh curated files if missing:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
node scripts/fetch-watermark-fonts.mjs
|
node scripts/fetch-watermark-fonts.mjs
|
||||||
```
|
```
|
||||||
|
|
||||||
Text length is capped at 80 characters.
|
|
||||||
|
|
||||||
## Dashboard workflow
|
## Dashboard workflow
|
||||||
|
|
||||||
1. Add audio (and optional per-track covers)
|
1. Add audio (and optional per-track covers)
|
||||||
2. Open **Layout Studio** on a track
|
2. Set **Video title** (YouTube) and, for art-tracks, **Song title** / **Artist**
|
||||||
3. Choose classic or a template, then tune blur, padding, gaps, and text offsets
|
3. Open **Layout Studio**
|
||||||
4. Configure watermark mode, font, and position
|
4. Choose classic or a template; tune blur, padding, gaps, and text offsets
|
||||||
5. Preview updates live; submit to enqueue encoding
|
5. Under **Typography**, pick a curated or uploaded font
|
||||||
|
6. Configure watermark mode (Songs2VID badge / text / logo), position, and offsets
|
||||||
|
7. Preview updates live; submit to enqueue encoding
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
@@ -107,7 +118,8 @@ See [Endpoints](./api/endpoints.md) for curl examples. Layout and watermark obje
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"title": "My Track",
|
"title": "My Artist - My Track (Official Audio)",
|
||||||
|
"songTitle": "My Track",
|
||||||
"artist": "My Artist",
|
"artist": "My Artist",
|
||||||
"layout": {
|
"layout": {
|
||||||
"template": "COVER_LEFT_TEXT_RIGHT",
|
"template": "COVER_LEFT_TEXT_RIGHT",
|
||||||
@@ -119,8 +131,7 @@ See [Endpoints](./api/endpoints.md) for curl examples. Layout and watermark obje
|
|||||||
"textOffsetY": 0
|
"textOffsetY": 0
|
||||||
},
|
},
|
||||||
"watermark": {
|
"watermark": {
|
||||||
"mode": "text",
|
"mode": "default",
|
||||||
"text": "My Label",
|
|
||||||
"fontKey": "montserrat",
|
"fontKey": "montserrat",
|
||||||
"position": "bottom-right",
|
"position": "bottom-right",
|
||||||
"offsetX": 24,
|
"offsetX": 24,
|
||||||
@@ -128,3 +139,5 @@ See [Endpoints](./api/endpoints.md) for curl examples. Layout and watermark obje
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Here `title` is the YouTube title, `songTitle` / `artist` appear on the art-track frame, and `fontKey` styles that on-video text even though the watermark is the default Songs2VID badge.
|
||||||
|
|||||||
Reference in New Issue
Block a user