Merge branch 'zamilmajdy/multimediafilesupport' into claude-image-blcok

This commit is contained in:
Nicholas Tindle
2025-01-26 15:48:30 +01:00
committed by GitHub
3 changed files with 48 additions and 32 deletions

View File

@@ -3,7 +3,7 @@ from typing import Any, List
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema, BlockType
from backend.data.model import SchemaField
from backend.util.file import store_temp_file
from backend.util.file import MediaFile, store_media_file
from backend.util.mock import MockObject
from backend.util.text import TextFormatter
from backend.util.type import convert
@@ -13,12 +13,12 @@ formatter = TextFormatter()
class FileStoreBlock(Block):
class Input(BlockSchema):
file_in: str = SchemaField(
file_in: MediaFile = SchemaField(
description="The file to store in the temporary directory, it can be a URL, data URI, or local path."
)
class Output(BlockSchema):
file_out: str = SchemaField(
file_out: MediaFile = SchemaField(
description="The relative path to the stored file in the temporary directory."
)
@@ -39,7 +39,7 @@ class FileStoreBlock(Block):
graph_exec_id: str,
**kwargs,
) -> BlockOutput:
file_path = store_temp_file(
file_path = store_media_file(
graph_exec_id=graph_exec_id,
file=input_data.file_in,
return_content=False,

View File

@@ -8,13 +8,13 @@ from moviepy.video.io.VideoFileClip import VideoFileClip
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import SchemaField
from backend.util.file import get_exec_file_path, store_temp_file
from backend.util.file import MediaFile, get_exec_file_path, store_media_file
class MediaDurationBlock(Block):
class Input(BlockSchema):
media_in: str = SchemaField(
media_in: MediaFile = SchemaField(
description="Media input (URL, data URI, or local path)."
)
is_video: bool = SchemaField(
@@ -47,7 +47,7 @@ class MediaDurationBlock(Block):
**kwargs,
) -> BlockOutput:
# 1) Store the input media locally
local_media_path = store_temp_file(
local_media_path = store_media_file(
graph_exec_id=graph_exec_id,
file=input_data.media_in,
return_content=False,
@@ -69,7 +69,7 @@ class LoopVideoBlock(Block):
"""
class Input(BlockSchema):
video_in: str = SchemaField(
video_in: MediaFile = SchemaField(
description="The input video (can be a URL, data URI, or local path)."
)
# Provide EITHER a `duration` or `n_loops` or both. We'll demonstrate `duration`.
@@ -114,7 +114,7 @@ class LoopVideoBlock(Block):
**kwargs,
) -> BlockOutput:
# 1) Store the input video locally
local_video_path = store_temp_file(
local_video_path = store_media_file(
graph_exec_id=graph_exec_id,
file=input_data.video_in,
return_content=False,
@@ -137,14 +137,16 @@ class LoopVideoBlock(Block):
assert isinstance(looped_clip, VideoFileClip)
# 4) Save the looped output
output_filename = f"{node_exec_id}_looped_{os.path.basename(local_video_path)}"
output_filename = MediaFile(
f"{node_exec_id}_looped_{os.path.basename(local_video_path)}"
)
output_abspath = get_exec_file_path(graph_exec_id, output_filename)
looped_clip = looped_clip.with_audio(clip.audio)
looped_clip.write_videofile(output_abspath, codec="libx264", audio_codec="aac")
# Return as data URI
video_out = store_temp_file(
video_out = store_media_file(
graph_exec_id=graph_exec_id,
file=output_filename,
return_content=input_data.output_return_type == "data_uri",
@@ -160,10 +162,10 @@ class AddAudioToVideoBlock(Block):
"""
class Input(BlockSchema):
video_in: str = SchemaField(
video_in: MediaFile = SchemaField(
description="Video input (URL, data URI, or local path)."
)
audio_in: str = SchemaField(
audio_in: MediaFile = SchemaField(
description="Audio input (URL, data URI, or local path)."
)
volume: float = SchemaField(
@@ -176,7 +178,7 @@ class AddAudioToVideoBlock(Block):
)
class Output(BlockSchema):
video_out: str = SchemaField(
video_out: MediaFile = SchemaField(
description="Final video (with attached audio), as a path or data URI."
)
error: str = SchemaField(
@@ -201,12 +203,12 @@ class AddAudioToVideoBlock(Block):
**kwargs,
) -> BlockOutput:
# 1) Store the inputs locally
local_video_path = store_temp_file(
local_video_path = store_media_file(
graph_exec_id=graph_exec_id,
file=input_data.video_in,
return_content=False,
)
local_audio_path = store_temp_file(
local_audio_path = store_media_file(
graph_exec_id=graph_exec_id,
file=input_data.audio_in,
return_content=False,
@@ -227,14 +229,14 @@ class AddAudioToVideoBlock(Block):
final_clip = video_clip.with_audio(audio_clip)
# 4) Write to output file
output_filename = (
output_filename = MediaFile(
f"{node_exec_id}_audio_attached_{os.path.basename(local_video_path)}"
)
output_abspath = os.path.join(abs_temp_dir, output_filename)
final_clip.write_videofile(output_abspath, codec="libx264", audio_codec="aac")
# 5) Return either path or data URI
video_out = store_temp_file(
video_out = store_media_file(
graph_exec_id=graph_exec_id,
file=output_filename,
return_content=input_data.output_return_type == "data_uri",

View File

@@ -29,30 +29,44 @@ def clean_exec_files(graph_exec_id: str, file: str = "") -> None:
shutil.rmtree(exec_path)
def store_temp_file(graph_exec_id: str, file: str, return_content: bool = False) -> str:
"""
MediaFile is a string that represents a file. It can be one of the following:
- Data URI: base64 encoded media file. See https://developer.mozilla.org/en-US/docs/Web/URI/Schemes/data/
- URL: Media file hosted on the internet, it starts with http:// or https://.
- Local path (anything else): A temporary file path living within graph execution time.
Note: Replace this type alias into a proper class, when more information is needed.
"""
MediaFile = str
def store_media_file(
graph_exec_id: str, file: MediaFile, return_content: bool = False
) -> MediaFile:
"""
Safely handle 'file' (a data URI, a URL, or a local path relative to {temp}/exec_file/{exec_id}),
placing or verifying it under:
{tempdir}/exec_file/{exec_id}/...
If 'return_content=True', return a data URI (data:<mime>;base64,<content>).
Otherwise, return the *relative path* (prefix stripped) inside that folder.
Otherwise, returns the file media path relative to the exec_id folder.
For each MediaFile type:
- Data URI:
-> decode and store in a new random file in that folder
- URL:
-> download and store in that folder
- Local path:
-> interpret as relative to that folder; verify it exists
(no copying, as it's presumably already there).
We realpath-check so no symlink or '..' can escape the folder.
For each 'file' type:
- Data URI (starting with "data:"):
-> decode and store in a new random file in that folder
- URL (http:// or https://):
-> download and store in that folder
- Local path (anything else):
-> interpret as relative to that folder; verify it exists
(no copying, as it's presumably already there).
We realpath-check so no symlink or '..' can escape the folder.
:param graph_exec_id: The unique ID of the graph execution.
:param file: Data URI, URL, or local (relative) path.
:param return_content: If True, return a data URI of the file content.
If False, return the *relative* path inside the exec_id folder.
:return: The requested result: data URI or relative path.
:return: The requested result: data URI or relative path of the media.
"""
# Build base path
base_path = Path(get_exec_file_path(graph_exec_id, ""))
@@ -124,6 +138,6 @@ def store_temp_file(graph_exec_id: str, file: str, return_content: bool = False)
# Return result
if return_content:
return _file_to_data_uri(target_path)
return MediaFile(_file_to_data_uri(target_path))
else:
return _strip_base_prefix(target_path, base_path)
return MediaFile(_strip_base_prefix(target_path, base_path))