euler314 commited on
Commit
4b63dbf
·
verified ·
1 Parent(s): d5f997b

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +238 -222
app.py CHANGED
@@ -446,6 +446,19 @@ async def render_animation_endpoint(
446
  )
447
 
448
 
 
 
 
 
 
 
 
 
 
 
 
 
 
449
  @app.get("/media/{request_id}/{filename}", tags=["media"])
450
  async def download_file(request_id: str, filename: str):
451
  """
@@ -494,7 +507,10 @@ async def download_file(request_id: str, filename: str):
494
  filename=filename,
495
  headers={
496
  "Cache-Control": "public, max-age=3600",
497
- "Content-Disposition": f'inline; filename="{filename}"'
 
 
 
498
  }
499
  )
500
 
@@ -621,18 +637,18 @@ def get_render_log_tail(request_id: str, max_chars: int = 2000) -> Optional[str]
621
  if not log_path.exists():
622
  return None
623
 
624
- try:
625
- max_bytes = max_chars * 4
626
- with log_path.open("rb") as handle:
627
- handle.seek(0, os.SEEK_END)
628
- size = handle.tell()
629
- read_size = min(size, max_bytes)
630
- handle.seek(-read_size, os.SEEK_END)
631
- data = handle.read()
632
- text = data.decode("utf-8", errors="replace").replace("\r", "\n")
633
- if len(text) > max_chars:
634
- text = text[-max_chars:]
635
- return text.strip()
636
  except OSError:
637
  return None
638
 
@@ -694,21 +710,21 @@ async def run_mcp_render(
694
  return False, error_message, {}
695
 
696
  # Convert to requested formats
697
- formats_to_convert = []
698
- output_format = arguments.get("output_format", "mp4")
699
-
700
- if output_format == "all":
701
- formats_to_convert = ["gif", "webm"]
702
- elif output_format != "mp4":
703
- formats_to_convert = [output_format]
704
-
705
- conversion_results = {"mp4": output_mp4}
706
- if formats_to_convert:
707
- conversion_results.update(await convert_all_formats(
708
- input_mp4=output_mp4,
709
- output_formats=formats_to_convert,
710
- request_id=request_id
711
- ))
712
 
713
  # Generate download URLs
714
  base_url = os.getenv("SPACE_HOST", "https://euler314-manim-mcp.hf.space")
@@ -894,78 +910,78 @@ Use `check_render_status` to see progress and get the output when ready."""
894
  # Calculate expiration
895
  expires_at = datetime.now(timezone.utc) + timedelta(hours=1)
896
 
897
- # Format response for ChatGPT
898
- url_list = "\n".join([f" - {fmt.upper()}: {url}" for fmt, url in urls.items()])
899
- log_tail = get_render_log_tail(request_id)
900
- log_block = ""
901
- if log_tail:
902
- log_block = f"""
903
- Recent output:
904
- ```
905
- {log_tail}
906
- ```"""
907
-
908
- response_text = f"""Animation rendered successfully.
909
-
910
- Scene: {scene_name}
911
- Quality: {arguments.get('quality', '720p')}
912
- Request ID: `{request_id}`
913
-
914
- Download URLs:
915
- {url_list}
916
-
917
- Files expire at: {expires_at.strftime('%Y-%m-%d %H:%M:%S UTC')}
918
- {log_block}
919
-
920
- Your animation is ready. Use the video player below or download via the links.
921
- """
922
 
923
  # Get base URL for resources
924
  resource_base_url = os.getenv("SPACE_HOST", "https://euler314-manim-mcp.hf.space")
925
  if not resource_base_url.startswith("http"):
926
  resource_base_url = f"https://{resource_base_url}"
927
 
