video_editor_task.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. import json
  2. import logging
  3. import os
  4. import subprocess
  5. import time
  6. from typing import Any
  7. from app.service.task.base_task import BaseTask
  8. from app.service.task.task_param import TaskParam
  9. from app.service.task.task_result import TaskResult
  10. from app.util.download_util import get_relative_path
  11. from app.util.file_util import copy_file, move_file
  12. from app.util.res_util import get_resources_path
  13. logger = logging.getLogger(__name__)
  14. class VideoEditorTask(BaseTask):
  15. def get_type(self) -> str:
  16. return "video_editor"
  17. def execute(self, param: TaskParam) -> TaskResult:
  18. images = param.inputs.get("images") if isinstance(param.inputs.get("images"), list) else []
  19. videos = param.inputs.get("videos") if isinstance(param.inputs.get("videos"), list) else []
  20. logger.info("[video_editor] Starting: %d images, %d videos, resources=%s",
  21. len(images), len(videos), param.resources)
  22. t0 = time.time()
  23. try:
  24. src = os.path.join(get_resources_path(), "video_editor", "compile")
  25. dst = os.path.join(param.workspace, "compile")
  26. copy_file(src, dst)
  27. os.chmod(dst, 0o755)
  28. logger.info("[video_editor] Copied compile to workspace")
  29. self._update_json(param.workspace, param.resources, videos, images)
  30. logger.info("[video_editor] Updated JSON config with media paths")
  31. self._execute_compile(param.workspace, param.resources)
  32. output_path = self._move_output(param.workspace, param.resources, param.step_id)
  33. if not output_path:
  34. raise RuntimeError("No output path generated by video_editor")
  35. logger.info("[video_editor] Completed: output=%s in %.2fs", output_path, time.time() - t0)
  36. return TaskResult.success({"videos": [output_path]})
  37. except Exception as e:
  38. logger.error("[video_editor] Failed in %.2fs: %s", time.time() - t0, e, exc_info=True)
  39. return TaskResult.failure(str(e))
  40. @staticmethod
  41. def _update_json(workspace: str, resources: str, videos: list, images: list):
  42. json_path = os.path.join(workspace, resources)
  43. with open(json_path, "r", encoding="utf-8") as f:
  44. data = json.load(f)
  45. video_track = data.get("videoTrack", [])
  46. vi, ii = 0, 0
  47. for track in video_track:
  48. t = track.get("type")
  49. if t == "video" and vi < len(videos):
  50. logger.info("[video_editor] Assigning video[%d] -> track path: %s", vi, videos[vi])
  51. track["path"] = videos[vi]
  52. vi += 1
  53. elif t == "image" and ii < len(images):
  54. logger.info("[video_editor] Assigning image[%d] -> track path: %s", ii, images[ii])
  55. track["path"] = images[ii]
  56. ii += 1
  57. with open(json_path, "w", encoding="utf-8") as f:
  58. json.dump(data, f, indent=2, ensure_ascii=False)
  59. @staticmethod
  60. def _execute_compile(workspace: str, resources: str):
  61. cmd = f"./compile -m {resources}"
  62. logger.info("[video_editor] Executing: %s (cwd=%s)", cmd, workspace)
  63. cmd_t0 = time.time()
  64. result = subprocess.run(
  65. ["sh", "-c", cmd], cwd=workspace, capture_output=True, text=True,
  66. )
  67. cmd_elapsed = time.time() - cmd_t0
  68. if result.stdout:
  69. logger.info("[video_editor] stdout: %s", result.stdout.strip())
  70. if result.stderr:
  71. logger.warning("[video_editor] stderr: %s", result.stderr.strip())
  72. if result.returncode != 0:
  73. logger.error("[video_editor] compile failed: exit_code=%d in %.2fs", result.returncode, cmd_elapsed)
  74. raise RuntimeError(f"compile failed with exit code {result.returncode}")
  75. logger.info("[video_editor] compile completed: exit_code=0 in %.2fs", cmd_elapsed)
  76. @staticmethod
  77. def _move_output(workspace: str, resources: str, step_id: str) -> str | None:
  78. json_path = os.path.join(workspace, resources)
  79. with open(json_path, "r", encoding="utf-8") as f:
  80. data = json.load(f)
  81. output_cfg = data.get("output", {})
  82. output_path = output_cfg.get("path")
  83. if not output_path:
  84. logger.warning("[video_editor] No output path found in config")
  85. return None
  86. src = os.path.join(workspace, output_path)
  87. dest_dir = os.path.join(workspace, step_id)
  88. os.makedirs(dest_dir, exist_ok=True)
  89. dest = os.path.join(dest_dir, os.path.basename(output_path))
  90. move_file(src, dest)
  91. rel_path = get_relative_path(dest, workspace)
  92. logger.info("[video_editor] Output moved: %s -> %s", output_path, rel_path)
  93. return rel_path