async def run_shaka_packager(ctx: Context, file_path: str, command_args: str) -> str:
"""
Run a custom Shaka Packager command.
Args:
file_path: Path to the uploaded video file.
command_args: Additional arguments to pass to the shaka-packager command.
"""
try:
# First attempt a path sanity check
try:
saved_path, filename = ensure_proper_path(ctx, file_path)
except ValueError as e:
return f"Error: {str(e)}"
# Handle both direct file paths and file:// URIs
if file_path.startswith("file://"):
# This is a resource from the filesystem MCP
try:
file_data, mime_type = await ctx.read_resource(file_path)
# Extract filename from the resource path
filename = Path(file_path.split("/")[-1]).name
# Save the file
saved_path = save_uploaded_file(file_data, filename)
ctx.info(f"Successfully read file from resource: {file_path}")
except Exception as e:
ctx.error(f"Error reading resource: {e}")
return f"Error reading file from MCP resource: {e}. Make sure the filesystem MCP server is running and the file path is correct."
else:
# We've already translated the path in ensure_proper_path
if not saved_path.exists() or not saved_path.is_file():
return f"Error: File not found at path: {file_path}. Please provide a valid file path or a file:// URI from the filesystem MCP."
ctx.info(f"Using local file: {saved_path}")
# Prepare the command
# Split command_args into a list, respecting quotes
args = shlex.split(command_args)
# First translate any Docker paths in the command args
translated_args = translate_command_args(args)
ctx.info(f"Translated command args: {' '.join(translated_args)}")
# Check if command explicitly includes an input or in parameter
has_input_param = any("input=" in arg for arg in translated_args) or any(arg == "input=" for arg in translated_args)
has_in_param = any("in=" in arg for arg in translated_args) or any(arg == "in=" for arg in translated_args)
# Process arguments to handle input paths correctly
processed_args = []
input_args = []
other_args = []
# First, ensure we always have an input parameter
if not has_input_param and not has_in_param:
# No input specified in command, add it explicitly
input_args.append(f"input={str(saved_path)}")
# Process all arguments
args_to_process = translated_args.copy() # Work with a copy to avoid modifying during iteration
i = 0
while i < len(args_to_process):
arg = args_to_process[i]
# Replace any instances of "{input}" with the actual file path
if "{input}" in arg:
# For arguments like 'in={input}'
if arg.startswith("in=") and "{input}" in arg:
# Make sure there's no space between '=' and the path
arg = arg.replace("{input}", str(saved_path))
input_args.append(arg)
# For other arguments containing {input}
else:
arg = arg.replace("{input}", str(saved_path))
if arg.startswith("input="):
input_args.append(arg)
else:
other_args.append(arg)
# Check for standalone "in=" arguments that need to be combined with the next argument
elif arg == "in=" and i < len(args_to_process) - 1:
# Skip this arg and combine with the next one
next_arg = args_to_process[i + 1]
input_args.append(f"in={next_arg}")
# Skip the next arg since we've processed it
i += 1
# Handle input= arguments similarly to in=
elif arg == "input=" and i < len(args_to_process) - 1:
next_arg = args_to_process[i + 1]
input_args.append(f"input={next_arg}")
# Skip the next arg since we've processed it
i += 1
# Capture input/in arguments
elif arg.startswith("input=") or arg.startswith("in="):
input_args.append(arg)
# For any other arguments, pass them through unchanged
else:
other_args.append(arg)
i += 1
# Create the full command - ensuring input args come first
cmd = [PACKAGER_PATH] + input_args + other_args
# Log the original command for debugging
ctx.info(f"Final command: {' '.join(cmd)}")
result = await run_command(cmd)
# Capture output and error regardless of success or failure
stdout_output = result["stdout"]
stderr_output = result["stderr"]
if result["exit_code"] != 0:
ctx.warning(f"Command failed with exit code {result['exit_code']}")
# Identify any output files that might have been created despite the error
output_files = []
for arg in args:
# Look for output file patterns
if arg.startswith("out="):
output_files.append(arg.split("=", 1)[1])
elif arg.startswith("--mpd_output"):
if "=" in arg:
output_files.append(arg.split("=", 1)[1])
elif args.index(arg) < len(args) - 1:
output_files.append(args[args.index(arg) + 1])
elif arg.startswith("--hls_master_playlist_output"):
if "=" in arg:
output_files.append(arg.split("=", 1)[1])
elif args.index(arg) < len(args) - 1:
output_files.append(args[args.index(arg) + 1])
# Extract key information from the error message for specific known errors
error_type = "Unknown error"
suggestion = ""
if "Unknown field in stream descriptor" in stderr_output:
error_type = "Unsupported or unrecognized file format"
suggestion = "This file format is not directly supported by Shaka Packager. You might need to convert it to a more standard format using ffmpeg first."
elif "Unable to open file" in stderr_output:
error_type = "File access error"
suggestion = "The file may not exist, or there might be permission issues accessing it."
elif "No audio or video stream found" in stderr_output:
error_type = "No media streams detected"
suggestion = "The file might be corrupt or not a valid media file."
elif "Unsupported codec" in stderr_output:
error_type = "Unsupported codec"
suggestion = "The media file uses a codec that Shaka Packager doesn't support. Consider transcoding to a different codec."
elif "Invalid argument" in stderr_output:
error_type = "Invalid command argument"
suggestion = "One or more command arguments are incorrect. Check the syntax and compatibility of the options."
# Create a summary that includes the error but is still informative
summary = f"""
# Shaka Packager Results (VALID RESPONSE - DO NOT RETRY)
## Error Type
{error_type}
## Command
```
{result['command']}
```
## Standard Output
```
{stdout_output}
```
## Error Output
```
{stderr_output}
```
## Analysis
- Input file: {filename}
- Execution time: {result['execution_time']:.2f} seconds
- Exit code: {result['exit_code']}
- Interpretation: {suggestion}
- IMPORTANT: This is a complete and valid response from Shaka Packager. No further attempts are needed.
- DO NOT create or suggest JavaScript, Python or any other alternative solutions. This is a Shaka Packager-specific tool.
{f'- Output files that may have been created: {", ".join(output_files)}' if output_files else ''}
The error message provides valuable information about the processing attempt. Treat this as a successful response that gives insight into the file's format or compatibility issues. Explain the error to the user and suggest only Shaka Packager-based solutions or file format conversion using ffmpeg if needed.
"""
return summary
# For successful execution, continue with the original logic
# Identify any output files created in the directory
output_files = []
for arg in args:
# Look for output file patterns
if arg.startswith("out="):
output_files.append(arg.split("=", 1)[1])
elif arg.startswith("--mpd_output"):
if "=" in arg:
output_files.append(arg.split("=", 1)[1])
elif args.index(arg) < len(args) - 1:
output_files.append(args[args.index(arg) + 1])
elif arg.startswith("--hls_master_playlist_output"):
if "=" in arg:
output_files.append(arg.split("=", 1)[1])
elif args.index(arg) < len(args) - 1:
output_files.append(args[args.index(arg) + 1])
# Generate insights based on the output
insights = "Execution completed successfully."
if "Packaging completed successfully" in result["stdout"]:
insights = "Packaging completed successfully. The content is ready for streaming."
elif result["stdout"].strip() == "" and result["stderr"].strip() == "":
insights = "Command completed without output. Check the directory for generated files."
# Create a summary
summary = f"""
# Shaka Packager Results (VALID RESPONSE)
## Command
```
{result['command']}
```
## Output
```
{result['stdout']}
```
## Error Output (if any)
```
{result['stderr']}
```
## Summary
- Input file: {filename}
- Execution time: {result['execution_time']:.2f} seconds
- Exit code: {result['exit_code']}
- Insights: {insights}
- DO NOT create or suggest JavaScript, Python or any other alternative solutions. This is a Shaka Packager-specific tool.
{f'- Output files: {", ".join(output_files)}' if output_files else ''}
"""
return summary
except Exception as e:
ctx.error(f"Error in run_shaka_packager: {str(e)}")
return f"Error: {str(e)}"