미디어 변환과 PDF 처리
TSv2와 Python은 미디어 변환 작업을 제출하고 완료를 기다린 뒤 변환 결과의 바이트를 읽을 수 있습니다. 회전, 크기 조절과 재인코딩, PDF 페이지 렌더링과 범위 추출 및 OCR에 사용합니다. 이 변환 기능은 Beta입니다.
입력 Media, 업로드 및 영구 연결은 미디어 기본 API를 따릅니다. 출력 바이트를 Media로 반환하려면 다시 업로드해야 하며, 객체에 연결하지 않은 업로드는 임시 상태입니다.
호출과 버전
TSv2는 @osdk/api/unstable의 transformAndWait를 사용합니다. @osdk/api 2.55.0 이전에는 이름이 __EXPERIMENTAL__NOT_SUPPORTED_YET__transformAndWait이고 입력 필드도 mediaReference였습니다. 아래 예제는 2.55.0 이상의 media 입력 계약입니다.
MediaTransformation은 $image, $video, $audio, $documentToText, $documentToImage, $documentToDocument와 $audioToText 같은 분기 중 하나를 선택하며 각 분기의 인코딩과 작업 필드는 다릅니다.
Python은 생성 FoundryClient의 client.ontology.media.transform_and_wait를 사용합니다. 비동기 메서드 async_transform_and_wait는 같은 인수를 받고 await합니다. 함수에 @function(beta=True)를 선언합니다.
180도 회전하여 JPEG 반환
TSv2 rotateImage.ts의 예제입니다. 작업 상태 조회 간격은 3,000밀리초, 대기 제한은 30,000밀리초로 지정합니다.
// Ensure you are using @osdk/api 2.55.0 or greater for transformAndWait.
// In versions before 2.55.0, this helper was exported as
// __EXPERIMENTAL__NOT_SUPPORTED_YET__transformAndWait and took a "mediaReference"
// argument instead of the "media" argument shown below.
//
// "MediaTransformation" is a discriminated union:
// each variant (`$image`, `$video`, `$audio`, `$documentToText`, `$documentToImage`, `$documentToDocument`, `$audioToText`, etc.)
// selects a transformation kind, with its own encoding and operation fields.
// See the "MediaTransformation" type definition for a full set of variants and operations:
// https://github.com/palantir/osdk-ts/blob/main/packages/api/src/experimental/MediaTransformation.ts
import { transformAndWait, type MediaTransformation } from "@osdk/api/unstable";
import type { Client, Media } from "@osdk/client";
import { uploadMedia } from "@osdk/functions";
export default async function rotateImage(
client: Client,
media: Media,
): Promise<Media> {
const transformation: MediaTransformation = {
$image: {
$encoding: "jpg",
$operations: [{ $rotate: { $angle: "DEGREE_180" } }],
},
};
const result: Response = await client(transformAndWait).transformAndWait({
media,
transformation,
options: { pollIntervalMs: 3000, pollTimeoutMs: 30000 },
});
if (!result.ok) {
// The transformation failed; inspect result.status / result.text() for details.
throw new Error(`Transformation failed with status ${result.status}`);
}
// Re-upload the transformed bytes so the function returns a Media.
return uploadMedia(client, { data: await result.blob(), fileName: "rotated.jpg" });
}
Python은 같은 설정을 초 단위로 지정합니다. MediaTransformationFailedError는 작업이 FAILED인 경우, MediaTransformationTimeoutError는 대기 시간 안에 끝나지 않은 경우입니다.
from foundry_sdk.v2.media_sets.models import ImageTransformation, JpgFormat, RotateImageOperation
from functions.api import Media, function
from ontology_sdk import FoundryClient
@function(beta=True)
def image_transform(image: Media) -> Media:
client = FoundryClient()
transformed = client.ontology.media.transform_and_wait(
media_reference=image.get_media_reference(),
transformation=ImageTransformation(
encoding=JpgFormat(), operations=[RotateImageOperation(angle="DEGREE_180")],
),
poll_interval_seconds=3.0,
poll_timeout_seconds=30.0,
)
return client.ontology.media.upload_media(body=transformed, filename="rotated.jpg")
실패와 시간 초과 예외를 잡지 않으면 호출자에게 전파됩니다. 이 예외 타입은 foundry_sdk_runtime.errors에서 가져와 구분할 수 있습니다. TSv2의 Response도 ok와 status를 확인한 뒤 내용을 소비합니다.
PDF의 페이지별 hOCR
hOCR는 인식한 단어와 줄의 텍스트 및 bbox 좌표를 함께 담은 HTML입니다. 한 번의 OCR 변환은 한 페이지를 처리하므로 전체 문서에는 페이지 수만큼 호출해야 합니다. 페이지 번호는 0부터 시작합니다.
TSv2 ocrPdfPages.ts는 pageCount를 실제 문서 페이지 수로 입력받습니다. 전체 메타데이터의 document.pages에서 이 수를 구할 수 있습니다. 출력은 페이지별 hOCR 문자열 목록이고 언어는 ENG로 지정합니다.
import { transformAndWait, type MediaTransformation } from "@osdk/api/unstable";
import type { Client, Media } from "@osdk/client";
import type { Integer } from "@osdk/functions";
export default async function ocrPdfPages(
client: Client,
media: Media,
pageCount: Integer,
): Promise<string[]> {
const runTransformation = client(transformAndWait).transformAndWait;
const pageResults: string[] = [];
for (let pageNumber = 0; pageNumber < pageCount; pageNumber++) {
const transformation: MediaTransformation = {
$documentToText: {
$operation: {
$ocrOnPage: {
$pageNumber: pageNumber,
$parameters: {
$outputFormat: { $hocr: {} },
$languages: [{ $language: "ENG" }],
},
},
},
},
};
const result = await runTransformation({
media,
transformation,
options: { pollTimeoutMs: 120_000 },
});
if (!result.ok) {
throw new Error(`OCR failed on page ${pageNumber}: ${result.status}`);
}
pageResults.push(await result.text());
}
return pageResults;
}
Python은 전체 메타데이터에서 페이지 수를 읽고 hOCR 바이너리 목록을 반환합니다. Python 함수는 bytes 반환을 지원합니다.
from foundry_sdk.v2.media_sets.models import (
DocumentMediaItemMetadata,
DocumentToTextTransformation,
OcrHocrOutputFormat,
OcrLanguageWrapper,
OcrOnPageOperation,
OcrParameters,
)
from foundry_sdk_runtime.media import Media
from functions.api import function
from ontology_sdk import FoundryClient
@function(beta=True)
def ocr_pdf_pages(document: Media) -> list[bytes]:
"""Run OCR on every page of a PDF and return the hOCR bytes per page.
Each hOCR document includes `bbox` attributes on detected words, lines, and
paragraphs; parse with any HTML parser to recover both text and bounding
boxes in a single pass.
"""
client = FoundryClient()
metadata = document.get_media_full_metadata().item_metadata
if not isinstance(metadata, DocumentMediaItemMetadata) or metadata.pages is None:
raise ValueError("Expected a PDF document with a known page count")
media_reference = document.get_media_reference()
page_results: list[bytes] = []
for page_number in range(metadata.pages):
transformation = DocumentToTextTransformation(
operation=OcrOnPageOperation(
page_number=page_number,
parameters=OcrParameters(
output_format=OcrHocrOutputFormat(),
languages=[OcrLanguageWrapper(language="ENG")],
),
),
)
hocr_bytes: bytes = client.ontology.media.transform_and_wait(
media_reference=media_reference,
transformation=transformation,
poll_timeout_seconds=120.0,
)
page_results.append(hocr_bytes)
return page_results
변환별 대기 제한은 120초입니다. 여러 페이지의 처리와 네트워크 왕복이 모두 함수 실행 시간에 포함되므로 함수 시간 한도를 전체 작업에 맞춰 설정해야 합니다. 페이지가 복잡하면 OCR 한 번도 기본 함수 시간 한도를 넘을 수 있습니다.
첫 페이지를 이미지로 렌더링
다음 예제는 PDF의 0번 페이지를 폭 1,200픽셀의 PNG로 만들고 다시 업로드합니다. 인코딩과 페이지 렌더링 설정은 문서-이미지 변환에 속합니다.
TSv2 renderFirstPageAsPng.ts입니다.
import { transformAndWait, type MediaTransformation } from "@osdk/api/unstable";
import type { Client, Media } from "@osdk/client";
import { uploadMedia } from "@osdk/functions";
export default async function renderFirstPageAsPng(
client: Client,
media: Media,
): Promise<Media> {
const transformation: MediaTransformation = {
$documentToImage: {
$encoding: "png",
$operation: { $renderPage: { $pageNumber: 0, $width: 1200 } },
},
};
const result = await client(transformAndWait).transformAndWait({
media,
transformation,
});
if (!result.ok) {
throw new Error(`Render failed: ${result.status}`);
}
// Re-upload the rendered page so the function returns a Media.
return uploadMedia(client, { data: await result.blob(), fileName: "page.png" });
}
Python의 같은 작업은 다음과 같습니다.
from foundry_sdk.v2.media_sets.models import (
DocumentToImageTransformation,
PngFormat,
RenderPageOperation,
)
from foundry_sdk_runtime.media import Media
from functions.api import function
from ontology_sdk import FoundryClient
@function(beta=True)
def render_first_page_as_png(document: Media) -> Media:
"""Render page 0 of a PDF at 1200px wide as a PNG and return it as a Media."""
client = FoundryClient()
transformation = DocumentToImageTransformation(
encoding=PngFormat(),
operation=RenderPageOperation(page_number=0, width=1200),
)
rendered_png: bytes = client.ontology.media.transform_and_wait(
media_reference=document.get_media_reference(),
transformation=transformation,
)
# Re-upload the rendered page so the function returns a Media.
return client.ontology.media.upload_media(body=rendered_png, filename="page.png")
앞의 열 페이지 추출
범위는 시작 포함과 끝 제외입니다. 0부터 10을 지정하면 0번부터 9번 페이지를 선택합니다. strictlyEnforceEndPage를 false로 두면 열 페이지보다 짧은 문서도 처리할 수 있습니다.
TSv2 sliceFirstTenPages.ts입니다.
import { transformAndWait, type MediaTransformation } from "@osdk/api/unstable";
import type { Client, Media } from "@osdk/client";
import { uploadMedia } from "@osdk/functions";
export default async function sliceFirstTenPages(
client: Client,
media: Media,
): Promise<Media> {
const transformation: MediaTransformation = {
$documentToDocument: {
$encoding: "pdf",
$operation: {
$slicePdfRange: {
$startPageInclusive: 0,
$endPageExclusive: 10,
$strictlyEnforceEndPage: false,
},
},
},
};
const result = await client(transformAndWait).transformAndWait({
media,
transformation,
});
if (!result.ok) {
throw new Error(`Slice failed: ${result.status}`);
}
// Re-upload the sliced PDF so the function returns a Media.
return uploadMedia(client, { data: await result.blob(), fileName: "slice.pdf" });
}
Python은 같은 필드를 snake_case로 지정합니다.
from foundry_sdk.v2.media_sets.models import (
DocumentToDocumentTransformation,
PdfFormat,
SlicePdfRangeOperation,
)
from foundry_sdk_runtime.media import Media
from functions.api import function
from ontology_sdk import FoundryClient
@function(beta=True)
def slice_first_ten_pages(document: Media) -> Media:
"""Return a new PDF containing pages 0-9 of the input PDF as a Media."""
client = FoundryClient()
transformation = DocumentToDocumentTransformation(
encoding=PdfFormat(),
operation=SlicePdfRangeOperation(
start_page_inclusive=0,
end_page_exclusive=10,
strictly_enforce_end_page=False, # tolerate documents shorter than 10 pages
),
)
sliced_pdf: bytes = client.ontology.media.transform_and_wait(
media_reference=document.get_media_reference(),
transformation=transformation,
)
# Re-upload the sliced PDF so the function returns a Media.
return client.ontology.media.upload_media(body=sliced_pdf, filename="slice.pdf")
결과의 해석
렌더링한 이미지의 픽셀 크기와 OCR이 사용한 페이지 좌표계가 같다고 가정하면 안 됩니다. hOCR 경계 상자 표시는 페이지 bbox의 원점과 실제 렌더 이미지의 폭 및 높이를 사용하여 단어 상자를 변환합니다.
OCR 언어와 입력 품질에 따라 인식 결과가 달라집니다. 변환 완료는 인식한 텍스트의 업무상 정확성을 확인했다는 뜻이 아닙니다.