928
- content_items = [
929
- {
930
- "type": "text",
931
- "text": response_text
932
- }
933
- ]
934
-
935
- mp4_url = urls.get("mp4")
936
- if mp4_url:
937
- content_items.append({
938
- "type": "resource",
939
- "resource": {
940
- "uri": mp4_url,
941
- "mimeType": "video/mp4"
942
- }
943
- })
944
-
945
- # Return with both text content and UI component
946
- return {
947
- "content": content_items + [{
948
- "type": "resource",
949
- "resource": {
950
- "uri": f"{resource_base_url}/mcp/resources/video-player",
951
- "mimeType": "text/html",
952
- "text": response_text
953
- }
954
- }],
955
- "isError": False,
956
- "_meta": {
957
- "openai/outputTemplate": f"{resource_base_url}/mcp/resources/video-player"
958
- },
959
- "structuredContent": {
960
- "urls": urls,
961
- "scene_name": scene_name,
962
- "quality": arguments.get("quality", "720p"),
963
- "request_id": request_id,
964
- "expires_at": expires_at.isoformat(),
965
- "log_tail": log_tail,
966
- "status": "ready"
967
- }
968
- }
969
 
970
  except Exception as e:
971
  logger.exception(f"MCP render error: {e}")
@@ -1004,22 +1020,22 @@ async def get_render_status_for_mcp(arguments: dict) -> dict:
1004
  }
1005
 
1006
  # Check if expired
1007
- if await cleanup_service.check_if_expired(request_id):
1008
- return {
1009
- "content": [{
1010
- "type": "text",
1011
- "text": f"""Render expired.
1012
 
1013
  Request ID: `{request_id}`
1014
-
1015
- This render has expired (files are deleted after 1 hour). You'll need to re-render the animation."""
1016
- }],
1017
- "isError": False,
1018
- "structuredContent": {
1019
- "request_id": request_id,
1020
- "status": "expired"
1021
- }
1022
- }
1023
 
1024
  status_info = read_render_status(request_id)
1025
  if status_info:
@@ -1035,40 +1051,40 @@ Recent output:
1035
  {log_tail}
1036
  ```"""
1037
 
1038
- if status == "error":
1039
- return {
1040
- "content": [{
1041
- "type": "text",
1042
- "text": f"""Render failed.
1043
-
1044
- Request ID: `{request_id}`
1045
-
1046
- {message}{log_block}"""
1047
- }],
1048
- "isError": True,
1049
- "structuredContent": {
1050
- "request_id": request_id,
1051
- "status": "error",
1052
- "log_tail": log_tail
1053
- }
1054
- }
1055
- if status == "processing":
1056
- return {
1057
- "content": [{
1058
- "type": "text",
1059
- "text": f"""Render in progress.
1060
-
1061
- Request ID: `{request_id}`
1062
-
1063
- The render is still processing. Please wait and try again.{log_block}"""
1064
- }],
1065
- "isError": False,
1066
- "structuredContent": {
1067
- "request_id": request_id,
1068
- "status": "processing",
1069
- "log_tail": log_tail
1070
- }
1071
- }
1072
 
1073
  # Get render info
1074
  render_info = await cleanup_service.get_render_info(request_id)
@@ -1086,38 +1102,38 @@ Recent output:
1086
  ```
1087
  {log_tail}
