Add new FileSourceType for 'id' and use that for local uploads, prefixed with 'pipecat:'

This commit is contained in:
mattie ruth backman
2026-02-09 11:50:34 -05:00
parent 96e06d2401
commit d9cebe602f
3 changed files with 38 additions and 13 deletions

View File

@@ -250,6 +250,14 @@ class FileUrl(FileSource):
type: FileSourceType = "url"
url: str
public: bool = True
class FileId(FileSource):
"""File source as a file ID."""
type: FileSourceType = "id"
id: str
class File(BaseModel):
@@ -257,7 +265,7 @@ class File(BaseModel):
format: str # Mime format of the file, e.g., 'application/pdf'
name: Optional[str] = None
source: FileBytes | FileUrl
source: FileBytes | FileUrl | FileId
class SendFileOptions(BaseModel):

View File

@@ -8,9 +8,11 @@
import asyncio
import base64
import io
import os
from typing import Any, Dict, Mapping, Optional
import aiohttp
from loguru import logger
from pydantic import BaseModel, ValidationError
@@ -574,19 +576,35 @@ class RTVIProcessor(FrameProcessor):
type = file.source.type
opts = data.options if data.options is not None else RTVI.SendFileOptions()
if type == "bytes":
source = file.source.bytes
elif type == "url":
if file.source.url.startswith("/files/"):
match type:
case "bytes":
source = file.source.bytes
case "url":
if not file.source.public:
# read bytes from URL and encode to base64
type = "bytes"
async with aiohttp.ClientSession() as session:
async with session.get(file.source.url) as response:
content = io.BytesIO(await response.content.read())
base64_string = base64.b64encode(content.getvalue()).decode("utf-8")
source = f"data:{file.format};base64,{base64_string}"
else:
source = file.source.url
case "id":
if not file.source.id.startswith("pipecat:"):
logger.warning(f"Unsupported file ID: {file.source.id}")
self.send_error_response(data.id, f"Unsupported file ID: {file.source.id}")
return
if not self._folder:
logger.warning(
"Send-file with /files/ URL requires uploads_folder on RTVIProcessor "
"Send-file with a pipecat id requires uploads_folder on RTVIProcessor "
"(e.g. uploads_folder=runner_uploads_folder())."
)
self.send_error_response(data.id, "Uploads folder not set")
return
# read bytes from file system, encode to base64, then delete the file
type = "bytes"
file_path = os.path.join(self._folder, file.source.url.removeprefix("/files/"))
file_path = os.path.join(self._folder, file.source.id.removeprefix("pipecat:"))
with open(file_path, "rb") as f:
raw_bytes = f.read()
encoded_image = base64.b64encode(raw_bytes).decode("utf-8")
@@ -595,11 +613,10 @@ class RTVIProcessor(FrameProcessor):
os.remove(file_path)
except OSError as e:
logger.warning(f"Failed to remove uploaded file {file_path}: {e}")
else:
source = file.source.url
else:
logger.warning(f"Unsupported file source type: {type}")
return
source = file.source.id
case _:
logger.warning(f"Unsupported file source type: {type}")
return
if type == "bytes" and file.format.startswith("image/"):
# Only access width/height if the original source is RTVIFileBytes (not RTVIFileUrl)

View File

@@ -311,7 +311,7 @@ def _setup_webrtc_routes(app: FastAPI, args: argparse.Namespace):
# Return the file as a URL that can be used in the client, matching the format of RTVIFile
return {
"name": original_name,
"source": {"type": "url", "url": f"/files/{safe_name}"},
"source": {"type": "id", "id": f"pipecat:{safe_name}"},
"format": media_type,
}