media-agent-mcp 2.6.14__py3-none-any.whl → 2.7.2__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.
@@ -1,8 +1,8 @@
1
1
  """Media Agent MCP Server - A Model Context Protocol server for media processing."""
2
2
 
3
3
  from . import ai_models, media_selectors, storage, video
4
- from .async_server import main
4
+ from .async_server import main as async_main
5
5
  from . import async_wrapper
6
6
 
7
7
  __version__ = "0.1.0"
8
- __all__ = ['ai_models', 'media_selectors', 'storage', 'video', 'main', 'async_wrapper']
8
+ __all__ = ['ai_models', 'media_selectors', 'storage', 'video', 'main', 'async_main', 'async_wrapper']
@@ -122,8 +122,9 @@ if __name__ == '__main__':
122
122
  time1 = time.time()
123
123
 
124
124
  print(generate_video_from_omni_human(
125
- image_url="https://ark-content-generation-v2-ap-southeast-1.tos-ap-southeast-1.volces.com/seedream-3-0-t2i/0217564979274996d76cd7acfd572da46abbcedd46e0f23a31db6.jpeg?X-Tos-Algorithm=TOS4-HMAC-SHA256&X-Tos-Credential=AKLTYWJkZTExNjA1ZDUyNDc3YzhjNTM5OGIyNjBhNDcyOTQ%2F20250829%2Fap-southeast-1%2Ftos%2Frequest&X-Tos-Date=20250829T200528Z&X-Tos-Expires=86400&X-Tos-Signature=abf8c7d516eecd4543691ffee5c3c5bcc7e38d5d047caf7f2fcfe3e41ab897d3&X-Tos-SignedHeaders=host",
126
- audio_url="https://carey.tos-ap-southeast-1.bytepluses.com/media_agent/2025-08-30/a72d04349e81454c8065674792256a1d.mp3"
125
+ image_url="https://carey.tos-ap-southeast-1.bytepluses.com/Art%20Portrait/Art%20Portrait/Art%20Portrait/Art%20Portrait%20(1).jpg",
126
+ audio_url="https://carey.tos-ap-southeast-1.bytepluses.com/media_agent/2025-09-02/66620bed2a5f4b2cbc641559ff93a2ed.mp3"
127
127
  ))
128
128
 
129
- print(time.time() - time1)
129
+ print(time.time() - time1)
130
+ # 16452282602934202024
@@ -26,12 +26,6 @@ from dotenv import load_dotenv
26
26
  import uvicorn
27
27
  import anyio
28
28
  from functools import wraps
29
- import uuid
30
- import weakref
31
- from starlette.applications import Starlette
32
- from starlette.middleware.base import BaseHTTPMiddleware
33
- from starlette.requests import Request
34
- from starlette.responses import Response
35
29
 
36
30
  def async_retry(max_retries=3, delay=2):
37
31
  def decorator(func):
@@ -50,20 +44,6 @@ def async_retry(max_retries=3, delay=2):
50
44
  await asyncio.sleep(delay)
51
45
  continue
52
46
  return result
53
- except anyio.ClosedResourceError as e:
54
- logger.warning(f"ClosedResourceError in {func.__name__} (attempt {attempt + 1}): {e}")
55
- # For ClosedResourceError, we should handle it gracefully
56
- if attempt < max_retries - 1:
57
- logger.info(f"Retrying {func.__name__} after ClosedResourceError...")
58
- await asyncio.sleep(delay)
59
- continue
60
- else:
61
- # On final attempt, return a structured error response
62
- return {
63
- "status": "error",
64
- "data": None,
65
- "message": f"Session expired during {func.__name__} execution. Please retry with a new session."
66
- }
67
47
  except Exception as e:
68
48
  last_exception = str(e)
69
49
  logger.error(f"Attempt {attempt + 1} of {max_retries} failed for {func.__name__} with exception: {e}. Retrying in {delay}s...")
@@ -77,46 +57,6 @@ def async_retry(max_retries=3, delay=2):
77
57
  return wrapper
78
58
  return decorator
79
59
 
