Skill: wondershare-boop/tomoviee-text-to-sound-effects (version 1.0.4)

--- name: tomoviee-text2sfx description: Generate sound effects from text prompts using Tomoviee Text-to-Sound-Effect API (`tm_text2sfx`) through Wondershare OpenAPI gateway (`https://openapi.wondershare.cc`). Use when users request text_to_sound_effect operations or related tasks. --- # Tomoviee AI Text-to-Sound-Effect ## Overview Generate sound effects from text prompts. - API capability: `tm_text2sfx` - Task creation endpoint: `https://openapi.wondershare.cc/v1/open/capacity/application/tm_text2sfx` - Result endpoint: `https://openapi.wondershare.cc/v1/open/pub/task` ## Provider and Endpoint Provenance Use this mapping to verify provider identity and runtime endpoints: - Vendor portals: `https://www.tomoviee.ai` and `https://www.tomoviee.cn` - API gateway host used by this skill: `https://openapi.wondershare.cc` - This skill sends runtime API calls only to `openapi.wondershare.cc` ## Credential Handling - `app_key` and `app_secret` are only used to construct `Authorization: Basic <base64(app_key:app_secret)>`. - Credentials are kept in process memory only and are not written to disk by this skill. - Do not commit credentials into `SKILL.md`, scripts, or repository files. ## Quick Start ### Install dependencies ```bash pip install requests ``` ### Authentication ```bash python scripts/generate_auth_token.py YOUR_APP_KEY YOUR_APP_SECRET ``` ### Python Client ```python from scripts.tomoviee_text2sfx_client import TomovieeText2SfxClient client = TomovieeText2SfxClient("app_key", "app_secret") ``` ## API Usage ### Basic Example ```python task_id = client.text_to_sound_effect( prompt="Heavy rain falling on roof with distant thunder", duration=30, qty=1, ) result = client.poll_until_complete(task_id) import json audio_url = json.loads(result["result"])["audio_path"][0] print(audio_url) ``` ### Parameters - `prompt` (required): Sound effect description - `duration` (required): Duration in seconds, range `5-180` - `qty` (optional): Number of generated results, range `1-4` - `callback` (optional): Callback URL - `params` (optional): Transparent callback parameter ## Async Workflow 1. Create task and get `task_id` 2. Poll with `poll_until_complete(task_id)` 3. Parse output URL from `result` Status codes: - `1` queued - `2` processing - `3` success - `4` failed - `5` cancelled - `6` timeout ## Resources - `scripts/tomoviee_text2sfx_client.py` - main API client - `scripts/tomoviee_text_to_sound_effect_client.py` - compatibility import shim - `scripts/generate_auth_token.py` - auth token helper - `references/audio_apis.md` - focused API reference for `tm_text2sfx` - `references/prompt_guide.md` - focused prompt writing guide for sound effects ## External Resources - Developer portal (global): `https://www.tomoviee.ai/developers.html` - API docs (global): `https://www.tomoviee.ai/doc/` - Developer portal (mainland): `https://www.tomoviee.cn/developers.html` - API docs (mainland): `https://www.tomoviee.cn/doc/` - API gateway host used by this package: `https://openapi.wondershare.cc`

path: _meta.json
content: ownerId: kn7bn8fv79mjtchs17087tdtsx82fc86
slug: tomoviee-text-to-sound-effects
version: 1.0.1
publishedAt: 1772934073311

path: scripts/generate_auth_token.py
content: #!/usr/bin/env python3
"""
Generate authentication token for Tomoviee API.

Usage:
    python generate_auth_token.py <app_key> <app_secret>

Output:
    Base64 encoded access_token in the format required by Authorization header
"""

import base64
import sys


def generate_access_token(app_key: str, app_secret: str) -> str:
    """
    Generate access token for Tomoviee API authentication.
    
    Args:
        app_key: Application key from Tomoviee console
        app_secret: Application secret from Tomoviee console
    
    Returns:
        Base64 encoded string in format: base64(app_key:app_secret)
    """
    credentials = f"{app_key}:{app_secret}"
    access_token = base64.b64encode(credentials.encode()).decode()
    return access_token


