progress.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import base64
  2. import io
  3. import time
  4. import gradio as gr
  5. from pydantic import BaseModel, Field
  6. from modules.shared import opts
  7. import modules.shared as shared
  8. current_task = None
  9. pending_tasks = {}
  10. finished_tasks = []
  11. recorded_results = []
  12. recorded_results_limit = 2
  13. def start_task(id_task):
  14. global current_task
  15. current_task = id_task
  16. pending_tasks.pop(id_task, None)
  17. def finish_task(id_task):
  18. global current_task
  19. if current_task == id_task:
  20. current_task = None
  21. finished_tasks.append(id_task)
  22. if len(finished_tasks) > 16:
  23. finished_tasks.pop(0)
  24. def record_results(id_task, res):
  25. recorded_results.append((id_task, res))
  26. if len(recorded_results) > recorded_results_limit:
  27. recorded_results.pop(0)
  28. def add_task_to_queue(id_job):
  29. pending_tasks[id_job] = time.time()
  30. class ProgressRequest(BaseModel):
  31. id_task: str = Field(default=None, title="Task ID", description="id of the task to get progress for")
  32. id_live_preview: int = Field(default=-1, title="Live preview image ID", description="id of last received last preview image")
  33. live_preview: bool = Field(default=True, title="Include live preview", description="boolean flag indicating whether to include the live preview image")
  34. class ProgressResponse(BaseModel):
  35. active: bool = Field(title="Whether the task is being worked on right now")
  36. queued: bool = Field(title="Whether the task is in queue")
  37. completed: bool = Field(title="Whether the task has already finished")
  38. progress: float = Field(default=None, title="Progress", description="The progress with a range of 0 to 1")
  39. eta: float = Field(default=None, title="ETA in secs")
  40. live_preview: str = Field(default=None, title="Live preview image", description="Current live preview; a data: uri")
  41. id_live_preview: int = Field(default=None, title="Live preview image ID", description="Send this together with next request to prevent receiving same image")
  42. textinfo: str = Field(default=None, title="Info text", description="Info text used by WebUI.")
  43. def setup_progress_api(app):
  44. return app.add_api_route("/internal/progress", progressapi, methods=["POST"], response_model=ProgressResponse)
  45. def progressapi(req: ProgressRequest):
  46. active = req.id_task == current_task
  47. queued = req.id_task in pending_tasks
  48. completed = req.id_task in finished_tasks
  49. if not active:
  50. textinfo = "Waiting..."
  51. if queued:
  52. sorted_queued = sorted(pending_tasks.keys(), key=lambda x: pending_tasks[x])
  53. queue_index = sorted_queued.index(req.id_task)
  54. textinfo = "In queue: {}/{}".format(queue_index + 1, len(sorted_queued))
  55. return ProgressResponse(active=active, queued=queued, completed=completed, id_live_preview=-1, textinfo=textinfo)
  56. progress = 0
  57. job_count, job_no = shared.state.job_count, shared.state.job_no
  58. sampling_steps, sampling_step = shared.state.sampling_steps, shared.state.sampling_step
  59. if job_count > 0:
  60. progress += job_no / job_count
  61. if sampling_steps > 0 and job_count > 0:
  62. progress += 1 / job_count * sampling_step / sampling_steps
  63. progress = min(progress, 1)
  64. elapsed_since_start = time.time() - shared.state.time_start
  65. predicted_duration = elapsed_since_start / progress if progress > 0 else None
  66. eta = predicted_duration - elapsed_since_start if predicted_duration is not None else None
  67. id_live_preview = req.id_live_preview
  68. shared.state.set_current_image()
  69. if opts.live_previews_enable and req.live_preview and shared.state.id_live_preview != req.id_live_preview:
  70. image = shared.state.current_image
  71. if image is not None:
  72. buffered = io.BytesIO()
  73. if opts.live_previews_image_format == "png":
  74. # using optimize for large images takes an enormous amount of time
  75. if max(*image.size) <= 256:
  76. save_kwargs = {"optimize": True}
  77. else:
  78. save_kwargs = {"optimize": False, "compress_level": 1}
  79. else:
  80. save_kwargs = {}
  81. image.save(buffered, format=opts.live_previews_image_format, **save_kwargs)
  82. base64_image = base64.b64encode(buffered.getvalue()).decode('ascii')
  83. live_preview = f"data:image/{opts.live_previews_image_format};base64,{base64_image}"
  84. id_live_preview = shared.state.id_live_preview
  85. else:
  86. live_preview = None
  87. else:
  88. live_preview = None
  89. return ProgressResponse(active=active, queued=queued, completed=completed, progress=progress, eta=eta, live_preview=live_preview, id_live_preview=id_live_preview, textinfo=shared.state.textinfo)
  90. def restore_progress(id_task):
  91. while id_task == current_task or id_task in pending_tasks:
  92. time.sleep(0.1)
  93. res = next(iter([x[1] for x in recorded_results if id_task == x[0]]), None)
  94. if res is not None:
  95. return res
  96. return gr.update(), gr.update(), gr.update(), f"Couldn't restore progress for {id_task}: results either have been discarded or never were obtained"