1088
  ```"""
1089
- return {
1090
- "content": [{
1091
- "type": "text",
1092
- "text": f"""Render in progress.
1093
-
1094
- Request ID: `{request_id}`
1095
-
1096
- The render is still processing. Please wait and try again.{log_block}"""
1097
- }],
1098
- "isError": False,
1099
- "structuredContent": {
1100
- "request_id": request_id,
1101
- "status": "processing",
1102
- "log_tail": log_tail
1103
- }
1104
- }
1105
- else:
1106
- return {
1107
- "content": [{
1108
- "type": "text",
1109
- "text": f"""Render not found.
1110
 
1111
  Request ID: `{request_id}`
1112
-
1113
- No render found with this ID. It may have expired or never existed."""
1114
- }],
1115
- "isError": True,
1116
- "structuredContent": {
1117
- "request_id": request_id,
1118
- "status": "not_found"
1119
- }
1120
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1121
 
1122
  # Render exists - provide details
1123
  base_url = os.getenv("SPACE_HOST", "https://euler314-manim-mcp.hf.space")
@@ -1133,56 +1149,56 @@ No render found with this ID. It may have expired or never existed."""
1133
  time_remaining = expires_at - datetime.now(timezone.utc)
1134
  minutes_remaining = int(time_remaining.total_seconds() / 60)
1135
 
1136
- url_list = "\n".join([f" - {fmt.upper()}: {url}" for fmt, url in urls.items()])
1137
- log_tail = get_render_log_tail(request_id)
1138
- log_block = ""
1139
- if log_tail:
1140
- log_block = f"""
1141
- Recent output:
1142
- ```
1143
- {log_tail}
1144
- ```"""
1145
-
1146
- status_text = f"""Render ready.
1147
-
1148
- Request ID: `{request_id}`
1149
- Available Formats: {', '.join(render_info['formats'])}
1150
- File Size: {render_info.get('size_bytes', 0) / 1024 / 1024:.2f} MB
1151
- Expires In: {minutes_remaining} minutes
1152
-
1153
- Download URLs:
1154
- {url_list}
1155
-
1156
- Expiration: {expires_at.strftime('%Y-%m-%d %H:%M:%S UTC')}
1157
- {log_block}
1158
- """
1159
-
1160
- content_items = [{
1161
- "type": "text",
1162
- "text": status_text
1163
- }]
1164
-
1165
- mp4_url = urls.get("mp4")
1166
- if mp4_url:
1167
- content_items.append({
1168
- "type": "resource",
1169
- "resource": {
1170
- "uri": mp4_url,
1171
- "mimeType": "video/mp4"
1172
- }
1173
- })
1174
-
1175
- return {
1176
- "content": content_items,
1177
- "isError": False,
1178
- "structuredContent": {
1179
- "request_id": request_id,
1180
- "status": "ready",
1181
- "urls": urls,
1182
- "expires_at": expires_at.isoformat(),
1183
- "log_tail": log_tail
1184
- }
1185
- }
1186
 
1187
  except Exception as e:
1188
  logger.exception(f"Error checking render status: {e}")
 
446
  )
447
 
448
 
449
+ @app.options("/media/{request_id}/{filename}", tags=["media"])
450
+ async def download_file_options(request_id: str, filename: str):
451
+ """Handle CORS preflight requests for media files"""
452
+ return JSONResponse(
453
+ content={},
454
+ headers={
455
+ "Access-Control-Allow-Origin": "*",
456
+ "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS",
457
+ "Access-Control-Allow-Headers": "*",
458
+ }
459
+ )
460
+
461
+
462
  @app.get("/media/{request_id}/{filename}", tags=["media"])
463
  async def download_file(request_id: str, filename: str):
464
  """
 
507
  filename=filename,
508
  headers={
509
  "Cache-Control": "public, max-age=3600",
510
+ "Content-Disposition": f'inline; filename="{filename}"',
511
+ "Access-Control-Allow-Origin": "*",
512
+ "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS",
513
+ "Access-Control-Allow-Headers": "*"
514
  }
515
  )
516
 
 
637
  if not log_path.exists():
638
  return None
639
 
640
+ try:
641
+ max_bytes = max_chars * 4
642
+ with log_path.open("rb") as handle:
643
+ handle.seek(0, os.SEEK_END)
644
+ size = handle.tell()
645
+ read_size = min(size, max_bytes)
646
+ handle.seek(-read_size, os.SEEK_END)
647
+ data = handle.read()
648
+ text = data.decode("utf-8", errors="replace").replace("\r", "\n")
649
+ if len(text) > max_chars:
650
+ text = text[-max_chars:]
651
+ return text.strip()
652
  except OSError:
653
  return None
654
 
 
710
  return False, error_message, {}
711
 
712
  # Convert to requested formats
713
+ formats_to_convert = []
714
+ output_format = arguments.get("output_format", "mp4")
715
+
716
+ if output_format == "all":
717
+ formats_to_convert = ["gif", "webm"]
718
+ elif output_format != "mp4":
719
+ formats_to_convert = [output_format]
720
+
721
+ conversion_results = {"mp4": output_mp4}
722
+ if formats_to_convert:
723
+ conversion_results.update(await convert_all_formats(
724
+ input_mp4=output_mp4,
725
+ output_formats=formats_to_convert,
726
+ request_id=request_id
727
+ ))
728
 
729
  # Generate download URLs
730
  base_url = os.getenv("SPACE_HOST", "https://euler314-manim-mcp.hf.space")
 
910
  # Calculate expiration
911
  expires_at = datetime.now(timezone.utc) + timedelta(hours=1)
912
 
913
+ # Format response for ChatGPT
914
+ url_list = "\n".join([f" - {fmt.upper()}: {url}" for fmt, url in urls.items()])
915
+ log_tail = get_render_log_tail(request_id)
916
+ log_block = ""
917
+ if log_tail:
918
+ log_block = f"""
919
+ Recent output:
920
+ ```
921
+ {log_tail}
922
+ ```"""
923
+
924
+ response_text = f"""Animation rendered successfully.
925
+
926
+ Scene: {scene_name}
927
+ Quality: {arguments.get('quality', '720p')}
928
+ Request ID: `{request_id}`
929
+
930
+ Download URLs:
931
+ {url_list}
932
+
933
+ Files expire at: {expires_at.strftime('%Y-%m-%d %H:%M:%S UTC')}
934
+ {log_block}
935
+
936
+ Your animation is ready. Use the video player below or download via the links.
937
+ """
938
 
939
  # Get base URL for resources
940
  resource_base_url = os.getenv("SPACE_HOST", "https://euler314-manim-mcp.hf.space")
941
  if not resource_base_url.startswith("http"):
942
  resource_base_url = f"https://{resource_base_url}"
943
 
944
+ content_items = [
945
+ {
946
+ "type": "text",
947
+ "text": response_text
948
+ }
949
+ ]
950
+
951
+ mp4_url = urls.get("mp4")
952
+ if mp4_url:
953
+ content_items.append({
954
+ "type": "resource",
955
+ "resource": {
956
+ "uri": mp4_url,
957
+ "mimeType": "video/mp4"
958
+ }
959
+ })
960
+
961
+ # Return with both text content and UI component
962
+ return {
963
+ "content": content_items + [{
964
+ "type": "resource",
965
+ "resource": {
966
+ "uri": f"{resource_base_url}/mcp/resources/video-player",
967
+ "mimeType": "text/html",
968
+ "text": response_text
969
+ }
970
+ }],
971
+ "isError": False,
972
+ "_meta": {
973
+ "openai/outputTemplate": f"{resource_base_url}/mcp/resources/video-player"
974
+ },
975
+ "structuredContent": {
976
+ "urls": urls,
977
+ "scene_name": scene_name,
978
+ "quality": arguments.get("quality", "720p"),
979
+ "request_id": request_id,
980
+ "expires_at": expires_at.isoformat(),
981
+ "log_tail": log_tail,
982
+ "status": "ready"
983
+ }
984
+ }
985
 
986
  except Exception as e:
987
  logger.exception(f"MCP render error: {e}")
 
1020
  }
1021
 
1022
  # Check if expired
1023
+ if await cleanup_service.check_if_expired(request_id):
1024
+ return {
1025
+ "content": [{
1026
+ "type": "text",
1027
+ "text": f"""Render expired.
1028
 
1029
  Request ID: `{request_id}`
1030
+
1031
+ This render has expired (files are deleted after 1 hour). You'll need to re-render the animation."""
1032
+ }],
1033
+ "isError": False,
1034
+ "structuredContent": {
1035
+ "request_id": request_id,
1036
+ "status": "expired"
1037
+ }
1038
+ }
1039
 
1040
  status_info = read_render_status(request_id)
1041
  if status_info:
 
1051
  {log_tail}
1052
  ```"""
1053
 
1054
+ if status == "error":
1055
+ return {
1056
+ "content": [{
1057
+ "type": "text",
1058
+ "text": f"""Render failed.
1059
+
1060
+ Request ID: `{request_id}`
1061
+
1062
+ {message}{log_block}"""
1063
+ }],
1064
+ "isError": True,
1065
+ "structuredContent": {
1066
+ "request_id": request_id,
1067
+ "status": "error",
1068
+ "log_tail": log_tail
1069
+ }
1070
+ }
1071
+ if status == "processing":
1072
+ return {
1073
+ "content": [{
1074
+ "type": "text",
1075
+ "text": f"""Render in progress.
1076
+
1077
+ Request ID: `{request_id}`
1078
+
1079
+ The render is still processing. Please wait and try again.{log_block}"""
1080
+ }],
1081
+ "isError": False,
1082
+ "structuredContent": {
1083
+ "request_id": request_id,
1084
+ "status": "processing",
1085
+ "log_tail": log_tail
1086
+ }
1087
+ }
1088
 
1089
  # Get render info
1090
  render_info = await cleanup_service.get_render_info(request_id)
 
1102
  ```
1103
  {log_tail}
1104
  ```"""
