From bef5dff5fa5f20a9f9c5c643030cd1935843415e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Cr=C3=A8me?= Date: Tue, 8 Nov 2022 21:48:31 +0100 Subject: [PATCH 01/17] Add trigger upload method --- src/nomalab.ts | 176 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 134 insertions(+), 42 deletions(-) diff --git a/src/nomalab.ts b/src/nomalab.ts index 08e4a6d..652dfb5 100644 --- a/src/nomalab.ts +++ b/src/nomalab.ts @@ -9,8 +9,8 @@ import { Organization, Path, Show, - ShowKind, ShowClass, + ShowKind, } from "./types.ts"; import * as mod from "https://deno.land/std@0.148.0/http/cookie.ts"; @@ -31,7 +31,12 @@ export class Nomalab { async getShow(showUuid: string): Promise { const response = await this.#fetch(`shows/${showUuid}`, {}); - if (!response.ok) this.#throwError(`ERROR - Can't find show with id ${showUuid}.`, response); + if (!response.ok) { + this.#throwError( + `ERROR - Can't find show with id ${showUuid}.`, + response, + ); + } return response.json() as Promise; } @@ -44,7 +49,12 @@ export class Nomalab { return response.json() as Promise; } - async createHierarchy(organizationId: string, name: string, kind: NodeKind, parent?: string): Promise { + async createHierarchy( + organizationId: string, + name: string, + kind: NodeKind, + parent?: string, + ): Promise { const response = await this.#requestWithSwitch( organizationId, "hierarchy", @@ -52,23 +62,31 @@ export class Nomalab { { name, parent, - kind - } + kind, + }, ); - if (!response.ok) this.#throwError(`ERROR - Can't create ${kind} ${name}.`, response); + if (!response.ok) { + this.#throwError(`ERROR - Can't create ${kind} ${name}.`, response); + } return response.json() as Promise; } - async createShow(nodeId: string, name: string, kind: ShowKind): Promise { + async createShow( + nodeId: string, + name: string, + kind: ShowKind, + ): Promise { const response = await this.#fetch(`hierarchy/${nodeId}/shows`, { method: "POST", bodyJsonObject: { name, - kind - } + kind, + }, }); - if (!response.ok) this.#throwError(`ERROR - Can't create show ${kind} ${name}.`, response); + if (!response.ok) { + this.#throwError(`ERROR - Can't create show ${kind} ${name}.`, response); + } const { id } = await response.json() as ShowClass; return id; } @@ -83,8 +101,9 @@ export class Nomalab { `users/switch`, { bodyJsonObject: { organization: organizationId }, - method: "POST" - }); + method: "POST", + }, + ); if (response.status != 200) { this.#throwError(`Can't switch to org ${organizationId}`, response); } @@ -99,17 +118,23 @@ export class Nomalab { const cookie = mod.getCookies(headers); const bodyJsonObject = method == "POST" ? (body ?? {}) : undefined; return this.#fetch( - partialUrl, { + partialUrl, + { bodyJsonObject, method, - cookieHeader: cookie - } + cookieHeader: cookie, + }, ); } async getChildren(nodeUuid: string): Promise { const response = await this.#fetch(`hierarchy/${nodeUuid}/children`, {}); - if (!response.ok) this.#throwError(`ERROR - Can't find children with id ${nodeUuid}.`, response); + if (!response.ok) { + this.#throwError( + `ERROR - Can't find children with id ${nodeUuid}.`, + response, + ); + } return response.json() as Promise; } @@ -129,13 +154,23 @@ export class Nomalab { async getNode(nodeUuid: string): Promise { const response = await this.#fetch(`hierarchy/${nodeUuid}`, {}); - if (!response.ok) this.#throwError(`ERROR - Can't find node with id ${nodeUuid}.`, response); + if (!response.ok) { + this.#throwError( + `ERROR - Can't find node with id ${nodeUuid}.`, + response, + ); + } return response.json() as Promise; } async getShowsForNode(nodeUuid: string): Promise { const response = await this.#fetch(`hierarchy/${nodeUuid}/shows`, {}); - if (!response.ok) this.#throwError(`ERROR - error when retrieving shows for node ${nodeUuid}.`, response); + if (!response.ok) { + this.#throwError( + `ERROR - error when retrieving shows for node ${nodeUuid}.`, + response, + ); + } return response.json() as Promise; } @@ -144,16 +179,23 @@ export class Nomalab { `admin/shows/path`, { bodyJsonObject: { showIds: [showUuid] }, - method: "POST" - } + method: "POST", + }, ); - if (!response.ok) this.#throwError(`ERROR - Can't find show with id ${showUuid}.`, response); + if (!response.ok) { + this.#throwError( + `ERROR - Can't find show with id ${showUuid}.`, + response, + ); + } return response.json() as Promise; } async getOrganizations(): Promise { const response = await this.#fetch(`organizations`, {}); - if (!response.ok) this.#throwError(`ERROR - Can't get organizations.`, response); + if (!response.ok) { + this.#throwError(`ERROR - Can't get organizations.`, response); + } return response.json() as Promise; } @@ -169,7 +211,9 @@ export class Nomalab { async getJob(jobUuid: string): Promise { const response = await this.#fetch(`jobs/${jobUuid}`, {}); - if (!response.ok) this.#throwError(`ERROR - Can't find job with id ${jobUuid}.`, response); + if (!response.ok) { + this.#throwError(`ERROR - Can't find job with id ${jobUuid}.`, response); + } return response.json() as Promise; } @@ -178,19 +222,26 @@ export class Nomalab { "aws/copy", { bodyJsonObject: payload, - method: "POST" - } + method: "POST", + }, ); - if (!response.ok) this.#throwError(`ERROR - Can't make a s3 copy with payload.${JSON.stringify(payload)}`, response); + if (!response.ok) { + this.#throwError( + `ERROR - Can't make a s3 copy with payload.${JSON.stringify(payload)}`, + response, + ); + } return Promise.resolve(); } async accept(showId: string): Promise { const response = await this.#fetch( `shows/${showId}/accept`, - { method: "POST" } + { method: "POST" }, ); - if (!response.ok) this.#throwError(`ERROR - Can't accept show. ${showId}`, response); + if (!response.ok) { + this.#throwError(`ERROR - Can't accept show. ${showId}`, response); + } return response.json() as Promise; } @@ -201,15 +252,39 @@ export class Nomalab { { bodyJsonObject: deliverPayload, method: "POST", + }, + ); + if (!response.ok) { + if (response.status == 409) { + throw new AlreadyPresentDeliverable( + "Can't deliver because of an already present deliverable.", + ); + } else { + this.#throwError( + `ERROR - Can't deliver with payload.${deliverPayload}`, + response, + ); } + } + return Promise.resolve() as Promise; + } + + async triggerUpload(broadcastableId: string) { + const response = await this.#fetch( + `broadcastables/${broadcastableId}/delivery`, + { method: "POST", bodyJsonObject: {} }, ); + console.log(response.headers); if (!response.ok) { if (response.status == 409) { throw new AlreadyPresentDeliverable( "Can't deliver because of an already present deliverable.", ); } else { - this.#throwError(`ERROR - Can't deliver with payload.${deliverPayload}`, response); + this.#throwError( + `ERROR - Can't accept and deliver show. [${broadcastableId}]`, + response, + ); } } return Promise.resolve() as Promise; @@ -225,14 +300,17 @@ export class Nomalab { `broadcastables/${broadcastableId}/delivery`, { method: "POST", bodyJsonObject: {} }, ); - console.log(response.headers) + console.log(response.headers); if (!response.ok) { if (response.status == 409) { throw new AlreadyPresentDeliverable( "Can't deliver because of an already present deliverable.", ); } else { - this.#throwError(`ERROR - Can't accept and deliver show. [${showId}]`, response); + this.#throwError( + `ERROR - Can't accept and deliver show. [${showId}]`, + response, + ); } } return Promise.resolve(); @@ -247,7 +325,7 @@ export class Nomalab { `broadcastables/${broadcastableId}/copyToOrganization`, { bodyJsonObject: { targetOrg: targetOrgId }, - method: "POST" + method: "POST", }, ); if (!response.ok) { @@ -256,7 +334,10 @@ export class Nomalab { "Can't deliver because of an already present deliverable.", ); } else { - this.#throwError(`ERROR - Can't deliver without transcoding to org id <${targetOrgId}>`, response); + this.#throwError( + `ERROR - Can't deliver without transcoding to org id <${targetOrgId}>`, + response, + ); } } return response.json() as Promise; @@ -266,10 +347,15 @@ export class Nomalab { const response = await this.#fetch( `files/${proxyId}/manifest`, { - contentType: "application/xml" - } + contentType: "application/xml", + }, ); - if (!response.ok) this.#throwError(`ERROR - Can't find manifest with proxyId ${proxyId}.`, response); + if (!response.ok) { + this.#throwError( + `ERROR - Can't find manifest with proxyId ${proxyId}.`, + response, + ); + } return response.blob() as Promise; } @@ -281,17 +367,23 @@ export class Nomalab { #fetch( partialUrl: string, optionalArg: { - method?: string, - bodyJsonObject?: unknown, - contentType?: string, - cookieHeader?: Record, + method?: string; + bodyJsonObject?: unknown; + contentType?: string; + cookieHeader?: Record; }, ): Promise { const myHeaders = new Headers(); - myHeaders.append("Content-Type", optionalArg.contentType ?? "application/json"); + myHeaders.append( + "Content-Type", + optionalArg.contentType ?? "application/json", + ); myHeaders.append("Authorization", `Bearer ${this.#apiToken} `); if (optionalArg.cookieHeader) { - myHeaders.append("Cookie", `sessionJwt=${optionalArg.cookieHeader["sessionJwt"]}`) + myHeaders.append( + "Cookie", + `sessionJwt=${optionalArg.cookieHeader["sessionJwt"]}`, + ); } const request = new Request( `https://${this.#contextSubDomain()}.nomalab.com/v3/${partialUrl}`, From 710ca104692213676f37cdd22c35bc94ef814c1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Cr=C3=A8me?= Date: Tue, 8 Nov 2022 22:24:52 +0100 Subject: [PATCH 02/17] change VF to VD --- src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index d90dce8..3296e12 100644 --- a/src/types.ts +++ b/src/types.ts @@ -133,7 +133,7 @@ export interface SubtitleFormat { } export interface DeliverPayload { format: string; - versionMapping: "VFVO"|"VF"|"VO"; + versionMapping: "VO"|"VD"|"VOVD"; timecodeOut: string | null; timecodeIn: string | null; subtitles: DeliverSubtitle | null; From 410249f255776a9397e23e195046b98c3721df42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Cr=C3=A8me?= Date: Tue, 8 Nov 2022 22:28:45 +0100 Subject: [PATCH 03/17] Fix VDVO --- src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index 3296e12..5602a46 100644 --- a/src/types.ts +++ b/src/types.ts @@ -133,7 +133,7 @@ export interface SubtitleFormat { } export interface DeliverPayload { format: string; - versionMapping: "VO"|"VD"|"VOVD"; + versionMapping: "VO"|"VD"|"VDVO"; timecodeOut: string | null; timecodeIn: string | null; subtitles: DeliverSubtitle | null; From 0f27750ae6438510db9dc4e5afb06073606b606b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Cr=C3=A8me?= Date: Wed, 9 Nov 2022 12:28:41 +0100 Subject: [PATCH 04/17] Add orgaByName --- src/nomalab.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/nomalab.ts b/src/nomalab.ts index 652dfb5..0eeb924 100644 --- a/src/nomalab.ts +++ b/src/nomalab.ts @@ -209,6 +209,16 @@ export class Nomalab { return Promise.resolve(organisation[0]); } + async getOrganizationByName(organizationName : string) : Promise { + const organisation = (await this.getOrganizations()).filter((org) => { + return org.name == organizationName; + }); + if (organisation.length == 0) { + return Promise.reject(`Org ${organizationName} not found`); + } + return Promise.resolve(organisation[0]); + } + async getJob(jobUuid: string): Promise { const response = await this.#fetch(`jobs/${jobUuid}`, {}); if (!response.ok) { From 432983a625e852a5bfefa170e0bc230b60aabb3d Mon Sep 17 00:00:00 2001 From: "perrine@nomalab.com" Date: Mon, 7 Nov 2022 10:56:02 +0100 Subject: [PATCH 05/17] If destRole is filled launch copyFromExt route --- src/nomalab.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/nomalab.ts b/src/nomalab.ts index 0eeb924..b73adb0 100644 --- a/src/nomalab.ts +++ b/src/nomalab.ts @@ -218,7 +218,7 @@ export class Nomalab { } return Promise.resolve(organisation[0]); } - + async getJob(jobUuid: string): Promise { const response = await this.#fetch(`jobs/${jobUuid}`, {}); if (!response.ok) { @@ -228,19 +228,22 @@ export class Nomalab { } async s3Upload(payload: CopyToBroadcastable): Promise { + const url = payload.destRole ? "aws/copyFromExt" : "aws/copy"; const response = await this.#fetch( - "aws/copy", + url, { bodyJsonObject: payload, method: "POST", }, ); + if (!response.ok) { this.#throwError( - `ERROR - Can't make a s3 copy with payload.${JSON.stringify(payload)}`, - response, + `ERROR - Can't make a s3 copy with payload.${JSON.stringify(payload)} on url ${url}.`, + response ); } + return Promise.resolve(); } From edd380a36313d8ef6ece49687a2f658a0f1655f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Cr=C3=A8me?= Date: Fri, 30 Dec 2022 12:09:53 +0100 Subject: [PATCH 06/17] Add Build. --- .github/workflows/build.yml | 15 ++++++++++++++ README.md | 13 ++++++++---- build.ts | 4 ++++ mod.ts | 8 ++++---- src/nomalab.ts | 8 +++++--- src/types.ts | 40 ++++++++++++++++++------------------- 6 files changed, 57 insertions(+), 31 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 build.ts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..7a9bc4f --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,15 @@ +name: Build + +on: push + +jobs: + build: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + - uses: denoland/setup-deno@v1.1.1 + with: + deno-version: v1.x # Run with latest stable Deno. + - run: deno fmt --check + - run: deno lint + - run: deno build.ts diff --git a/README.md b/README.md index ec35f00..c80cb71 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,17 @@ # deno_lib + Deno lib to handle Nomalab API with deno # Example + ```ts -import { Nomalab, Show } from "https://raw.githubusercontent.com/nomalab/deno_lib/main/mod.ts"; +import { + Nomalab, + Show, +} from "https://raw.githubusercontent.com/nomalab/deno_lib/main/mod.ts"; -const apiToken = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' -const context = 'beta' +const apiToken = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; +const context = "beta"; const nomalab = new Nomalab(context, apiToken); -const show = await nomalab.getShow('xxx-x-xxx--xxxx-xxxxx'); +const show = await nomalab.getShow("xxx-x-xxx--xxxx-xxxxx"); ``` diff --git a/build.ts b/build.ts new file mode 100644 index 0000000..ce89e25 --- /dev/null +++ b/build.ts @@ -0,0 +1,4 @@ +let resp = await fetch("https://example.com"); +console.log(resp.status); // 200 +console.log(resp.headers.get("Content-Type")); // "text/html" +console.log(await resp.text()); // "Hello, World!" diff --git a/mod.ts b/mod.ts index 11045c7..b10ca34 100644 --- a/mod.ts +++ b/mod.ts @@ -1,19 +1,19 @@ export * from "./src/nomalab.ts"; export type { BroadcastableKind, - Delivery, Deliveries, DeliverPayload, + Delivery, + FileWrapper, Job, Material, - FileWrapper, Node, - NodeKind, NodeClass, + NodeKind, Organization, Path, Show, - ShowKind, ShowClass, + ShowKind, Subtitle, } from "./src/types.ts"; diff --git a/src/nomalab.ts b/src/nomalab.ts index b73adb0..bfe4174 100644 --- a/src/nomalab.ts +++ b/src/nomalab.ts @@ -209,7 +209,7 @@ export class Nomalab { return Promise.resolve(organisation[0]); } - async getOrganizationByName(organizationName : string) : Promise { + async getOrganizationByName(organizationName: string): Promise { const organisation = (await this.getOrganizations()).filter((org) => { return org.name == organizationName; }); @@ -239,8 +239,10 @@ export class Nomalab { if (!response.ok) { this.#throwError( - `ERROR - Can't make a s3 copy with payload.${JSON.stringify(payload)} on url ${url}.`, - response + `ERROR - Can't make a s3 copy with payload.${ + JSON.stringify(payload) + } on url ${url}.`, + response, ); } diff --git a/src/types.ts b/src/types.ts index 5602a46..d2fcd2e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -70,7 +70,7 @@ export interface Files { } export interface FileWrapper { - file : FileClass; + file: FileClass; } export interface Material extends FileWrapper { container: Container; @@ -92,28 +92,28 @@ export interface Container { } export interface Deliveries { - shows: Show[]; - nodes: DeliveryNode[]; - formats: DeliveriesFormat[]; + shows: Show[]; + nodes: DeliveryNode[]; + formats: DeliveriesFormat[]; subtitleFormats: SubtitleFormat[]; } export interface DeliveriesFormat { - organizationId: string; - organizationName: string; + organizationId: string; + organizationName: string; organizationAllowDeliveryWithoutTranscoding: boolean; - formats: FormatElement[]; + formats: FormatElement[]; } export interface FormatElement { - id: string; + id: string; name: string; } export interface DeliveryNode { showId: string; - id: string; - name: string; + id: string; + name: string; parent: null | string; } @@ -127,18 +127,18 @@ export enum ArchiveState { } export interface SubtitleFormat { - organizationId: string; + organizationId: string; organizationName: string; - subtitleFormats: FormatElement[]; + subtitleFormats: FormatElement[]; } export interface DeliverPayload { - format: string; - versionMapping: "VO"|"VD"|"VDVO"; - timecodeOut: string | null; - timecodeIn: string | null; - subtitles: DeliverSubtitle | null; - targetOrg: string; - targetId: null; + format: string; + versionMapping: "VO" | "VD" | "VDVO"; + timecodeOut: string | null; + timecodeIn: string | null; + subtitles: DeliverSubtitle | null; + targetOrg: string; + targetId: null; } export interface DeliverSubtitle { format: string | null; @@ -570,7 +570,7 @@ export enum BroadcastableKind { Subtitle = "Subtitle", Material = "Material", Audio = "Audio", - Extra = "Extra" + Extra = "Extra", } export interface CopyToBroadcastable { From 2addb2e2924ad5e0920771db5062af1eaf7064ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Cr=C3=A8me?= Date: Fri, 30 Dec 2022 14:12:52 +0100 Subject: [PATCH 07/17] remove lint problems. --- build.ts | 2 +- src/types.ts | 45 +++++++++++++++++++++++---------------------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/build.ts b/build.ts index ce89e25..6408829 100644 --- a/build.ts +++ b/build.ts @@ -1,4 +1,4 @@ -let resp = await fetch("https://example.com"); +const resp = await fetch("https://example.com"); console.log(resp.status); // 200 console.log(resp.headers.get("Content-Type")); // "text/html" console.log(await resp.text()); // "Hello, World!" diff --git a/src/types.ts b/src/types.ts index d2fcd2e..62337c4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -37,18 +37,18 @@ export interface Show { jobs: Job[]; creator: Creator; organization: ShowOrganization; - channels: any[]; - invitations: any[]; - timeline: any[]; - extras: any[]; + channels: unknown[]; + invitations: unknown[]; + timeline: unknown[]; + extras: unknown[]; activeBroadcastable: ActiveBroadcastable; - previousBroadcastables: any[]; + previousBroadcastables: unknown[]; } export interface ActiveBroadcastable { broadcastable: Broadcastable; files: Files; - comments: any[]; + comments: unknown[]; } export interface Broadcastable { @@ -118,7 +118,7 @@ export interface DeliveryNode { } export interface Subtitles { - subtitle: any[]; + subtitle: unknown[]; } export enum ArchiveState { @@ -160,7 +160,7 @@ export interface DeliveryTranscoding { startedAt: string; progressedAt: string; log: null | string; - warning: any[]; + warning: unknown[]; } export enum Phase { @@ -197,7 +197,7 @@ export interface FileTranscoding { startedAt: string; progressedAt: string; log: null | string; - warning: any[]; + warning: unknown[]; } export interface Upload { @@ -247,8 +247,8 @@ export interface StreamValue { } export interface Issues { - errors: any[]; - warnings: any[]; + errors: unknown[]; + warnings: unknown[]; } export interface PurpleNodeType { @@ -340,7 +340,7 @@ export interface LivingstoneSouthernWhiteFacedOwl { } export interface Purple { - area: any[]; + area: unknown[]; name: string; value: null | string; mapping: MaxShortTermLoudness[]; @@ -361,6 +361,7 @@ export interface Fluffy { value: null | string; } +// deno-lint-ignore no-empty-interface export interface ProgramLoudnessEbu { } @@ -369,16 +370,16 @@ export interface MP4TimeCodeTrackClass { } export interface MP4TimeCodeTrack { - area: any[]; + area: unknown[]; name: string; value: null; - mapping: any[]; + mapping: unknown[]; SystemItem: ProgramLoudnessEbu[]; TimeCodeTrack: TimeCodeTrack[]; TimecodeTrack: ProgramLoudnessEbu[]; ProgramLoudnessEBU: ProgramLoudnessEbu[]; VideoTrackProperty: ProgramLoudnessEbu[]; - MaxShortTermLoudness: any[]; + MaxShortTermLoudness: unknown[]; } export interface TimeCodeTrack { @@ -397,16 +398,16 @@ export interface ProgLoudnessEBUClass { } export interface ProgLoudnessEBU { - area: any[]; + area: unknown[]; name: string; value: null; - mapping: any[]; + mapping: unknown[]; SystemItem: ProgramLoudnessEbu[]; TimeCodeTrack: ProgramLoudnessEbu[]; TimecodeTrack: ProgramLoudnessEbu[]; ProgramLoudnessEBU: ProgramLoudnessEBU[]; VideoTrackProperty: ProgramLoudnessEbu[]; - MaxShortTermLoudness: any[]; + MaxShortTermLoudness: unknown[]; } export interface ProgramLoudnessEBU { @@ -484,8 +485,8 @@ export interface Subtitle extends FileWrapper { proxies: Proxies; reportXml: null; reportPdf: null; - deliveries: any[]; - segments: any[]; + deliveries: unknown[]; + segments: unknown[]; } export interface FluffyStream { @@ -532,8 +533,8 @@ export interface ShowOrganization { allowDeliveryWithoutTranscoding: boolean; logo: null; webhooks: Webhook[]; - formats: any[]; - subtitleFormats: any[]; + formats: unknown[]; + subtitleFormats: unknown[]; } export interface Webhook { From 34ccf897e9a9ad3a3b99e3fc201481f96b9d8a4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Cr=C3=A8me?= Date: Fri, 30 Dec 2022 14:14:02 +0100 Subject: [PATCH 08/17] fix. --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7a9bc4f..aa0a337 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,4 +12,4 @@ jobs: deno-version: v1.x # Run with latest stable Deno. - run: deno fmt --check - run: deno lint - - run: deno build.ts + - run: deno run --allow-all build.ts From a02a386e50675cbfb7b519cda595809d33496f70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Cr=C3=A8me?= Date: Fri, 30 Dec 2022 15:10:29 +0100 Subject: [PATCH 09/17] test --- build.ts | 5 +---- deno.jsonc | 12 ++++++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 deno.jsonc diff --git a/build.ts b/build.ts index 6408829..21c37c3 100644 --- a/build.ts +++ b/build.ts @@ -1,4 +1 @@ -const resp = await fetch("https://example.com"); -console.log(resp.status); // 200 -console.log(resp.headers.get("Content-Type")); // "text/html" -console.log(await resp.text()); // "Hello, World!" +console.log(`GITHUB_REF_NAME : ${Deno.env.get("GITHUB_REF_NAME")}`); diff --git a/deno.jsonc b/deno.jsonc new file mode 100644 index 0000000..8c07a93 --- /dev/null +++ b/deno.jsonc @@ -0,0 +1,12 @@ +{ + "lint": { + "files": { + "include": ["src/"] + } + }, + "fmt": { + "files": { + "include": ["src/"] + } + } +} From ac00eff265c8f419cd5b217a30699235ed0fd668 Mon Sep 17 00:00:00 2001 From: Maxime Dantec Date: Fri, 16 Dec 2022 11:18:33 +0100 Subject: [PATCH 10/17] add needed apis for multi delivery et al. --- deno.jsonc | 3 + mod.ts | 20 +---- src/deps.ts | 2 + src/formats.ts | 196 +++++++++++++++++++++++++++++++++++++++++++++++++ src/nomalab.ts | 139 ++++++++++++++++++++++++----------- src/types.ts | 73 ++++++++++++++++-- 6 files changed, 366 insertions(+), 67 deletions(-) create mode 100644 src/deps.ts create mode 100644 src/formats.ts diff --git a/deno.jsonc b/deno.jsonc index 8c07a93..41cb719 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -7,6 +7,9 @@ "fmt": { "files": { "include": ["src/"] + }, + "options": { + "lineWidth": 80 } } } diff --git a/mod.ts b/mod.ts index b10ca34..3d18903 100644 --- a/mod.ts +++ b/mod.ts @@ -1,19 +1,3 @@ export * from "./src/nomalab.ts"; -export type { - BroadcastableKind, - Deliveries, - DeliverPayload, - Delivery, - FileWrapper, - Job, - Material, - Node, - NodeClass, - NodeKind, - Organization, - Path, - Show, - ShowClass, - ShowKind, - Subtitle, -} from "./src/types.ts"; +export * from "./src/types.ts"; +export * from "./src/formats.ts"; diff --git a/src/deps.ts b/src/deps.ts new file mode 100644 index 0000000..57bd117 --- /dev/null +++ b/src/deps.ts @@ -0,0 +1,2 @@ +export * from "https://deno.land/std@0.170.0/http/cookie.ts"; +export * from "https://deno.land/std@0.170.0/testing/asserts.ts"; diff --git a/src/formats.ts b/src/formats.ts new file mode 100644 index 0000000..c17e899 --- /dev/null +++ b/src/formats.ts @@ -0,0 +1,196 @@ +export type Format = { + id: string; + name: string; + specification: FormatSpec; + audioCodec: FormatAudioCodec; + audioBitrate?: number; + startTimecode: string; + frameRate: FrameRate; + noFrameRateConversion: boolean; + ffmpegArgs?: string; + audioArgs?: string; + bmxArgs?: string; + options?: FormatMxfOptions; + noLoudness?: boolean; + loudnessRange?: number; + loudnessProgram?: number; + loudnessTruePeak?: number; + videoEdit?: FormatVideoEdit; + writeTimecode: boolean; + videoBitrate?: number; + encodeSubtitle: boolean; + crop?: FormatCropParameters; + scale?: FormatScaleParameters; + clip?: FormatClipParameters; + qcTestPlan: string; + qcReportTemplate: string; + subtitleVersion?: Version; + subtitleTypeVersion?: SubtitleTypeVersion; + subtitleFormat?: string; +}; + +export enum FormatSpec { + Mxf = "Mxf", + Mp4 = "Mp4", + ProRes422 = "ProRes422", + ProRes4444 = "ProRes4444", + ADN = "ADN", + AVCIntra100 = "AVCIntra100", + IMX50 = "IMX50", + Mp4Salto = "Mp4Salto", + MovH264 = "MovH264", + MovHevc = "MovHevc", + MxfProgressive = "MxfProgressive", + Demux = "Demux", + AudioExtract = "AudioExtract", +} + +export enum FormatAudioCodec { + PCMS24LE = "PCMS24LE", + PCMS16LE = "PCMS16LE", + PCMS24BE = "PCMS24BE", + AAC = "AAC", + MP3 = "MP3", + MOV_Conteneur = "MOV_Conteneur", +} + +export type FrameRate = { + id: string; + numerator: number; + denominator: number; +}; + +export type FormatMxfOptions = { + controlInstantaneousBitrate: boolean; + as10: boolean; + as11: boolean; + bwf: boolean; + afd?: number; + version12: boolean; + deinterlacing: boolean; + qmax?: number; +}; + +export type FormatVideoEdit = { + before: FormatSegment[]; + after: FormatSegment[]; +}; + +export type FormatSegment = { + index: number; + kind: FormatSegmentKind; + duration: number; + sourceName?: string; + sourceBucket?: string; + sourceKey?: string; + subtract: boolean; +}; + +export enum FormatSegmentKind { + Mire = "Mire", + Black = "Black", + Slate = "Slate", + Video = "Video", + Countdown = "Countdown", +} + +export type FormatCropParameters = { + leftPx: number; + rightPx: number; + topPx: number; + bottomPx: number; +}; + +export type FormatScaleParameters = { + width: number; + height: number; + scaleAspectRatio: boolean; + scaleLetterbox: boolean; +}; + +export type FormatClipParameters = { + clipMin: number; + clipMax: number; +}; + +export type FormatSubtitle = { + id: string; + name: string; + fileFormat: SubtitleFileFormat; + subtitleTimecode?: string; + subtitleFrameRate?: FrameRate; + displayStandard?: SubtitleDisplayStandard; + offset?: string; +}; + +export enum SubtitleFileFormat { + STL = "STL", + WebVTT = "WebVTT", + SRT = "SRT", +} + +export enum SubtitleDisplayStandard { + Open = "Open", + Teletext1 = "Teletext1", + Teletext2 = "Teletext2", +} + +export enum SubtitleTypeVersion { + PARTIAL = "PARTIAL", + COMPLETE = "COMPLETE", + COMPLETE_WITHOUT_PARTIAL = "COMPLETE_WITHOUT_PARTIAL", + SDH = "SDH", +} + +export enum SegmentLabel { + OpeningCredits = "OpeningCredits", + EndingCredits = "EndingCredits", + Introduction = "Introduction", + Program = "Program", + Trailer = "Trailer", + Advertising = "Advertising", + TestPattern = "TestPattern", + Black = "Black", + Slate = "Slate", + NeutralBases = "NeutralBases", + CustomDelivery = "CustomDelivery", +} + +export enum Version { + ARA = "ARA", + CHI = "CHI", + KOR = "KOR", + DAN = "DAN", + DUT = "DUT", + HEB = "HEB", + NLD = "NLD", + RUS = "RUS", + SWE = "SWE", + FRA = "FRA", + GER = "GER", + ITA = "ITA", + POR = "POR", + ENG = "ENG", + SPA = "SPA", + JPN = "JPN", + NOR = "NOR", + UKR = "UKR", + INT = "INT", + NOTHING = "", +} + +export enum Mapping { + AsMaster = "AsMaster", + NoSound = "NoSound", + VD = "VD", + VO = "VO", + VI = "VI", + VDVO = "VDVO", + VOAD = "VOAD", + VDAD = "VDAD", + VIVD = "VIVD", + VIVO = "VIVO", + VDVOAD = "VDVOAD", + VDVIVONLY = "VDVIVONLY", + VDVIMEVONLY = "VDVIMEVONLY", +} diff --git a/src/nomalab.ts b/src/nomalab.ts index bfe4174..a4d2a50 100644 --- a/src/nomalab.ts +++ b/src/nomalab.ts @@ -1,18 +1,24 @@ import { CopyToBroadcastable, + DeliverableOrganization, Deliveries, DeliverPayload, + DeliveryApi, Job, + MeUser, Node, NodeClass, NodeKind, Organization, Path, + Segment, Show, ShowClass, ShowKind, + SubtitleFormat, } from "./types.ts"; -import * as mod from "https://deno.land/std@0.148.0/http/cookie.ts"; +import { Format } from "./formats.ts"; +import { assert, getSetCookies } from "./deps.ts"; export class AlreadyPresentDeliverable extends Error { constructor(msg: string) { @@ -29,6 +35,14 @@ export class Nomalab { this.#apiToken = apiToken; } + async me(): Promise { + const response = await this.#fetch(`users/me`, {}); + if (!response.ok) { + this.#throwError(`ERROR - Can't load user infos.`, response); + } + return response.json() as Promise; + } + async getShow(showUuid: string): Promise { const response = await this.#fetch(`shows/${showUuid}`, {}); if (!response.ok) { @@ -44,6 +58,7 @@ export class Nomalab { const response = await this.#requestWithSwitch( organizationId, "hierarchy", + {}, ); if (!response.ok) this.#throwError(`ERROR - Can't find root.`, response); return response.json() as Promise; @@ -58,11 +73,13 @@ export class Nomalab { const response = await this.#requestWithSwitch( organizationId, "hierarchy", - "POST", { - name, - parent, - kind, + method: "POST", + bodyJsonObject: { + name, + parent, + kind, + }, }, ); if (!response.ok) { @@ -94,8 +111,12 @@ export class Nomalab { async #requestWithSwitch( organizationId: string, partialUrl: string, - method?: "GET" | "POST", - body?: unknown, + optionalArg: { + method?: string; + bodyJsonObject?: unknown; + contentType?: string; + cookieHeader?: Record; + }, ): Promise { const response = await this.#fetch( `users/switch`, @@ -104,27 +125,17 @@ export class Nomalab { method: "POST", }, ); - if (response.status != 200) { + if (!response.ok) { this.#throwError(`Can't switch to org ${organizationId}`, response); } - const headers = new Headers(); - const setCookie = response.headers.get("set-cookie"); - if (setCookie != null) { - headers.append("Cookie", setCookie); - } + + const setCookie = getSetCookies(response.headers)[0]; + assert(setCookie, "No cookie"); + this.#apiToken = setCookie.value; + // To avoid leak since we don't use the body of the response await response.body?.cancel(); - - const cookie = mod.getCookies(headers); - const bodyJsonObject = method == "POST" ? (body ?? {}) : undefined; - return this.#fetch( - partialUrl, - { - bodyJsonObject, - method, - cookieHeader: cookie, - }, - ); + return this.#fetch(partialUrl, optionalArg); } async getChildren(nodeUuid: string): Promise { @@ -219,6 +230,20 @@ export class Nomalab { return Promise.resolve(organisation[0]); } + async jobs(organizationId: string): Promise { + const response = await this.#fetch( + `organizations/${organizationId}/jobs`, + {}, + ); + if (!response.ok) { + this.#throwError( + `ERROR - Can't find jobs for organisation id ${organizationId}.`, + response, + ); + } + return response.json() as Promise; + } + async getJob(jobUuid: string): Promise { const response = await this.#fetch(`jobs/${jobUuid}`, {}); if (!response.ok) { @@ -236,16 +261,10 @@ export class Nomalab { method: "POST", }, ); - if (!response.ok) { - this.#throwError( - `ERROR - Can't make a s3 copy with payload.${ - JSON.stringify(payload) - } on url ${url}.`, - response, - ); + console.log("[S3 UPLOAD]", "url:", url, "payload:", payload); + this.#throwError(`ERROR - Can't make a s3 copy with payload.`, response); } - return Promise.resolve(); } @@ -275,10 +294,7 @@ export class Nomalab { "Can't deliver because of an already present deliverable.", ); } else { - this.#throwError( - `ERROR - Can't deliver with payload.${deliverPayload}`, - response, - ); + this.#throwError(`ERROR - Can't deliver with payload.`, response); } } return Promise.resolve() as Promise; @@ -289,17 +305,13 @@ export class Nomalab { `broadcastables/${broadcastableId}/delivery`, { method: "POST", bodyJsonObject: {} }, ); - console.log(response.headers); if (!response.ok) { if (response.status == 409) { throw new AlreadyPresentDeliverable( "Can't deliver because of an already present deliverable.", ); } else { - this.#throwError( - `ERROR - Can't accept and deliver show. [${broadcastableId}]`, - response, - ); + this.#throwError(`ERROR - Can't deliver show.`, response); } } return Promise.resolve() as Promise; @@ -315,7 +327,6 @@ export class Nomalab { `broadcastables/${broadcastableId}/delivery`, { method: "POST", bodyJsonObject: {} }, ); - console.log(response.headers); if (!response.ok) { if (response.status == 409) { throw new AlreadyPresentDeliverable( @@ -374,11 +385,53 @@ export class Nomalab { return response.blob() as Promise; } + async getDeliverableOrgs() { + const response = await this.#fetch(`organizations/deliverables`, {}); + return response.json() as Promise; + } + + async getFileSegments(materialId: string) { + const response = await this.#fetch(`files/${materialId}/segments`, {}); + return response.json() as Promise; + } + + async getOrganizationDeliveries(orgId: string) { + const response = await this.#fetch( + `organizations/${orgId}/shows/deliveries`, + {}, + ); + return response.json() as Promise; + } + + async getFormats(orgId: string) { + const response = await this.#fetch(`organizations/${orgId}/formats`, {}); + return response.json() as Promise; + } + + async getSubtitleFormats(orgId: string) { + const response = await this.#fetch( + `organizations/${orgId}/subtitleFormats`, + {}, + ); + return response.json() as Promise; + } + #throwError(message: string, response: Response): void { console.error(message); console.error(response); throw new Error(message); } + proxy( + partialUrl: string, + optionalArg?: { + method?: string; + bodyJsonObject?: unknown; + contentType?: string; + cookieHeader?: Record; + }, + ): Promise { + return this.#fetch(partialUrl, optionalArg || {}); + } #fetch( partialUrl: string, optionalArg: { @@ -393,7 +446,7 @@ export class Nomalab { "Content-Type", optionalArg.contentType ?? "application/json", ); - myHeaders.append("Authorization", `Bearer ${this.#apiToken} `); + myHeaders.append("Authorization", `Bearer ${this.#apiToken}`); if (optionalArg.cookieHeader) { myHeaders.append( "Cookie", diff --git a/src/types.ts b/src/types.ts index 62337c4..1ce8bdc 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,3 +1,20 @@ +import * as Formats from "./formats.ts"; + +export interface MeUser { + admin: boolean; + avatar: string; + disableOrganizationEmails: boolean; + email: string; + id: string; + name: string; + organization: string; + organizations: { + organizationId: string; + organizationName: string; + logo?: string; + }[]; +} + export interface Job { id: string; createdAt: string; @@ -80,6 +97,7 @@ export interface Material extends FileWrapper { reportPdf: FileClass; deliveries: Delivery[]; segments: Segment[]; + subtitleWarnings: SubtitleWarning[]; } export interface Container { @@ -121,6 +139,11 @@ export interface Subtitles { subtitle: unknown[]; } +export interface SubtitleWarning { + name: string; + timecode: string; +} + export enum ArchiveState { Active = "active", Archived = "archived", @@ -133,12 +156,13 @@ export interface SubtitleFormat { } export interface DeliverPayload { format: string; - versionMapping: "VO" | "VD" | "VDVO"; + versionMapping: Formats.Mapping; timecodeOut: string | null; timecodeIn: string | null; + segments?: string[]; subtitles: DeliverSubtitle | null; targetOrg: string; - targetId: null; + targetId: string | null; } export interface DeliverSubtitle { format: string | null; @@ -424,7 +448,7 @@ export interface Proxies { export interface Segment { id: string; - label: string; + label: Formats.SegmentLabel; creator: Creator; createdAt: string; file: string; @@ -600,11 +624,30 @@ export interface Organization { allowDeliveryWithoutTranscoding: boolean; replication: boolean; logo: null | string; - formats: Format[]; - subtitleFormats: Format[]; + formats: FormatClass[]; + subtitleFormats: FormatClass[]; +} +export interface ShowOrganization { + id: string; + name: string; + createdAt: string; + qcMasterTestPlan: string; + qcMasterReportTemplate: string; + enableCreationEmail: boolean; + enableVideoReadyEmail: boolean; + enableUploadSuccessEmail: boolean; + enableAutoAccept: boolean; + enableAutoReject: boolean; + broadcaster: null; + manualDelivery: boolean; + allowDeliveryWithoutTranscoding: boolean; + logo: null; + webhooks: Webhook[]; + formats: unknown[]; + subtitleFormats: unknown[]; } -export interface Format { +export interface FormatClass { id: string; name: string; } @@ -700,3 +743,21 @@ export interface NodeClass { kind: NodeKind; state: string; } + +export type DeliverableOrganization = { + id: string; + name: string; + allowDeliveryWithoutTranscoding: boolean; +}; + +export type DeliveryApi = { + nodes: NodeDelivery[]; + shows: ShowClass[]; +}; + +export type NodeDelivery = { + showId: string; + id: string; + name: string; + parent?: string; +}; From 2a03a7215b4257e93f00571cc99573b5d399d9aa Mon Sep 17 00:00:00 2001 From: Maxime Dantec Date: Tue, 3 Jan 2023 18:15:38 +0100 Subject: [PATCH 11/17] add .githooks/ --- .githooks/pre-commit | 3 +++ 1 file changed, 3 insertions(+) create mode 100755 .githooks/pre-commit diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..f361c3e --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,3 @@ +#!/bin/sh + +deno fmt From 699c18075aa5a72d3206a6276f1cc692a4d9b705 Mon Sep 17 00:00:00 2001 From: Maxime Dantec Date: Fri, 3 Feb 2023 10:19:10 +0100 Subject: [PATCH 12/17] add broadcastable apis --- src/formats.ts | 27 ++++ src/types.ts | 419 +++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 363 insertions(+), 83 deletions(-) diff --git a/src/formats.ts b/src/formats.ts index c17e899..4222ad5 100644 --- a/src/formats.ts +++ b/src/formats.ts @@ -194,3 +194,30 @@ export enum Mapping { VDVIVONLY = "VDVIVONLY", VDVIMEVONLY = "VDVIMEVONLY", } + +export enum Layout { + Mono = "Mono", + DualMono = "DualMono", + Stereo = "Stereo", + StereoL = "StereoL", + StereoR = "StereoR", + FiveDotOne = "FiveDotOne", + FiveDotOneL = "FiveDotOneL", + FiveDotOneR = "FiveDotOneR", + FiveDotOneC = "FiveDotOneC", + FiveDotOneSL = "FiveDotOneSL", + FiveDotOneSR = "FiveDotOneSR", + FiveDotOneLFE = "FiveDotOneLFE", + SevenDotOne = "SevenDotOne", + OneTrack = "OneTrack", +} + +export enum TypeVersion { + ORIGINAL = "ORIGINAL", + DUBBED = "DUBBED", + AD = "AD", + MUTE = "MUTE", + INT = "INT", + ME = "ME", + VONLY = "VONLY", +} diff --git a/src/types.ts b/src/types.ts index 1ce8bdc..4d41f96 100644 --- a/src/types.ts +++ b/src/types.ts @@ -91,7 +91,7 @@ export interface FileWrapper { } export interface Material extends FileWrapper { container: Container; - streams: Array>; + streams: FileStream[]; proxies: Proxies; reportXml: FileClass; reportPdf: FileClass; @@ -144,11 +144,6 @@ export interface SubtitleWarning { timecode: string; } -export enum ArchiveState { - Active = "active", - Archived = "archived", -} - export interface SubtitleFormat { organizationId: string; organizationName: string; @@ -174,24 +169,7 @@ export interface Delivery { id: string; title: string; organizationName: string; - transcoding: DeliveryTranscoding; -} - -export interface DeliveryTranscoding { - file: string; - phase: Phase; - progress: number | null | string; - startedAt: string; - progressedAt: string; - log: null | string; - warning: unknown[]; -} - -export enum Phase { - Encoding = "Encoding", - Finished = "Finished", - Packaging = "Packaging", - Waiting = "Waiting", + transcoding: Transcoding; } export interface FileClass { @@ -210,34 +188,10 @@ export interface FileClass { uploadedAt: null | string; verification: Verification | null; sourceId: null | string; - transcoding: FileTranscoding | null; + transcoding: Transcoding | null; format: null; } -export interface FileTranscoding { - file: string; - phase: Phase; - progress: number | null; - startedAt: string; - progressedAt: string; - log: null | string; - warning: unknown[]; -} - -export interface Upload { - file: string; - user: string; - progress: number; - progressedAt: string; - pausedAt: null; - completedAt: string; - error: null; - s3Id: string; - speed: number; - secondsLeft: number; - source: string; -} - export interface Verification { progress: number; error: null; @@ -266,7 +220,7 @@ export interface StreamValue { name: string; issues: Issues; parent: string; - nodeType: Array | FluffyNodeType | string>; + nodeType: NodeType; properties: Properties; } @@ -275,24 +229,6 @@ export interface Issues { warnings: unknown[]; } -export interface PurpleNodeType { - duration: string; - startTimecode: string; -} - -export interface FluffyNodeType { - frameRate?: null; - chromaFormat?: null; - scanningType?: null; - cadencePattern?: null; - activePixelsArea?: null; - displayAspectRatio?: null; - isMute?: boolean; - channels?: Channel[]; - loudnessRange?: string; - programLoudness?: string; -} - export interface Channel { label: string; truePeakLevel: string; @@ -443,7 +379,7 @@ export interface ProgramLoudnessEBU { export interface Proxies { lowRes: FileClass; - hiRes: null; + hiRes: FileClass | null; } export interface Segment { @@ -475,7 +411,13 @@ export interface OrganizationElement { logo: null | string; } -export interface PurpleStream { +export type FileStream = + | ["VideoStream", FileVideoStream] + | ["AudioStream", FileAudioStream] + | ["SubtitleStream", FileSubtitleStream] + | ["DataStream", FileDataStream]; + +export interface FileVideoStream { fileId: string; index: number; codecName?: string; @@ -486,21 +428,50 @@ export interface PurpleStream { displayAspectRatioNumerator?: number; displayAspectRatioDenominator?: number; bitRate?: number; - rFrameRateNumerator?: number; - rFrameRateDenominator?: number; - level?: null; - profile?: null; - startTime?: number; + rFrameRateNumerator: number; + rFrameRateDenominator: number; + level?: number; + profile?: string; chromaSubsampling?: string; scanningType?: string; timecode?: string; - sampleRate?: number; - sampleFormat?: string; - channels?: number; - bitsPerSample?: number; - channelLayout?: string; - version?: string; - typeVersion?: string; + nbFrames?: number; +} + +export interface FileAudioStream { + fileId: string; + index: number; + codecName: string; + codecLongName: string; + duration?: number; + sampleRate: number; + sampleFormat: string; + channels: number; + bitsPerSample: number; + bitRate: number; + channelLayout: Formats.Layout | null; + version: Formats.Version | null; + typeVersion: Formats.TypeVersion | null; +} + +export interface FileSubtitleStream { + fileId: string; + index: number; + codecName?: string; + codecLongName?: string; + version?: Formats.Version; + frameRateNumerator?: number; + frameRateDenominator?: number; + startTimecode?: string; + firstCue?: string; + subtitleType?: string; + typeVersion: Formats.SubtitleTypeVersion | null; +} + +export interface FileDataStream { + fileId: string; + index: number; + timecode?: string; } export interface Subtitle extends FileWrapper { @@ -587,7 +558,7 @@ export interface ShowClass { accepted: boolean; commandInfoXML: null; kind: ShowKind; - state: ArchiveState; + state: State; parent: string; } @@ -627,6 +598,7 @@ export interface Organization { formats: FormatClass[]; subtitleFormats: FormatClass[]; } + export interface ShowOrganization { id: string; name: string; @@ -761,3 +733,284 @@ export type NodeDelivery = { name: string; parent?: string; }; + +export interface FileContainer { + fileId: string; + formatName: string; + formatLongName: string; + duration?: number; + bitRate?: number; + timecode?: string; +} + +export interface FileLike { + name: string; + mimeType?: string; +} + +export enum BroadcastableFileKind { + ProxyManifest = "ProxyManifest", + ProxyDashVideo = "ProxyDashVideo", + ProxyAudio = "ProxyAudio", + ProxySubtitle = "ProxySubtitle", + VerificationReportPdf = "VerificationReportPdf", + VerificationReportXml = "VerificationReportXml", + Video = "Video", + Audio = "Audio", + Subtitle = "Subtitle", + Extra = "Extra", +} + +export interface File { + id: string; + createdAt: string; + name: string; + size: number; + mimeType?: string; + bucket: string; + key: string; + kind: Kind; + uploaderId?: string; + upload: Upload | null; + uploadedAt?: string; + verification: Verification | null; + sourceId?: string; + transcoding: Transcoding | null; + state: State; + stateExpireAt?: string; + format: Formats.Format | null; +} + +export interface ExtraApi { + file: File; + proxy: File | null; + segments: FileSegment[]; + container: FileContainer | null; + streams: FileStream[]; +} + +export interface FileUploads { + uploads: File[]; + parts: UploadPart[]; +} + +export interface UploadPart { + uploadId: string; + key: string; +} + +export interface FileLinkQueries { + download: boolean; +} + +export interface NewFile { + name: string; + size: number; + mimeType?: string; + bucket: string; + key: string; + kind: Kind; + uploaderId?: string; + upload: Upload | null; + sourceId?: string; + transcoding: Transcoding | null; +} + +export interface BroadcastableApi { + file: File; + container: FileContainer | null; + streams: FileStream[]; + proxies: Proxies; + reportXml: File | null; + reportPdf: File | null; + deliveries: Delivery[]; + segments: FileSegment[]; + subtitleWarnings: { name: string; timecode: string }[]; +} + +export interface FileSegment { + id: string; + label: SegmentLabel; + creator: User; + createdAt: string; + file: string; + frameIn: number; + frameOut: number; +} + +export enum SegmentLabel { + OpeningCredits = "OpeningCredits", + EndingCredits = "EndingCredits", + Introduction = "Introduction", + Program = "Program", + Trailer = "Trailer", + Advertising = "Advertising", + TestPattern = "TestPattern", + Black = "Black", + Slate = "Slate", + NeutralBases = "NeutralBases", + CustomDelivery = "CustomDelivery", +} + +export interface FileSegmentPayload { + label: SegmentLabel; + frameIn: number; + frameOut: number; +} + +export interface CreateFile { + name: string; + size: number; + mimeType?: string; + source?: string; + kind: BroadcastableFileKind; +} + +export interface ResumeFile { + name: string; + kind: BroadcastableFileKind; +} + +export enum FileTypeVideo { + Mxf = "Mxf", + Qtff = "Qtff", + Mp4 = "Mp4", +} + +export enum FileTypeAudio { + Mp3 = "Mp3", +} + +export type FileType = + | ["FileTypeVideo", FileTypeVideo] + | ["FileTypeAudio", FileTypeAudio] + | ["FileTypeSubtitle"]; + +export enum Phase { + Waiting = "Waiting", + Downloading = "Downloading", + Encoding = "Encoding", + Packaging = "Packaging", + Uploading = "Uploading", + Finished = "Finished", +} + +export interface TranscodeWarning { + name: string; + count: number; + firstFrameApprox: number; +} + +export interface Transcoding { + file?: string; + phase: Phase; + progress?: number; + startedAt: string; + progressedAt: string; + log?: string; + warning: TranscodeWarning[]; +} + +export interface Progress { + phase: Phase; + progress?: number; +} + +export interface Upload { + file: string; + user: string; + progress: number; + progressedAt: string; + pausedAt?: string; + completedAt?: string; + error?: string; + s3Id?: string; + speed: number; + secondsLeft?: number; + source?: string; +} + +export interface User { + id: string; + name: string; + email: string; + avatar: string; + organization?: string; + organizations: OrganizationUserWithLabel[]; + admin: boolean; + disableOrganizationEmails: boolean; +} + +export interface OrganizationUserWithLabel { + organizationId: string; + organizationName: string; + logo?: string; +} + +export interface OrganizationUser { + userId: string; + organizationId: string; +} + +export type NodeType = + | ["AudioProgram", AudioProgram] + | ["VideoProgram", VideoProgram] + | ["Container", ContainerProgram] + | ["UnknownNodeType"]; + +export interface AudioProgram { + programLoudness?: string; + loudnessRange?: string; + channels: AudioChannel[]; + isMute: boolean; +} + +export interface AudioChannel { + label: string; + truePeakLevel: string; +} + +export interface VideoProgram { + displayAspectRatio?: string; + frameRate?: string; + activePixelsArea?: string; + cadencePattern?: string; + chromaFormat?: string; + scanningType?: string; +} + +export type ContainerProgram = + | ["Mxf", MxfContainerProperties] + | ["Mov", MovContainerProperties] + | ["UnsupportedContainer"]; + +export interface MovContainerProperties { + startTimecode?: string; + duration?: string; +} + +export interface MxfContainerProperties { + operationalPattern?: string; + timeCodes: TimeCodes; + product: Product; +} + +export interface TimeCodes { + duration?: string; + systemItemStart?: string; + sourcePackageStart?: string; + materialPackageStart?: string; + hasVitc?: string; +} + +export interface Product { + name?: string; + version?: string; + issuer?: string; +} + +export enum State { + Active = "Active", + Archived = "Archived", + Restoring = "Restoring", +} From dde9630a29b1b6fcc273f0a18f3f7812eff101af Mon Sep 17 00:00:00 2001 From: Maxime Dantec Date: Mon, 13 Mar 2023 15:20:59 +0100 Subject: [PATCH 13/17] tmp --- src/nomalab.ts | 21 +++++++-------------- src/types.ts | 51 ++++++++++++++++---------------------------------- 2 files changed, 23 insertions(+), 49 deletions(-) diff --git a/src/nomalab.ts b/src/nomalab.ts index a4d2a50..bbdb771 100644 --- a/src/nomalab.ts +++ b/src/nomalab.ts @@ -442,25 +442,18 @@ export class Nomalab { }, ): Promise { const myHeaders = new Headers(); - myHeaders.append( - "Content-Type", - optionalArg.contentType ?? "application/json", - ); - myHeaders.append("Authorization", `Bearer ${this.#apiToken}`); - if (optionalArg.cookieHeader) { - myHeaders.append( - "Cookie", - `sessionJwt=${optionalArg.cookieHeader["sessionJwt"]}`, - ); - } + myHeaders.append("Content-Type", optionalArg.contentType ?? "application/json"); + myHeaders.append("Cookie", `sessionJwt=${this.#apiToken}`); + // myHeaders.append("Authorization", `Bearer ${this.#apiToken} `); + // if (optionalArg.cookieHeader) { + // myHeaders.append("Cookie", `sessionJwt=${optionalArg.cookieHeader["sessionJwt"]}`) + // } const request = new Request( `https://${this.#contextSubDomain()}.nomalab.com/v3/${partialUrl}`, { method: optionalArg.method ?? "GET", headers: myHeaders, - body: (optionalArg.bodyJsonObject === undefined) - ? null - : JSON.stringify(optionalArg.bodyJsonObject), + body: (optionalArg.bodyJsonObject === undefined) ? null : JSON.stringify(optionalArg.bodyJsonObject), credentials: "include", }, ); diff --git a/src/types.ts b/src/types.ts index 4d41f96..86ccc51 100644 --- a/src/types.ts +++ b/src/types.ts @@ -172,26 +172,6 @@ export interface Delivery { transcoding: Transcoding; } -export interface FileClass { - state: string; - stateExpireAt: null; - id: string; - createdAt: string; - name: string; - size: number; - mimeType: null | string; - bucket: string; - key: string; - kind: string; - uploaderId: null | string; - upload: Upload | null; - uploadedAt: null | string; - verification: Verification | null; - sourceId: null | string; - transcoding: Transcoding | null; - format: null; -} - export interface Verification { progress: number; error: null; @@ -761,36 +741,37 @@ export enum BroadcastableFileKind { Extra = "Extra", } -export interface File { + +export interface FileClass { + state: string; + stateExpireAt: string | null; id: string; createdAt: string; name: string; size: number; - mimeType?: string; + mimeType: null | string; bucket: string; key: string; - kind: Kind; - uploaderId?: string; + kind: string; + uploaderId: null | string; upload: Upload | null; - uploadedAt?: string; + uploadedAt: null | string; verification: Verification | null; - sourceId?: string; + sourceId: null | string; transcoding: Transcoding | null; - state: State; - stateExpireAt?: string; - format: Formats.Format | null; + format: Formats.Format; } export interface ExtraApi { - file: File; - proxy: File | null; + file: FileClass; + proxy: FileClass | null; segments: FileSegment[]; container: FileContainer | null; streams: FileStream[]; } export interface FileUploads { - uploads: File[]; + uploads: FileClass[]; parts: UploadPart[]; } @@ -817,12 +798,12 @@ export interface NewFile { } export interface BroadcastableApi { - file: File; + file: FileClass; container: FileContainer | null; streams: FileStream[]; proxies: Proxies; - reportXml: File | null; - reportPdf: File | null; + reportXml: FileClass | null; + reportPdf: FileClass | null; deliveries: Delivery[]; segments: FileSegment[]; subtitleWarnings: { name: string; timecode: string }[]; From 8e5a7b1aba9aec24cf982f866b8886dd19cd590b Mon Sep 17 00:00:00 2001 From: Maxime Dantec Date: Thu, 15 Jun 2023 10:13:11 +0200 Subject: [PATCH 14/17] fmt --- src/nomalab.ts | 4 +++- src/types.ts | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/nomalab.ts b/src/nomalab.ts index 2c7135e..9cad06f 100644 --- a/src/nomalab.ts +++ b/src/nomalab.ts @@ -464,7 +464,9 @@ export class Nomalab { { method: optionalArg.method ?? "GET", headers: myHeaders, - body: (optionalArg.bodyJsonObject === undefined) ? null : JSON.stringify(optionalArg.bodyJsonObject), + body: (optionalArg.bodyJsonObject === undefined) + ? null + : JSON.stringify(optionalArg.bodyJsonObject), credentials: "include", }, ); diff --git a/src/types.ts b/src/types.ts index 046309b..42e093d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -821,7 +821,6 @@ export enum BroadcastableFileKind { Extra = "Extra", } - export interface FileClass { state: string; stateExpireAt: string | null; From a5d17af8341f97cbb29d0238a16c5c5ce5ea73ec Mon Sep 17 00:00:00 2001 From: Maxime Dantec Date: Thu, 15 Jun 2023 10:23:54 +0200 Subject: [PATCH 15/17] fix login --- src/nomalab.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/nomalab.ts b/src/nomalab.ts index 9cad06f..c314d83 100644 --- a/src/nomalab.ts +++ b/src/nomalab.ts @@ -133,15 +133,9 @@ export class Nomalab { // To avoid leak since we don't use the body of the response await response.body?.cancel(); - const cookie = mod.getCookies(headers); - const bodyJsonObject = method == "POST" ? (body ?? {}) : undefined; return this.#fetch( partialUrl, - { - bodyJsonObject, - method, - cookieHeader: cookie, - }, + optionalArg, ); } @@ -458,6 +452,12 @@ export class Nomalab { "Cookie", `sessionJwt=${optionalArg.cookieHeader["sessionJwt"]}`, ); + } else { + myHeaders.append( + "Cookie", + `sessionJwt=${this.#apiToken}`, + ); + } const request = new Request( `https://${this.#contextSubDomain()}.nomalab.com/v3/${partialUrl}`, From 66e48e9bfc497c0be0117e8f41eeebffd74e33ba Mon Sep 17 00:00:00 2001 From: Maxime Dantec Date: Mon, 19 Jun 2023 09:20:04 +0200 Subject: [PATCH 16/17] fix --- src/nomalab.ts | 11 +++++++++++ src/types.ts | 26 ++++++++++---------------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/nomalab.ts b/src/nomalab.ts index c314d83..f4f067c 100644 --- a/src/nomalab.ts +++ b/src/nomalab.ts @@ -4,6 +4,7 @@ import { Deliveries, DeliverPayload, DeliveryApi, + AudioMappingPayload, Job, MeUser, Node, @@ -416,6 +417,16 @@ export class Nomalab { return response.json() as Promise; } + setAudioMapping(fileId : string, mappingPayload : AudioMappingPayload): Promise { + return this.#fetch( + `files/${fileId}/audioMapping`, + { + bodyJsonObject: mappingPayload, + method: "POST", + }, + ); + } + #throwError(message: string, response: Response): void { console.error(message); console.error(response); diff --git a/src/types.ts b/src/types.ts index 42e093d..b3e39e9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -109,6 +109,13 @@ export interface Container { timecode: null; } +export interface AudioMappingPayload { + index: number; + channelLayout?: string; + version?: string; + typeVersion?: string; +} + export interface Deliveries { shows: Show[]; nodes: DeliveryNode[]; @@ -491,6 +498,7 @@ export interface FileVideoStream { rFrameRateDenominator: number; level?: number; profile?: string; + startTime?: number; chromaSubsampling?: string; scanningType?: string; timecode?: string; @@ -890,7 +898,7 @@ export interface BroadcastableApi { export interface FileSegment { id: string; - label: SegmentLabel; + label: Formats.SegmentLabel; creator: User; createdAt: string; file: string; @@ -898,22 +906,8 @@ export interface FileSegment { frameOut: number; } -export enum SegmentLabel { - OpeningCredits = "OpeningCredits", - EndingCredits = "EndingCredits", - Introduction = "Introduction", - Program = "Program", - Trailer = "Trailer", - Advertising = "Advertising", - TestPattern = "TestPattern", - Black = "Black", - Slate = "Slate", - NeutralBases = "NeutralBases", - CustomDelivery = "CustomDelivery", -} - export interface FileSegmentPayload { - label: SegmentLabel; + label: Formats.SegmentLabel; frameIn: number; frameOut: number; } From 442dcaf54770b53dbc2768d10c7785244b2bd1ff Mon Sep 17 00:00:00 2001 From: Maxime Dantec Date: Thu, 22 Jun 2023 09:16:37 +0200 Subject: [PATCH 17/17] segments & fix cookie error --- src/nomalab.ts | 38 +++++++++----------------------------- src/types.ts | 10 +++++----- 2 files changed, 14 insertions(+), 34 deletions(-) diff --git a/src/nomalab.ts b/src/nomalab.ts index f4f067c..d5df9bb 100644 --- a/src/nomalab.ts +++ b/src/nomalab.ts @@ -130,6 +130,7 @@ export class Nomalab { const setCookie = getSetCookies(response.headers)[0]; assert(setCookie, "No cookie"); this.#apiToken = setCookie.value; + getSetCookies(response.headers)[0]; // To avoid leak since we don't use the body of the response await response.body?.cancel(); @@ -274,27 +275,14 @@ export class Nomalab { } // Deliver with starting a transcode - async deliver(showId: string, deliverPayload: DeliverPayload): Promise { - const response = await this.#fetch( - `broadcastables/${showId}/deliver`, + deliver(broadcastableId: string, deliverPayload: DeliverPayload): Promise { + return this.#fetch( + `broadcastables/${broadcastableId}/deliver`, { bodyJsonObject: deliverPayload, method: "POST", }, - ); - if (!response.ok) { - if (response.status == 409) { - throw new AlreadyPresentDeliverable( - "Can't deliver because of an already present deliverable.", - ); - } else { - this.#throwError( - `ERROR - Can't deliver with payload.${deliverPayload}`, - response, - ); - } - } - return Promise.resolve() as Promise; + ) as Promise; } async triggerUpload(broadcastableId: string) { @@ -457,19 +445,11 @@ export class Nomalab { "Content-Type", optionalArg.contentType ?? "application/json", ); - myHeaders.append("Authorization", `Bearer ${this.#apiToken} `); - if (optionalArg.cookieHeader) { - myHeaders.append( - "Cookie", - `sessionJwt=${optionalArg.cookieHeader["sessionJwt"]}`, - ); - } else { - myHeaders.append( - "Cookie", - `sessionJwt=${this.#apiToken}`, - ); + myHeaders.append( + "Cookie", + `sessionJwt=${this.#apiToken}`, + ); - } const request = new Request( `https://${this.#contextSubDomain()}.nomalab.com/v3/${partialUrl}`, { diff --git a/src/types.ts b/src/types.ts index b3e39e9..a6b2468 100644 --- a/src/types.ts +++ b/src/types.ts @@ -152,15 +152,15 @@ export interface SubtitleWarning { } export interface SubtitleFormat { - organizationId: string; - organizationName: string; - subtitleFormats: FormatElement[]; + id: string; + name: string; + formats: string; } export interface DeliverPayload { format: string; versionMapping: Formats.Mapping; - timecodeOut: string | null; - timecodeIn: string | null; + timecodeOut?: string; + timecodeIn?: string; segments?: string[]; subtitles: DeliverSubtitle | null; targetOrg: string;