@financial-times/cp-content-pipeline-schema 3.18.0 → 3.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/lib/generated/index.d.ts +9 -0
  3. package/lib/helpers/qualityWorkaround.d.ts +2 -0
  4. package/lib/helpers/qualityWorkaround.js +37 -0
  5. package/lib/helpers/qualityWorkaround.js.map +1 -0
  6. package/lib/model/Clip.d.ts +1 -17
  7. package/lib/model/Clip.js +27 -8
  8. package/lib/model/Clip.js.map +1 -1
  9. package/lib/model/Clip.test.js +77 -0
  10. package/lib/model/Clip.test.js.map +1 -1
  11. package/lib/model/schemas/capi/article.d.ts +18 -0
  12. package/lib/model/schemas/capi/audio.d.ts +14 -0
  13. package/lib/model/schemas/capi/base-schema.d.ts +42 -0
  14. package/lib/model/schemas/capi/base-schema.js +2 -0
  15. package/lib/model/schemas/capi/base-schema.js.map +1 -1
  16. package/lib/model/schemas/capi/content-package.d.ts +14 -0
  17. package/lib/model/schemas/capi/custom-code-component.d.ts +18 -0
  18. package/lib/model/schemas/capi/index.d.ts +96 -0
  19. package/lib/model/schemas/capi/live-blog-package.d.ts +18 -0
  20. package/lib/model/schemas/capi/placeholder.d.ts +18 -0
  21. package/lib/model/schemas/capi/video.d.ts +14 -0
  22. package/lib/resolvers/clip.d.ts +4 -9
  23. package/lib/resolvers/clip.js +3 -0
  24. package/lib/resolvers/clip.js.map +1 -1
  25. package/lib/resolvers/index.d.ts +4 -9
  26. package/lib/types/clip.d.ts +21 -0
  27. package/lib/types/clip.js +3 -0
  28. package/lib/types/clip.js.map +1 -0
  29. package/package.json +1 -1
  30. package/queries/article.graphql +3 -0
  31. package/src/generated/index.ts +9 -0
  32. package/src/helpers/qualityWorkaround.ts +44 -0
  33. package/src/model/Clip.test.ts +95 -0
  34. package/src/model/Clip.ts +33 -27
  35. package/src/model/schemas/capi/base-schema.ts +2 -0
  36. package/src/resolvers/clip.ts +3 -0
  37. package/src/types/clip.ts +23 -0
  38. package/tsconfig.tsbuildinfo +1 -1
  39. package/typedefs/clip.graphql +9 -0