if __name__ == "__main__":
    if len(sys.argv) != 3:
        print("Usage: python generate_auth_token.py <app_key> <app_secret>")
        sys.exit(1)
    
    app_key = sys.argv[1]
    app_secret = sys.argv[2]
    
    [REDACTED_SECRET], app_secret)
    print(f"Access [REDACTED_SECRET]")
    print(f"\nUse in Authorization header as: Basic {token}")


path: scripts/tomoviee_text2sfx_client.py
content: #!/usr/bin/env python3
"""Tomoviee AI - Text-to-Sound-Effect API client."""

import base64
import json
import time
from typing import Any, Dict, Optional

import requests


class TomovieeText2SfxClient:
    """Text-to-Sound-Effect API client for Tomoviee AI."""

    BASE_URL = "https://openapi.wondershare.cc/v1/open/capacity/application"
    RESULT_ENDPOINT = "https://openapi.wondershare.cc/v1/open/pub/task"
    ENDPOINT = "tm_text2sfx"
    REQUEST_TIMEOUT = 60

    def __init__(self, app_key: str, app_secret: str):
        self.app_key = app_key
        self.access_token = self._generate_token(app_key, app_secret)

    def _generate_token(self, app_key: str, app_secret: str) -> str:
        credentials = f"{app_key}:{app_secret}"
        return base64.b64encode(credentials.encode()).decode()

    def _get_headers(self) -> Dict[str, str]:
        return {
            "Content-Type": "application/json",
            "X-App-Key": self.app_key,
            "Authorization": f"Basic {self.access_token}",
        }

    def _safe_json(self, response: requests.Response) -> Dict[str, Any]:
        try:
            return response.json()
        except ValueError as exc:
            raise Exception(f"Invalid JSON response: {response.text}") from exc

    def _make_request(self, payload: Dict[str, Any]) -> str:
        url = f"{self.BASE_URL}/{self.ENDPOINT}"
        response = requests.post(
            url,
            headers=self._get_headers(),
            json=payload,
            timeout=self.REQUEST_TIMEOUT,
        )
        response.raise_for_status()
        result = self._safe_json(response)

        if result.get("code") != 0:
            raise Exception(f"API Error: {result.get('msg', 'Unknown error')}")

        task_id = result.get("data", {}).get("task_id")
        if not task_id:
            raise Exception(f"Missing task_id in response: {result}")
        return task_id

    def text_to_sound_effect(
        self,
        prompt: str,
        duration: int,
        qty: Optional[int] = None,
        callback: Optional[str] = None,
        params: Optional[str] = None,
    ) -> str:
        """Create a text-to-sound-effect task and return task_id."""
        if duration < 5 or duration > 180:
            raise ValueError("duration must be in range 5..180")
        if qty is not None and (qty < 1 or qty > 4):
            raise ValueError("qty must be in range 1..4")

        payload: Dict[str, Any] = {
            "prompt": prompt,
            "duration": duration,
        }
        if qty is not None:
            payload["qty"] = qty
        if callback:
            payload["callback"] = callback
        if params:
            payload["params"] = params

        return self._make_request(payload)

    def get_result(self, task_id: str) -> Dict[str, Any]:
        response = requests.post(
            self.RESULT_ENDPOINT,
            headers=self._get_headers(),
            json={"task_id": task_id},
            timeout=self.REQUEST_TIMEOUT,
        )
        response.raise_for_status()
        result = self._safe_json(response)

        if result.get("code") != 0 and not result.get("data"):
            raise Exception(f"API Error: {result.get('msg', 'Unknown error')}")

        data = result.get("data")
        if not data:
            raise Exception(f"Missing task data in response: {result}")
        return data

    def poll_until_complete(
        self,
        task_id: str,
        poll_interval: int = 10,
        timeout: int = 600,
    ) -> Dict[str, Any]:
        elapsed = 0
        while elapsed < timeout:
            result = self.get_result(task_id)
            status = result.get("status")

            if status == 3:
                return result
            if status in [4, 5, 6]:
                raise Exception(f"Task failed: {result.get('reason', 'Unknown error')}")

            time.sleep(poll_interval)
            elapsed += poll_interval

        raise TimeoutError(f"Task did not complete within {timeout} seconds")


# Backward-compatible alias
TomovieeClient = TomovieeText2SfxClient


