pycaps-ai 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pycaps/__init__.py +13 -0
- pycaps/ai/__init__.py +5 -0
- pycaps/ai/gpt.py +35 -0
- pycaps/ai/llm.py +11 -0
- pycaps/ai/llm_provider.py +16 -0
- pycaps/animation/__init__.py +31 -0
- pycaps/animation/animation.py +11 -0
- pycaps/animation/builtin/__init__.py +18 -0
- pycaps/animation/builtin/preset/__init__.py +21 -0
- pycaps/animation/builtin/preset/fade_in.py +17 -0
- pycaps/animation/builtin/preset/fade_out.py +19 -0
- pycaps/animation/builtin/preset/pop_in.py +22 -0
- pycaps/animation/builtin/preset/pop_in_bounce.py +20 -0
- pycaps/animation/builtin/preset/pop_out.py +22 -0
- pycaps/animation/builtin/preset/slide_in.py +25 -0
- pycaps/animation/builtin/preset/slide_out.py +26 -0
- pycaps/animation/builtin/preset/zoom_in.py +24 -0
- pycaps/animation/builtin/preset/zoom_out.py +25 -0
- pycaps/animation/builtin/primitive/__init__.py +11 -0
- pycaps/animation/builtin/primitive/fade_in_primitive.py +6 -0
- pycaps/animation/builtin/primitive/pop_in_primitive.py +64 -0
- pycaps/animation/builtin/primitive/slide_in_primitive.py +54 -0
- pycaps/animation/builtin/primitive/zoom_in_primitive.py +64 -0
- pycaps/animation/definitions.py +22 -0
- pycaps/animation/element_animator.py +52 -0
- pycaps/animation/preset_animation.py +16 -0
- pycaps/animation/primitive_animation.py +89 -0
- pycaps/api/__init__.py +3 -0
- pycaps/api/api_key_service.py +21 -0
- pycaps/api/api_sender.py +53 -0
- pycaps/api/emoji_in_segments_api.py +42 -0
- pycaps/api/pycaps_tagger_api.py +24 -0
- pycaps/bootstrap.py +35 -0
- pycaps/cli/__init__.py +1 -0
- pycaps/cli/cli.py +23 -0
- pycaps/cli/config_cli.py +25 -0
- pycaps/cli/preview_styles_cli.py +30 -0
- pycaps/cli/render_cli.py +103 -0
- pycaps/cli/template_cli.py +39 -0
- pycaps/common/__init__.py +43 -0
- pycaps/common/config_service.py +50 -0
- pycaps/common/element_container.py +48 -0
- pycaps/common/models.py +298 -0
- pycaps/common/types.py +63 -0
- pycaps/effect/__init__.py +22 -0
- pycaps/effect/clip/__init__.py +9 -0
- pycaps/effect/clip/animate_segment_emojis_effect.py +105 -0
- pycaps/effect/clip/clip_effect.py +6 -0
- pycaps/effect/clip/typewriting_effect.py +54 -0
- pycaps/effect/effect.py +5 -0
- pycaps/effect/sound/__init__.py +5 -0
- pycaps/effect/sound/builtin_sound.py +42 -0
- pycaps/effect/sound/presets/click-light.mp3 +0 -0
- pycaps/effect/sound/presets/click.mp3 +0 -0
- pycaps/effect/sound/presets/ding-long.mp3 +0 -0
- pycaps/effect/sound/presets/ding-short.mp3 +0 -0
- pycaps/effect/sound/presets/ding.mp3 +0 -0
- pycaps/effect/sound/presets/glitch-static.mp3 +0 -0
- pycaps/effect/sound/presets/glitch.mp3 +0 -0
- pycaps/effect/sound/presets/heart-beat.mp3 +0 -0
- pycaps/effect/sound/presets/hit-intense.mp3 +0 -0
- pycaps/effect/sound/presets/hit-strong.mp3 +0 -0
- pycaps/effect/sound/presets/pop-2.mp3 +0 -0
- pycaps/effect/sound/presets/pop.mp3 +0 -0
- pycaps/effect/sound/presets/slide-paper.mp3 +0 -0
- pycaps/effect/sound/presets/swoosh.mp3 +0 -0
- pycaps/effect/sound/presets/whoosh-2.mp3 +0 -0
- pycaps/effect/sound/presets/whoosh-deep.mp3 +0 -0
- pycaps/effect/sound/presets/whoosh.mp3 +0 -0
- pycaps/effect/sound/sound.py +15 -0
- pycaps/effect/sound/sound_effect.py +74 -0
- pycaps/effect/text/__init__.py +14 -0
- pycaps/effect/text/emoji_in_segment_effect.py +88 -0
- pycaps/effect/text/emoji_in_segment_getter.py +34 -0
- pycaps/effect/text/emoji_in_segment_llm_getter.py +38 -0
- pycaps/effect/text/emoji_in_word_effect.py +43 -0
- pycaps/effect/text/modify_words_effect.py +26 -0
- pycaps/effect/text/remove_punctuation_marks_effect.py +41 -0
- pycaps/effect/text/text_effect.py +4 -0
- pycaps/layout/__init__.py +19 -0
- pycaps/layout/definitions.py +61 -0
- pycaps/layout/layout_updater.py +55 -0
- pycaps/layout/layout_utils.py +38 -0
- pycaps/layout/line_splitter.py +75 -0
- pycaps/layout/positions_calculator.py +105 -0
- pycaps/layout/word_size_calculator.py +19 -0
- pycaps/logger.py +50 -0
- pycaps/pipeline/__init__.py +9 -0
- pycaps/pipeline/caps_pipeline.py +290 -0
- pycaps/pipeline/caps_pipeline_builder.py +137 -0
- pycaps/pipeline/json_config_loader.py +231 -0
- pycaps/pipeline/json_schema.py +181 -0
- pycaps/pipeline/subtitle_data_service.py +14 -0
- pycaps/renderer/__init__.py +13 -0
- pycaps/renderer/css_subtitle_renderer.py +296 -0
- pycaps/renderer/letter_size_cache.py +26 -0
- pycaps/renderer/pictex_subtitle_renderer.py +147 -0
- pycaps/renderer/playwright_screenshot_capturer.py +38 -0
- pycaps/renderer/previewer/__init__.py +3 -0
- pycaps/renderer/previewer/css_subtitle_previewer.py +52 -0
- pycaps/renderer/previewer/previewer.html +340 -0
- pycaps/renderer/rendered_image_cache.py +35 -0
- pycaps/renderer/renderer_page.py +80 -0
- pycaps/renderer/subtitle_renderer.py +36 -0
- pycaps/selector/__init__.py +11 -0
- pycaps/selector/tag_based_selector.py +17 -0
- pycaps/selector/time_event_selector.py +72 -0
- pycaps/selector/word_clip_selector.py +31 -0
- pycaps/tag/__init__.py +11 -0
- pycaps/tag/definitions.py +19 -0
- pycaps/tag/tag_condition.py +113 -0
- pycaps/tag/tagger/__init__.py +7 -0
- pycaps/tag/tagger/ai_tagger.py +28 -0
- pycaps/tag/tagger/external_llm_tagger.py +76 -0
- pycaps/tag/tagger/semantic_tagger.py +114 -0
- pycaps/tag/tagger/structure_tagger.py +51 -0
- pycaps/template/__init__.py +17 -0
- pycaps/template/builtin_template.py +10 -0
- pycaps/template/constants.py +3 -0
- pycaps/template/local_template.py +8 -0
- pycaps/template/preset/classic/pycaps.template.json +3 -0
- pycaps/template/preset/classic/styles.css +10 -0
- pycaps/template/preset/default/pycaps.template.json +16 -0
- pycaps/template/preset/default/resources/black.ttf +0 -0
- pycaps/template/preset/default/styles.css +16 -0
- pycaps/template/preset/explosive/pycaps.template.json +66 -0
- pycaps/template/preset/explosive/resources/black.ttf +0 -0
- pycaps/template/preset/explosive/styles.css +44 -0
- pycaps/template/preset/fast/pycaps.template.json +21 -0
- pycaps/template/preset/fast/styles.css +15 -0
- pycaps/template/preset/hype/pycaps.template.json +65 -0
- pycaps/template/preset/hype/resources/komika.ttf +0 -0
- pycaps/template/preset/hype/styles.css +26 -0
- pycaps/template/preset/line-focus/pycaps.template.json +29 -0
- pycaps/template/preset/line-focus/resources/black.ttf +0 -0
- pycaps/template/preset/line-focus/styles.css +32 -0
- pycaps/template/preset/minimalist/pycaps.template.json +33 -0
- pycaps/template/preset/minimalist/styles.css +26 -0
- pycaps/template/preset/model/main.py +0 -0
- pycaps/template/preset/model/preview.hash +4 -0
- pycaps/template/preset/model/pycaps.template.json +0 -0
- pycaps/template/preset/model/styles.css +0 -0
- pycaps/template/preset/neo-minimal/pycaps.template.json +44 -0
- pycaps/template/preset/neo-minimal/styles.css +46 -0
- pycaps/template/preset/retro-gaming/pycaps.template.json +48 -0
- pycaps/template/preset/retro-gaming/resources/PressStart2P.ttf +0 -0
- pycaps/template/preset/retro-gaming/styles.css +29 -0
- pycaps/template/preset/vibrant/pycaps.template.json +57 -0
- pycaps/template/preset/vibrant/resources/black.ttf +0 -0
- pycaps/template/preset/vibrant/styles.css +34 -0
- pycaps/template/preset/word-focus/pycaps.template.json +29 -0
- pycaps/template/preset/word-focus/resources/black.ttf +0 -0
- pycaps/template/preset/word-focus/styles.css +22 -0
- pycaps/template/template.py +14 -0
- pycaps/template/template_factory.py +17 -0
- pycaps/template/template_loader.py +31 -0
- pycaps/template/template_service.py +21 -0
- pycaps/transcriber/__init__.py +23 -0
- pycaps/transcriber/base_transcriber.py +18 -0
- pycaps/transcriber/editor/__init__.py +3 -0
- pycaps/transcriber/editor/editor.html +731 -0
- pycaps/transcriber/editor/transcription_editor.py +54 -0
- pycaps/transcriber/google_audio_transcriber.py +103 -0
- pycaps/transcriber/preview_transcriber.py +29 -0
- pycaps/transcriber/splitter/__init__.py +12 -0
- pycaps/transcriber/splitter/base_segment_splitter.py +12 -0
- pycaps/transcriber/splitter/limit_by_chars_splitter.py +87 -0
- pycaps/transcriber/splitter/limit_by_words_splitter.py +45 -0
- pycaps/transcriber/splitter/split_into_sentences_splitter.py +33 -0
- pycaps/transcriber/transcript_format.py +9 -0
- pycaps/transcriber/transcript_loader.py +383 -0
- pycaps/transcriber/whisper_audio_transcriber.py +88 -0
- pycaps/utils/__init__.py +7 -0
- pycaps/utils/script_utils.py +30 -0
- pycaps/utils/time_utils.py +3 -0
- pycaps/video/__init__.py +7 -0
- pycaps/video/audio_utils.py +29 -0
- pycaps/video/subtitle_clips_generator.py +103 -0
- pycaps/video/video_generator.py +128 -0
- pycaps_ai-0.2.1.dist-info/METADATA +225 -0
- pycaps_ai-0.2.1.dist-info/RECORD +185 -0
- pycaps_ai-0.2.1.dist-info/WHEEL +5 -0
- pycaps_ai-0.2.1.dist-info/entry_points.txt +2 -0
- pycaps_ai-0.2.1.dist-info/licenses/LICENSE +21 -0
- pycaps_ai-0.2.1.dist-info/top_level.txt +1 -0
pycaps/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .pipeline import CapsPipeline, CapsPipelineBuilder, JsonConfigLoader
|
|
2
|
+
from .renderer import CssSubtitleRenderer, PictexSubtitleRenderer
|
|
3
|
+
from .transcriber import WhisperAudioTranscriber, GoogleAudioTranscriber, AudioTranscriber, LimitByWordsSplitter, LimitByCharsSplitter, SplitIntoSentencesSplitter, TranscriptFormat, load_transcription
|
|
4
|
+
from .effect import *
|
|
5
|
+
from .animation import *
|
|
6
|
+
from .selector import WordClipSelector
|
|
7
|
+
from .tag import TagCondition, BuiltinTag, TagConditionFactory, SemanticTagger
|
|
8
|
+
from .common import *
|
|
9
|
+
from .layout.definitions import *
|
|
10
|
+
from .ai import LlmProvider
|
|
11
|
+
from .template import TemplateLoader, TemplateFactory, DEFAULT_TEMPLATE_NAME
|
|
12
|
+
|
|
13
|
+
__version__ = "0.2.1"
|
pycaps/ai/__init__.py
ADDED
pycaps/ai/gpt.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from pycaps.ai.llm import Llm
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
class Gpt(Llm):
|
|
5
|
+
|
|
6
|
+
OPENAI_API_KEY_NAME = "PYCAPS_OPENAI_API_KEY"
|
|
7
|
+
|
|
8
|
+
def __init__(self):
|
|
9
|
+
self._client = None
|
|
10
|
+
|
|
11
|
+
def send_message(self, prompt: str, model: str = "gpt-4.1-mini") -> str:
|
|
12
|
+
return self._get_client().responses.create(model=model, input=prompt).output_text
|
|
13
|
+
|
|
14
|
+
def is_enabled(self) -> bool:
|
|
15
|
+
return os.getenv(self.OPENAI_API_KEY_NAME) is not None
|
|
16
|
+
|
|
17
|
+
def _get_client(self):
|
|
18
|
+
try:
|
|
19
|
+
from openai import OpenAI
|
|
20
|
+
|
|
21
|
+
if self._client:
|
|
22
|
+
return self._client
|
|
23
|
+
|
|
24
|
+
self._client = OpenAI(api_key=os.getenv(self.OPENAI_API_KEY_NAME))
|
|
25
|
+
return self._client
|
|
26
|
+
except ImportError:
|
|
27
|
+
raise ImportError(
|
|
28
|
+
"OpenAI API not found. "
|
|
29
|
+
"Please install it with: pip install openai"
|
|
30
|
+
)
|
|
31
|
+
except Exception as e:
|
|
32
|
+
raise RuntimeError(
|
|
33
|
+
f"Error initializing OpenAI client: {e}\n\n"
|
|
34
|
+
"Please ensure you have authenticated correctly via PYCAPS_OPENAI_API_KEY."
|
|
35
|
+
)
|
pycaps/ai/llm.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
from .llm import Llm
|
|
3
|
+
from .gpt import Gpt
|
|
4
|
+
|
|
5
|
+
class LlmProvider:
|
|
6
|
+
_llm: Optional[Llm] = None
|
|
7
|
+
|
|
8
|
+
@staticmethod
|
|
9
|
+
def get() -> Llm:
|
|
10
|
+
if LlmProvider._llm is None:
|
|
11
|
+
LlmProvider._llm = Gpt()
|
|
12
|
+
return LlmProvider._llm
|
|
13
|
+
|
|
14
|
+
@staticmethod
|
|
15
|
+
def set(llm: Llm):
|
|
16
|
+
LlmProvider._llm = llm
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# src/pycaps/animator/__init__.py
|
|
2
|
+
|
|
3
|
+
from .builtin import *
|
|
4
|
+
from .definitions import Transformer, OvershootConfig, Direction
|
|
5
|
+
from .primitive_animation import PrimitiveAnimation
|
|
6
|
+
from .preset_animation import PresetAnimation
|
|
7
|
+
from .animation import Animation
|
|
8
|
+
from .element_animator import ElementAnimator
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"Animation",
|
|
12
|
+
"PrimitiveAnimation",
|
|
13
|
+
"PresetAnimation",
|
|
14
|
+
"ElementAnimator",
|
|
15
|
+
"Transformer",
|
|
16
|
+
"OvershootConfig",
|
|
17
|
+
"Direction",
|
|
18
|
+
"SlideInPrimitive",
|
|
19
|
+
"ZoomInPrimitive",
|
|
20
|
+
"PopInPrimitive",
|
|
21
|
+
"FadeInPrimitive",
|
|
22
|
+
"FadeIn",
|
|
23
|
+
"FadeOut",
|
|
24
|
+
"PopIn",
|
|
25
|
+
"PopOut",
|
|
26
|
+
"PopInBounce",
|
|
27
|
+
"SlideIn",
|
|
28
|
+
"SlideOut",
|
|
29
|
+
"ZoomIn",
|
|
30
|
+
"ZoomOut",
|
|
31
|
+
]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from pycaps.common import WordClip, ElementType
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
|
|
4
|
+
class Animation(ABC):
|
|
5
|
+
def __init__(self, duration: float, delay: float = 0.0) -> None:
|
|
6
|
+
self._duration: float = duration
|
|
7
|
+
self._delay: float = delay
|
|
8
|
+
|
|
9
|
+
@abstractmethod
|
|
10
|
+
def run(self, clip: WordClip, offset: float, what: ElementType) -> None:
|
|
11
|
+
pass
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from .primitive import *
|
|
2
|
+
from .preset import *
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"FadeIn",
|
|
6
|
+
"FadeOut",
|
|
7
|
+
"SlideIn",
|
|
8
|
+
"SlideOut",
|
|
9
|
+
"ZoomIn",
|
|
10
|
+
"ZoomOut",
|
|
11
|
+
"PopIn",
|
|
12
|
+
"PopOut",
|
|
13
|
+
"PopInBounce",
|
|
14
|
+
"FadeInPrimitive",
|
|
15
|
+
"PopInPrimitive",
|
|
16
|
+
"ZoomInPrimitive",
|
|
17
|
+
"SlideInPrimitive",
|
|
18
|
+
]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from .fade_in import FadeIn
|
|
2
|
+
from .fade_out import FadeOut
|
|
3
|
+
from .pop_in import PopIn
|
|
4
|
+
from .pop_out import PopOut
|
|
5
|
+
from .pop_in_bounce import PopInBounce
|
|
6
|
+
from .slide_in import SlideIn
|
|
7
|
+
from .slide_out import SlideOut
|
|
8
|
+
from .zoom_in import ZoomIn
|
|
9
|
+
from .zoom_out import ZoomOut
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"FadeIn",
|
|
13
|
+
"FadeOut",
|
|
14
|
+
"PopIn",
|
|
15
|
+
"PopOut",
|
|
16
|
+
"PopInBounce",
|
|
17
|
+
"SlideIn",
|
|
18
|
+
"SlideOut",
|
|
19
|
+
"ZoomIn",
|
|
20
|
+
"ZoomOut",
|
|
21
|
+
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from ...animation import Animation
|
|
2
|
+
from ...preset_animation import PresetAnimation
|
|
3
|
+
from ..primitive import FadeInPrimitive
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
class FadeIn(PresetAnimation):
|
|
7
|
+
|
|
8
|
+
def __init__(self, duration: float = 0.2, delay: float = 0.0):
|
|
9
|
+
super().__init__(duration, delay)
|
|
10
|
+
|
|
11
|
+
def _build_animations(self) -> List[Animation]:
|
|
12
|
+
return [
|
|
13
|
+
FadeInPrimitive(
|
|
14
|
+
duration=self._duration,
|
|
15
|
+
delay=self._delay,
|
|
16
|
+
)
|
|
17
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from ...animation import Animation
|
|
2
|
+
from ...preset_animation import PresetAnimation
|
|
3
|
+
from typing import List
|
|
4
|
+
from ...definitions import Transformer
|
|
5
|
+
from ..primitive import FadeInPrimitive
|
|
6
|
+
|
|
7
|
+
class FadeOut(PresetAnimation):
|
|
8
|
+
|
|
9
|
+
def __init__(self, duration: float = 0.2, delay: float = 0.0):
|
|
10
|
+
super().__init__(duration, delay)
|
|
11
|
+
|
|
12
|
+
def _build_animations(self) -> List[Animation]:
|
|
13
|
+
return [
|
|
14
|
+
FadeInPrimitive(
|
|
15
|
+
duration=self._duration,
|
|
16
|
+
delay=self._delay,
|
|
17
|
+
transformer=Transformer.INVERT
|
|
18
|
+
)
|
|
19
|
+
]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from ...animation import Animation
|
|
2
|
+
from ...preset_animation import PresetAnimation
|
|
3
|
+
from ..primitive import PopInPrimitive, FadeInPrimitive
|
|
4
|
+
from typing import List
|
|
5
|
+
from ...definitions import OvershootConfig
|
|
6
|
+
|
|
7
|
+
class PopIn(PresetAnimation):
|
|
8
|
+
|
|
9
|
+
def __init__(self, duration: float = 0.3, delay: float = 0.0):
|
|
10
|
+
super().__init__(duration, delay)
|
|
11
|
+
|
|
12
|
+
def _build_animations(self) -> List[Animation]:
|
|
13
|
+
return [
|
|
14
|
+
PopInPrimitive(
|
|
15
|
+
duration=self._duration,
|
|
16
|
+
delay=self._delay,
|
|
17
|
+
overshoot=OvershootConfig(),
|
|
18
|
+
min_scale=0.5,
|
|
19
|
+
init_scale=0.5
|
|
20
|
+
),
|
|
21
|
+
FadeInPrimitive(duration=self._duration, delay=self._delay)
|
|
22
|
+
]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from ...animation import Animation
|
|
2
|
+
from ...preset_animation import PresetAnimation
|
|
3
|
+
from ..primitive import PopInPrimitive, FadeInPrimitive
|
|
4
|
+
from typing import List
|
|
5
|
+
from ...definitions import OvershootConfig
|
|
6
|
+
|
|
7
|
+
class PopInBounce(PresetAnimation):
|
|
8
|
+
|
|
9
|
+
def __init__(self, duration: float = 0.4, delay: float = 0.0):
|
|
10
|
+
super().__init__(duration, delay)
|
|
11
|
+
|
|
12
|
+
def _build_animations(self) -> List[Animation]:
|
|
13
|
+
return [
|
|
14
|
+
PopInPrimitive(
|
|
15
|
+
duration=self._duration,
|
|
16
|
+
delay=self._delay,
|
|
17
|
+
overshoot=OvershootConfig()
|
|
18
|
+
),
|
|
19
|
+
FadeInPrimitive(duration=self._duration*0.2, delay=self._delay)
|
|
20
|
+
]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from ...animation import Animation
|
|
2
|
+
from ...preset_animation import PresetAnimation
|
|
3
|
+
from ..primitive import PopInPrimitive, FadeInPrimitive
|
|
4
|
+
from typing import List
|
|
5
|
+
from ...definitions import Transformer
|
|
6
|
+
|
|
7
|
+
class PopOut(PresetAnimation):
|
|
8
|
+
|
|
9
|
+
def __init__(self, duration: float = 0.2, delay: float = 0.0):
|
|
10
|
+
super().__init__(duration, delay)
|
|
11
|
+
|
|
12
|
+
def _build_animations(self) -> List[Animation]:
|
|
13
|
+
return [
|
|
14
|
+
PopInPrimitive(
|
|
15
|
+
duration=self._duration,
|
|
16
|
+
delay=self._delay,
|
|
17
|
+
transformer=Transformer.INVERT,
|
|
18
|
+
init_scale=0.2,
|
|
19
|
+
min_scale=0.2
|
|
20
|
+
),
|
|
21
|
+
FadeInPrimitive(duration=self._duration, delay=self._delay, transformer=Transformer.INVERT)
|
|
22
|
+
]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from ...animation import Animation
|
|
2
|
+
from ...preset_animation import PresetAnimation
|
|
3
|
+
from ..primitive import SlideInPrimitive, FadeInPrimitive
|
|
4
|
+
from typing import List
|
|
5
|
+
from ...definitions import Direction, OvershootConfig
|
|
6
|
+
|
|
7
|
+
class SlideIn(PresetAnimation):
|
|
8
|
+
|
|
9
|
+
def __init__(self, direction: Direction = Direction.LEFT, duration: float = 0.3, delay: float = 0.0) -> None:
|
|
10
|
+
super().__init__(duration, delay)
|
|
11
|
+
self._direction: Direction = direction
|
|
12
|
+
|
|
13
|
+
def _build_animations(self) -> List[Animation]:
|
|
14
|
+
return [
|
|
15
|
+
SlideInPrimitive(
|
|
16
|
+
duration=self._duration,
|
|
17
|
+
delay=self._delay,
|
|
18
|
+
direction=self._direction,
|
|
19
|
+
overshoot=OvershootConfig()
|
|
20
|
+
),
|
|
21
|
+
FadeInPrimitive(
|
|
22
|
+
duration=self._duration,
|
|
23
|
+
delay=self._delay,
|
|
24
|
+
)
|
|
25
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from ...animation import Animation
|
|
2
|
+
from ...preset_animation import PresetAnimation
|
|
3
|
+
from ..primitive import SlideInPrimitive, FadeInPrimitive
|
|
4
|
+
from typing import List
|
|
5
|
+
from ...definitions import Transformer, Direction
|
|
6
|
+
|
|
7
|
+
class SlideOut(PresetAnimation):
|
|
8
|
+
|
|
9
|
+
def __init__(self, direction: Direction = Direction.RIGHT, duration: float = 0.3, delay: float = 0.0) -> None:
|
|
10
|
+
super().__init__(duration, delay)
|
|
11
|
+
self._direction: Direction = direction
|
|
12
|
+
|
|
13
|
+
def _build_animations(self) -> List[Animation]:
|
|
14
|
+
return [
|
|
15
|
+
SlideInPrimitive(
|
|
16
|
+
duration=self._duration,
|
|
17
|
+
delay=self._delay,
|
|
18
|
+
direction=self._direction,
|
|
19
|
+
transformer=Transformer.INVERT
|
|
20
|
+
),
|
|
21
|
+
FadeInPrimitive(
|
|
22
|
+
duration=self._duration,
|
|
23
|
+
delay=self._delay,
|
|
24
|
+
transformer=Transformer.INVERT
|
|
25
|
+
)
|
|
26
|
+
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from ...animation import Animation
|
|
2
|
+
from ...preset_animation import PresetAnimation
|
|
3
|
+
from ..primitive import ZoomInPrimitive, FadeInPrimitive
|
|
4
|
+
from typing import List
|
|
5
|
+
from ...definitions import OvershootConfig, Transformer
|
|
6
|
+
|
|
7
|
+
class ZoomIn(PresetAnimation):
|
|
8
|
+
|
|
9
|
+
def __init__(self, duration: float = 0.3, delay: float = 0.0):
|
|
10
|
+
super().__init__(duration, delay)
|
|
11
|
+
|
|
12
|
+
def _build_animations(self) -> List[Animation]:
|
|
13
|
+
return [
|
|
14
|
+
ZoomInPrimitive(
|
|
15
|
+
duration=self._duration,
|
|
16
|
+
delay=self._delay,
|
|
17
|
+
overshoot=OvershootConfig(),
|
|
18
|
+
transformer=Transformer.EASE_OUT
|
|
19
|
+
),
|
|
20
|
+
FadeInPrimitive(
|
|
21
|
+
duration=self._duration*0.5,
|
|
22
|
+
delay=self._delay,
|
|
23
|
+
)
|
|
24
|
+
]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from ...animation import Animation
|
|
2
|
+
from ...preset_animation import PresetAnimation
|
|
3
|
+
from ..primitive import ZoomInPrimitive, FadeInPrimitive
|
|
4
|
+
from typing import List
|
|
5
|
+
from ...definitions import Transformer
|
|
6
|
+
|
|
7
|
+
class ZoomOut(PresetAnimation):
|
|
8
|
+
|
|
9
|
+
def __init__(self, duration: float = 0.3, delay: float = 0.0):
|
|
10
|
+
super().__init__(duration, delay)
|
|
11
|
+
|
|
12
|
+
def _build_animations(self) -> List[Animation]:
|
|
13
|
+
return [
|
|
14
|
+
ZoomInPrimitive(
|
|
15
|
+
duration=self._duration,
|
|
16
|
+
delay=self._delay,
|
|
17
|
+
init_scale=0.2,
|
|
18
|
+
transformer=Transformer.INVERT
|
|
19
|
+
),
|
|
20
|
+
FadeInPrimitive(
|
|
21
|
+
duration=self._duration,
|
|
22
|
+
delay=self._delay,
|
|
23
|
+
transformer=Transformer.INVERT
|
|
24
|
+
)
|
|
25
|
+
]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .fade_in_primitive import FadeInPrimitive
|
|
2
|
+
from .pop_in_primitive import PopInPrimitive
|
|
3
|
+
from .zoom_in_primitive import ZoomInPrimitive
|
|
4
|
+
from .slide_in_primitive import SlideInPrimitive
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"FadeInPrimitive",
|
|
8
|
+
"PopInPrimitive",
|
|
9
|
+
"ZoomInPrimitive",
|
|
10
|
+
"SlideInPrimitive",
|
|
11
|
+
]
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from typing import Tuple, Callable, Optional
|
|
2
|
+
from ...definitions import Transformer, OvershootConfig
|
|
3
|
+
from ...primitive_animation import PrimitiveAnimation
|
|
4
|
+
from pycaps.common import WordClip
|
|
5
|
+
from pycaps.layout import LayoutUtils
|
|
6
|
+
|
|
7
|
+
class PopInPrimitive(PrimitiveAnimation):
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
duration: float,
|
|
11
|
+
delay: float = 0.0,
|
|
12
|
+
transformer: Callable[[float], float] = Transformer.LINEAR,
|
|
13
|
+
init_scale: float = 0.7,
|
|
14
|
+
min_scale: float = 0.3,
|
|
15
|
+
min_scale_at: float = 0.5,
|
|
16
|
+
overshoot: Optional[OvershootConfig] = None,
|
|
17
|
+
) -> None:
|
|
18
|
+
super().__init__(duration, delay, transformer)
|
|
19
|
+
self._init_scale: float = init_scale
|
|
20
|
+
self._min_scale: float = min_scale
|
|
21
|
+
self._min_scale_at: float = min_scale_at
|
|
22
|
+
self._overshoot: Optional[OvershootConfig] = overshoot
|
|
23
|
+
|
|
24
|
+
if self._overshoot is not None and self._min_scale_at >= self._overshoot.peak_at:
|
|
25
|
+
raise ValueError("min_scale_at must be less than overshoot.peak_at")
|
|
26
|
+
|
|
27
|
+
def _apply_animation(self, clip: WordClip, offset: float) -> None:
|
|
28
|
+
group_center = LayoutUtils.get_clip_container_center(clip, self._what)
|
|
29
|
+
word_original_width = clip.layout.size.width
|
|
30
|
+
word_original_height = clip.layout.size.height
|
|
31
|
+
word_final_center_x = clip.layout.position.x + word_original_width / 2
|
|
32
|
+
word_final_center_y = clip.layout.position.y + word_original_height / 2
|
|
33
|
+
|
|
34
|
+
def get_size_factor(t: float) -> float:
|
|
35
|
+
peak_at = self._overshoot.peak_at if self._overshoot is not None else 1.0
|
|
36
|
+
overshoot_scale = 1 + self._overshoot.amount if self._overshoot is not None else 1.0
|
|
37
|
+
|
|
38
|
+
if t < self._min_scale_at:
|
|
39
|
+
progress = t / self._min_scale_at
|
|
40
|
+
return self._init_scale + (self._min_scale - self._init_scale) * progress
|
|
41
|
+
elif self._min_scale_at < t < peak_at:
|
|
42
|
+
progress = (t - self._min_scale_at) / (peak_at - self._min_scale_at)
|
|
43
|
+
return self._min_scale + (overshoot_scale - self._min_scale) * progress
|
|
44
|
+
elif peak_at < t < 1.0:
|
|
45
|
+
progress = (t - peak_at) / (1.0 - peak_at) if peak_at != 1.0 else 1.0
|
|
46
|
+
return overshoot_scale + (1.0 - overshoot_scale) * progress
|
|
47
|
+
else:
|
|
48
|
+
return 1.0
|
|
49
|
+
|
|
50
|
+
def get_position(t: float) -> Tuple[float, float]:
|
|
51
|
+
scale = get_size_factor(t)
|
|
52
|
+
current_width = word_original_width * scale
|
|
53
|
+
current_height = word_original_height * scale
|
|
54
|
+
|
|
55
|
+
current_center_x = group_center[0] + (word_final_center_x - group_center[0]) * t
|
|
56
|
+
current_center_y = group_center[1] + (word_final_center_y - group_center[1]) * t
|
|
57
|
+
|
|
58
|
+
final_x = current_center_x - (current_width / 2)
|
|
59
|
+
final_y = current_center_y - (current_height / 2)
|
|
60
|
+
|
|
61
|
+
return (final_x, final_y)
|
|
62
|
+
|
|
63
|
+
self._apply_size(clip, offset, get_size_factor)
|
|
64
|
+
self._apply_position(clip, offset, get_position)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from ...primitive_animation import PrimitiveAnimation
|
|
2
|
+
from ...definitions import Direction, OvershootConfig, Transformer
|
|
3
|
+
from pycaps.common import WordClip
|
|
4
|
+
from typing import Tuple, Callable, Optional
|
|
5
|
+
|
|
6
|
+
class SlideInPrimitive(PrimitiveAnimation):
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
duration: float,
|
|
10
|
+
delay: float = 0.0,
|
|
11
|
+
transformer: Callable[[float], float] = Transformer.LINEAR,
|
|
12
|
+
direction: Direction = Direction.LEFT,
|
|
13
|
+
distance: float = 100,
|
|
14
|
+
overshoot: Optional[OvershootConfig] = None,
|
|
15
|
+
) -> None:
|
|
16
|
+
super().__init__(duration, delay, transformer)
|
|
17
|
+
self._direction: Direction = direction
|
|
18
|
+
self._distance: float = distance
|
|
19
|
+
self._overshoot: Optional[OvershootConfig] = overshoot
|
|
20
|
+
|
|
21
|
+
def _apply_animation(self, clip: WordClip, offset: float) -> None:
|
|
22
|
+
final_pos = clip.layout.position
|
|
23
|
+
|
|
24
|
+
def get_displacement(t: float) -> float:
|
|
25
|
+
if self._overshoot is None:
|
|
26
|
+
return self._distance * (t-1)
|
|
27
|
+
|
|
28
|
+
overshoot_distance = self._distance * self._overshoot.amount
|
|
29
|
+
if t < self._overshoot.peak_at:
|
|
30
|
+
progress = t / self._overshoot.peak_at
|
|
31
|
+
start_offset = -(self._distance)
|
|
32
|
+
target_offset = overshoot_distance
|
|
33
|
+
return start_offset + (target_offset - start_offset) * progress
|
|
34
|
+
else:
|
|
35
|
+
progress = (t - self._overshoot.peak_at) / (1.0 - self._overshoot.peak_at)
|
|
36
|
+
start_offset = overshoot_distance
|
|
37
|
+
target_offset = 0
|
|
38
|
+
return start_offset + (target_offset - start_offset) * progress
|
|
39
|
+
|
|
40
|
+
def get_position(t: float) -> Tuple[float, float]:
|
|
41
|
+
current_displacement = get_displacement(t)
|
|
42
|
+
|
|
43
|
+
if self._direction == Direction.LEFT:
|
|
44
|
+
return final_pos.x + current_displacement, final_pos.y
|
|
45
|
+
elif self._direction == Direction.RIGHT:
|
|
46
|
+
return final_pos.x - current_displacement, final_pos.y
|
|
47
|
+
elif self._direction == Direction.UP:
|
|
48
|
+
return final_pos.x, final_pos.y + current_displacement
|
|
49
|
+
elif self._direction == Direction.DOWN:
|
|
50
|
+
return final_pos.x, final_pos.y - current_displacement
|
|
51
|
+
return final_pos.x, final_pos.y
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
self._apply_position(clip, offset, get_position)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from pycaps.common import WordClip
|
|
2
|
+
from pycaps.layout import LayoutUtils
|
|
3
|
+
from typing import Tuple, Callable, Optional
|
|
4
|
+
from ...definitions import Transformer, OvershootConfig
|
|
5
|
+
from ...primitive_animation import PrimitiveAnimation
|
|
6
|
+
|
|
7
|
+
# TODO: it has an error while size is being animated, it's probably a precision error (because of using floats for the size in the scale)
|
|
8
|
+
# To notice it, you need to use a color background for a whole line, and run this animation over the line.
|
|
9
|
+
class ZoomInPrimitive(PrimitiveAnimation):
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
duration: float,
|
|
14
|
+
delay: float = 0.0,
|
|
15
|
+
transformer: Callable[[float], float] = Transformer.LINEAR,
|
|
16
|
+
init_scale: float = 0.5,
|
|
17
|
+
overshoot: Optional[OvershootConfig] = None,
|
|
18
|
+
) -> None:
|
|
19
|
+
super().__init__(duration, delay, transformer)
|
|
20
|
+
self._init_scale: float = init_scale
|
|
21
|
+
self._overshoot: Optional[OvershootConfig] = overshoot
|
|
22
|
+
|
|
23
|
+
def _apply_animation(self, clip: WordClip, offset: float) -> None:
|
|
24
|
+
group_center = LayoutUtils.get_clip_container_center(clip, self._what)
|
|
25
|
+
word_final_center = (
|
|
26
|
+
clip.layout.position.x + clip.layout.size.width / 2,
|
|
27
|
+
clip.layout.position.y + clip.layout.size.height / 2
|
|
28
|
+
)
|
|
29
|
+
relative_pos_vector = (
|
|
30
|
+
word_final_center[0] - group_center[0],
|
|
31
|
+
word_final_center[1] - group_center[1]
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def get_size_factor(t: float) -> float:
|
|
35
|
+
if self._overshoot is None:
|
|
36
|
+
return self._init_scale + (1.0 - self._init_scale) * t
|
|
37
|
+
|
|
38
|
+
if t < self._overshoot.peak_at:
|
|
39
|
+
progress = t / self._overshoot.peak_at
|
|
40
|
+
start_offset = self._init_scale
|
|
41
|
+
target_offset = 1 + self._overshoot.amount
|
|
42
|
+
return start_offset + (target_offset - start_offset) * progress
|
|
43
|
+
|
|
44
|
+
progress = (t - self._overshoot.peak_at) / (1.0 - self._overshoot.peak_at)
|
|
45
|
+
start_offset = 1 + self._overshoot.amount
|
|
46
|
+
target_offset = 1
|
|
47
|
+
return start_offset - (start_offset - target_offset) * progress
|
|
48
|
+
|
|
49
|
+
def get_position(t: float) -> Tuple[float, float]:
|
|
50
|
+
progress = get_size_factor(t)
|
|
51
|
+
|
|
52
|
+
current_width = clip.layout.size.width * progress
|
|
53
|
+
current_height = clip.layout.size.height * progress
|
|
54
|
+
|
|
55
|
+
current_center_x = group_center[0] + (relative_pos_vector[0] * progress)
|
|
56
|
+
current_center_y = group_center[1] + (relative_pos_vector[1] * progress)
|
|
57
|
+
|
|
58
|
+
final_x = current_center_x - (current_width / 2)
|
|
59
|
+
final_y = current_center_y - (current_height / 2)
|
|
60
|
+
|
|
61
|
+
return (final_x, final_y)
|
|
62
|
+
|
|
63
|
+
self._apply_position(clip, offset, get_position)
|
|
64
|
+
self._apply_size(clip, offset, get_size_factor)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from pydantic import BaseModel, ConfigDict
|
|
4
|
+
|
|
5
|
+
class Direction(str, Enum):
|
|
6
|
+
LEFT = "left"
|
|
7
|
+
RIGHT = "right"
|
|
8
|
+
UP = "up"
|
|
9
|
+
DOWN = "down"
|
|
10
|
+
|
|
11
|
+
class OvershootConfig(BaseModel):
|
|
12
|
+
model_config = ConfigDict(frozen=True)
|
|
13
|
+
|
|
14
|
+
amount: float = 0.1
|
|
15
|
+
peak_at: float = 0.7
|
|
16
|
+
|
|
17
|
+
class Transformer:
|
|
18
|
+
LINEAR = lambda t: t
|
|
19
|
+
EASE_IN = lambda t: t**2
|
|
20
|
+
EASE_OUT = lambda t: 1 - (1 - t)**2
|
|
21
|
+
EASE_IN_OUT = lambda t: t**2 * (3 - 2 * t)
|
|
22
|
+
INVERT = lambda t: 1 - t
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from typing import Optional, List
|
|
2
|
+
from pycaps.common import ElementType, EventType, Document, WordClip
|
|
3
|
+
from pycaps.tag import TagCondition
|
|
4
|
+
from pycaps.selector import WordClipSelector
|
|
5
|
+
from .animation import Animation
|
|
6
|
+
|
|
7
|
+
class ElementAnimator:
|
|
8
|
+
|
|
9
|
+
def __init__(self, animation: Animation, when: EventType, what: ElementType, tag_condition: Optional[TagCondition] = None) -> None:
|
|
10
|
+
self._animation: Animation = animation
|
|
11
|
+
self._when: EventType = when
|
|
12
|
+
self._what: ElementType = what
|
|
13
|
+
self._tag_condition: Optional[TagCondition] = tag_condition
|
|
14
|
+
|
|
15
|
+
def run(self, document: Document) -> None:
|
|
16
|
+
clips = self._filter_clips(document)
|
|
17
|
+
for clip in clips:
|
|
18
|
+
offset = self.__get_time_offset(clip)
|
|
19
|
+
self._animation.run(clip, offset, self._what)
|
|
20
|
+
|
|
21
|
+
def _filter_clips(self, document: Document) -> List[WordClip]:
|
|
22
|
+
selector = WordClipSelector().filter_by_time(self._when, self._what, self._animation._duration, self._animation._delay)
|
|
23
|
+
if self._tag_condition:
|
|
24
|
+
selector = selector.filter_by_tag(self._tag_condition)
|
|
25
|
+
return selector.select(document)
|
|
26
|
+
|
|
27
|
+
def __get_time_offset(self, clip: WordClip) -> float:
|
|
28
|
+
if self._when == EventType.ON_NARRATION_STARTS:
|
|
29
|
+
return self.__get_on_start_offset(clip)
|
|
30
|
+
elif self._when == EventType.ON_NARRATION_ENDS:
|
|
31
|
+
return self.__get_on_end_offset(clip)
|
|
32
|
+
|
|
33
|
+
def __get_on_start_offset(self, clip: WordClip) -> float:
|
|
34
|
+
start_time = 0
|
|
35
|
+
if self._what == ElementType.WORD:
|
|
36
|
+
start_time = clip.get_word().time.start
|
|
37
|
+
elif self._what == ElementType.LINE:
|
|
38
|
+
start_time = clip.get_line().time.start
|
|
39
|
+
elif self._what == ElementType.SEGMENT:
|
|
40
|
+
start_time = clip.get_segment().time.start
|
|
41
|
+
|
|
42
|
+
return clip.media_clip.start - start_time - self._animation._delay
|
|
43
|
+
|
|
44
|
+
def __get_on_end_offset(self, clip: WordClip) -> float:
|
|
45
|
+
end_time = 0
|
|
46
|
+
if self._what == ElementType.WORD:
|
|
47
|
+
end_time = clip.get_word().time.end
|
|
48
|
+
elif self._what == ElementType.LINE:
|
|
49
|
+
end_time = clip.get_line().time.end
|
|
50
|
+
elif self._what == ElementType.SEGMENT:
|
|
51
|
+
end_time = clip.get_segment().time.end
|
|
52
|
+
return -(end_time - self._animation._duration - self._animation._delay - clip.media_clip.start)
|