Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add register font to ts-sdk #934

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,30 @@ where
}
}
}

pub(super) struct Multipart(pub axum::extract::Multipart);

#[async_trait]
impl<S> FromRequest<S> for Multipart
where
S: Send + Sync,
{
type Rejection = (StatusCode, axum::Json<Value>);

async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
let (parts, body) = req.into_parts();
let req = Request::from_parts(parts, body);

match axum::extract::Multipart::from_request(req, state).await {
Ok(multipart) => Ok(Multipart(multipart)),
Err(rejection) => {
let payload = json!({
"error_code": "MALFORMED_MULTIPART",
"message": rejection.body_text(),
});

Err((StatusCode::BAD_REQUEST, axum::Json(payload)))
}
}
}
}
6 changes: 3 additions & 3 deletions src/routes/register_request.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use std::sync::Arc;

use axum::extract::{Multipart, Path, State};
use axum::extract::{Path, State};
use compositor_pipeline::pipeline::{input::InputInitInfo, Port};
use glyphon::fontdb::Source;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use crate::{
routes::Json,
routes::{Json, Multipart},
state::{Pipeline, Response},
};
use compositor_api::{
Expand Down Expand Up @@ -150,7 +150,7 @@ pub(super) async fn handle_image(

pub(super) async fn handle_font(
State(api): State<ApiState>,
mut multipart: Multipart,
Multipart(mut multipart): Multipart,
) -> Result<Response, ApiError> {
let Some(field) = multipart
.next_field()
Expand Down
6 changes: 6 additions & 0 deletions ts/@live-compositor/core/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ export type ApiRequest = {
body?: object;
};

export type MultipartRequest = {
method: 'POST';
route: string;
body: any; //FormData
};

export type RegisterInputResponse = {
video_duration_ms?: number;
audio_duration_ms?: number;
Expand Down
3 changes: 2 additions & 1 deletion ts/@live-compositor/core/src/compositorManager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Logger } from 'pino';
import type { ApiRequest } from './api.js';
import type { ApiRequest, MultipartRequest } from './api.js';

export interface SetupInstanceOptions {
/**
Expand All @@ -13,6 +13,7 @@ export interface SetupInstanceOptions {
export interface CompositorManager {
setupInstance(opts: SetupInstanceOptions): Promise<void>;
sendRequest(request: ApiRequest): Promise<object>;
sendMultipartRequest(request: MultipartRequest): Promise<object>;
registerEventListener(cb: (event: unknown) => void): void;
terminate(): Promise<void>;
}
2 changes: 1 addition & 1 deletion ts/@live-compositor/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as Output from './api/output.js';
import * as Input from './api/input.js';

export { Output, Input };
export { ApiClient, ApiRequest, RegisterInputResponse } from './api.js';
export { ApiClient, ApiRequest, MultipartRequest, RegisterInputResponse } from './api.js';
export { LiveCompositor } from './live/compositor.js';
export { OfflineCompositor } from './offline/compositor.js';
export { CompositorManager, SetupInstanceOptions } from './compositorManager.js';
Expand Down
2 changes: 1 addition & 1 deletion ts/@live-compositor/core/src/live/compositor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import type { Logger } from 'pino';
import type { ImageRef } from '../api/image.js';

export class LiveCompositor {
private manager: CompositorManager;
public readonly manager: CompositorManager;
private api: ApiClient;
private store: _liveCompositorInternals.LiveInputStreamStore<string>;
private outputs: Record<string, Output> = {};
Expand Down
2 changes: 1 addition & 1 deletion ts/@live-compositor/core/src/offline/compositor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import type { ImageRef } from '../api/image.js';
export const OFFLINE_OUTPUT_ID = 'offline_output';

export class OfflineCompositor {
private manager: CompositorManager;
public readonly manager: CompositorManager;
private api: ApiClient;
private store: _liveCompositorInternals.OfflineInputStreamStore<string>;
private renderStarted: boolean = false;
Expand Down
5 changes: 4 additions & 1 deletion ts/@live-compositor/node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@
"@types/node": "^20.14.10",
"@types/node-fetch": "^2.6.11",
"@types/uuid": "^10.0.0",
"@types/ws": "^8.5.12"
"@types/ws": "^8.5.12",
"@types/react": "^18.3.3"
wkazmierczak marked this conversation as resolved.
Show resolved Hide resolved
},
"dependencies": {
"@live-compositor/core": "workspace:^0.1.0",
"live-compositor": "workspace:^0.1.0",
"fs-extra": "^11.2.0",
"node-fetch": "^2.6.7",
"form-data": "^4.0.1",
"pino": "^9.5.0",
"pino-pretty": "^13.0.0",
"tar": "^7.4.3",
Expand Down
25 changes: 24 additions & 1 deletion ts/@live-compositor/node/src/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { Stream } from 'stream';
import { promisify } from 'util';

import fetch from 'node-fetch';
import type { ApiRequest } from '@live-compositor/core';
import type FormData from 'form-data';
import type { ApiRequest, MultipartRequest } from '@live-compositor/core';

const pipeline = promisify(Stream.pipeline);
const httpAgent = new http.Agent({ keepAlive: true });
Expand Down Expand Up @@ -33,6 +34,28 @@ export async function sendRequest(baseUrl: string, request: ApiRequest): Promise
return (await response.json()) as object;
}

export async function sendMultipartRequest(
baseUrl: string,
request: MultipartRequest
): Promise<object> {
const response = await fetch(new URL(request.route, baseUrl), {
method: request.method,
body: request.body as FormData,
agent: url => (url.protocol === 'http:' ? httpAgent : httpsAgent),
});
if (response.status >= 400) {
const err: any = new Error(`Request to compositor failed.`);
err.response = response;
try {
err.body = await response.json();
} catch {
err.body = await response.text();
}
throw err;
}
return (await response.json()) as object;
}

export async function download(url: string, destination: string): Promise<void> {
const response = await fetch(url, { method: 'GET' });
if (response.status >= 400) {
Expand Down
25 changes: 4 additions & 21 deletions ts/@live-compositor/node/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,5 @@
import type { CompositorManager } from '@live-compositor/core';
import {
LiveCompositor as CoreLiveCompositor,
OfflineCompositor as CoreOfflineCompositor,
} from '@live-compositor/core';
import LocallySpawnedInstance from './manager/locallySpawnedInstance';
import ExistingInstance from './manager/existingInstance';
import { createLogger } from './logger';
import LiveCompositor from './live/compositor';
import OfflineCompositor from './offline/compositor';

export { LocallySpawnedInstance, ExistingInstance };

export default class LiveCompositor extends CoreLiveCompositor {
constructor(manager?: CompositorManager) {
super(manager ?? LocallySpawnedInstance.defaultManager(), createLogger());
}
}

export class OfflineCompositor extends CoreOfflineCompositor {
constructor(manager?: CompositorManager) {
super(manager ?? LocallySpawnedInstance.defaultManager(), createLogger());
}
}
export default LiveCompositor;
export { OfflineCompositor };
86 changes: 86 additions & 0 deletions ts/@live-compositor/node/src/live/compositor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import type {
CompositorManager,
Input as CoreInput,
Output as CoreOutput,
} from '@live-compositor/core';
import { LiveCompositor as CoreLiveCompositor } from '@live-compositor/core';
import { createLogger } from '../logger';
import LocallySpawnedInstance from '../manager/locallySpawnedInstance';
import type { ReactElement } from 'react';
import type { Renderers } from 'live-compositor';
import FormData from 'form-data';
import fetch from 'node-fetch';

export default class LiveCompositor {
private coreCompositor: CoreLiveCompositor;

public constructor(manager?: CompositorManager) {
this.coreCompositor = new CoreLiveCompositor(
manager ?? LocallySpawnedInstance.defaultManager(),
createLogger()
);
}

public async init(): Promise<void> {
await this.coreCompositor.init();
}

public async registerOutput(
outputId: string,
root: ReactElement,
request: CoreOutput.RegisterOutput
): Promise<void> {
await this.coreCompositor.registerOutput(outputId, root, request);
}

public async unregisterOutput(outputId: string): Promise<void> {
await this.coreCompositor.unregisterOutput(outputId);
}

public async registerInput(inputId: string, request: CoreInput.RegisterInput): Promise<void> {
await this.coreCompositor.registerInput(inputId, request);
}

public async unregisterInput(inputId: string): Promise<void> {
await this.coreCompositor.unregisterInput(inputId);
}

public async registerImage(imageId: string, request: Renderers.RegisterImage): Promise<void> {
await this.coreCompositor.registerImage(imageId, request);
}

public async unregisterImage(imageId: string): Promise<void> {
await this.coreCompositor.unregisterImage(imageId);
}

public async registerFont(fontSource: string | ArrayBuffer): Promise<object> {
let fontBuffer: Buffer;

if (fontSource instanceof ArrayBuffer) {
fontBuffer = Buffer.from(fontSource);
} else {
const response = await fetch(fontSource);
if (!response.ok) {
throw new Error(`Failed to fetch the font file from ${fontSource}`);
}
fontBuffer = await response.buffer();
}

const formData = new FormData();
formData.append('fontFile', fontBuffer);

return this.coreCompositor.manager.sendMultipartRequest({
method: 'POST',
route: `/api/font/register`,
body: formData,
});
}

public async start(): Promise<void> {
await this.coreCompositor.start();
}

public async terminate(): Promise<void> {
await this.coreCompositor.terminate();
}
}
13 changes: 11 additions & 2 deletions ts/@live-compositor/node/src/manager/existingInstance.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import type { ApiRequest, CompositorManager, SetupInstanceOptions } from '@live-compositor/core';
import type {
ApiRequest,
MultipartRequest,
CompositorManager,
SetupInstanceOptions,
} from '@live-compositor/core';

import { sendRequest } from '../fetch';
import { sendRequest, sendMultipartRequest } from '../fetch';
import { retry, sleep } from '../utils';
import { WebSocketConnection } from '../ws';

Expand Down Expand Up @@ -44,6 +49,10 @@ class ExistingInstance implements CompositorManager {
return await sendRequest(`${this.protocol}://${this.ip}:${this.port}`, request);
}

async sendMultipartRequest(request: MultipartRequest): Promise<object> {
return await sendMultipartRequest(`${this.protocol}://${this.ip}:${this.port}`, request);
}

public registerEventListener(cb: (event: object) => void): void {
this.wsConnection.registerEventListener(cb);
}
Expand Down
14 changes: 11 additions & 3 deletions ts/@live-compositor/node/src/manager/locallySpawnedInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@ import path from 'path';
import { v4 as uuidv4 } from 'uuid';
import * as fs from 'fs-extra';
import * as tar from 'tar';
import type { ApiRequest, CompositorManager, SetupInstanceOptions } from '@live-compositor/core';

import { download, sendRequest } from '../fetch';
import type {
ApiRequest,
MultipartRequest,
CompositorManager,
SetupInstanceOptions,
} from '@live-compositor/core';

import { download, sendRequest, sendMultipartRequest } from '../fetch';
import { retry, sleep } from '../utils';
import type { SpawnPromise } from '../spawn';
import { killProcess, spawn } from '../spawn';
Expand Down Expand Up @@ -94,6 +99,9 @@ class LocallySpawnedInstance implements CompositorManager {
return await sendRequest(`http://127.0.0.1:${this.port}`, request);
}

async sendMultipartRequest(request: MultipartRequest): Promise<object> {
return await sendMultipartRequest(`http://127.0.0.1:${this.port}`, request);
}
public registerEventListener(cb: (event: object) => void): void {
this.wsConnection.registerEventListener(cb);
}
Expand Down
Loading