@@ -853,6 +853,8 @@ export declare const liveBlogPackageSchema: import("zod").ZodObject<Pick<{
853
853
  pixelHeight: import("zod").ZodOptional<import("zod").ZodNumber>;
854
854
  pixelWidth: import("zod").ZodOptional<import("zod").ZodNumber>;
855
855
  videoCodec: import("zod").ZodOptional<import("zod").ZodString>;
856
+ quality: import("zod").ZodOptional<import("zod").ZodString>;
857
+ dppx: import("zod").ZodOptional<import("zod").ZodNumber>;
856
858
  }, "strip", import("zod").ZodTypeAny, {
857
859
  binaryUrl?: string | undefined;
858
860
  pixelWidth?: number | undefined;
@@ -861,6 +863,8 @@ export declare const liveBlogPackageSchema: import("zod").ZodObject<Pick<{
861
863
  duration?: number | undefined;
862
864
  mediaType?: string | undefined;
863
865
  videoCodec?: string | undefined;
866
+ quality?: string | undefined;
867
+ dppx?: number | undefined;
864
868
  }, {
865
869
  binaryUrl?: string | undefined;
866
870
  pixelWidth?: number | undefined;
@@ -869,6 +873,8 @@ export declare const liveBlogPackageSchema: import("zod").ZodObject<Pick<{
869
873
  duration?: number | undefined;
870
874
  mediaType?: string | undefined;
871
875
  videoCodec?: string | undefined;
876
+ quality?: string | undefined;
877
+ dppx?: number | undefined;
872
878
  }>, "many">;
873
879
  poster: import("zod").ZodOptional<import("zod").ZodObject<{
874
880
  id: import("zod").ZodString;
@@ -1046,6 +1052,8 @@ export declare const liveBlogPackageSchema: import("zod").ZodObject<Pick<{
1046
1052
  duration?: number | undefined;
1047
1053
  mediaType?: string | undefined;
1048
1054
  videoCodec?: string | undefined;
1055
+ quality?: string | undefined;
1056
+ dppx?: number | undefined;
1049
1057
  }[];
1050
1058
  format?: "standardInline" | "mobile" | undefined;
1051
1059
  poster?: {
@@ -1093,6 +1101,8 @@ export declare const liveBlogPackageSchema: import("zod").ZodObject<Pick<{
1093
1101
  duration?: number | undefined;
1094
1102
  mediaType?: string | undefined;
1095
1103
  videoCodec?: string | undefined;
1104
+ quality?: string | undefined;
1105
+ dppx?: number | undefined;
1096
1106
  }[];
1097
1107
  format?: "standardInline" | "mobile" | undefined;
1098
1108
  poster?: {
@@ -1179,6 +1189,8 @@ export declare const liveBlogPackageSchema: import("zod").ZodObject<Pick<{
1179
1189
  duration?: number | undefined;
1180
1190
  mediaType?: string | undefined;
1181
1191
  videoCodec?: string | undefined;
1192
+ quality?: string | undefined;
1193
+ dppx?: number | undefined;
1182
1194
  }[];
1183
1195
  format?: "standardInline" | "mobile" | undefined;
1184
1196
  poster?: {
@@ -1247,6 +1259,8 @@ export declare const liveBlogPackageSchema: import("zod").ZodObject<Pick<{
1247
1259
  duration?: number | undefined;
1248
1260
  mediaType?: string | undefined;
1249
1261
  videoCodec?: string | undefined;
1262
+ quality?: string | undefined;
1263
+ dppx?: number | undefined;
1250
1264
  }[];
1251
1265
  format?: "standardInline" | "mobile" | undefined;
1252
1266
  poster?: {
@@ -1705,6 +1719,8 @@ export declare const liveBlogPackageSchema: import("zod").ZodObject<Pick<{
1705
1719
  duration?: number | undefined;
1706
1720
  mediaType?: string | undefined;
1707
1721
  videoCodec?: string | undefined;
1722
+ quality?: string | undefined;
1723
+ dppx?: number | undefined;
1708
1724
  }[];
1709
1725
  format?: "standardInline" | "mobile" | undefined;
1710
1726
  poster?: {
@@ -1966,6 +1982,8 @@ export declare const liveBlogPackageSchema: import("zod").ZodObject<Pick<{
1966
1982
  duration?: number | undefined;
1967
1983
  mediaType?: string | undefined;
1968
1984
  videoCodec?: string | undefined;
1985
+ quality?: string | undefined;
1986
+ dppx?: number | undefined;
1969
1987
  }[];
1970
1988
  format?: "standardInline" | "mobile" | undefined;
1971
1989
  poster?: {
@@ -853,6 +853,8 @@ export declare const placeholderSchema: import("zod").ZodObject<Pick<{
853
853
  pixelHeight: import("zod").ZodOptional<import("zod").ZodNumber>;
854
854
  pixelWidth: import("zod").ZodOptional<import("zod").ZodNumber>;
855
855
  videoCodec: import("zod").ZodOptional<import("zod").ZodString>;
856
+ quality: import("zod").ZodOptional<import("zod").ZodString>;
857
+ dppx: import("zod").ZodOptional<import("zod").ZodNumber>;
856
858
  }, "strip", import("zod").ZodTypeAny, {
857
859
  binaryUrl?: string | undefined;
858
860
  pixelWidth?: number | undefined;
@@ -861,6 +863,8 @@ export declare const placeholderSchema: import("zod").ZodObject<Pick<{
861
863
  duration?: number | undefined;
862
864
  mediaType?: string | undefined;
863
865
  videoCodec?: string | undefined;
866
+ quality?: string | undefined;
867
+ dppx?: number | undefined;
864
868
  }, {
865
869
  binaryUrl?: string | undefined;
866
870
  pixelWidth?: number | undefined;
@@ -869,6 +873,8 @@ export declare const placeholderSchema: import("zod").ZodObject<Pick<{
869
873
  duration?: number | undefined;
870
874
  mediaType?: string | undefined;
871
875
  videoCodec?: string | undefined;
876
+ quality?: string | undefined;
877
+ dppx?: number | undefined;
872
878
  }>, "many">;
873
879
  poster: import("zod").ZodOptional<import("zod").ZodObject<{
874
880
  id: import("zod").ZodString;
@@ -1046,6 +1052,8 @@ export declare const placeholderSchema: import("zod").ZodObject<Pick<{
1046
1052
  duration?: number | undefined;
1047
1053
  mediaType?: string | undefined;
1048
1054
  videoCodec?: string | undefined;
1055
+ quality?: string | undefined;
1056
+ dppx?: number | undefined;
1049
1057
  }[];
1050
1058
  format?: "standardInline" | "mobile" | undefined;
1051
1059
  poster?: {
@@ -1093,6 +1101,8 @@ export declare const placeholderSchema: import("zod").ZodObject<Pick<{
1093
1101
  duration?: number | undefined;
1094
1102
  mediaType?: string | undefined;
1095
1103
  videoCodec?: string | undefined;
1104
+ quality?: string | undefined;
1105
+ dppx?: number | undefined;
1096
1106
  }[];
1097
1107
  format?: "standardInline" | "mobile" | undefined;
1098
1108
  poster?: {
@@ -1179,6 +1189,8 @@ export declare const placeholderSchema: import("zod").ZodObject<Pick<{
1179
1189
  duration?: number | undefined;
1180
1190
  mediaType?: string | undefined;
1181
1191
  videoCodec?: string | undefined;
1192
+ quality?: string | undefined;
1193
+ dppx?: number | undefined;
1182
1194
  }[];
1183
1195
  format?: "standardInline" | "mobile" | undefined;
1184
1196
  poster?: {
@@ -1247,6 +1259,8 @@ export declare const placeholderSchema: import("zod").ZodObject<Pick<{
1247
1259
  duration?: number | undefined;
1248
1260
  mediaType?: string | undefined;
1249
1261
  videoCodec?: string | undefined;
1262
+ quality?: string | undefined;
1263
+ dppx?: number | undefined;
1250
1264
  }[];
1251
1265
  format?: "standardInline" | "mobile" | undefined;
1252
1266
  poster?: {
@@ -1728,6 +1742,8 @@ export declare const placeholderSchema: import("zod").ZodObject<Pick<{
1728
1742
  duration?: number | undefined;
1729
1743
  mediaType?: string | undefined;
1730
1744
  videoCodec?: string | undefined;
1745
+ quality?: string | undefined;
1746
+ dppx?: number | undefined;
1731
1747
  }[];
1732
1748
  format?: "standardInline" | "mobile" | undefined;
1733
1749
  poster?: {
@@ -2012,6 +2028,8 @@ export declare const placeholderSchema: import("zod").ZodObject<Pick<{
2012
2028
  duration?: number | undefined;
2013
2029
  mediaType?: string | undefined;
2014
2030
  videoCodec?: string | undefined;
2031
+ quality?: string | undefined;
2032
+ dppx?: number | undefined;
2015
2033
  }[];
2016
2034
  format?: "standardInline" | "mobile" | undefined;
2017
2035
  poster?: {
@@ -853,6 +853,8 @@ export declare const videoSchema: import("zod").ZodObject<Pick<{
853
853
  pixelHeight: import("zod").ZodOptional<import("zod").ZodNumber>;
854
854
  pixelWidth: import("zod").ZodOptional<import("zod").ZodNumber>;
855
855
  videoCodec: import("zod").ZodOptional<import("zod").ZodString>;
856
+ quality: import("zod").ZodOptional<import("zod").ZodString>;
857
+ dppx: import("zod").ZodOptional<import("zod").ZodNumber>;
856
858
  }, "strip", import("zod").ZodTypeAny, {
857
859
  binaryUrl?: string | undefined;
858
860
  pixelWidth?: number | undefined;
@@ -861,6 +863,8 @@ export declare const videoSchema: import("zod").ZodObject<Pick<{
861
863
  duration?: number | undefined;
862
864
  mediaType?: string | undefined;
863
865
  videoCodec?: string | undefined;
866
+ quality?: string | undefined;
867
+ dppx?: number | undefined;
864
868
  }, {
865
869
  binaryUrl?: string | undefined;
866
870
  pixelWidth?: number | undefined;
@@ -869,6 +873,8 @@ export declare const videoSchema: import("zod").ZodObject<Pick<{
869
873
  duration?: number | undefined;
870
874
  mediaType?: string | undefined;
871
875
  videoCodec?: string | undefined;
876
+ quality?: string | undefined;
877
+ dppx?: number | undefined;
872
878
  }>, "many">;
873
879
  poster: import("zod").ZodOptional<import("zod").ZodObject<{
874
880
  id: import("zod").ZodString;
@@ -1046,6 +1052,8 @@ export declare const videoSchema: import("zod").ZodObject<Pick<{
1046
1052
  duration?: number | undefined;
1047
1053
  mediaType?: string | undefined;
1048
1054
  videoCodec?: string | undefined;
1055
+ quality?: string | undefined;
1056
+ dppx?: number | undefined;
1049
1057
  }[];
1050
1058
  format?: "standardInline" | "mobile" | undefined;
1051
1059
  poster?: {
@@ -1093,6 +1101,8 @@ export declare const videoSchema: import("zod").ZodObject<Pick<{
1093
1101
  duration?: number | undefined;
1094
1102
  mediaType?: string | undefined;
1095
1103
  videoCodec?: string | undefined;
1104
+ quality?: string | undefined;
1105
+ dppx?: number | undefined;
1096
1106
  }[];
1097
1107
  format?: "standardInline" | "mobile" | undefined;
1098
1108
  poster?: {
@@ -1179,6 +1189,8 @@ export declare const videoSchema: import("zod").ZodObject<Pick<{
1179
1189
  duration?: number | undefined;
1180
1190
  mediaType?: string | undefined;
1181
1191
  videoCodec?: string | undefined;
1192
+ quality?: string | undefined;
1193
+ dppx?: number | undefined;
1182
1194
  }[];
1183
1195
  format?: "standardInline" | "mobile" | undefined;
1184
1196
  poster?: {
@@ -1247,6 +1259,8 @@ export declare const videoSchema: import("zod").ZodObject<Pick<{
1247
1259
  duration?: number | undefined;
1248
1260
  mediaType?: string | undefined;
1249
1261
  videoCodec?: string | undefined;
1262
+ quality?: string | undefined;
1263
+ dppx?: number | undefined;
1250
1264
  }[];
1251
1265
  format?: "standardInline" | "mobile" | undefined;
1252
1266
  poster?: {
@@ -1,15 +1,7 @@
1
1
  declare const resolvers: {
2
2
  Clip: {
3
3
  __resolveType: () => string;
4
- dataSource: (clip: import("../model/Clip").Clip) => {
5
- binaryUrl: string;
6
- mediaType: string;
7
- audioCodec?: string;
8
- duration?: number;
9
- pixelHeight?: number;
10
- pixelWidth?: number;
11
- videoCodec?: string;
12
- }[];
4
+ dataSource: (clip: import("../model/Clip").Clip) => import("../types/clip").ClipSource[];
13
5
  type: (clip: import("../model/Clip").Clip) => string;
14
6
  format: (clip: import("../model/Clip").Clip) => "mobile" | "standard-inline";
15
7
  poster: (clip: import("../model/Clip").Clip) => string;
@@ -23,6 +15,9 @@ declare const resolvers: {
23
15
  pixelWidth: (parent: import("../generated").ClipSource) => number | null;
24
16
  pixelHeight: (parent: import("../generated").ClipSource) => number | null;
25
17
  videoCodec: (parent: import("../generated").ClipSource) => string | null;
18
+ quality: (parent: import("../generated").ClipSource) => string | null;
19
+ dppx: (parent: import("../generated").ClipSource) => number | null;
20
+ previousSourceWidth: (parent: import("../generated").ClipSource) => number | null;
26
21
  };
27
22
  };
28
23
  export default resolvers;
@@ -17,6 +17,9 @@ const resolvers = {
17
17
  pixelWidth: (parent) => parent.pixelWidth ?? null,
18
18
  pixelHeight: (parent) => parent.pixelHeight ?? null,
19
19
  videoCodec: (parent) => parent.videoCodec ?? null,
20
+ quality: (parent) => parent.quality ?? null,
21
+ dppx: (parent) => parent.dppx ?? null,
22
+ previousSourceWidth: (parent) => parent.previousSourceWidth ?? null,
20
23
  },
21
24
  };
22
25
  exports.default = resolvers;
@@ -1 +1 @@
1
- {"version":3,"file":"clip.js","sourceRoot":"","sources":["../../src/resolvers/clip.ts"],"names":[],"mappings":";;AAEA,MAAM,SAAS,GAAG;IAChB,IAAI,EAAE;QACJ,aAAa,EAAE,GAAG,EAAE,CAAC,MAAM;QAC3B,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE;QACvC,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE;QAC3B,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE;QAC/B,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE;QAC/B,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE;KACxB;IAED,UAAU,EAAE;QACV,UAAU,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,IAAI,IAAI;QACjD,SAAS,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS;QACvC,QAAQ,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,IAAI,IAAI;QAC7C,SAAS,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS;QACvC,UAAU,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,IAAI,IAAI;QACjD,WAAW,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,WAAW,IAAI,IAAI;QACnD,UAAU,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,IAAI,IAAI;KAClD;CACiE,CAAA;AAEpE,kBAAe,SAAS,CAAA"}
1
+ {"version":3,"file":"clip.js","sourceRoot":"","sources":["../../src/resolvers/clip.ts"],"names":[],"mappings":";;AAEA,MAAM,SAAS,GAAG;IAChB,IAAI,EAAE;QACJ,aAAa,EAAE,GAAG,EAAE,CAAC,MAAM;QAC3B,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE;QACvC,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE;QAC3B,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE;QAC/B,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE;QAC/B,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE;KACxB;IAED,UAAU,EAAE;QACV,UAAU,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,IAAI,IAAI;QACjD,SAAS,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS;QACvC,QAAQ,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,IAAI,IAAI;QAC7C,SAAS,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS;QACvC,UAAU,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,IAAI,IAAI;QACjD,WAAW,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,WAAW,IAAI,IAAI;QACnD,UAAU,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,IAAI,IAAI;QACjD,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,IAAI,IAAI;QAC3C,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,IAAI,IAAI;QACrC,mBAAmB,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,mBAAmB,IAAI,IAAI;KACpE;CACiE,CAAA;AAEpE,kBAAe,SAAS,CAAA"}
@@ -344,15 +344,7 @@ declare const resolvers: {
344
344
  MetaLink: import("../generated").MetaLinkResolvers;
345
345
  Clip: {
346
346
  __resolveType: () => string;
347
- dataSource: (clip: import("../model/Clip").Clip) => {
348
- binaryUrl: string;
349
- mediaType: string;
350
- audioCodec?: string;
351
- duration?: number;
352
- pixelHeight?: number;
353
- pixelWidth?: number;
354
- videoCodec?: string;
355
- }[];
347
+ dataSource: (clip: import("../model/Clip").Clip) => import("../types/clip").ClipSource[];
356
348
  type: (clip: import("../model/Clip").Clip) => string;
357
349
  format: (clip: import("../model/Clip").Clip) => "mobile" | "standard-inline";
358
350
  poster: (clip: import("../model/Clip").Clip) => string;
@@ -366,6 +358,9 @@ declare const resolvers: {
366
358
  pixelWidth: (parent: import("../generated").ClipSource) => number | null;
367
359
  pixelHeight: (parent: import("../generated").ClipSource) => number | null;
368
360
  videoCodec: (parent: import("../generated").ClipSource) => string | null;
361
+ quality: (parent: import("../generated").ClipSource) => string | null;
362
+ dppx: (parent: import("../generated").ClipSource) => number | null;
363
+ previousSourceWidth: (parent: import("../generated").ClipSource) => number | null;
369
364
  };
370
365
  Image: {
371
366
  __resolveType: import("../generated").TypeResolveFn<"ImageDesktop" | "ImageLandscape" | "ImageMobile" | "ImagePortrait" | "ImageSquare" | "ImageSquareFTEdit" | "ImageStandard" | "ImageStandardInline" | "ImageWide", import("../model/Image").Image, import("..").QueryContext>;
@@ -0,0 +1,21 @@
1
+ import { LiteralUnionScalarValues } from '../resolvers/literal-union';
2
+ import { ClipFormat } from '../resolvers/scalars';
3
+ export type ClipSource = {
4
+ binaryUrl: string;
5
+ mediaType: string;
6
+ audioCodec?: string;
7
+ duration?: number;
8
+ pixelHeight?: number;
9
+ pixelWidth?: number;
10
+ videoCodec?: string;
11
+ quality?: string;
12
+ dppx?: number;
13
+ previousSourceWidth?: number;
14
+ };
15
+ export interface ClipVideo {
16
+ id(): string;
17
+ type(): string;
18
+ format(): LiteralUnionScalarValues<typeof ClipFormat>;
19
+ poster(): string;
20
+ dataSource(): ClipSource[];
21
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=clip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clip.js","sourceRoot":"","sources":["../../src/types/clip.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@financial-times/cp-content-pipeline-schema",
3
- "version": "3.18.0",
3
+ "version": "3.19.0",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -358,6 +358,9 @@ fragment Clip on Clip {
358
358
  pixelHeight
359
359
  pixelWidth
360
360
  videoCodec
361
+ quality
362
+ dppx
363
+ previousSourceWidth
361
364
  }
362
365
  poster
363
366
  }
@@ -377,6 +377,8 @@ export type ClipSource = {
377
377
  readonly audioCodec?: Maybe<Scalars['String']['output']>;
378
378
  /** The url of the clip source, eg. 'https://next-media-api.ft.com/renditions/16868569859480/0x0.mp3'. */
379
379
  readonly binaryUrl: Scalars['String']['output'];
380
+ /** The encoding settings intention for the video media */
381
+ readonly dppx?: Maybe<Scalars['Int']['output']>;
380
382
  /** The duration of the clip in milliseconds. */
381
383
  readonly duration?: Maybe<Scalars['Int']['output']>;
382
384
  /** The media type eg. video/mp4. */
@@ -385,6 +387,10 @@ export type ClipSource = {
385
387
  readonly pixelHeight?: Maybe<Scalars['Int']['output']>;
386
388
  /** The width of the clip in pixels. */
387
389
  readonly pixelWidth?: Maybe<Scalars['Int']['output']>;
390
+ /** The width of the source immediately narrower than this one. */
391
+ readonly previousSourceWidth?: Maybe<Scalars['Int']['output']>;
392
+ /** The encoding quality of the video media */
393
+ readonly quality?: Maybe<Scalars['String']['output']>;
388
394
  /** The video encoding format of the source, eg. h264. */
389
395
  readonly videoCodec?: Maybe<Scalars['String']['output']>;
390
396
  };
@@ -2413,10 +2419,13 @@ export type ClipSetResolvers<ContextType = QueryContext, ParentType extends Reso
2413
2419
  export type ClipSourceResolvers<ContextType = QueryContext, ParentType extends ResolversParentTypes['ClipSource'] = ResolversParentTypes['ClipSource']> = ResolversObject<{
2414
2420
  audioCodec: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
2415
2421
  binaryUrl: Resolver<ResolversTypes['String'], ParentType, ContextType>;
2422
+ dppx: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
2416
2423
  duration: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
2417
2424
  mediaType: Resolver<ResolversTypes['String'], ParentType, ContextType>;
2418
2425
  pixelHeight: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
2419
2426
  pixelWidth: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
2427
+ previousSourceWidth: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
2428
+ quality: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
2420
2429
  videoCodec: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
2421
2430
  __isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
2422
2431
  }>;
@@ -0,0 +1,44 @@
1
+ // until we get quality setting data through from CAPI, we infer the quality and ddpx from the details in the filename
2
+ // once we have quality data from CAPI, we can remove this module
3
+ import type { ClipSource } from '../types/clip'
4
+
5
+ // possible output sizes are set in https://github.com/Financial-Times/ip-ovide/tree/main/common/n-zencoder/src/profiles
6
+ const dimensions = new Map([
7
+ [1920, 1080],
8
+ [1280, 720],
9
+ [960, 540],
10
+ [640, 360],
11
+ [480, 270],
12
+ ])
13
+
14
+ // utility to extract width from filename pattern like /640x360.mp4
15
+ function getDimensionsFromFilename(url: string): number[] {
16
+ // regex: get the first set of digits after a "/" and before "x", followed by 1+ digits, followed by ".mp" and 1 digit, then end of string.
17
+ const widthRegex = /\/(\d+)x(\d+)\.mp\d$/i
18
+ const widthMatch = url.match(widthRegex)
19
+ return [
20
+ parseInt(widthMatch?.[1] ?? '0', 10),
21
+ parseInt(widthMatch?.[2] ?? '0', 10),
22
+ ]
23
+ }
24
+
25
+ export default function qualityWorkaround(
26
+ dataSource: ClipSource[]
27
+ ): ClipSource[] {
28
+ return dataSource.map((source) => {
29
+ const originalDimensions = getDimensionsFromFilename(source.binaryUrl ?? '')
30
+ if (originalDimensions[0] === 0) {
31
+ // this will apply to audio files
32
+ return source
33
+ }
34
+ const smallerDimension = Math.min(...originalDimensions)
35
+ source.quality = `${
36
+ Array.from(dimensions.values()).includes(smallerDimension)
37
+ ? smallerDimension
38
+ : dimensions.get(smallerDimension)
39
+ }p`
40
+
41
+ source.dppx = smallerDimension >= 1080 ? 2 : 1
42
+ return source
43
+ })
44
+ }
@@ -4,6 +4,42 @@ const mockContext = {
4
4
  systemCode: 'cp-content-pipeline',
5
5
  } as unknown as QueryContext
6
6
 
7
+ const mockDataSources = [
8
+ {
9
+ audioCodec: 'mp3',
10
+ binaryUrl: 'https://clips.ft.com/someguid/0x0.mp3',
11
+ duration: 21720,
12
+ mediaType: 'audio/mpeg',
13
+ },
14
+ {
15
+ audioCodec: 'aac',
16
+ binaryUrl: 'https://clips.ft.com/someguid/640x360.mp4',
17
+ duration: 21684,
18
+ mediaType: 'video/mp4',
19
+ pixelHeight: 360,
20
+ pixelWidth: 640,
21
+ videoCodec: 'h264',
22
+ },
23
+ {
24
+ audioCodec: 'aac',
25
+ binaryUrl: 'https://clips.ft.com/someguid/1280x720.mp4',
26
+ duration: 21684,
27
+ mediaType: 'video/mp4',
28
+ pixelHeight: 720,
29
+ pixelWidth: 1280,
30
+ videoCodec: 'h264',
31
+ },
32
+ {
33
+ audioCodec: 'aac',
34
+ binaryUrl: 'https://clips.ft.com/someguid/1920x1080.mp4',
35
+ duration: 21684,
36
+ mediaType: 'video/mp4',
37
+ pixelHeight: 720,
38
+ pixelWidth: 1280,
39
+ videoCodec: 'h264',
40
+ },
41
+ ]
42
+
7
43
  describe('Clip', () => {
8
44
  describe('poster', () => {
9
45
  it('uses the Origami Image Service when a url is provided', () => {
@@ -170,4 +206,63 @@ describe('Clip', () => {
170
206
  expect(clip.poster()).toContain('width=1200')
171
207
  })
172
208
  })
209
+ describe('datasources', () => {
210
+ it('creates a quality value and dppx value based on the width in the URL when quality is not provided', () => {
211
+ const clip = new Clip(
212
+ {
213
+ id: 'http://api.ft.com/things/1234',
214
+ type: 'http://www.ft.com/ontology/content/Clip',
215
+ dataSource: mockDataSources,
216
+ },
217
+ mockContext
218
+ )
219
+ const dataSource = clip.dataSource()
220
+ expect(dataSource).toHaveLength(4)
221
+ expect(dataSource?.[0]?.quality).toBe('1080p')
222
+ expect(dataSource?.[0]?.dppx).toBe(2)
223
+ expect(dataSource?.[1]?.quality).toBe('720p')
224
+ expect(dataSource?.[2]?.quality).toBe('360p')
225
+ expect(dataSource?.[3]?.quality).toBeUndefined()
226
+ })
227
+ it('orders the data sources by width and then dppx', () => {
228
+ const clip = new Clip(
229
+ {
230
+ id: 'http://api.ft.com/things/1234',
231
+ type: 'http://www.ft.com/ontology/content/Clip',
232
+ dataSource: mockDataSources,
233
+ },
234
+ mockContext
235
+ )
236
+ const dataSource = clip.dataSource()
237
+ expect(dataSource).toHaveLength(4)
238
+ expect(dataSource?.[0]?.binaryUrl).toBe(
239
+ 'https://clips.ft.com/someguid/1920x1080.mp4'
240
+ )
241
+ expect(dataSource?.[1]?.binaryUrl).toBe(
242
+ 'https://clips.ft.com/someguid/1280x720.mp4'
243
+ )
244
+ expect(dataSource?.[2]?.binaryUrl).toBe(
245
+ 'https://clips.ft.com/someguid/640x360.mp4'
246
+ )
247
+ expect(dataSource?.[3]?.binaryUrl).toBe(
248
+ 'https://clips.ft.com/someguid/0x0.mp3'
249
+ )
250
+ })
251
+ it('sets the previousSourceWidth to the next most narrow source', () => {
252
+ const clip = new Clip(
253
+ {
254
+ id: 'http://api.ft.com/things/1234',
255
+ type: 'http://www.ft.com/ontology/content/Clip',
256
+ dataSource: mockDataSources,
257
+ },
258
+ mockContext
259
+ )
260
+ const dataSource = clip.dataSource()
261
+ expect(dataSource).toHaveLength(4)
262
+ expect(dataSource?.[0]?.previousSourceWidth).toBe(640)
263
+ expect(dataSource?.[1]?.previousSourceWidth).toBe(640)
264
+ expect(dataSource?.[2]?.previousSourceWidth).toBeUndefined()
265
+ expect(dataSource?.[3]?.previousSourceWidth).toBeUndefined()
266
+ })
267
+ })
173
268
  })
package/src/model/Clip.ts CHANGED
@@ -6,25 +6,9 @@ import {
6
6
  } from '../resolvers/literal-union'
7
7
  import { ClipFormat } from '../resolvers/scalars'
8
8
  import imageServiceUrl from '../helpers/imageService'
9
+ import qualityWorkaround from '../helpers/qualityWorkaround'
9
10
  import { QueryContext } from '..'
10
-
11
- type ClipSource = {
12
- binaryUrl: string
13
- mediaType: string
14
- audioCodec?: string
15
- duration?: number
16
- pixelHeight?: number
17
- pixelWidth?: number
18
- videoCodec?: string
19
- }
20
-
21
- interface ClipVideo {
22
- id(): string
23
- type(): string
24
- format(): LiteralUnionScalarValues<typeof ClipFormat>
25
- poster(): string
26
- dataSource(): ClipSource[]
27
- }
11
+ import type { ClipSource, ClipVideo } from '../types/clip'
28
12
 
29
13
  export class Clip implements ClipVideo {
30
14
  constructor(
@@ -60,18 +44,40 @@ export class Clip implements ClipVideo {
60
44
  return 'standard-inline'
61
45
  }
62
46
 
47
+ // apply 'quality' workaround
48
+ // order sources by width and then quality
49
+ // get the min width from the first previous more narrow item
63
50
  dataSource() {
64
- // Order clip dataSource to have an order of video formats first then audio/mpeg, as the browser will first check if it can play the MIME type of the first source
65
- const dataSource = this.clip.dataSource.slice().sort((a, b) => {
66
- if (a.mediaType === 'video/mp4' && b.mediaType !== 'video/mp4') {
67
- return -1
68
- }
69
- if (a.mediaType !== 'video/mp4' && b.mediaType === 'video/mp4') {
70
- return 1
51
+ // create a copy of the array so we don't modify the original data
52
+ let dataSource = this.clip.dataSource.map((src) => ({
53
+ ...src,
54
+ })) as ClipSource[]
55
+ dataSource = qualityWorkaround(dataSource as ClipSource[])
56
+ dataSource = dataSource.sort((a, b) => {
57
+ const widthDiff = (b.pixelWidth ?? 0) - (a.pixelWidth ?? 0)
58
+ if (widthDiff !== 0) {
59
+ return widthDiff
71
60
  }
72
- return 0
61
+ return (b?.dppx ?? 0) - (a?.dppx ?? 0)
73
62
  })
74
- return dataSource as ClipSource[]
63
+
64
+ // get the min width from the previous item if it has pixelWidth set, checking it is not the same as pixelWidth as the current item
65
+ for (let i = 0; i < dataSource.length; i++) {
66
+ let next = 1
67
+ let minWidth = dataSource[i + next]?.pixelWidth
68
+ while (
69
+ typeof minWidth !== 'undefined' &&
70
+ minWidth === dataSource[i]?.pixelWidth
71
+ ) {
72
+ next++
73
+ minWidth = dataSource[i + next]?.pixelWidth
74
+ }
75
+ if (minWidth && dataSource[i]) {
76
+ dataSource[i]!.previousSourceWidth = minWidth
77
+ }
78
+ }
79
+
80
+ return dataSource
75
81
  }
76
82
 
77
83
  poster() {
@@ -103,6 +103,8 @@ const ClipSource = z.object({
103
103
  pixelHeight: z.number().optional(),
104
104
  pixelWidth: z.number().optional(),
105
105
  videoCodec: z.string().optional(),
106
+ quality: z.string().optional(),
107
+ dppx: z.number().optional(),
106
108
  })
107
109
 
108
110
  export const Clip = z.object({
@@ -18,6 +18,9 @@ const resolvers = {
18
18
  pixelWidth: (parent) => parent.pixelWidth ?? null,
19
19
  pixelHeight: (parent) => parent.pixelHeight ?? null,
20
20
  videoCodec: (parent) => parent.videoCodec ?? null,
21
+ quality: (parent) => parent.quality ?? null,
22
+ dppx: (parent) => parent.dppx ?? null,
23
+ previousSourceWidth: (parent) => parent.previousSourceWidth ?? null,
21
24
  },
22
25
  } satisfies { Clip: ClipResolvers; ClipSource: ClipSourceResolvers }
23
26
 
@@ -0,0 +1,23 @@
1
+ import { LiteralUnionScalarValues } from '../resolvers/literal-union'
2
+ import { ClipFormat } from '../resolvers/scalars'
3
+
4
+ export type ClipSource = {
5
+ binaryUrl: string
6
+ mediaType: string
7
+ audioCodec?: string
8
+ duration?: number
9
+ pixelHeight?: number
10
+ pixelWidth?: number
11
+ videoCodec?: string
12
+ quality?: string
13
+ dppx?: number
14
+ previousSourceWidth?: number
15
+ }
16
+
17
+ export interface ClipVideo {
18
+ id(): string
19
+ type(): string
20
+ format(): LiteralUnionScalarValues<typeof ClipFormat>
21
+ poster(): string
22
+ dataSource(): ClipSource[]
23
+ }