80
- def session_aware_retry(max_retries=3, delay=2):
81
- """Enhanced retry decorator that handles session expiration specifically."""
82
- def decorator(func):
83
- @wraps(func)
84
- async def wrapper(*args, **kwargs):
85
- for attempt in range(max_retries):
86
- try:
87
- result = await func(*args, **kwargs)
88
- return result
89
- except anyio.ClosedResourceError as e:
90
- logger.warning(f"Session expired during {func.__name__} execution (attempt {attempt + 1}): {e}")
91
-
92
- if attempt < max_retries - 1:
93
- # Generate new session for retry
94
- new_session_id = session_manager.generate_session_id()
95
- logger.info(f"Generated new session {new_session_id} for retry of {func.__name__}")
96
- await asyncio.sleep(delay)
97
- continue
98
- else:
99
- return {
100
- "status": "error",
101
- "data": None,
102
- "message": f"Session expired during {func.__name__} execution. A new session has been generated. Please retry your request."
103
- }
104
- except Exception as e:
105
- logger.error(f"Unexpected error in {func.__name__} (attempt {attempt + 1}): {e}")
106
- if attempt < max_retries - 1:
107
- await asyncio.sleep(delay)
108
- continue
109
- else:
110
- return {
111
- "status": "error",
112
- "data": None,
113
- "message": f"Function {func.__name__} failed: {str(e)}"
114
- }
115
-
116
- return {"status": "error", "data": None, "message": f"Function {func.__name__} failed after {max_retries} retries"}
117
- return wrapper
118
- return decorator
119
-
120
60
  from mcp.server.fastmcp import FastMCP
121
61
 
122
62
  # Import async wrappers
