| |
| """ |
| Script to break large pickle files into individual motion sequence files. |
| |
| This script reads large pickle files and breaks each motion sequence |
| within them into individual pickle files in subdirectories. |
| This enables motion_lib_base.py to use directory mode for efficient loading. |
| Supports any pkl file format, not just bone motion files. |
| """ |
|
|
| import argparse |
| from pathlib import Path |
| import shutil |
| import sys |
| import time |
|
|
| import joblib |
| from tqdm import tqdm |
|
|
|
|
| def create_output_structure(output_dir, clean=False): |
| """Create the output directory structure.""" |
| output_path = Path(output_dir) |
|
|
| if clean and output_path.exists(): |
| print(f"Removing existing output directory: {output_path}") |
| shutil.rmtree(output_path) |
|
|
| output_path.mkdir(parents=True, exist_ok=True) |
| print(f"Created output directory: {output_path}") |
| return output_path |
|
|
|
|
| def extract_motion_metadata(motion_data): |
| """Extract metadata (length, fps) from motion data.""" |
| metadata = {} |
|
|
| |
| fps = None |
| if hasattr(motion_data, "get"): |
| fps = motion_data.get("fps", motion_data.get("frame_rate", motion_data.get("framerate"))) |
|
|
| |
| if fps is None: |
| fps = 30.0 |
|
|
| |
| length = 0 |
| length = motion_data["root_trans_offset"].shape[0] |
|
|
| |
| if length == 0 and hasattr(motion_data, "__len__"): |
| length = len(motion_data) |
|
|
| return {"length": length, "fps": fps, "duration": length / fps if fps > 0 else 0.0} |
|
|
|
|
| def process_motion_file(input_file, output_dir, verbose=False): |
| """Process a single large motion file and break it into individual files.""" |
| input_path = Path(input_file) |
|
|
| |
| file_subdir = input_path.stem |
| output_path = Path(output_dir) / file_subdir |
| output_path.mkdir(parents=True, exist_ok=True) |
|
|
| try: |
| print(f"Loading {input_path.name}...") |
| motion_data = joblib.load(input_path) |
| print(f"Loaded {len(motion_data)} motion sequences -> {output_path}") |
|
|
| |
| metadata = {} |
|
|
| |
| for motion_key, motion_sequence_data in tqdm( |
| motion_data.items(), desc=f"Processing {input_path.name}" |
| ): |
| individual_filepath = output_path / f"{motion_key}.pkl" |
| individual_dict = {motion_key: motion_sequence_data} |
| joblib.dump(individual_dict, individual_filepath) |
|
|
| |
| motion_metadata = extract_motion_metadata(motion_sequence_data) |
| metadata[motion_key] = motion_metadata |
|
|
| |
| metadata_filepath = output_path / "metadata.pkl" |
| joblib.dump(metadata, metadata_filepath) |
|
|
| if verbose: |
| print( |
| f"Successfully processed {input_path.name} -> {len(motion_data)} individual files + metadata" |
| ) |
| return True |
|
|
| except Exception as e: |
| print(f"Error processing {input_path.name}: {e}") |
| return False |
|
|
|
|
| def main(): |
| parser = argparse.ArgumentParser(description="Break pickle files into individual sequences") |
| parser.add_argument("input", help="Input directory containing pkl files or single pkl file") |
| parser.add_argument( |
| "--output", |
| default="data/processed_pkl/", |
| help="Output directory for individual motion files", |
| ) |
| parser.add_argument("--verbose", "-v", action="store_true", help="Enable verbose output") |
| parser.add_argument( |
| "--file-pattern", |
| default="*.pkl", |
| help="Pattern to match input files (only used for directories)", |
| ) |
| parser.add_argument( |
| "--clean", action="store_true", help="Remove output directory if it already exists" |
| ) |
|
|
| args = parser.parse_args() |
|
|
| input_path = Path(args.input) |
| if not input_path.exists(): |
| print(f"Error: Input path {input_path} does not exist!") |
| return 1 |
|
|
| |
| output_path = create_output_structure(args.output, args.clean) |
|
|
| |
| if input_path.is_file(): |
| |
| if not input_path.suffix == ".pkl": |
| print(f"Error: Input file must be a .pkl file, got {input_path.suffix}") |
| return 1 |
| input_files = [input_path] |
| print(f"Processing single file: {input_path}") |
| else: |
| |
| input_files = sorted(input_path.glob(args.file_pattern)) |
| if not input_files: |
| print(f"No files found matching pattern {args.file_pattern} in {input_path}") |
| return 1 |
| print(f"Processing directory: {input_path}") |
|
|
| print(f"Found {len(input_files)} files to process:") |
| for f in input_files[:5]: |
| print(f" {f.name}") |
| if len(input_files) > 5: |
| print(f" ... and {len(input_files) - 5} more files") |
|
|
| |
| successful = failed = total_individual_files = 0 |
| start_time = time.time() |
|
|
| for input_file in input_files: |
| print(f"\n{'='*60}") |
| if process_motion_file(input_file, output_path, args.verbose): |
| successful += 1 |
| |
| subdir_path = output_path / input_file.stem |
| individual_files = [ |
| f for f in subdir_path.glob("*.pkl") if not f.name.endswith("metadata.pkl") |
| ] |
| total_individual_files += len(individual_files) |
| print( |
| f"Created {len(individual_files)} individual files + metadata from {input_file.name}" |
| ) |
| else: |
| failed += 1 |
|
|
| elapsed = time.time() - start_time |
|
|
| |
| print(f"\n{'='*60}") |
| print("PROCESSING SUMMARY") |
| print(f"{'='*60}") |
| print(f"Input: {input_path}") |
| print(f"Output directory: {output_path}") |
| print(f"Files processed successfully: {successful}") |
| print(f"Files failed: {failed}") |
| print(f"Total individual motion files created: {total_individual_files}") |
| print(f"Processing time: {elapsed:.2f} seconds") |
|
|
| if successful > 0: |
| print(f"\nSuccess! Individual motion files are available in: {output_path}") |
| print(f"To use with motion_lib_base.py: motion_file = '{output_path}'") |
|
|
| return 0 if failed == 0 else 1 |
|
|
|
|
| if __name__ == "__main__": |
| sys.exit(main()) |
|
|