Skip to content

Commit

Permalink
Merge pull request #6 from FolhaSP/feat/news-video-script-generator
Browse files Browse the repository at this point in the history
feat: add news video script generator and fix effects
  • Loading branch information
leodiegues authored Nov 19, 2024
2 parents 0b6ee60 + 083d37f commit a63f233
Show file tree
Hide file tree
Showing 9 changed files with 423 additions and 294 deletions.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ assemblyai = ["assemblyai>=0.34.0"]
langchain = ["langchain>=0.3.7"]
haystack = ["haystack-ai>=2.6.1"]
elevenlabs = ["elevenlabs>=1.9.0"]
news = ["instructor>=1.6.4", "litellm>=1.52.9"]

[build-system]
requires = ["hatchling"]
Expand Down
2 changes: 1 addition & 1 deletion src/mosaico/assets/subtitle.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class SubtitleAsset(BaseTextAsset):
default_factory=lambda: TextAssetParams(
position=RegionPosition(x="center", y="bottom"),
font_color=Color("white"),
font_size=48,
font_size=45,
stroke_width=1,
align="center",
shadow_blur=10,
Expand Down
4 changes: 2 additions & 2 deletions src/mosaico/script_generators/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ class Shot(BaseModel):
subtitle: str
"""The subtitle for the shot."""

media_references: list[int] = Field(..., min_length=1)
"""The media references for the shot."""
media_id: str
"""The media reference for the shot."""

effects: list[VideoEffectType] = Field(default_factory=list)
"""The effects applied to the shot."""
Expand Down
5 changes: 4 additions & 1 deletion src/mosaico/speech_synthesizers/openai.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from collections.abc import Sequence
from typing import Annotated, Any, ClassVar, Literal

from openai import OpenAI
from pydantic import BaseModel
from pydantic.fields import Field, PrivateAttr
from pydantic.functional_validators import model_validator
Expand Down Expand Up @@ -45,6 +44,10 @@ def _set_client(self) -> Self:
"""
Set the OpenAI client.
"""
try:
from openai import OpenAI
except ImportError:
raise ImportError("The 'openai' package is required for using the OpenAISpeechSynthesizer.")
self._client = OpenAI(api_key=self.api_key, base_url=self.base_url, timeout=self.timeout)
return self

Expand Down
28 changes: 14 additions & 14 deletions src/mosaico/video/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,26 +127,26 @@ def from_script_generator(

# Create assets and asset references from the script.
for shot in script.shots:
shot_assets: list[Asset] = [SubtitleAsset.from_data(shot.subtitle)]
shot_scene = Scene(description=shot.description).add_asset_references(
references=AssetReference.from_asset(shot_assets[0])
.with_start_time(shot.start_time)
.with_end_time(shot.end_time)
)
referenced_media = next(m for m in media if m.id == shot.media_id)
shot_subtitle = SubtitleAsset.from_data(shot.subtitle)
shot_effects = [create_effect(effect) for effect in shot.effects]

for media_ref in shot.media_references:
shot_asset = convert_media_to_asset(media[media_ref])
shot_asset_ref = (
shot_asset = convert_media_to_asset(referenced_media)
shot_scene = (
Scene(description=shot.description)
.add_asset_references(
AssetReference.from_asset(shot_subtitle)
.with_start_time(shot.start_time)
.with_end_time(shot.end_time)
)
.add_asset_references(
AssetReference.from_asset(shot_asset)
.with_start_time(shot.start_time)
.with_end_time(shot.end_time)
.with_effects(shot_effects)
.with_effects(shot_effects if shot_asset.type == "image" else [])
)
shot_scene = shot_scene.add_asset_references(shot_asset_ref)
shot_assets.append(shot_asset)
)

project = project.add_assets(shot_assets).add_timeline_events(shot_scene)
project = project.add_assets(shot_asset).add_assets(shot_subtitle).add_timeline_events(shot_scene)

return project

Expand Down
17 changes: 7 additions & 10 deletions src/mosaico/video/rendering.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

if TYPE_CHECKING:
from mosaico.assets.types import Asset
from mosaico.effects.protocol import Effect
from mosaico.types import FrameSize
from mosaico.video.project import VideoProject
from mosaico.video.types import TimelineEvent
Expand All @@ -35,21 +34,19 @@ def render_video(project: VideoProject, output_dir: str | Path, *, overwrite: bo
output_path = output_dir / f"{project.config.title}.mp4"

if not output_dir.exists():
raise FileNotFoundError(f"Output directory does not exist: {output_dir}")
msg = f"Output directory does not exist: {output_dir}"
raise FileNotFoundError(msg)

if output_path.exists() and not overwrite:
raise FileExistsError(f"Output file already exists: {output_path}")
msg = f"Output file already exists: {output_path}"
raise FileExistsError(msg)

video_clips = []
audio_clips = []

for event in project.timeline:
event_asset_ref_pairs = _get_event_assets_and_refs(event, project)
event_video_clips, event_audio_clips = _render_event_clips(
asset_and_ref_pairs=event_asset_ref_pairs,
video_resolution=project.config.resolution,
effects=getattr(event, "effects", []),
)
event_video_clips, event_audio_clips = _render_event_clips(event_asset_ref_pairs, project.config.resolution)
video_clips.extend(event_video_clips or [])
audio_clips.extend(event_audio_clips or [])

Expand Down Expand Up @@ -99,7 +96,7 @@ def _get_event_asset_refs(event: TimelineEvent) -> list[AssetReference]:


def _render_event_clips(
asset_and_ref_pairs: Sequence[tuple[Asset, AssetReference]], video_resolution: FrameSize, effects: Sequence[Effect]
asset_and_ref_pairs: Sequence[tuple[Asset, AssetReference]], video_resolution: FrameSize
) -> tuple[list[VideoClip], list[AudioClip]]:
"""
Compose a video clip from the given assets.
Expand All @@ -108,7 +105,7 @@ def _render_event_clips(
video_clips = []

for asset, asset_ref in asset_and_ref_pairs:
clip = make_clip(asset, asset_ref.duration, video_resolution, effects)
clip = make_clip(asset, asset_ref.duration, video_resolution, asset_ref.effects)
clip = clip.set_start(asset_ref.start_time)

if hasattr(asset.params, "z_index"):
Expand Down
10 changes: 7 additions & 3 deletions tests/integrations/langchain/test_script_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@

@pytest.fixture
def sample_media():
return [Media.from_data("Test video content", metadata={"description": "A test video", "credit": "Test Author"})]
return [
Media.from_data(
"Test video content", id="media_1", metadata={"description": "A test video", "credit": "Test Author"}
)
]


@pytest.fixture
Expand All @@ -30,7 +34,7 @@ def sample_script():
start_time=0.0,
end_time=5.0,
subtitle="Test subtitle",
media_references=[0],
media_id="media_1",
)
],
)
Expand Down Expand Up @@ -76,7 +80,7 @@ def generate(self, media: Sequence[Media], **kwargs) -> ShootingScript:
start_time=0.0,
end_time=5.0,
subtitle="Test",
media_references=[0],
media_id=media[0].id,
)
],
)
Expand Down
4 changes: 2 additions & 2 deletions tests/video/test_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,15 +218,15 @@ def generate(self, media: Sequence[Media], **kwargs: Any) -> ShootingScript:
start_time=0,
end_time=5,
subtitle="Hello world",
media_references=[0],
media_id=media[0].id,
),
Shot(
number=2,
description="Shot 2",
start_time=5,
end_time=10,
subtitle="This is a test",
media_references=[1],
media_id=media[1].id,
),
],
)
Expand Down
Loading

0 comments on commit a63f233

Please sign in to comment.