@@ -141,133 +81,6 @@ from media_agent_mcp.async_wrapper import (
141
81
  logging.basicConfig(level=logging.INFO)
142
82
  logger = logging.getLogger(__name__)
143
83
 
144
- # Session management for handling expired sessions
145
- class SessionManager:
146
- def __init__(self):
147
- self._sessions = {} # Changed from WeakValueDictionary to regular dict
148
- self._session_routes = {}
149
- self._session_timestamps = {} # Track session creation times
150
-
151
- def generate_session_id(self) -> str:
152
- """Generate a new unique session ID."""
153
- return str(uuid.uuid4()).replace('-', '')
154
-
155
- def register_session(self, session_id: str, session_obj):
156
- """Register a session object."""
157
- import time
158
- self._sessions[session_id] = session_obj
159
- self._session_timestamps[session_id] = time.time()
160
- logger.info(f"Registered session: {session_id}")
161
-
162
- def get_session(self, session_id: str):
163
- """Get session object by ID."""
164
- return self._sessions.get(session_id)
165
-
166
- def remove_session(self, session_id: str, remove_route_mapping: bool = True):
167
- """Remove a session."""
168
- if session_id in self._sessions:
169
- del self._sessions[session_id]
170
- if remove_route_mapping and session_id in self._session_routes:
171
- del self._session_routes[session_id]
172
- if session_id in self._session_timestamps:
173
- del self._session_timestamps[session_id]
174
- logger.info(f"Removed session: {session_id} (route_mapping_removed: {remove_route_mapping})")
175
-
176
- def cleanup_expired_sessions(self, max_age_seconds: int = 3600):
177
- """Clean up sessions older than max_age_seconds."""
178
- import time
179
- current_time = time.time()
180
- expired_sessions = []
181
-
182
- for session_id, timestamp in self._session_timestamps.items():
183
- if current_time - timestamp > max_age_seconds:
184
- expired_sessions.append(session_id)
185
-
186
- for session_id in expired_sessions:
187
- self.remove_session(session_id)
188
- logger.info(f"Cleaned up expired session: {session_id}")
189
-
190
- return len(expired_sessions)
191
-
192
- def get_session_count(self) -> int:
193
- """Get the number of active sessions."""
194
- return len(self._sessions)
195
-
196
- def get_route_count(self) -> int:
197
- """Get the number of route mappings."""
198
- return len(self._session_routes)
199
-
200
- def add_route_mapping(self, old_session_id: str, new_session_id: str):
201
- """Add route mapping for session forwarding."""
202
- self._session_routes[old_session_id] = new_session_id
203
- logger.info(f"Added route mapping: {old_session_id} -> {new_session_id}")
204
-
205
- def get_route_mapping(self, session_id: str) -> Optional[str]:
206
- """Get route mapping for a session."""
207
- return self._session_routes.get(session_id)
208
-
209
- # Global session manager
210
- session_manager = SessionManager()
211
-
212
- def _extract_session_id_from_url(url: str) -> Optional[str]:
213
- """Extract session_id from URL."""
214
- import re
215
- from urllib.parse import urlparse, parse_qs
216
-
217
- # Parse the URL
218
- parsed = urlparse(url)
219
-
220
- # Try to get from query parameters
221
- query_params = parse_qs(parsed.query)
222
- if 'session_id' in query_params:
223
- return query_params['session_id'][0]
224
-
225
- # Try to extract from URL path using regex
226
- match = re.search(r'session_id=([a-f0-9]+)', url)
227
- if match:
228
- return match.group(1)
229
-
230
- return None
231
-
232
- # Global exception handler for ClosedResourceError
233
- async def handle_closed_resource_error(request, exc):
234
- """Global handler for ClosedResourceError exceptions."""
235
- logger.error(f"Global ClosedResourceError handler triggered: {exc}")
236
-
237
- # Extract session_id from request if possible
238
- session_id = None
239
- if hasattr(request, 'query_params'):
240
- session_id = request.query_params.get('session_id')
241
-
242
- if session_id:
243
- # Generate new session ID
244
- new_session_id = session_manager.generate_session_id()
245
- session_manager.add_route_mapping(session_id, new_session_id)
246
- session_manager.remove_session(session_id, remove_route_mapping=False)
247
-
248
- logger.info(f"Global handler: Generated new session {new_session_id} to replace {session_id}")
249
-
250
- from starlette.responses import JSONResponse
251
- return JSONResponse(
252
- status_code=410, # Gone - indicates the resource is no longer available
253
- content={
254
- "error": "session_expired",
255
- "message": "Session has expired. A new session has been generated.",
256
- "old_session_id": session_id,
257
- "new_session_id": new_session_id,
258
- "action": "retry_with_new_session"
259
- }
260
- )
261
-
262
- from starlette.responses import JSONResponse
263
- return JSONResponse(
264
- status_code=500,
265
- content={
266
- "error": "internal_server_error",
267
- "message": "An internal server error occurred. Please try again."
268
- }
269
- )
270
-
271
84
  # Initialize FastMCP server (will be configured in main function)
272
85
  load_dotenv()
273
86
  mcp = FastMCP("Media-Agent-MCP-Async")
@@ -623,76 +436,6 @@ async def tts_tool(text: str, speaker_id: str) -> dict:
623
436
  return result
624
437
 
625
438
 
626
- @mcp.tool()
627
- async def get_session_status() -> dict:
628
- """
629
- Get current session management status and statistics.
630
-
631
- Returns:
632
- Dictionary with session statistics and status information
633
- """
634
- try:
635
- active_sessions = session_manager.get_session_count()
636
- route_mappings = session_manager.get_route_count()
637
-
638
- # Clean up expired sessions (older than 1 hour)
639
- cleaned_sessions = session_manager.cleanup_expired_sessions(3600)
640
-
641
- return {
642
- "status": "success",
643
- "data": {
644
- "active_sessions": active_sessions,
645
- "route_mappings": route_mappings,
646
- "cleaned_sessions": cleaned_sessions,
647
- "session_manager_enabled": True,
648
- "features": [
649
- "automatic_session_recovery",
650
- "closed_resource_error_handling",
651
- "request_forwarding",
652
- "session_route_mapping",
653
- "automatic_session_cleanup"
654
- ]
655
- },
656
- "message": f"Session management is active. Cleaned {cleaned_sessions} expired sessions."
657
- }
658
- except Exception as e:
659
- logger.error(f"Error getting session status: {e}")
660
- return {
661
- "status": "error",
662
- "data": None,
663
- "message": f"Failed to get session status: {str(e)}"
664
- }
665
-
666
-
667
- @mcp.tool()
668
- async def generate_new_session() -> dict:
669
- """
670
- Manually generate a new session ID for testing or recovery purposes.
671
-
672
- Returns:
673
- Dictionary with new session ID
674
- """
675
- try:
676
- new_session_id = session_manager.generate_session_id()
677
- logger.info(f"Manually generated new session: {new_session_id}")
678
-
679
- return {
680
- "status": "success",
681
- "data": {
682
- "session_id": new_session_id,
683
- "timestamp": asyncio.get_event_loop().time()
684
- },
685
- "message": f"New session generated: {new_session_id}"
686
- }
687
- except Exception as e:
688
- logger.error(f"Error generating new session: {e}")
689
- return {
690
- "status": "error",
691
- "data": None,
692
- "message": f"Failed to generate new session: {str(e)}"
693
- }
694
-
695
-
696
439
  def main():
697
440
  """Main entry point for the Async MCP server."""
698
441
  import os
@@ -706,8 +449,6 @@ def main():
706
449
  help='Host for SSE transport (default: 127.0.0.1)')
707
450
  parser.add_argument('--port', type=int, default=8000,
708
451
  help='Port for SSE transport (default: 8000)')
709
- parser.add_argument('--version', action='store_true',
710
- help='Show version information')
711
452
  parser.add_argument('--run-be', action='store_true',
712
453
  help='Run the backend server')
