mirror of
https://github.com/facefusion/facefusion.git
synced 2026-08-05 16:48:36 +02:00
* mark as next * unify the dependency checks in pre_check and add ffprobe (#1181) * drop keep_temp and the common options component (#1180) Claude-Session: https://claude.ai/code/session_01Tbcd6VWCiU4BQP1gywPr2a Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * introduce ffprobe and ffprobe_builder (#1182) * introduce ffprobe and ffprobe_builder Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tbcd6VWCiU4BQP1gywPr2a * introduce ffprobe and ffprobe_builder Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tbcd6VWCiU4BQP1gywPr2a --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * probe video metadata via ffprobe in vision (#1184) * probe video metadata via ffprobe in vision Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tbcd6VWCiU4BQP1gywPr2a * probe video metadata via ffprobe in vision Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tbcd6VWCiU4BQP1gywPr2a --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * adopt the workflow task vocabulary from next major (#1185) Claude-Session: https://claude.ai/code/session_01Tbcd6VWCiU4BQP1gywPr2a Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * introduce workflow-mode and workflow-strategy like next major (#1187) Claude-Session: https://claude.ai/code/session_01Tbcd6VWCiU4BQP1gywPr2a Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * restrict hdr color transfer and tag the merge output as bt709 (#1188) * restrict hdr color transfer and tag the merge output as bt709 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tbcd6VWCiU4BQP1gywPr2a * restrict hdr color transfer and tag the merge output as bt709 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tbcd6VWCiU4BQP1gywPr2a * full video migration * compose the hdr fixture via the builder chain Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tbcd6VWCiU4BQP1gywPr2a * compose the test fixtures via the builder and run_ffmpeg Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tbcd6VWCiU4BQP1gywPr2a --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * compose every test fixture via the builder and run_ffmpeg (#1189) * compose every test fixture via the builder and run_ffmpeg Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tbcd6VWCiU4BQP1gywPr2a * use loops in tests for ffmpeg stuff --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * New Video Manager (#1191) * tiny adjustment for tests * address the review on the video manager Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tbcd6VWCiU4BQP1gywPr2a * introduce the stream strategy for the video workflow (#1192) * introduce the stream strategy for the video workflow Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tbcd6VWCiU4BQP1gywPr2a * address the review on the stream strategy Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tbcd6VWCiU4BQP1gywPr2a --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * annotate the changes for review Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tbcd6VWCiU4BQP1gywPr2a * annotate the new tests for review Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tbcd6VWCiU4BQP1gywPr2a * match the temp pixel format help to the locale style Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tbcd6VWCiU4BQP1gywPr2a * question the set_input_seek naming Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tbcd6VWCiU4BQP1gywPr2a * question the reader and writer keys Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tbcd6VWCiU4BQP1gywPr2a * drop the review annotations from the encoder mapping tests Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tbcd6VWCiU4BQP1gywPr2a * drop the review annotations from the thread count tests Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tbcd6VWCiU4BQP1gywPr2a * drop the review annotations from the ui files Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tbcd6VWCiU4BQP1gywPr2a * capture the open review questions as annotations Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tbcd6VWCiU4BQP1gywPr2a * drop the settled annotations from the types Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tbcd6VWCiU4BQP1gywPr2a --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * switch to ffmpeg.style for audio.py * remove todos that were never needed * fix for ffmpeg7 * Add frame_store module (#1194) * add frame_store module * rename and change tests * rename and update tests * route window read through frame_store (#1196) * route window read through frame_store * update proper id * restore todos * restore todos * go v4 style for workflow (#1197) * go v4 style for workflow * remove some todos * route chunk read through frame_store (#1198) * vision integration * Deleted read_video_chunk + read_static_video_chunk * margin decouple (#1199) * fix windows CI fail (#1200) * Cleanup Part1 (#1201) * remove some todos, improve video manager, simplify ffmpeg commands and more * do more * remove thread count for filters * Cleanup Part 2 (#1202) * tons of renaming * tons of renaming * multi reader approach * bring tests to an okay-ish state * bring drain back * improve read_video_frame speed * rename method * move variables * seek video reader only when trim frame start is larger 0 * make stream the default * Cleanup/part 3 (#1203) * remove todo * sort out workflow, to match upcoming v4 * remove look ahead * remove core namespace again * Revamp execution provider overrides/adjustments (#1206) * Split provider hooks into override/adjust with cached CoreML base Replace the single resolve_inference_providers processor hook with two: override_inference_providers (full replacement) and adjust_inference_providers (merge options onto the base providers built by create_inference_providers). This lets CoreML processors inherit ModelCacheDirectory + SpecializationStrategy from the base while layering ModelFormat/MLComputeUnits on top. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HTQCZiYjJyUX11bDpbRSiB * fix caching for execution provider by having override and adjust ways * fix caching for execution provider by having override and adjust ways * fix lint * use proper pytest fixtures --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix update preview bug (#1205) * fix update preview bug * fix update preview bug * remove guard * add is_vision_frame * Restrict the preview frame slider and the reader seek to the last frame index (#1207) * fix index bug * fix rounding bug * avoid tobytes copy (#1208) * beautify tests * hide ffmpeg warnings * simplify process_stream_frame * Use is vision frame everywhere (#1210) * use is_vision_frame everywhere * fix hash * fix lint * fix hash creation in face store * that model does not exist * update workflow ffmpeg * guard workflow (#1211) * bump version and dependencies * Update preview * switch workflow strategy to disk|memory * update preview * update preview * fix wording * last minute change workflow position * adjust wording --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Harisreedhar <46858047+harisreedhar@users.noreply.github.com> Co-authored-by: harisreedhar <h4harisreedhar.s.s@gmail.com>
156 lines
5.1 KiB
Python
156 lines
5.1 KiB
Python
import hashlib
|
|
import uuid
|
|
from io import BufferedReader
|
|
from typing import Optional, cast
|
|
|
|
import numpy
|
|
|
|
from facefusion import ffmpeg, ffprobe, frame_store, vision
|
|
from facefusion.common_helper import get_first, get_last
|
|
from facefusion.types import Fps, Resolution, VideoPoolSet, VideoReader, VideoWriter, VisionFrame, VisionFrameSet
|
|
|
|
VIDEO_POOL_SET : VideoPoolSet =\
|
|
{
|
|
'reader': {},
|
|
'writer': {}
|
|
}
|
|
|
|
|
|
def get_reader(video_path : str, context : str) -> VideoReader:
|
|
reader_id = hashlib.sha1((video_path + '_' + context).encode()).hexdigest()
|
|
|
|
if reader_id not in VIDEO_POOL_SET.get('reader'):
|
|
video_metadata = ffprobe.extract_static_video_metadata(video_path)
|
|
|
|
VIDEO_POOL_SET['reader'][reader_id] =\
|
|
{
|
|
'id': reader_id,
|
|
'file_path': video_path,
|
|
'process': ffmpeg.create_video_reader(video_path, 0, video_metadata),
|
|
'metadata': video_metadata,
|
|
'frame_number': 0
|
|
}
|
|
|
|
return VIDEO_POOL_SET.get('reader').get(reader_id)
|
|
|
|
|
|
def conditional_seek_video_reader(video_reader : VideoReader, frame_number : int = 0) -> None:
|
|
frame_total = video_reader.get('metadata').get('frame_total')
|
|
frame_number = min(frame_total - 1, frame_number)
|
|
skip_total = frame_number - video_reader.get('frame_number')
|
|
skip_margin = 128
|
|
|
|
if 0 < skip_total <= skip_margin:
|
|
drain_video_reader(video_reader, skip_total)
|
|
|
|
if not video_reader.get('frame_number') == frame_number:
|
|
seek_video_reader(video_reader, frame_number)
|
|
|
|
|
|
def seek_video_reader(video_reader : VideoReader, frame_number : int = 0) -> None:
|
|
close_video_reader(video_reader)
|
|
|
|
video_reader['process'] = ffmpeg.create_video_reader(video_reader.get('file_path'), frame_number, video_reader.get('metadata'))
|
|
video_reader['frame_number'] = frame_number
|
|
|
|
|
|
def drain_video_reader(video_reader : VideoReader, skip_total : int) -> None:
|
|
width, height = video_reader.get('metadata').get('resolution')
|
|
channel_total = 3
|
|
frame_size = width * height * channel_total
|
|
|
|
for _ in range(skip_total):
|
|
video_reader.get('process').stdout.read(frame_size)
|
|
|
|
video_reader['frame_number'] = video_reader.get('frame_number') + skip_total
|
|
|
|
|
|
def read_video_frame(video_reader : VideoReader) -> Optional[VisionFrame]:
|
|
width, height = video_reader.get('metadata').get('resolution')
|
|
channel_total = 3
|
|
video_stream = cast(BufferedReader, video_reader.get('process').stdout)
|
|
vision_frame = numpy.empty(width * height * channel_total, numpy.uint8)
|
|
|
|
if video_stream.readinto(vision_frame) == vision_frame.size:
|
|
video_reader['frame_number'] = video_reader.get('frame_number') + 1
|
|
return vision_frame.reshape(height, width, channel_total)
|
|
|
|
return None
|
|
|
|
|
|
def read_video_frames(video_reader : VideoReader, frame_start : int, frame_end : int) -> VisionFrameSet:
|
|
reader_id = video_reader.get('id')
|
|
frame_set = frame_store.get_frame_store(reader_id)
|
|
keep_margin = 4
|
|
frame_gaps = []
|
|
|
|
for frame_number in range(frame_start, frame_end + 1):
|
|
if frame_number not in frame_set:
|
|
frame_gaps.append(frame_number)
|
|
|
|
if frame_gaps:
|
|
collect_video_frames(video_reader, get_first(frame_gaps), get_last(frame_gaps))
|
|
|
|
frame_store.reduce_frames(reader_id, frame_start - keep_margin, frame_end + keep_margin)
|
|
return frame_store.select_frame_set(reader_id, frame_start, frame_end)
|
|
|
|
|
|
def collect_video_frames(video_reader : VideoReader, frame_start : int, frame_end : int) -> None:
|
|
reader_id = video_reader.get('id')
|
|
skip_total = frame_start - video_reader.get('frame_number')
|
|
skip_margin = 16
|
|
|
|
if skip_total < 0 or skip_total > skip_margin:
|
|
seek_video_reader(video_reader, frame_start)
|
|
|
|
for frame_number in range(video_reader.get('frame_number'), frame_end + 1):
|
|
vision_frame = read_video_frame(video_reader)
|
|
|
|
if vision.is_vision_frame(vision_frame):
|
|
frame_store.set_frame(reader_id, frame_number, vision_frame)
|
|
|
|
|
|
def close_video_reader(video_reader : VideoReader) -> None:
|
|
video_reader.get('process').kill()
|
|
video_reader.get('process').wait()
|
|
|
|
|
|
def get_writer(video_path : str, temp_video_fps : Fps, temp_video_resolution : Resolution, output_video_resolution : Resolution, output_video_fps : Fps) -> VideoWriter:
|
|
if video_path not in VIDEO_POOL_SET.get('writer'):
|
|
VIDEO_POOL_SET['writer'][video_path] =\
|
|
{
|
|
'id': uuid.uuid4().hex,
|
|
'file_path': video_path,
|
|
'process': ffmpeg.create_video_writer(video_path, temp_video_fps, temp_video_resolution, output_video_resolution, output_video_fps),
|
|
'metadata':
|
|
{
|
|
'fps': output_video_fps,
|
|
'resolution': output_video_resolution
|
|
}
|
|
}
|
|
|
|
return VIDEO_POOL_SET.get('writer').get(video_path)
|
|
|
|
|
|
def write_video_frame(video_writer : VideoWriter, vision_frame : VisionFrame) -> None:
|
|
video_writer.get('process').stdin.write(vision_frame.data)
|
|
|
|
|
|
def close_video_writer(video_writer : VideoWriter) -> bool:
|
|
video_writer.get('process').stdin.close()
|
|
video_writer.get('process').wait()
|
|
|
|
return video_writer.get('process').returncode == 0
|
|
|
|
|
|
def clear_video_pool() -> None:
|
|
for video_reader in VIDEO_POOL_SET.get('reader').values():
|
|
close_video_reader(video_reader)
|
|
frame_store.clear_frames(video_reader.get('id'))
|
|
|
|
for video_writer in VIDEO_POOL_SET.get('writer').values():
|
|
close_video_writer(video_writer)
|
|
|
|
VIDEO_POOL_SET['reader'].clear()
|
|
VIDEO_POOL_SET['writer'].clear()
|