1105
+ return {
1106
+ "content": [{
1107
+ "type": "text",
1108
+ "text": f"""Render in progress.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1109
 
1110
  Request ID: `{request_id}`
1111
+
1112
+ The render is still processing. Please wait and try again.{log_block}"""
1113
+ }],
1114
+ "isError": False,
1115
+ "structuredContent": {
1116
+ "request_id": request_id,
1117
+ "status": "processing",
1118
+ "log_tail": log_tail
1119
+ }
1120
+ }
1121
+ else:
1122
+ return {
1123
+ "content": [{
1124
+ "type": "text",
1125
+ "text": f"""Render not found.
1126
+
1127
+ Request ID: `{request_id}`
1128
+
1129
+ No render found with this ID. It may have expired or never existed."""
1130
+ }],
1131
+ "isError": True,
1132
+ "structuredContent": {
1133
+ "request_id": request_id,
1134
+ "status": "not_found"
1135
+ }
1136
+ }
1137
 
1138
  # Render exists - provide details
1139
  base_url = os.getenv("SPACE_HOST", "https://euler314-manim-mcp.hf.space")
 
1149
  time_remaining = expires_at - datetime.now(timezone.utc)
1150
  minutes_remaining = int(time_remaining.total_seconds() / 60)
1151
 
1152
+ url_list = "\n".join([f" - {fmt.upper()}: {url}" for fmt, url in urls.items()])
1153
+ log_tail = get_render_log_tail(request_id)
1154
+ log_block = ""
1155
+ if log_tail:
1156
+ log_block = f"""
1157
+ Recent output:
1158
+ ```
1159
+ {log_tail}
1160
+ ```"""
1161
+
1162
+ status_text = f"""Render ready.
1163
+
1164
+ Request ID: `{request_id}`
1165
+ Available Formats: {', '.join(render_info['formats'])}
1166
+ File Size: {render_info.get('size_bytes', 0) / 1024 / 1024:.2f} MB
1167
+ Expires In: {minutes_remaining} minutes
1168
+
1169
+ Download URLs:
1170
+ {url_list}
1171
+
1172
+ Expiration: {expires_at.strftime('%Y-%m-%d %H:%M:%S UTC')}
1173
+ {log_block}
1174
+ """
1175
+
1176
+ content_items = [{
1177
+ "type": "text",
1178
+ "text": status_text
1179
+ }]
1180
+
1181
+ mp4_url = urls.get("mp4")
1182
+ if mp4_url:
1183
+ content_items.append({
1184
+ "type": "resource",
1185
+ "resource": {
1186
+ "uri": mp4_url,
1187
+ "mimeType": "video/mp4"
1188
+ }
1189
+ })
1190
+
1191
+ return {
1192
+ "content": content_items,
1193
+ "isError": False,
1194
+ "structuredContent": {
1195
+ "request_id": request_id,
1196
+ "status": "ready",
1197
+ "urls": urls,
1198
+ "expires_at": expires_at.isoformat(),
1199
+ "log_tail": log_tail
1200
+ }
1201
+ }
1202
 
1203
  except Exception as e:
1204
  logger.exception(f"Error checking render status: {e}")