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 diff --git a/deno.jsonc b/deno.jsonc index 8c07a93..8febc66 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -1,12 +1,11 @@ { + "imports": { + "std/": "https://deno.land/std@0.199.0/" + }, "lint": { - "files": { - "include": ["src/"] - } + "include": ["src/"] }, "fmt": { - "files": { - "include": ["src/"] - } + "include": ["src/"] } } diff --git a/mod.js b/mod.js new file mode 100644 index 0000000..4f2cbfa --- /dev/null +++ b/mod.js @@ -0,0 +1,491 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file +// This code was bundled using `deno bundle` and it's not recommended to edit it manually + +class AlreadyPresentDeliverable extends Error { + constructor(msg){ + super(msg); + this.name = "Inaccessible"; + } +} +class Nomalab { + #context; + #apiToken; + constructor(context, apiToken){ + this.#context = context; + this.#apiToken = apiToken; + } + async me() { + const response = await this.#fetch(`users/me`); + return response.json(); + } + async getShow(showUuid) { + const response = await this.#fetch(`shows/${showUuid}`); + return response.json(); + } + async getRoots(organizationId) { + const response = await this.#requestWithSwitch(organizationId, "hierarchy"); + return response.json(); + } + async createHierarchy(organizationId, name, kind, parent) { + const response = await this.#requestWithSwitch(organizationId, "hierarchy", { + method: "POST", + bodyJsonObject: { + name, + parent, + kind + } + }); + return response.json(); + } + async createShow(nodeId, name, kind) { + const response = await this.#fetch(`hierarchy/${nodeId}/shows`, { + method: "POST", + bodyJsonObject: { + name, + kind + } + }); + const { id } = await response.json(); + return id; + } + async #requestWithSwitch(organizationId, partialUrl, optionalArg = {}) { + const response = await this.#fetch(`users/switch`, { + bodyJsonObject: { + organization: organizationId + }, + method: "POST" + }); + if (this.#apiToken && response.headers.has("set-cookie")) { + response.headers.getSetCookie().forEach((sc)=>{ + const [name, value, ..._xs] = sc.split(";")[0]?.split("="); + if (name == "sessionJwt") { + this.#apiToken = value; + } + }); + } + await response.body?.cancel(); + return this.#fetch(partialUrl, optionalArg); + } + async getChildren(nodeUuid) { + const response = await this.#fetch(`hierarchy/${nodeUuid}/children`, {}); + return response.json(); + } + async getDeliveries() { + const response = await this.#fetch(`shows/deliveries`, {}); + return response.json(); + } + async getNode(nodeUuid) { + const response = await this.#fetch(`hierarchy/${nodeUuid}`, {}); + return response.json(); + } + async getShowsForNode(nodeUuid) { + const response = await this.#fetch(`hierarchy/${nodeUuid}/shows`, {}); + return response.json(); + } + async getPath(showUuid) { + const response = await this.#fetch(`admin/shows/path`, { + bodyJsonObject: { + showIds: [ + showUuid + ] + }, + method: "POST" + }); + return response.json(); + } + async getOrganizations() { + const response = await this.#fetch(`organizations`, {}); + return response.json(); + } + async getOrganization(organizationId) { + const organisation = (await this.getOrganizations()).filter((org)=>{ + return org.id == organizationId; + }); + if (organisation.length == 0) { + return Promise.reject(`Org ${organizationId} not found`); + } + return Promise.resolve(organisation[0]); + } + async getOrganizationByName(organizationName) { + 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) { + const response = await this.#fetch(`jobs/${jobUuid}`, {}); + return response.json(); + } + async getSegments(fileId) { + const response = await this.#fetch(`files/${fileId}/segments`, {}); + return response.json(); + } + async s3Upload(payload) { + const url = payload.destRole ? "aws/copyFromExt" : "aws/copy"; + await this.#fetch(url, { + bodyJsonObject: payload, + method: "POST" + }); + return Promise.resolve(); + } + async accept(showId) { + const response = await this.#fetch(`shows/${showId}/accept`, { + method: "POST" + }); + return response.json(); + } + deliver(broadcastableId, deliverPayload) { + return this.#fetch(`broadcastables/${broadcastableId}/deliver`, { + bodyJsonObject: deliverPayload, + method: "POST" + }); + } + async triggerUpload(broadcastableId) { + await this.#fetch(`broadcastables/${broadcastableId}/delivery`, { + method: "POST", + bodyJsonObject: {} + }); + return Promise.resolve(); + } + async acceptAndDeliver(broadcastableId, showId) { + await this.accept(showId).then(async ()=>{ + await this.#fetch(`broadcastables/${broadcastableId}/delivery`, { + method: "POST", + bodyJsonObject: {} + }); + return Promise.resolve(); + }); + } + async deliverWithoutTranscoding(broadcastableId, targetOrgId) { + const response = await this.#fetch(`broadcastables/${broadcastableId}/copyToOrganization`, { + bodyJsonObject: { + targetOrg: targetOrgId + }, + method: "POST" + }); + return response.json(); + } + async copyToShow(broadcastableId, target, subtitles) { + const response = await this.#fetch(`broadcastables/${broadcastableId}/copyToShow`, { + bodyJsonObject: { + target, + subtitles + }, + method: "POST" + }); + return response.json(); + } + async getManifest(proxyId) { + const response = await this.#fetch(`files/${proxyId}/manifest`, { + contentType: "application/xml" + }); + return response.blob(); + } + async getDeliverableOrgs() { + const response = await this.#fetch(`organizations/deliverables`, {}); + return response.json(); + } + async getFileSegments(materialId) { + const response = await this.#fetch(`files/${materialId}/segments`, {}); + return response.json(); + } + async getOrganizationDeliveries(orgId) { + const response = await this.#fetch(`organizations/${orgId}/shows/deliveries`); + return response.json(); + } + async getFormats(orgId) { + const response = await this.#fetch(`organizations/${orgId}/formats`, {}); + return response.json(); + } + async getSubtitleFormats(orgId) { + const response = await this.#fetch(`organizations/${orgId}/subtitleFormats`); + return response.json(); + } + setAudioMapping(fileId, mappingPayload) { + return this.#fetch(`files/${fileId}/audioMapping`, { + bodyJsonObject: mappingPayload, + method: "POST" + }); + } + proxy(partialUrl, optionalArg) { + return this.#fetch(partialUrl, optionalArg || {}); + } + #fetch(partialUrl, optionalArg = {}) { + const myHeaders = new Headers(); + myHeaders.append("Content-Type", optionalArg.contentType ?? "application/json"); + if (this.#apiToken) { + myHeaders.append("Authorization", `Bearer ${this.#apiToken}`); + myHeaders.append("Cookie", `sessionJwt=${this.#apiToken}`); + } + const request = new Request(`${this.#contextSubDomain()}/v3/${partialUrl}`, { + method: optionalArg.method ?? "GET", + headers: myHeaders, + body: optionalArg.bodyJsonObject === undefined ? null : JSON.stringify(optionalArg.bodyJsonObject), + credentials: this.#context ? "include" : undefined + }); + console.log(request.url); + console.log(this.#contextSubDomain()); + console.log(this.#apiToken); + console.log(myHeaders); + return fetch(request).then(async (response)=>{ + if (response.ok) { + return response; + } else { + throw await response.json(); + } + }); + } + #contextSubDomain() { + if (this.#context) { + const ctx = this.#context == "www" ? "app" : `app-${this.#context}`; + return `https://${ctx}.nomalab.com`; + } else { + return ""; + } + } +} +export { AlreadyPresentDeliverable as AlreadyPresentDeliverable }; +export { Nomalab as Nomalab }; +var BroadcastableKind; +(function(BroadcastableKind) { + BroadcastableKind["Subtitle"] = "Subtitle"; + BroadcastableKind["Material"] = "Material"; + BroadcastableKind["Audio"] = "Audio"; + BroadcastableKind["Extra"] = "Extra"; +})(BroadcastableKind || (BroadcastableKind = {})); +var Kind; +(function(Kind) { + Kind["Create"] = "Create"; + Kind["Finish"] = "Finish"; + Kind["Request"] = "Request"; +})(Kind || (Kind = {})); +var MXFVersion; +(function(MXFVersion) { + MXFVersion["AsMaster"] = "AsMaster"; + MXFVersion["Vd"] = "VD"; + MXFVersion["Vo"] = "VO"; +})(MXFVersion || (MXFVersion = {})); +var TranscodeKind; +(function(TranscodeKind) { + TranscodeKind["Copy"] = "copy"; + TranscodeKind["Deliverable"] = "deliverable"; + TranscodeKind["ProxyLowRes"] = "proxy_low_res"; + TranscodeKind["ProxySubtitle"] = "proxy_subtitle"; +})(TranscodeKind || (TranscodeKind = {})); +var NodeKind; +(function(NodeKind) { + NodeKind["Season"] = "Season"; + NodeKind["Collection"] = "Collection"; + NodeKind["Episode"] = "Episode"; + NodeKind["Unitary"] = "Unitary"; +})(NodeKind || (NodeKind = {})); +var ShowKind; +(function(ShowKind) { + ShowKind["Delivery"] = "Delivery"; + ShowKind["Master"] = "Master"; +})(ShowKind || (ShowKind = {})); +var EventEnum; +(function(EventEnum) { + EventEnum["Ingest"] = "Ingest"; + EventEnum["Lifecycle"] = "Lifecycle"; + EventEnum["QualityCheck"] = "QualityCheck"; + EventEnum["Transcode"] = "Transcode"; +})(EventEnum || (EventEnum = {})); +var BroadcastableFileKind; +(function(BroadcastableFileKind) { + BroadcastableFileKind["ProxyManifest"] = "ProxyManifest"; + BroadcastableFileKind["ProxyDashVideo"] = "ProxyDashVideo"; + BroadcastableFileKind["ProxyAudio"] = "ProxyAudio"; + BroadcastableFileKind["ProxySubtitle"] = "ProxySubtitle"; + BroadcastableFileKind["VerificationReportPdf"] = "VerificationReportPdf"; + BroadcastableFileKind["VerificationReportXml"] = "VerificationReportXml"; + BroadcastableFileKind["Video"] = "Video"; + BroadcastableFileKind["Audio"] = "Audio"; + BroadcastableFileKind["Subtitle"] = "Subtitle"; + BroadcastableFileKind["Extra"] = "Extra"; +})(BroadcastableFileKind || (BroadcastableFileKind = {})); +var FileTypeVideo; +(function(FileTypeVideo) { + FileTypeVideo["Mxf"] = "Mxf"; + FileTypeVideo["Qtff"] = "Qtff"; + FileTypeVideo["Mp4"] = "Mp4"; +})(FileTypeVideo || (FileTypeVideo = {})); +var FileTypeAudio; +(function(FileTypeAudio) { + FileTypeAudio["Mp3"] = "Mp3"; +})(FileTypeAudio || (FileTypeAudio = {})); +var Phase; +(function(Phase) { + Phase["Waiting"] = "Waiting"; + Phase["Downloading"] = "Downloading"; + Phase["Encoding"] = "Encoding"; + Phase["Packaging"] = "Packaging"; + Phase["Uploading"] = "Uploading"; + Phase["Finished"] = "Finished"; +})(Phase || (Phase = {})); +var State; +(function(State) { + State["Active"] = "Active"; + State["Archived"] = "Archived"; + State["Restoring"] = "Restoring"; +})(State || (State = {})); +export { BroadcastableKind as BroadcastableKind }; +export { Kind as Kind }; +export { MXFVersion as MXFVersion }; +export { TranscodeKind as TranscodeKind }; +export { NodeKind as NodeKind }; +export { ShowKind as ShowKind }; +export { EventEnum as EventEnum }; +export { BroadcastableFileKind as BroadcastableFileKind }; +export { FileTypeVideo as FileTypeVideo }; +export { FileTypeAudio as FileTypeAudio }; +export { Phase as Phase }; +export { State as State }; +var FormatSpec; +(function(FormatSpec) { + FormatSpec["Mxf"] = "Mxf"; + FormatSpec["Mp4"] = "Mp4"; + FormatSpec["ProRes422"] = "ProRes422"; + FormatSpec["ProRes4444"] = "ProRes4444"; + FormatSpec["ADN"] = "ADN"; + FormatSpec["AVCIntra100"] = "AVCIntra100"; + FormatSpec["IMX50"] = "IMX50"; + FormatSpec["Mp4Salto"] = "Mp4Salto"; + FormatSpec["MovH264"] = "MovH264"; + FormatSpec["MovHevc"] = "MovHevc"; + FormatSpec["MxfProgressive"] = "MxfProgressive"; + FormatSpec["Demux"] = "Demux"; + FormatSpec["AudioExtract"] = "AudioExtract"; +})(FormatSpec || (FormatSpec = {})); +var FormatAudioCodec; +(function(FormatAudioCodec) { + FormatAudioCodec["PCMS24LE"] = "PCMS24LE"; + FormatAudioCodec["PCMS16LE"] = "PCMS16LE"; + FormatAudioCodec["PCMS24BE"] = "PCMS24BE"; + FormatAudioCodec["AAC"] = "AAC"; + FormatAudioCodec["MP3"] = "MP3"; + FormatAudioCodec["MOV_Conteneur"] = "MOV_Conteneur"; +})(FormatAudioCodec || (FormatAudioCodec = {})); +var FormatSegmentKind; +(function(FormatSegmentKind) { + FormatSegmentKind["Mire"] = "Mire"; + FormatSegmentKind["Black"] = "Black"; + FormatSegmentKind["Slate"] = "Slate"; + FormatSegmentKind["Video"] = "Video"; + FormatSegmentKind["Countdown"] = "Countdown"; +})(FormatSegmentKind || (FormatSegmentKind = {})); +var SubtitleFileFormat; +(function(SubtitleFileFormat) { + SubtitleFileFormat["STL"] = "STL"; + SubtitleFileFormat["WebVTT"] = "WebVTT"; + SubtitleFileFormat["SRT"] = "SRT"; +})(SubtitleFileFormat || (SubtitleFileFormat = {})); +var SubtitleDisplayStandard; +(function(SubtitleDisplayStandard) { + SubtitleDisplayStandard["Open"] = "Open"; + SubtitleDisplayStandard["Teletext1"] = "Teletext1"; + SubtitleDisplayStandard["Teletext2"] = "Teletext2"; +})(SubtitleDisplayStandard || (SubtitleDisplayStandard = {})); +var SubtitleTypeVersion; +(function(SubtitleTypeVersion) { + SubtitleTypeVersion["PARTIAL"] = "PARTIAL"; + SubtitleTypeVersion["COMPLETE"] = "COMPLETE"; + SubtitleTypeVersion["COMPLETE_WITHOUT_PARTIAL"] = "COMPLETE_WITHOUT_PARTIAL"; + SubtitleTypeVersion["SDH"] = "SDH"; +})(SubtitleTypeVersion || (SubtitleTypeVersion = {})); +var SegmentLabel; +(function(SegmentLabel) { + SegmentLabel["OpeningCredits"] = "OpeningCredits"; + SegmentLabel["EndingCredits"] = "EndingCredits"; + SegmentLabel["Introduction"] = "Introduction"; + SegmentLabel["Program"] = "Program"; + SegmentLabel["Trailer"] = "Trailer"; + SegmentLabel["Advertising"] = "Advertising"; + SegmentLabel["TestPattern"] = "TestPattern"; + SegmentLabel["Black"] = "Black"; + SegmentLabel["Slate"] = "Slate"; + SegmentLabel["NeutralBases"] = "NeutralBases"; + SegmentLabel["CustomDelivery"] = "CustomDelivery"; +})(SegmentLabel || (SegmentLabel = {})); +var Version; +(function(Version) { + Version["ARA"] = "ARA"; + Version["CHI"] = "CHI"; + Version["KOR"] = "KOR"; + Version["DAN"] = "DAN"; + Version["DUT"] = "DUT"; + Version["HEB"] = "HEB"; + Version["NLD"] = "NLD"; + Version["RUS"] = "RUS"; + Version["SWE"] = "SWE"; + Version["FRA"] = "FRA"; + Version["GER"] = "GER"; + Version["ITA"] = "ITA"; + Version["POR"] = "POR"; + Version["ENG"] = "ENG"; + Version["SPA"] = "SPA"; + Version["JPN"] = "JPN"; + Version["NOR"] = "NOR"; + Version["UKR"] = "UKR"; + Version["INT"] = "INT"; + Version["NOTHING"] = ""; +})(Version || (Version = {})); +var Mapping; +(function(Mapping) { + Mapping["AsMaster"] = "AsMaster"; + Mapping["NoSound"] = "NoSound"; + Mapping["VD"] = "VD"; + Mapping["VO"] = "VO"; + Mapping["VI"] = "VI"; + Mapping["VDVO"] = "VDVO"; + Mapping["VOAD"] = "VOAD"; + Mapping["VDAD"] = "VDAD"; + Mapping["VIVD"] = "VIVD"; + Mapping["VIVO"] = "VIVO"; + Mapping["VDVOAD"] = "VDVOAD"; + Mapping["VDVIVONLY"] = "VDVIVONLY"; + Mapping["VDVIMEVONLY"] = "VDVIMEVONLY"; +})(Mapping || (Mapping = {})); +var Layout; +(function(Layout) { + Layout["Mono"] = "Mono"; + Layout["DualMono"] = "DualMono"; + Layout["Stereo"] = "Stereo"; + Layout["StereoL"] = "StereoL"; + Layout["StereoR"] = "StereoR"; + Layout["FiveDotOne"] = "FiveDotOne"; + Layout["FiveDotOneL"] = "FiveDotOneL"; + Layout["FiveDotOneR"] = "FiveDotOneR"; + Layout["FiveDotOneC"] = "FiveDotOneC"; + Layout["FiveDotOneSL"] = "FiveDotOneSL"; + Layout["FiveDotOneSR"] = "FiveDotOneSR"; + Layout["FiveDotOneLFE"] = "FiveDotOneLFE"; + Layout["SevenDotOne"] = "SevenDotOne"; + Layout["OneTrack"] = "OneTrack"; +})(Layout || (Layout = {})); +var TypeVersion; +(function(TypeVersion) { + TypeVersion["ORIGINAL"] = "ORIGINAL"; + TypeVersion["DUBBED"] = "DUBBED"; + TypeVersion["AD"] = "AD"; + TypeVersion["MUTE"] = "MUTE"; + TypeVersion["INT"] = "INT"; + TypeVersion["ME"] = "ME"; + TypeVersion["VONLY"] = "VONLY"; +})(TypeVersion || (TypeVersion = {})); +export { FormatSpec as FormatSpec }; +export { FormatAudioCodec as FormatAudioCodec }; +export { FormatSegmentKind as FormatSegmentKind }; +export { SubtitleFileFormat as SubtitleFileFormat }; +export { SubtitleDisplayStandard as SubtitleDisplayStandard }; +export { SubtitleTypeVersion as SubtitleTypeVersion }; +export { SegmentLabel as SegmentLabel }; +export { Version as Version }; +export { Mapping as Mapping }; +export { Layout as Layout }; +export { TypeVersion as TypeVersion }; + 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/package.json b/package.json new file mode 100644 index 0000000..2861c0b --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "deno_lib", + "version": "1.0.0", + "description": "Deno lib to handle Nomalab API with deno", + "main": "mod.js", + "types": "mod.ts", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "typescript": "^5.1.6" + } +} diff --git a/src/formats.ts b/src/formats.ts new file mode 100644 index 0000000..4222ad5 --- /dev/null +++ b/src/formats.ts @@ -0,0 +1,223 @@ +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", +} + +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/nomalab.ts b/src/nomalab.ts index bfe4174..b53bcab 100644 --- a/src/nomalab.ts +++ b/src/nomalab.ts @@ -1,18 +1,25 @@ import { + AudioMappingPayload, CopyToBroadcastable, + DeliverableOrganization, Deliveries, DeliverPayload, + DeliveryApi, Job, + MeUser, Node, NodeClass, NodeKind, Organization, - Path, + Segment, Show, ShowClass, ShowKind, + ShowPath, + SubtitleFormatApi, + SubtitleFormats, } from "./types.ts"; -import * as mod from "https://deno.land/std@0.148.0/http/cookie.ts"; +import { Format } from "./formats.ts"; export class AlreadyPresentDeliverable extends Error { constructor(msg: string) { @@ -21,22 +28,22 @@ export class AlreadyPresentDeliverable extends Error { } } export class Nomalab { - #context: string; - #apiToken: string; + // both unset in the front-end + #context?: string; + #apiToken?: string; - constructor(context: string, apiToken: string) { + constructor(context?: string, apiToken?: string) { this.#context = context; this.#apiToken = apiToken; } + async me(): Promise { + const response = await this.#fetch(`users/me`); + return response.json() as Promise; + } + 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, - ); - } + const response = await this.#fetch(`shows/${showUuid}`); return response.json() as Promise; } @@ -45,7 +52,6 @@ export class Nomalab { organizationId, "hierarchy", ); - if (!response.ok) this.#throwError(`ERROR - Can't find root.`, response); return response.json() as Promise; } @@ -58,16 +64,15 @@ export class Nomalab { const response = await this.#requestWithSwitch( organizationId, "hierarchy", - "POST", { - name, - parent, - kind, + method: "POST", + bodyJsonObject: { + name, + parent, + kind, + }, }, ); - if (!response.ok) { - this.#throwError(`ERROR - Can't create ${kind} ${name}.`, response); - } return response.json() as Promise; } @@ -83,10 +88,6 @@ export class Nomalab { kind, }, }); - - if (!response.ok) { - this.#throwError(`ERROR - Can't create show ${kind} ${name}.`, response); - } const { id } = await response.json() as ShowClass; return id; } @@ -94,8 +95,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,77 +109,46 @@ export class Nomalab { method: "POST", }, ); - if (response.status != 200) { - 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); + + if (this.#apiToken && response.headers.has("set-cookie")) { + response.headers.getSetCookie().forEach((sc) => { + const [name, value, ..._xs] = sc.split(";")[0]?.split("="); + if (name == "sessionJwt") { + this.#apiToken = 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, - }, + optionalArg, ); } 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, - ); - } return response.json() as Promise; } async getDeliveries(): Promise { const response = await this.#fetch(`shows/deliveries`, {}); - 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 get deliveries.`, response); - } - } return response.json() as Promise; } 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, - ); - } 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, - ); - } return response.json() as Promise; } - async getPath(showUuid: string): Promise { + async getPath(showUuid: string): Promise { const response = await this.#fetch( `admin/shows/path`, { @@ -182,20 +156,11 @@ export class Nomalab { method: "POST", }, ); - if (!response.ok) { - this.#throwError( - `ERROR - Can't find show with id ${showUuid}.`, - response, - ); - } - return response.json() as Promise; + 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); - } return response.json() as Promise; } @@ -221,15 +186,17 @@ 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); - } return response.json() as Promise; } + async getSegments(fileId: string): Promise { + const response = await this.#fetch(`files/${fileId}/segments`, {}); + return response.json() as Promise; + } + async s3Upload(payload: CopyToBroadcastable): Promise { const url = payload.destRole ? "aws/copyFromExt" : "aws/copy"; - const response = await this.#fetch( + await this.#fetch( url, { bodyJsonObject: payload, @@ -237,15 +204,6 @@ export class Nomalab { }, ); - if (!response.ok) { - this.#throwError( - `ERROR - Can't make a s3 copy with payload.${ - JSON.stringify(payload) - } on url ${url}.`, - response, - ); - } - return Promise.resolve(); } @@ -254,54 +212,28 @@ export class Nomalab { `shows/${showId}/accept`, { method: "POST" }, ); - if (!response.ok) { - this.#throwError(`ERROR - Can't accept show. ${showId}`, response); - } return response.json() as Promise; } // 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) { - const response = await this.#fetch( + 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 accept and deliver show. [${broadcastableId}]`, - response, - ); - } - } return Promise.resolve() as Promise; } @@ -311,23 +243,10 @@ export class Nomalab { showId: string, ): Promise { await this.accept(showId).then(async () => { - const response = await this.#fetch( + 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 accept and deliver show. [${showId}]`, - response, - ); - } - } return Promise.resolve(); }); } @@ -343,18 +262,21 @@ export class Nomalab { 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 without transcoding to org id <${targetOrgId}>`, - response, - ); - } - } + return response.json() as Promise; + } + + async copyToShow( + broadcastableId: string, + target: string, + subtitles?: SubtitleFormatApi, + ): Promise { + const response = await this.#fetch( + `broadcastables/${broadcastableId}/copyToShow`, + { + bodyJsonObject: { target, subtitles }, + method: "POST", + }, + ); return response.json() as Promise; } @@ -365,20 +287,74 @@ export class Nomalab { contentType: "application/xml", }, ); - if (!response.ok) { - this.#throwError( - `ERROR - Can't find manifest with proxyId ${proxyId}.`, - response, - ); - } return response.blob() as Promise; } + async getDeliverableOrgs() { + const response = await this.#fetch(`organizations/deliverables`, {}); + return response.json() as Promise; + } + + async getSubtitleFormatsList(): Promise { + const response = await this.#fetch(`subtitleFormats`, {}); + return response.json() as Promise; + } + #throwError(message: string, response: Response): void { console.error(message); console.error(response); throw new Error(message); } + + 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; + } + + setAudioMapping( + fileId: string, + mappingPayload: AudioMappingPayload, + ): Promise { + return this.#fetch( + `files/${fileId}/audioMapping`, + { + bodyJsonObject: mappingPayload, + method: "POST", + }, + ); + } + + proxy( + partialUrl: string, + optionalArg?: { + method?: string; + bodyJsonObject?: unknown; + contentType?: string; + cookieHeader?: Record; + }, + ): Promise { + return this.#fetch(partialUrl, optionalArg || {}); + } + #fetch( partialUrl: string, optionalArg: { @@ -386,37 +362,61 @@ export class Nomalab { bodyJsonObject?: unknown; contentType?: string; cookieHeader?: Record; - }, + } = {}, ): Promise { const myHeaders = new Headers(); myHeaders.append( "Content-Type", optionalArg.contentType ?? "application/json", ); - myHeaders.append("Authorization", `Bearer ${this.#apiToken} `); - if (optionalArg.cookieHeader) { + + if (this.#apiToken) { + myHeaders.append("Authorization", `Bearer ${this.#apiToken}`); myHeaders.append( "Cookie", - `sessionJwt=${optionalArg.cookieHeader["sessionJwt"]}`, + `sessionJwt=${this.#apiToken}`, ); } + const request = new Request( - `https://${this.#contextSubDomain()}.nomalab.com/v3/${partialUrl}`, + `${this.#contextSubDomain()}/v3/${partialUrl}`, { method: optionalArg.method ?? "GET", headers: myHeaders, body: (optionalArg.bodyJsonObject === undefined) ? null : JSON.stringify(optionalArg.bodyJsonObject), - credentials: "include", + credentials: this.#context ? "include" : undefined, }, ); - return fetch(request); + + console.log(request.url); + console.log(this.#contextSubDomain()); + console.log(this.#apiToken); + console.log(myHeaders); + + return fetch(request).then(async (response) => { + if (response.ok) { + return response; + } else { + throw (await response.json()); + } + }); } + #contextSubDomain(): string { - if (this.#context == "www") return "app"; - else { - return `app-${this.#context}`; + if (this.#context) { + const ctx = this.#context == "www" ? "app" : `app-${this.#context}`; + return `https://${ctx}.nomalab.com`; + } else { + // front-end just wants to keep its own context + return ""; } } } + +declare global { + interface Headers { + getSetCookie(): string[]; + } +} diff --git a/src/types.ts b/src/types.ts index b676d3b..b127a7d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,8 +1,25 @@ +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; - startedAt: null; - completedAt: string; + startedAt: null | string; + completedAt: null | string; show: string; organization: string; requester: string; @@ -12,14 +29,15 @@ export interface Job { | "QC" | "SimpleTranscode" | "Spotcheck"; - externalJobId: null; - format: string; - startedBy: null; - completedBy: null; + externalJobId: null | string; + format: null | string; + startedBy: null | string; + completedBy: null | string; acknowledge: boolean; - acknowledgedBy: null; + acknowledgedBy: null | string; } -export interface Path { + +export interface ShowPath { showId: string; path: PathElement[]; } @@ -39,13 +57,12 @@ export interface Show { organization: ShowOrganization; channels: unknown[]; invitations: unknown[]; - timeline: unknown[]; - extras: FileWrapper[]; - activeBroadcastable: ActiveBroadcastable; - previousBroadcastables: unknown[]; + extras: ExtraFile[]; + activeBroadcastable: BroadcastableApi; + previousBroadcastables: BroadcastableApi[]; } -export interface ActiveBroadcastable { +export interface BroadcastableApi { broadcastable: Broadcastable; files: Files; comments: unknown[]; @@ -66,20 +83,27 @@ export interface Broadcastable { export interface Files { material: Material; audios: Material[]; - subtitles: Subtitle[]; + subtitles: Material[]; } -export interface FileWrapper { +export interface ExtraFile { file: FileClass; + container: null | Container; + streams: FileStream[]; + proxy: null | FileClass; + segments: FileSegment[]; } -export interface Material extends FileWrapper { + +export interface Material { + file: FileClass; container: Container; - streams: Array>; + streams: FileStream[]; proxies: Proxies; reportXml: FileClass; reportPdf: FileClass; deliveries: Delivery[]; segments: Segment[]; + subtitleWarnings: SubtitleWarning[]; } export interface Container { @@ -91,6 +115,13 @@ export interface Container { timecode: null; } +export interface AudioMappingPayload { + index: number; + channelLayout?: string; + version?: string; + typeVersion?: string; +} + export interface Deliveries { shows: Show[]; nodes: DeliveryNode[]; @@ -117,13 +148,9 @@ export interface DeliveryNode { parent: null | string; } -export interface Subtitles { - subtitle: unknown[]; -} - -export enum ArchiveState { - Active = "active", - Archived = "archived", +export interface SubtitleWarning { + name: string; + timecode: string; } export interface SubtitleFormat { @@ -131,14 +158,50 @@ export interface SubtitleFormat { organizationName: string; subtitleFormats: FormatElement[]; } + +export interface SubtitleFormatApi { + id: string; + name: string; + format: string; +} + +export interface SubtitleFormats { + id: string; + name: string; + format: SubtitleFileFormat; + start_timecode?: string; + frame_rate?: FrameRate; + display_standard?: SubtitleDisplayStandard; + offset?: string; +} + +export enum SubtitleFileFormat { + STL = "STL", + WebVTT = "WebVTT", + SRT = "SRT", +} + +export enum SubtitleDisplayStandard { + Open = "Open", + Teletext1 = "Teletext1", + Teletext2 = "Teletext2", +} + +export interface FrameRate { + id: string; + numerator: number; + denominator: number; +} + export interface DeliverPayload { format: string; - versionMapping: "VO" | "VD" | "VDVO"; - timecodeOut: string | null; - timecodeIn: string | null; + versionMapping: Formats.Mapping; + timecodeOut?: string; + timecodeIn?: string; + segments?: string[]; subtitles: DeliverSubtitle | null; targetOrg: string; - targetId: null; + targetId: string | null; } export interface DeliverSubtitle { format: string | null; @@ -156,38 +219,11 @@ export interface Delivery { export interface DeliveryTranscoding { file: string; phase: Phase; - progress: number | null | string; + progress: null | number; startedAt: string; progressedAt: string; log: null | string; - warning: unknown[]; -} - -export enum Phase { - Encoding = "Encoding", - Finished = "Finished", - Packaging = "Packaging", - Waiting = "Waiting", -} - -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: FileTranscoding | null; - format: null; + warning: TranscodeWarning[]; } export interface FileTranscoding { @@ -200,20 +236,6 @@ export interface FileTranscoding { 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; @@ -242,7 +264,7 @@ export interface StreamValue { name: string; issues: Issues; parent: string; - nodeType: Array | FluffyNodeType | string>; + nodeType: NodeType; properties: Properties; } @@ -419,12 +441,12 @@ export interface ProgramLoudnessEBU { export interface Proxies { lowRes: FileClass; - hiRes: null; + hiRes: FileClass | null; } export interface Segment { id: string; - label: string; + label: string | Formats.SegmentLabel; creator: Creator; createdAt: string; file: string; @@ -451,7 +473,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; @@ -462,32 +490,51 @@ export interface PurpleStream { displayAspectRatioNumerator?: number; displayAspectRatioDenominator?: number; bitRate?: number; - rFrameRateNumerator?: number; - rFrameRateDenominator?: number; - level?: null; - profile?: null; + rFrameRateNumerator: number; + rFrameRateDenominator: number; + level?: number; + profile?: string; startTime?: number; chromaSubsampling?: string; scanningType?: string; timecode?: string; - sampleRate?: number; - sampleFormat?: string; - channels?: number; - bitsPerSample?: number; - channelLayout?: string; - version?: string; - typeVersion?: string; + nbFrames?: number; } -export interface Subtitle { - file: FileClass; - container: null; - streams: Array>; - proxies: Proxies; - reportXml: null; - reportPdf: null; - deliveries: unknown[]; - segments: unknown[]; +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 FluffyStream { @@ -564,7 +611,7 @@ export interface ShowClass { accepted: boolean; commandInfoXML: null; kind: ShowKind; - state: ArchiveState; + state: State; parent: string; } @@ -601,11 +648,31 @@ 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; } @@ -701,3 +768,280 @@ 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; +}; + +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 FileClass { + state: string; + stateExpireAt: string | 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: Formats.Format; +} + +export interface FileUploads { + uploads: FileClass[]; + 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: FileClass; + container: FileContainer | null; + streams: FileStream[]; + proxies: Proxies; + reportXml: FileClass | null; + reportPdf: FileClass | null; + deliveries: Delivery[]; + segments: FileSegment[]; + subtitleWarnings: { name: string; timecode: string }[]; +} + +export interface FileSegment { + id: string; + label: Formats.SegmentLabel; + creator: User; + createdAt: string; + file: string; + frameIn: number; + frameOut: number; +} + +export interface FileSegmentPayload { + label: Formats.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", +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a37f7d9 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "sourceMap": true, + "noImplicitAny": true, + "module": "es6", + "moduleResolution": "nodenext", + "target": "es2021", + "noEmit": true, + "allowImportingTsExtensions": true + }, + "include": ["mod.ts", "src/**/*"] + } \ No newline at end of file