Skip to content

Commit 47e08af

Browse files
committed
feat: add ClipSet to schema
There are a few changes to the current ClipSet workaround in cp-content-pipeline: - dataLayout -> layoutWidth (for consistency with theother nodes) - changes to how the Body for transcripts is represented (pending further discussion with CP) This commit also updates the from-bodyxml transformerfor clipset
1 parent ca5e2a3 commit 47e08af

File tree

6 files changed

+528
-6
lines changed

6 files changed

+528
-6
lines changed

README.md

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ type BodyBlock =
105105
| Tweet
106106
| Video
107107
| YoutubeVideo
108+
| ClipSet
108109
```
109110
110111
`BodyBlock` nodes are the only things that are valid as the top level of a `Body`.
@@ -543,8 +544,6 @@ interface Video extends Node {
543544

544545
The `title` can be obtained by fetching the Video from the content API.
545546

546-
TODO: Figure out how Clips work, how they are different?
547-
548547
### `YoutubeVideo`
549548

550549
```ts
@@ -554,8 +553,72 @@ interface YoutubeVideo extends Node {
554553
}
555554
```
556555

556+
557557
**YoutubeVideo** represents a video referenced by a Youtube URL.
558558

559+
### `ClipSet`
560+
561+
```ts
562+
interface ClipSet extends Node {
563+
type: "clip-set"
564+
id: string
565+
autoplay: boolean
566+
loop: boolean
567+
muted: boolean
568+
layoutWidth: 'in-line' | 'mid-grid' | 'full-grid'
569+
external noAudio: boolean
570+
external caption: string
571+
external credits: string
572+
external description: string
573+
external displayTitle: string
574+
external systemTitle: string
575+
external source: string
576+
external contentWarning: string[]
577+
external publishedDate: string
578+
external subtitle: string
579+
external clips: Clip[]
580+
external accessibility: ClipAccessibility
581+
}
582+
```
583+
584+
```ts
585+
type Clip = {
586+
id: string
587+
format: 'standard-inline' | 'mobile'
588+
dataSource: ClipSource[]
589+
poster: string
590+
}
591+
```
592+
593+
```ts
594+
type ClipSource = {
595+
audioCodec: string
596+
binaryUrl: string
597+
duration: number
598+
mediaType: string
599+
pixelHeight: number
600+
pixelWidth: number
601+
videoCodec: string
602+
}
603+
```
604+
605+
```ts
606+
type ClipCaption = {
607+
mediaType?: string
608+
url?: string
609+
}
610+
```
611+
```ts
612+
type ClipAccessibility = {
613+
captions?: ClipCaption[]
614+
transcript?: Body
615+
}
616+
```
617+
618+
**ClipSet** represents a short piece of possibly-looping video content for an article.
619+
620+
The external fields are derived from the separately published [ClipSet](https://api.ft.com/schemas/clip-set.json) and [Clip](https://api.ft.com/schemas/clip.json) objects in the Content API.
621+
559622
### `ScrollyBlock`
560623
561624
```ts

content-tree.d.ts

