andro1241 commited on
Commit
c1e636a
·
verified ·
1 Parent(s): 47e953f

Upload 5 files

Browse files
jobs/job_helper.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from datetime import datetime
3
+ from typing import Optional
4
+
5
+ from facefusion.filesystem import get_file_extension, get_file_name
6
+
7
+
8
+ def get_step_output_path(job_id : str, step_index : int, output_path : str) -> Optional[str]:
9
+ if output_path:
10
+ output_directory_path, output_file_path = os.path.split(output_path)
11
+ output_file_name = get_file_name(output_file_path)
12
+ output_file_extension = get_file_extension(output_file_path)
13
+
14
+ if output_file_name and output_file_extension:
15
+ return os.path.join(output_directory_path, output_file_name + '-' + job_id + '-' + str(step_index) + output_file_extension)
16
+ return None
17
+
18
+
19
+ def suggest_job_id(job_prefix : str = 'job') -> str:
20
+ return job_prefix + '-' + datetime.now().strftime('%Y-%m-%d-%H-%M-%S')
jobs/job_list.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime
2
+ from typing import List, Optional, Tuple
3
+
4
+ from facefusion.jobs import job_manager
5
+ from facefusion.time_helper import describe_time_ago
6
+ from facefusion.types import JobStatus, TableContent, TableHeader
7
+
8
+
9
+ def compose_job_list(job_status : JobStatus) -> Tuple[List[TableHeader], List[List[TableContent]]]:
10
+ jobs = job_manager.find_jobs(job_status)
11
+ job_headers : List[TableHeader] = [ 'job id', 'steps', 'date created', 'date updated', 'job status' ]
12
+ job_contents : List[List[TableContent]] = []
13
+
14
+ for index, job_id in enumerate(jobs):
15
+ if job_manager.validate_job(job_id):
16
+ job = jobs[job_id]
17
+ step_total = job_manager.count_step_total(job_id)
18
+ date_created = prepare_describe_datetime(job.get('date_created'))
19
+ date_updated = prepare_describe_datetime(job.get('date_updated'))
20
+ job_contents.append(
21
+ [
22
+ job_id,
23
+ step_total,
24
+ date_created,
25
+ date_updated,
26
+ job_status
27
+ ])
28
+ return job_headers, job_contents
29
+
30
+
31
+ def prepare_describe_datetime(date_time : Optional[str]) -> Optional[str]:
32
+ if date_time:
33
+ return describe_time_ago(datetime.fromisoformat(date_time))
34
+ return None
jobs/job_manager.py ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from copy import copy
3
+ from typing import List, Optional
4
+
5
+ import facefusion.choices
6
+ from facefusion.filesystem import create_directory, get_file_name, is_directory, is_file, move_file, remove_directory, remove_file, resolve_file_pattern
7
+ from facefusion.jobs.job_helper import get_step_output_path
8
+ from facefusion.json import read_json, write_json
9
+ from facefusion.sanitizer import sanitize_job_id
10
+ from facefusion.time_helper import get_current_date_time
11
+ from facefusion.types import Args, Job, JobSet, JobStatus, JobStep, JobStepStatus
12
+
13
+ JOBS_PATH : Optional[str] = None
14
+
15
+
16
+ def init_jobs(jobs_path : str) -> bool:
17
+ global JOBS_PATH
18
+
19
+ JOBS_PATH = jobs_path
20
+ job_status_paths = [ os.path.join(JOBS_PATH, job_status) for job_status in facefusion.choices.job_statuses ]
21
+
22
+ for job_status_path in job_status_paths:
23
+ create_directory(job_status_path)
24
+ return all(is_directory(status_path) for status_path in job_status_paths)
25
+
26
+
27
+ def clear_jobs(jobs_path : str) -> bool:
28
+ return remove_directory(jobs_path)
29
+
30
+
31
+ def create_job(job_id : str) -> bool:
32
+ job : Job =\
33
+ {
34
+ 'version': '1',
35
+ 'date_created': get_current_date_time().isoformat(),
36
+ 'date_updated': None,
37
+ 'steps': []
38
+ }
39
+
40
+ return create_job_file(job_id, job)
41
+
42
+
43
+ def submit_job(job_id : str) -> bool:
44
+ drafted_job_ids = find_job_ids('drafted')
45
+ steps = get_steps(job_id)
46
+
47
+ if job_id in drafted_job_ids and steps:
48
+ return set_steps_status(job_id, 'queued') and move_job_file(job_id, 'queued')
49
+ return False
50
+
51
+
52
+ def submit_jobs(halt_on_error : bool) -> bool:
53
+ drafted_job_ids = find_job_ids('drafted')
54
+ has_error = False
55
+
56
+ if drafted_job_ids:
57
+ for job_id in drafted_job_ids:
58
+ if not submit_job(job_id):
59
+ has_error = True
60
+ if halt_on_error:
61
+ return False
62
+ return not has_error
63
+ return False
64
+
65
+
66
+ def delete_job(job_id : str) -> bool:
67
+ return delete_job_file(job_id)
68
+
69
+
70
+ def delete_jobs(halt_on_error : bool) -> bool:
71
+ job_ids = find_job_ids('drafted') + find_job_ids('queued') + find_job_ids('failed') + find_job_ids('completed')
72
+ has_error = False
73
+
74
+ if job_ids:
75
+ for job_id in job_ids:
76
+ if not delete_job(job_id):
77
+ has_error = True
78
+ if halt_on_error:
79
+ return False
80
+ return not has_error
81
+ return False
82
+
83
+
84
+ def find_jobs(job_status : JobStatus) -> JobSet:
85
+ job_ids = find_job_ids(job_status)
86
+ job_set : JobSet = {}
87
+
88
+ for job_id in job_ids:
89
+ job_set[job_id] = read_job_file(job_id)
90
+ return job_set
91
+
92
+
93
+ def find_job_ids(job_status : JobStatus) -> List[str]:
94
+ job_pattern = os.path.join(JOBS_PATH, job_status, '*.json')
95
+ job_paths = resolve_file_pattern(job_pattern)
96
+ job_paths.sort(key = os.path.getmtime)
97
+ job_ids = []
98
+
99
+ for job_path in job_paths:
100
+ job_id = get_file_name(job_path)
101
+ job_ids.append(job_id)
102
+ return job_ids
103
+
104
+
105
+ def validate_job(job_id : str) -> bool:
106
+ job = read_job_file(job_id)
107
+ return bool(job and 'version' in job and 'date_created' in job and 'date_updated' in job and 'steps' in job)
108
+
109
+
110
+ def has_step(job_id : str, step_index : int) -> bool:
111
+ step_total = count_step_total(job_id)
112
+ return step_index in range(step_total)
113
+
114
+
115
+ def add_step(job_id : str, step_args : Args) -> bool:
116
+ job = read_job_file(job_id)
117
+
118
+ if job:
119
+ job.get('steps').append(
120
+ {
121
+ 'args': step_args,
122
+ 'status': 'drafted'
123
+ })
124
+ return update_job_file(job_id, job)
125
+ return False
126
+
127
+
128
+ def remix_step(job_id : str, step_index : int, step_args : Args) -> bool:
129
+ steps = get_steps(job_id)
130
+ step_args = copy(step_args)
131
+
132
+ if step_index and step_index < 0:
133
+ step_index = count_step_total(job_id) - 1
134
+
135
+ if has_step(job_id, step_index):
136
+ output_path = steps[step_index].get('args').get('output_path')
137
+ step_args['target_path'] = get_step_output_path(job_id, step_index, output_path)
138
+ return add_step(job_id, step_args)
139
+ return False
140
+
141
+
142
+ def insert_step(job_id : str, step_index : int, step_args : Args) -> bool:
143
+ job = read_job_file(job_id)
144
+ step_args = copy(step_args)
145
+
146
+ if step_index and step_index < 0:
147
+ step_index = count_step_total(job_id) - 1
148
+
149
+ if job and has_step(job_id, step_index):
150
+ job.get('steps').insert(step_index,
151
+ {
152
+ 'args': step_args,
153
+ 'status': 'drafted'
154
+ })
155
+ return update_job_file(job_id, job)
156
+ return False
157
+
158
+
159
+ def remove_step(job_id : str, step_index : int) -> bool:
160
+ job = read_job_file(job_id)
161
+
162
+ if step_index and step_index < 0:
163
+ step_index = count_step_total(job_id) - 1
164
+
165
+ if job and has_step(job_id, step_index):
166
+ job.get('steps').pop(step_index)
167
+ return update_job_file(job_id, job)
168
+ return False
169
+
170
+
171
+ def get_steps(job_id : str) -> List[JobStep]:
172
+ job = read_job_file(job_id)
173
+
174
+ if job:
175
+ return job.get('steps')
176
+ return []
177
+
178
+
179
+ def count_step_total(job_id : str) -> int:
180
+ steps = get_steps(job_id)
181
+
182
+ if steps:
183
+ return len(steps)
184
+ return 0
185
+
186
+
187
+ def set_step_status(job_id : str, step_index : int, step_status : JobStepStatus) -> bool:
188
+ job = read_job_file(job_id)
189
+
190
+ if job:
191
+ steps = job.get('steps')
192
+ if has_step(job_id, step_index):
193
+ steps[step_index]['status'] = step_status
194
+ return update_job_file(job_id, job)
195
+ return False
196
+
197
+
198
+ def set_steps_status(job_id : str, step_status : JobStepStatus) -> bool:
199
+ job = read_job_file(job_id)
200
+
201
+ if job:
202
+ for step in job.get('steps'):
203
+ step['status'] = step_status
204
+ return update_job_file(job_id, job)
205
+ return False
206
+
207
+
208
+ def read_job_file(job_id : str) -> Optional[Job]:
209
+ job_path = find_job_path(job_id)
210
+ return read_json(job_path) #type:ignore[return-value]
211
+
212
+
213
+ def create_job_file(job_id : str, job : Job) -> bool:
214
+ job_path = find_job_path(job_id)
215
+
216
+ if not is_file(job_path):
217
+ job_create_path = suggest_job_path(job_id, 'drafted')
218
+ return write_json(job_create_path, job) #type:ignore[arg-type]
219
+ return False
220
+
221
+
222
+ def update_job_file(job_id : str, job : Job) -> bool:
223
+ job_path = find_job_path(job_id)
224
+
225
+ if is_file(job_path):
226
+ job['date_updated'] = get_current_date_time().isoformat()
227
+ return write_json(job_path, job) #type:ignore[arg-type]
228
+ return False
229
+
230
+
231
+ def move_job_file(job_id : str, job_status : JobStatus) -> bool:
232
+ job_path = find_job_path(job_id)
233
+ job_move_path = suggest_job_path(job_id, job_status)
234
+ return move_file(job_path, job_move_path)
235
+
236
+
237
+ def delete_job_file(job_id : str) -> bool:
238
+ job_path = find_job_path(job_id)
239
+ return remove_file(job_path)
240
+
241
+
242
+ def suggest_job_path(job_id : str, job_status : JobStatus) -> Optional[str]:
243
+ job_file_name = get_job_file_name(job_id)
244
+
245
+ if job_file_name:
246
+ return os.path.join(JOBS_PATH, job_status, job_file_name)
247
+ return None
248
+
249
+
250
+ def find_job_path(job_id : str) -> Optional[str]:
251
+ job_file_name = get_job_file_name(job_id)
252
+
253
+ if job_file_name:
254
+ for job_status in facefusion.choices.job_statuses:
255
+ job_pattern = os.path.join(JOBS_PATH, job_status, job_file_name)
256
+ job_paths = resolve_file_pattern(job_pattern)
257
+
258
+ for job_path in job_paths:
259
+ return job_path
260
+ return None
261
+
262
+
263
+ def get_job_file_name(job_id : str) -> Optional[str]:
264
+ if job_id:
265
+ job_id = sanitize_job_id(job_id)
266
+ return job_id + '.json'
267
+ return None
jobs/job_runner.py ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from facefusion.ffmpeg import concat_video
2
+ from facefusion.filesystem import are_images, are_videos, move_file, remove_file
3
+ from facefusion.jobs import job_helper, job_manager
4
+ from facefusion.types import JobOutputSet, JobStep, ProcessStep
5
+
6
+
7
+ def run_job(job_id : str, process_step : ProcessStep) -> bool:
8
+ queued_job_ids = job_manager.find_job_ids('queued')
9
+
10
+ if job_id in queued_job_ids:
11
+ if run_steps(job_id, process_step) and finalize_steps(job_id):
12
+ clean_steps(job_id)
13
+ return job_manager.move_job_file(job_id, 'completed')
14
+ clean_steps(job_id)
15
+ job_manager.move_job_file(job_id, 'failed')
16
+ return False
17
+
18
+
19
+ def run_jobs(process_step : ProcessStep, halt_on_error : bool) -> bool:
20
+ queued_job_ids = job_manager.find_job_ids('queued')
21
+ has_error = False
22
+
23
+ if queued_job_ids:
24
+ for job_id in queued_job_ids:
25
+ if not run_job(job_id, process_step):
26
+ has_error = True
27
+ if halt_on_error:
28
+ return False
29
+ return not has_error
30
+ return False
31
+
32
+
33
+ def retry_job(job_id : str, process_step : ProcessStep) -> bool:
34
+ failed_job_ids = job_manager.find_job_ids('failed')
35
+
36
+ if job_id in failed_job_ids:
37
+ return job_manager.set_steps_status(job_id, 'queued') and job_manager.move_job_file(job_id, 'queued') and run_job(job_id, process_step)
38
+ return False
39
+
40
+
41
+ def retry_jobs(process_step : ProcessStep, halt_on_error : bool) -> bool:
42
+ failed_job_ids = job_manager.find_job_ids('failed')
43
+ has_error = False
44
+
45
+ if failed_job_ids:
46
+ for job_id in failed_job_ids:
47
+ if not retry_job(job_id, process_step):
48
+ has_error = True
49
+ if halt_on_error:
50
+ return False
51
+ return not has_error
52
+ return False
53
+
54
+
55
+ def run_step(job_id : str, step_index : int, step : JobStep, process_step : ProcessStep) -> bool:
56
+ step_args = step.get('args')
57
+
58
+ if job_manager.set_step_status(job_id, step_index, 'started') and process_step(job_id, step_index, step_args):
59
+ output_path = step_args.get('output_path')
60
+ step_output_path = job_helper.get_step_output_path(job_id, step_index, output_path)
61
+
62
+ return move_file(output_path, step_output_path) and job_manager.set_step_status(job_id, step_index, 'completed')
63
+ job_manager.set_step_status(job_id, step_index, 'failed')
64
+ return False
65
+
66
+
67
+ def run_steps(job_id : str, process_step : ProcessStep) -> bool:
68
+ steps = job_manager.get_steps(job_id)
69
+
70
+ if steps:
71
+ for index, step in enumerate(steps):
72
+ if not run_step(job_id, index, step, process_step):
73
+ return False
74
+ return True
75
+ return False
76
+
77
+
78
+ def finalize_steps(job_id : str) -> bool:
79
+ output_set = collect_output_set(job_id)
80
+
81
+ for output_path, temp_output_paths in output_set.items():
82
+ if are_videos(temp_output_paths):
83
+ if not concat_video(output_path, temp_output_paths):
84
+ return False
85
+ if are_images(temp_output_paths):
86
+ for temp_output_path in temp_output_paths:
87
+ if not move_file(temp_output_path, output_path):
88
+ return False
89
+ return True
90
+
91
+
92
+ def clean_steps(job_id: str) -> bool:
93
+ output_set = collect_output_set(job_id)
94
+
95
+ for temp_output_paths in output_set.values():
96
+ for temp_output_path in temp_output_paths:
97
+ if not remove_file(temp_output_path):
98
+ return False
99
+ return True
100
+
101
+
102
+ def collect_output_set(job_id : str) -> JobOutputSet:
103
+ steps = job_manager.get_steps(job_id)
104
+ job_output_set : JobOutputSet = {}
105
+
106
+ for index, step in enumerate(steps):
107
+ output_path = step.get('args').get('output_path')
108
+
109
+ if output_path:
110
+ step_output_path = job_manager.get_step_output_path(job_id, index, output_path)
111
+ job_output_set.setdefault(output_path, []).append(step_output_path)
112
+ return job_output_set
jobs/job_store.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List
2
+
3
+ from facefusion.types import JobStore
4
+
5
+ JOB_STORE : JobStore =\
6
+ {
7
+ 'job_keys': [],
8
+ 'step_keys': []
9
+ }
10
+
11
+
12
+ def get_job_keys() -> List[str]:
13
+ return JOB_STORE.get('job_keys')
14
+
15
+
16
+ def get_step_keys() -> List[str]:
17
+ return JOB_STORE.get('step_keys')
18
+
19
+
20
+ def register_job_keys(job_keys : List[str]) -> None:
21
+ for job_key in job_keys:
22
+ JOB_STORE['job_keys'].append(job_key)
23
+
24
+
25
+ def register_step_keys(step_keys : List[str]) -> None:
26
+ for step_key in step_keys:
27
+ JOB_STORE['step_keys'].append(step_key)