if __name__ == "__main__":
    import sys

    if len(sys.argv) < 5:
        print(
            "Usage: python scripts/tomoviee_text2sfx_client.py "
            "<app_key> <app_secret> <prompt> <duration> [qty]"
        )
        sys.exit(1)

    app_key = sys.argv[1]
    app_secret = sys.argv[2]
    prompt = sys.argv[3]
    duration = int(sys.argv[4])
    qty = None
    if len(sys.argv) > 5:
        qty = int(sys.argv[5])

    client = TomovieeText2SfxClient(app_key, app_secret)

    try:
        print("Creating text-to-sound-effect task...")
        task_id = client.text_to_sound_effect(
            prompt=prompt,
            duration=duration,
            qty=qty,
        )
        print(f"Task created: {task_id}")
        print("Polling for result...")

        result = client.poll_until_complete(task_id)

        print("\nTask completed")
        print(f"Progress: {result.get('progress', 'N/A')}%")

        result_data = json.loads(result["result"])
        print(json.dumps(result_data, indent=2, ensure_ascii=False))

    except Exception as exc:
        print(f"Error: {exc}")
        sys.exit(1)


path: scripts/tomoviee_text_to_sound_effect_client.py
content: #!/usr/bin/env python3
"""Compatibility shim for legacy import path.

Use `tomoviee_text2sfx_client.py` as the primary implementation.
"""

from .tomoviee_text2sfx_client import TomovieeClient, TomovieeText2SfxClient

__all__ = ["TomovieeText2SfxClient", "TomovieeClient"]


path: references/audio_apis.md
content: # Tomoviee Text-to-Sound-Effect API Reference

## Scope

This reference is intentionally scoped to one capability:

- `tm_text2sfx` (text_to_sound_effect)

## Endpoints

- Task creation: `https://openapi.wondershare.cc/v1/open/capacity/application/tm_text2sfx`
- Task result: `https://openapi.wondershare.cc/v1/open/pub/task`

## Authentication

Send both headers on each request:

- `X-App-Key: <app_key>`
- `Authorization: Basic <base64(app_key:app_secret)>`

The access token is generated by Base64-encoding `app_key:app_secret`.

## Request Parameters

- `prompt` (required): Description of the desired sound effect.
- `duration` (required): Duration in seconds, range `5-180`.
- `qty` (optional): Number of generated variants, range `1-4`.
- `callback` (optional): Callback URL.
- `params` (optional): Opaque string forwarded back in callback payload.

## Async Workflow

1. Create task and get `task_id`.
2. Poll result endpoint with `task_id`.
3. Check status and parse final `result` JSON.

Status codes:

- `1` queued
- `2` processing
- `3` success
- `4` failed
- `5` cancelled
- `6` timeout

## Result Parsing Example

```python
import json

result_payload = json.loads(result["result"])
audio_url = result_payload["audio_path"][0]
```

## Error Handling Notes

- Validate `duration` and `qty` before request.
- Treat non-zero `code` as API error.
- Handle network timeout and non-JSON responses.
- Handle terminal task statuses (`4`, `5`, `6`) as failure paths.


path: references/prompt_guide.md
content: # Text-to-Sound-Effect Prompt Guide

## Goal

Write concise, concrete prompts that describe one coherent sound event or ambience.

## Formula

`Sound Source + Action/Context + Acoustic Characteristics`

## Good Prompt Patterns

- Source: what makes the sound
- Context: where or under what condition
- Characteristics: loudness, texture, rhythm, distance, reverb

Examples:

- `Heavy rain hitting a metal roof, steady rhythm, distant thunder`
- `Glass bottle shattering on concrete, sharp and bright transient`
- `Busy office ambience with keyboard typing and quiet chatter`
- `Retro game UI confirmation beep, short and clean`
- `Wooden door creaking open in an empty hallway, long reverb`

## Do

- Keep one primary sound scene per request.
- Include intensity and environment cues.
- Specify if you want short event vs continuous ambience.

## Avoid

- Multiple unrelated scenes in one prompt.
- Contradictory adjectives (for example: `very soft and extremely loud`).
- Overly abstract instructions with no physical source.

## Iteration Strategy

1. Start with a short prompt.
2. If output is too generic, add source detail.
3. If output is too busy, remove secondary sounds.
4. Tune duration for intended usage (UI, transition, ambience loop).

## Duration Guidance

- `5-10s`: UI or single events
- `10-30s`: transitions and short scenes
- `30-180s`: ambience beds and environmental loops


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)