713
454
  parser.add_argument('--be-host', type=str, default='0.0.0.0',
@@ -717,10 +458,6 @@ def main():
717
458
 
718
459
  args = parser.parse_args()
719
460
 
720
- if args.version:
721
- print("Async Media Agent MCP Server v0.1.0")
722
- return
723
-
724
461
  if args.run_be:
725
462
  logger.info(f"Starting backend server on {args.be_host}:{args.be_port}...")
726
463
  be_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'be'))
@@ -730,22 +467,20 @@ def main():
730
467
  try:
731
468
  subprocess.run(
732
469
  [
733
- sys.executable,
734
- "-c",
735
- (
736
- "import sys, app; "
737
- "flask_app = getattr(app, '_flask_app', getattr(app, 'app', None)); "
738
- "assert flask_app is not None, 'No Flask application instance found in app.py'; "
739
- "flask_app.run(host=sys.argv[1], port=int(sys.argv[2]), debug=False)"
740
- ),
741
- args.be_host,
742
- str(args.be_port),
470
+ "gunicorn",
471
+ "--workers",
472
+ "4",
473
+ "--bind",
474
+ f"{args.be_host}:{args.be_port}",
475
+ "--log-level",
476
+ "debug",
477
+ "app:app",
743
478
  ],
744
479
  cwd=be_path,
745
480
  check=True
746
481
  )
747
482
  except FileNotFoundError:
748
- logger.error("`uvicorn` command not found. Please ensure it is installed in your environment.")
483
+ logger.error("`gunicorn` command not found. Please ensure it is installed in your environment.")
749
484
  except subprocess.CalledProcessError as e:
750
485
  logger.error(f"Failed to start backend server: {e}")
751
486
  return
@@ -754,11 +489,6 @@ def main():
754
489
  logger.info(f"Transport: {args.transport}")
755
490
  if args.transport == 'sse':
756
491
  logger.info(f"SSE Server will run on {args.host}:{args.port}")
757
- logger.info("Session management features enabled:")
758
- logger.info(" - Automatic session expiration detection")
759
- logger.info(" - Auto-generation of new session IDs")
760
- logger.info(" - Request forwarding with route mapping")
761
- logger.info(" - ClosedResourceError handling")
762
492
 
763
493
  logger.info("Available async tools:")
764
494
  logger.info(" 1. video_last_frame_tool_async - Extract last frame from video and upload to TOS")
@@ -776,11 +506,8 @@ def main():
776
506
  logger.info(" 13. install_tools_plugin_async - Install development tools (ffmpeg and ffprobe)")
777
507
  logger.info(" 14. omni_human_tool_async - Generate a video using Omni Human AI model")
778
508
  logger.info(" 15. google_edit_tool_async - Edit images with Google Gemini (async)")
779
- logger.info(" 16. get_session_status - Get current session management status and statistics")
780
- logger.info(" 17. generate_new_session - Manually generate a new session ID")
781
509
  logger.info("")
782
510
  logger.info("All tools support concurrent execution using asyncio.gather() or run_multiple_tools_concurrently()")
783
- logger.info("Session management tools (16-17) help monitor and manage connection sessions")
784
511
 
785
512
  try:
786
513
  # Start the server with specified transport
@@ -788,18 +515,9 @@ def main():
788
515
  logger.info(f"Starting async SSE server on {args.host}:{args.port}")
789
516
  mcp.settings.host = args.host
790
517
  mcp.settings.port = args.port
791
-
792
- # Get the SSE app and add global exception handler
793
- sse_app = mcp.sse_app()
794
-
795
- # Add global exception handler for ClosedResourceError
796
- sse_app.add_exception_handler(anyio.ClosedResourceError, handle_closed_resource_error)
797
-
798
- logger.info("Added global ClosedResourceError handler for automatic session recovery")
799
-
800
518
  # Use uvicorn to run SSE app with extended keep-alive timeout (5 minutes)