Lines changed: 164 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export declare namespace ContentTree {
2-
type BodyBlock = Paragraph | Heading | ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | List | Blockquote | Pullquote | ScrollyBlock | ThematicBreak | Table | Recommended | Tweet | Video | YoutubeVideo;
2+
type BodyBlock = Paragraph | Heading | ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | List | Blockquote | Pullquote | ScrollyBlock | ThematicBreak | Table | Recommended | Tweet | Video | YoutubeVideo | ClipSet;
33
type LayoutWidth = "auto" | "in-line" | "inset-left" | "inset-right" | "full-bleed" | "full-grid" | "mid-grid" | "full-width";
44
type Phrasing = Text | Break | Strong | Emphasis | Strikethrough | Link;
55
interface Node {
@@ -171,6 +171,49 @@ export declare namespace ContentTree {
171171
type: "youtube-video";
172172
url: string;
173173
}
174+
interface ClipSet extends Node {
175+
type: "clip-set";
176+
id: string;
177+
autoplay: boolean;
178+
loop: boolean;
179+
muted: boolean;
180+
layoutWidth: 'in-line' | 'mid-grid' | 'full-grid';
181+
noAudio: boolean;
182+
caption: string;
183+
credits: string;
184+
description: string;
185+
displayTitle: string;
186+
systemTitle: string;
187+
source: string;
188+
contentWarning: string[];
189+
publishedDate: string;
190+
subtitle: string;
191+
clips: Clip[];
192+
accessibility: ClipAccessibility;
193+
}
194+
type Clip = {
195+
id: string;
196+
format: 'standard-inline' | 'mobile';
197+
dataSource: ClipSource[];
198+
poster: string;
199+
};
200+
type ClipSource = {
201+
audioCodec: string;
202+
binaryUrl: string;
203+
duration: number;
204+
mediaType: string;
205+
pixelHeight: number;
206+
pixelWidth: number;
207+
videoCodec: string;
208+
};
209+
type ClipCaption = {
210+
mediaType?: string;
211+
url?: string;
212+
};
213+
type ClipAccessibility = {
214+
captions?: ClipCaption[];
215+
transcript?: Body;
216+
};
174217
interface ScrollyBlock extends Parent {
175218
type: "scrolly-block";
176219
theme: "sans" | "serif";
@@ -272,7 +315,7 @@ export declare namespace ContentTree {
272315
attributes: CustomCodeComponentAttributes;
273316
}
274317
namespace full {
275-
type BodyBlock = Paragraph | Heading | ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | List | Blockquote | Pullquote | ScrollyBlock | ThematicBreak | Table | Recommended | Tweet | Video | YoutubeVideo;
318+
type BodyBlock = Paragraph | Heading | ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | List | Blockquote | Pullquote | ScrollyBlock | ThematicBreak | Table | Recommended | Tweet | Video | YoutubeVideo | ClipSet;
276319
type LayoutWidth = "auto" | "in-line" | "inset-left" | "inset-right" | "full-bleed" | "full-grid" | "mid-grid" | "full-width";
277320
type Phrasing = Text | Break | Strong | Emphasis | Strikethrough | Link;
278321
interface Node {
@@ -444,6 +487,49 @@ export declare namespace ContentTree {
444487
type: "youtube-video";
445488
url: string;
446489
}
490+
interface ClipSet extends Node {
491+
type: "clip-set";
492+
id: string;
493+
autoplay: boolean;
494+
loop: boolean;
495+
muted: boolean;
496+
layoutWidth: 'in-line' | 'mid-grid' | 'full-grid';
497+
noAudio: boolean;
498+
caption: string;
499+
credits: string;
500+
description: string;
501+
displayTitle: string;
502+
systemTitle: string;
503+
source: string;
504+
contentWarning: string[];
505+
publishedDate: string;
506+
subtitle: string;
507+
clips: Clip[];
508+
accessibility: ClipAccessibility;
509+
}
510+
type Clip = {
511+
id: string;
512+
format: 'standard-inline' | 'mobile';
513+
dataSource: ClipSource[];
514+
poster: string;
515+
};
516+
type ClipSource = {
517+
audioCodec: string;
518+
binaryUrl: string;
519+
duration: number;
520+
mediaType: string;
521+
pixelHeight: number;
522+
pixelWidth: number;
523+
videoCodec: string;
524+
};
525+
type ClipCaption = {
526+
mediaType?: string;
527+
url?: string;
528+
};
529+
type ClipAccessibility = {
530+
captions?: ClipCaption[];
531+
transcript?: Body;
532+
};
447533
interface ScrollyBlock extends Parent {
448534
type: "scrolly-block";
449535
theme: "sans" | "serif";
@@ -546,7 +632,7 @@ export declare namespace ContentTree {
546632
}
547633
}
548634
namespace transit {
549-
type BodyBlock = Paragraph | Heading | ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | List | Blockquote | Pullquote | ScrollyBlock | ThematicBreak | Table | Recommended | Tweet | Video | YoutubeVideo;
635+
type BodyBlock = Paragraph | Heading | ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | List | Blockquote | Pullquote | ScrollyBlock | ThematicBreak | Table | Recommended | Tweet | Video | YoutubeVideo | ClipSet;
550636
type LayoutWidth = "auto" | "in-line" | "inset-left" | "inset-right" | "full-bleed" | "full-grid" | "mid-grid" | "full-width";
551637
type Phrasing = Text | Break | Strong | Emphasis | Strikethrough | Link;
552638
interface Node {
@@ -713,6 +799,37 @@ export declare namespace ContentTree {
713799
type: "youtube-video";
714800
url: string;
715801
}
802+
interface ClipSet extends Node {
803+
type: "clip-set";
804+
id: string;
805+
autoplay: boolean;
806+
loop: boolean;
807+
muted: boolean;
808+
layoutWidth: 'in-line' | 'mid-grid' | 'full-grid';
809+
}
810+
type Clip = {
811+
id: string;
812+
format: 'standard-inline' | 'mobile';
813+
dataSource: ClipSource[];
814+
poster: string;
815+
};
816+
type ClipSource = {
817+
audioCodec: string;
818+
binaryUrl: string;
819+
duration: number;
820+
mediaType: string;
821+
pixelHeight: number;
822+
pixelWidth: number;
823+
videoCodec: string;
824+
};
825+
type ClipCaption = {
826+
mediaType?: string;
827+
url?: string;
828+
};
829+
type ClipAccessibility = {
830+
captions?: ClipCaption[];
831+
transcript?: Body;
832+
};
716833
interface ScrollyBlock extends Parent {
717834
type: "scrolly-block";
718835
theme: "sans" | "serif";
@@ -805,7 +922,7 @@ export declare namespace ContentTree {
805922
}
806923
}
807924
namespace loose {
808-
type BodyBlock = Paragraph | Heading | ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | List | Blockquote | Pullquote | ScrollyBlock | ThematicBreak | Table | Recommended | Tweet | Video | YoutubeVideo;
925+
type BodyBlock = Paragraph | Heading | ImageSet | Flourish | BigNumber | CustomCodeComponent | Layout | List | Blockquote | Pullquote | ScrollyBlock | ThematicBreak | Table | Recommended | Tweet | Video | YoutubeVideo | ClipSet;
809926
type LayoutWidth = "auto" | "in-line" | "inset-left" | "inset-right" | "full-bleed" | "full-grid" | "mid-grid" | "full-width";
810927
type Phrasing = Text | Break | Strong | Emphasis | Strikethrough | Link;
811928
interface Node {
@@ -977,6 +1094,49 @@ export declare namespace ContentTree {
9771094
type: "youtube-video";
9781095
url: string;
9791096
}
1097+
interface ClipSet extends Node {
1098+
type: "clip-set";
1099+
id: string;
1100+
autoplay: boolean;
1101+
loop: boolean;
1102+
muted: boolean;
1103+
layoutWidth: 'in-line' | 'mid-grid' | 'full-grid';
1104+
noAudio?: boolean;
1105+
caption?: string;
1106+
credits?: string;
1107+
description?: string;
1108+
displayTitle?: string;
1109+
systemTitle?: string;
1110+
source?: string;
1111+
contentWarning?: string[];
1112+
publishedDate?: string;
1113+
subtitle?: string;
1114+
clips?: Clip[];
1115+
accessibility?: ClipAccessibility;
1116+
}
1117+
type Clip = {
1118+
id: string;
1119+
format: 'standard-inline' | 'mobile';
1120+
dataSource: ClipSource[];
1121+
poster: string;
1122+
};
1123+
type ClipSource = {
1124+
audioCodec: string;
1125+
binaryUrl: string;
1126+
duration: number;
1127+
mediaType: string;
1128+
pixelHeight: number;
1129+
pixelWidth: number;
1130+
videoCodec: string;
1131+
};
1132+
type ClipCaption = {
1133+
mediaType?: string;
1134+
url?: string;
1135+
};
1136+
type ClipAccessibility = {
1137+
captions?: ClipCaption[];
1138+
transcript?: Body;
1139+
};
9801140
interface ScrollyBlock extends Parent {
9811141
type: "scrolly-block";
9821142
theme: "sans" | "serif";

libraries/from-bodyxml/index.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ let ContentType = {
88
content: "http://www.ft.com/ontology/content/Content",
99
article: "http://www.ft.com/ontology/content/Article",
1010
customCodeComponent: "http://www.ft.com/ontology/content/CustomCodeComponent",
11+
clipSet: "http://www.ft.com/ontology/content/ClipSet",
1112
};
1213

1314
/**
@@ -32,6 +33,24 @@ function toValidLayoutWidth(layoutWidth) {
3233
return "full-width";
3334
}
3435
}
36+
37+
/**
38+
* @param {string} layoutWidth
39+
* @returns {ContentTree.ClipSet["layoutWidth"]}
40+
*/
41+
function toValidClipLayoutWidth(layoutWidth) {
42+
if (
43+
[
44+
"in-line",
45+
"full-grid",
46+
"mid-grid",
47+
].includes(layoutWidth)
48+
) {
49+
return /** @type {ContentTree.ClipSet["layoutWidth"]} */ (layoutWidth);
50+
} else {
51+
return "in-line";
52+
}
53+
}
3554
/**
3655
* @typedef {import("unist").Parent} UParent
3756
* @typedef {import("unist").Node} UNode
@@ -308,6 +327,24 @@ export let defaultTransformers = {
308327
children: null,
309328
};
310329
},
330+
/**
331+
* @type {Transformer<ContentTree.transit.ClipSet>}
332+
*/
333+
[ContentType.clipSet](clip) {
334+
const id = clip.attributes.url ?? "";
335+
const uuid = id.split("/").pop();
336+
return {
337+
type: "clip-set",
338+
id: uuid ?? "",
339+
layoutWidth: toValidClipLayoutWidth(
340+
clip.attributes["data-layout-width"] || ""
341+
),
342+
autoplay: clip.attributes?.autoplay === "true",
343+
loop: clip.attributes?.loop === "true",
344+
muted: clip.attributes?.muted === "true",
345+
children: null,
346+
};
347+
},
311348
/**
312349
* @type {Transformer<ContentTree.transit.Recommended>}
313350
*/

0 commit comments

Comments
 (0)