801
519
  uvicorn.run(
802
- sse_app,
520
+ mcp.sse_app(),
803
521
  host=args.host,
804
522
  port=args.port,
805
523
  timeout_keep_alive=300
media_agent_mcp/be/app.py CHANGED
@@ -6,6 +6,7 @@ from flask import Flask
6
6
  # Register blueprints from modularized routes
7
7
  from media_agent_mcp.be.routes_media import media_bp
8
8
  from media_agent_mcp.be.routes_subtitles import subtitles_bp
9
+ from media_agent_mcp.be.routes_omni_human import omni_human_bp
9
10
 
10
11
 
11
12
  logger = logging.getLogger(__name__)
@@ -14,6 +15,7 @@ app = Flask(__name__)
14
15
  # Register blueprints (keep original paths unchanged)
15
16
  app.register_blueprint(media_bp)
16
17
  app.register_blueprint(subtitles_bp)
18
+ app.register_blueprint(omni_human_bp)
17
19
 
18
20
 
19
21
  if __name__ == "__main__":
@@ -5,6 +5,7 @@ import subprocess
5
5
  import tempfile
6
6
  from pathlib import Path
7
7
  from typing import List, Optional, Dict, Any
8
+ from concurrent.futures import ThreadPoolExecutor
8
9
 
9
10
  from flask import Blueprint, jsonify, request, send_file, after_this_request
10
11
 
@@ -160,13 +161,15 @@ def concat_videos():
160
161
  pass
161
162
  return response
162
163
 
163
- logger.info('Downloading videos')
164
+ logger.info('Downloading videos concurrently')
164
165
  video_paths = []
165
- for url in video_urls:
166
- dl = download_video_from_url(url)
166
+ with ThreadPoolExecutor(max_workers=min(len(video_urls), 5)) as executor:
167
+ download_results = list(executor.map(download_video_from_url, video_urls))
168
+
169
+ for dl in download_results:
167
170
  if dl.get("status") == "error":
168
171
  return jsonify(dl), 400
169
- path = Path(dl["data"]["file_path"]) # type: ignore[index]
172
+ path = Path(dl["data"]["file_path"])
170
173
  temp_files.append(path)
171
174
  video_paths.append(path)
172
175
 
@@ -0,0 +1,34 @@
1
+ from flask import Blueprint, request, jsonify
2
+ from media_agent_mcp.video.omni_human import generate_video_from_omni_human
3
+
4
+ omni_human_bp = Blueprint("omni_human", __name__)
5
+
6
+ @omni_human_bp.post("/generate-video-from-omni-human")
7
+ def generate_video():
8
+ """
9
+ Generates a video from an image and audio using the Omni Human API.
10
+ """
11
+ try:
12
+ data = request.get_json(silent=True) or {}
13
+ image_url = data.get("image_url")
14
+ audio_url = data.get("audio_url")
15
+
16
+ if not image_url or not audio_url:
17
+ return jsonify({
18
+ "status": "error",
19
+ "data": None,
20
+ "message": "Fields image_url and audio_url are required"
21
+ }), 400
22
+
23
+ video_url = generate_video_from_omni_human(image_url, audio_url)
24
+
25
+ return jsonify({
26
+ "status": "success",
27
+ "data": {
28
+ "video_url": video_url
29
+ },
30
+ "message": "Video generated successfully"
31
+ })
32
+
33
+ except Exception as e:
34
+ return jsonify({"status": "error", "data": None, "message": str(e)}), 500
@@ -0,0 +1,118 @@
1
+ import hashlib
2
+ import json
3
+ import os
4
+ import random
5
+ import time
6
+ from typing import Dict, Any
7
+ import requests
8
+
9
+
10
+ def _generate_signature(nonce: int, timestamp: int, security_key: str) -> str:
11
+ """
12
+ Generates a signature for the API request.
13
+ """
14
+ keys = [str(nonce), str(security_key), str(timestamp)]
15
+ keys.sort()
16
+ key_str = "".join(keys).encode("utf-8")
17
+ signature = hashlib.sha1(key_str).hexdigest()
18
+ return signature.lower()
19
+
20
+
21
+ def _submit_task(image_url: str, audio_url: str, api_key: str, security_key: str) -> str:
22
+ """
23
+ Submits a video generation task.
24
+ """
25
+ submit_task_url = "https://cv-api.byteintlapi.com/api/common/v2/submit_task"
26
+ timestamp = int(time.time())
27
+ nonce = random.randint(0, (1 << 31) - 1)
28
+ signature = _generate_signature(nonce, timestamp, security_key)
29
+
30
+ params = {
31
+ "api_key": api_key,
32
+ "timestamp": str(timestamp),
33
+ "nonce": str(nonce),
34
+ "sign": signature,
35
+ }
36
+ headers = {"Content-Type": "application/json"}
37
+ body = {
38
+ "req_key": "realman_avatar_picture_omni_cv",
39
+ "image_url": image_url,
40
+ "audio_url": audio_url,
41
+ }
42
+
43
+ response = requests.post(submit_task_url, params=params, headers=headers, json=body)
44
+ response.raise_for_status()
45
+ data = response.json()
46
+ if data["code"] != 10000:
47
+ raise Exception(f"Failed to submit task: {data['message']}")
48
+ return data["data"]["task_id"]
49
+
50
+
51
+ def _get_task_result(task_id: str, api_key: str, security_key: str) -> Dict[str, Any]:
52
+ """
53
+ Gets the result of a video generation task.
54
+ """
55
+ get_result_url = "https://cv-api.byteintlapi.com/api/common/v2/get_result"
56
+ timestamp = int(time.time())
57
+ nonce = random.randint(0, (1 << 31) - 1)
58
+ signature = _generate_signature(nonce, timestamp, security_key)
59
+
60
+ params = {
61
+ "api_key": api_key,
62
+ "timestamp": str(timestamp),
63
+ "nonce": str(nonce),
64
+ "sign": signature,
65
+ }
66
+ headers = {"Content-Type": "application/json"}
67
+ body = {
68
+ "req_key": "realman_avatar_picture_omni_cv",
69
+ "task_id": task_id,
70
+ }
71
+
72
+ response = requests.post(get_result_url, params=params, headers=headers, json=body)
73
+ print(response.text)
74
+ response.raise_for_status()
75
+ return response.json()
76
+
77
+
78
+ def generate_video_from_omni_human(image_url: str, audio_url: str) -> str:
79
+ """
80
+ Generates a video from an image and audio using the Omni Human API.
81
+
82
+ Args:
83
+ image_url: The URL of the portrait image.
84
+ audio_url: The URL of the audio.
85
+
86
+ Returns:
87
+ The URL of the generated video.
88
+ """
89
+ api_key = os.environ.get("OMNI_HUMAN_AK")
90
+ security_key = os.environ.get("OMNI_HUMAN_SK")
91
+
92
+ if not api_key or not security_key:
93
+ raise ValueError("OMNI_HUMAN_AK and OMNI_HUMAN_SK environment variables must be set")
94
+
95
+ task_id = _submit_task(image_url, audio_url, api_key, security_key)
96
+ print('Submitted task, task_id:', task_id)
97
+ while True:
98
+ result = _get_task_result(task_id, api_key, security_key)
99
+ if result["code"] != 10000:
100
+ raise Exception(f"Failed to get task result: {result['message']}")
101
+
102
+ status = result.get("data", {}).get("status")
103
+ if status == "done":
104
+ # Parse resp_data JSON string to get video_url
105
+ resp_data_str = result["data"].get("resp_data", "{}")
106
+ try:
107
+ resp_data = json.loads(resp_data_str)
108
+ video_url = resp_data.get("video_url")
109
+ if video_url:
110
+ return video_url
111
+ else:
112
+ raise Exception(f"No video_url found in response: {resp_data}")
113
+ except json.JSONDecodeError as e:
114
+ raise Exception(f"Failed to parse resp_data JSON: {e}")
115
+ elif status in ["failed", "error"]:
116
+ raise Exception(f"Video generation failed: {result}")
117
+
118
+ time.sleep(5)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: media-agent-mcp
3
- Version: 2.6.14
3
+ Version: 2.7.2
4
4
  Summary: A Model Context Protocol server for media processing with AI tools
5
5
  Author-email: Media Agent Team <team@mediaagent.com>
6
6
  Keywords: mcp,ai,media,video,image,processing
@@ -26,6 +26,7 @@ Requires-Dist: loguru>=0.7.3
26
26
  Requires-Dist: imageio-ffmpeg>=0.4.0
27
27
  Requires-Dist: Flask>=3.0.0
28
28
  Requires-Dist: pydub>=0.25.1
29
+ Requires-Dist: gunicorn>=22.0.0
29
30
  Requires-Dist: audioop-lts; python_version >= "3.13"
30
31
  Requires-Dist: google-genai>=1.33.0
31
32
  Requires-Dist: mcp==1.11.0
@@ -1,8 +1,8 @@
1
- media_agent_mcp/__init__.py,sha256=4GfH0flV1CGHefl-eNXt-Gidk7aDXA6Jlh2uVDOXGSo,312
2
- media_agent_mcp/async_server.py,sha256=3y1Xse4lavfzuNKUD9AU-REzhlAc3Po42GNn2el6Pn8,31091
1
+ media_agent_mcp/__init__.py,sha256=s1rx7OkUul6PrxbxziYAV6c0AuamPlnelltQieHqX_I,340
2
+ media_agent_mcp/async_server.py,sha256=gsc_VbZd8PwzuPc9UmtTgAYPyBzh3PNoTUNM3yTsgp4,19418
3
3
  media_agent_mcp/async_wrapper.py,sha256=hiiBhhz9WeVDfSBWVh6ovhf5jeP5ZbsieBbz9P-KPn0,15351
4
4
  media_agent_mcp/ai_models/__init__.py,sha256=2kHzTYwjQw89U4QGDq0e2WqJScqDkDNlDaWHGak5JeY,553
5
- media_agent_mcp/ai_models/omni_human.py,sha256=s_Ja4gEmHG_bgii1x4SoeV73lz0Zg_3iBRvu36goxVA,4643
5
+ media_agent_mcp/ai_models/omni_human.py,sha256=WAWO6pI4go9FLpVwxMvizz1Ays66CMUi8XkGZmIu08w,4337
6
6
  media_agent_mcp/ai_models/openaiedit.py,sha256=uu4d2BgXSrjWRdNPs_SryI9muxO93pItVtEze9nDhjc,9776
7
7
  media_agent_mcp/ai_models/seed16.py,sha256=cX0ZONj2Jpu_dzSIq8oXSJfnsfGWVcaEmWyRxg6jMfQ,5110
8
8
  media_agent_mcp/ai_models/seedance.py,sha256=ni7LtXn4jTn5wX2NtcWDMj5Eea8LoP1QLYgwSx_GvBs,9014
@@ -14,18 +14,20 @@ media_agent_mcp/audio/speed_controller.py,sha256=IS9glznrTtbNR1ZHmaJe8Js545ttbWO
14
14
  media_agent_mcp/audio/tts.py,sha256=5lWZLXm56OUzUQYXAOu0sLUCp9FNDBrlrtvzQScXgHU,22524
15
15
  media_agent_mcp/be/README.md,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  media_agent_mcp/be/__init__.py,sha256=pyMpzXQGyOOjKlxKfYZur8EmoJcNIDQSM6Zgp63HPVE,97
17
- media_agent_mcp/be/app.py,sha256=_Tq7LJ9BOWcELvebYnHKCM42jY6e3fCXDtm-kDfTDvw,945
17
+ media_agent_mcp/be/app.py,sha256=XdTI6fGELc083H8xEYGAcX1PJ6ISgWFu9R9C8i74_lo,1046
18
18
  media_agent_mcp/be/pyproject.toml,sha256=2c-vG61OpjH9Z2lz2aRoiDR047A-bOv3tFYKdxKXX68,199
19
- media_agent_mcp/be/routes_media.py,sha256=SsLNi0L3b8bbQpJV3sEiAwJw3S5PmjeCYnpQtVCjSGE,14454
19
+ media_agent_mcp/be/routes_media.py,sha256=yBDQkOx5t7v9gkSoxGnaE4Okoia3SueX8m2N5ljKpeE,14623
20
+ media_agent_mcp/be/routes_omni_human.py,sha256=SSmiGLvSJAQniwg-oODkoQnANL5iCuAKnwDTVQsPSlA,1097
20
21
  media_agent_mcp/be/routes_subtitles.py,sha256=jZYsPqyPqvIyuFyCnh7-EUpqjz-syqeqBUlhwwX0eLA,6833
21
22
  media_agent_mcp/be/utils.py,sha256=YQB_Fzwz0jhfJCOEHtahrv33VQuZZ3-_v5B8qGt3gkY,8027
22
23
  media_agent_mcp/be/uv.lock,sha256=U49x5h0EU0Sd2dn3EVvBbppM-XiujHk3Suw_z2U94SY,23502
23
- media_agent_mcp/be/__pycache__/__init__.cpython-312.pyc,sha256=ypxitYvyIAWOULz0FiRF3Wr_tlx4wKjUyTjnv-fKgsk,232
24
- media_agent_mcp/be/__pycache__/app.cpython-312.pyc,sha256=FCJLa895c4fIVJaQYDn4ipv3Spto5__lW-374cnwRNk,26673
25
- media_agent_mcp/be/__pycache__/routes_media.cpython-312.pyc,sha256=CQfwVSouBToVKnIoS1ZitthS_zjWhT97h0KOFIlO7gs,11094
24
+ media_agent_mcp/be/__pycache__/__init__.cpython-312.pyc,sha256=5vVeA7KwnQ_acE3Mdjd36WbzeqRjhO6umus1RyAoxO4,225
25
+ media_agent_mcp/be/__pycache__/app.cpython-312.pyc,sha256=QK7vq-fgrSYTb0laVfU3Yx_lug6CVVOR2KhgXtgpjFU,1401
26
+ media_agent_mcp/be/__pycache__/routes_media.cpython-312.pyc,sha256=vVIANk-HcRxYGftPDh7XeUywHrk3cGXdGe0ckyOhytw,16374
26
27
  media_agent_mcp/be/__pycache__/routes_omni.cpython-312.pyc,sha256=ZwJctEAhdE74byCLlJ_F23j0pfVcIeLbvtKeygARdr0,12443
27
- media_agent_mcp/be/__pycache__/routes_subtitles.cpython-312.pyc,sha256=5Fy7RKhr535LpAj4UVT8bBAipSwgmTm1Q2b_1_evzPU,7916
28
- media_agent_mcp/be/__pycache__/utils.cpython-312.pyc,sha256=jsHMVf1DBvQa2gHPF7_OCIFnL2tAyUXBX5H_XTKXSyA,10161
28
+ media_agent_mcp/be/__pycache__/routes_omni_human.cpython-312.pyc,sha256=VgJeY1aneYy4P0GEiyhpSUgTPLQf4664YeRXNpZ9dns,1485
29
+ media_agent_mcp/be/__pycache__/routes_subtitles.cpython-312.pyc,sha256=W931dzRYi1Dk-YhWdLXfOUJE2Zjj741Fehxd_ZuNtfs,7883
30
+ media_agent_mcp/be/__pycache__/utils.cpython-312.pyc,sha256=EKoj5x6Bt8Nv80tjL06ElO_9ZkvUBKvECzEEIKZ0oI0,11450
29
31
  media_agent_mcp/be/fonts/en/EduNSWACTCursive-VariableFont_wght.ttf,sha256=1SSZL9RXj58M7uJ_F68fRR1VQqxF6ZWK7mtoRCU-0hU,663016
30
32
  media_agent_mcp/be/fonts/en/MozillaText-VariableFont_wght.ttf,sha256=_hP_0MLtUZ6o1vG9yKaCNO0HpxXDET7DzzwicmUCSSo,84740
31
33
  media_agent_mcp/be/fonts/en/Roboto_Condensed-Regular.ttf,sha256=2Ob3xaE1bUHdgrDAAhn-vSHHkcxc0ez4oDI8vq_mSWw,145908
@@ -40,11 +42,12 @@ media_agent_mcp/media_selectors/video_selector.py,sha256=yuPbZMRm7fM0lyhoHaVN0-g
40
42
  media_agent_mcp/storage/__init__.py,sha256=eio7ZiSeLjCxICSZwZisiR7wKJfXlT2PV7aDEQKBUQQ,215
41
43
  media_agent_mcp/storage/tos_client.py,sha256=t2I-q63tMBahm4Ze1VRiN2Kn-VJGAjgqI0HAzJEJr7Y,3138
42
44
  media_agent_mcp/video/__init__.py,sha256=tfz22XEeFSeuKa3AggYCE0vCDt4IwXRCKW6avofyUsY,325
45
+ media_agent_mcp/video/omni_human.py,sha256=vXGQM9pjbKdyhUkXVnsyLIfTobXokOa-PEZOz17yOpo,3908
43
46
  media_agent_mcp/video/processor.py,sha256=twfqmN5DbVryjDawZUcqTUcnglcBJYpUbAnApqHgD0c,12787
44
47
  media_agent_mcp/video/stack.py,sha256=pyoJiJ9NhU1tjy2l3kARI9sWFoC00Fj97psxYOBi2NU,1736
45
48
  media_agent_mcp/video/subtitle.py,sha256=TlrWVhWJqYTUJpnVz7eccwMAn8ixfrRzRxS6ETMY-DM,16323
46
- media_agent_mcp-2.6.14.dist-info/METADATA,sha256=OrIMFu2iuILwbJ2sjRQp15v4Qm0_MnutbD7ocHDf5CU,11306
47
- media_agent_mcp-2.6.14.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
48
- media_agent_mcp-2.6.14.dist-info/entry_points.txt,sha256=qhOUwR-ORVf9GO7emhhl7Lgd6MISgqbZr8bEuSH_VdA,70
49
- media_agent_mcp-2.6.14.dist-info/top_level.txt,sha256=WEa0YfchpTxZgiKn8gdxYgs-dir5HepJaTOrxAGx9nY,16
50
- media_agent_mcp-2.6.14.dist-info/RECORD,,
49
+ media_agent_mcp-2.7.2.dist-info/METADATA,sha256=ZaP4F-KrJm6eOAnNxnUcO2tzx3dnEibwavZNr0J-nqs,11337
50
+ media_agent_mcp-2.7.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
51
+ media_agent_mcp-2.7.2.dist-info/entry_points.txt,sha256=qhOUwR-ORVf9GO7emhhl7Lgd6MISgqbZr8bEuSH_VdA,70
52
+ media_agent_mcp-2.7.2.dist-info/top_level.txt,sha256=WEa0YfchpTxZgiKn8gdxYgs-dir5HepJaTOrxAGx9nY,16
53
+ media_agent_mcp-2.7.2.dist-info/RECORD,,