InferenceWorker/BodyModelDownloads.cs
using HumanoidMocap.Inference;
using System.Security.Cryptography;
using System.Text.Json;

namespace HumanoidMocap.Worker;

public static class BodyModelDownloads
{
    record Asset(string Path,string Url,long Bytes,string Sha256);
    static readonly Asset[] Assets={
        new("models/person/person_detection_mediapipe_2023mar.onnx","https://media.githubusercontent.com/media/opencv/opencv_zoo/47534e27c9851bb1128ccc0102f1145e27f23f98/models/person_detection_mediapipe/person_detection_mediapipe_2023mar.onnx",11990159,PersonDetector.CheckpointSha256),
        new("models/gvhmr/gvhmr_siga24_release.ckpt","https://huggingface.co/camenduru/GVHMR/resolve/21b32d5389e2e59c0737d4c4095bbc0b8c23f66b/gvhmr/gvhmr_siga24_release.ckpt",163508011,"4fae7da2de388d5da3514cb27a2d003f364dacb280e9cf88972b710e589c6b91"),
        new("models/hmr2/hmr2.ckpt","https://huggingface.co/camenduru/GVHMR/resolve/21b32d5389e2e59c0737d4c4095bbc0b8c23f66b/hmr2/epoch%3D10-step%3D25000.ckpt",2709494041,"2dcf79638109781d1ae5f5c44fee5f55bc83291c210653feead9b7f04fa6f20e"),
        new("models/vitpose/vitpose-h-multi-coco.pth","https://huggingface.co/camenduru/GVHMR/resolve/21b32d5389e2e59c0737d4c4095bbc0b8c23f66b/vitpose/vitpose-h-multi-coco.pth",2549075546,"50e33f4077ef2a6bcfd7110c58742b24c5859b7798fb0eedd6d2215e0a8980bc"),
        new("models/moge/moge-2-vits-normal.pt","https://huggingface.co/Ruicheng/moge-2-vits-normal/resolve/26b477f41595707c5db6770294c0d1721e8ed4ed/model.pt",140550416,MogeLens.CheckpointSha256),
        new("models/smplx/SMPLX_NEUTRAL.npz","https://huggingface.co/camenduru/SMPLer-X/resolve/9e5548b70b48efe6992faa3b2a418df108542bf7/SMPLX_NEUTRAL.npz",108752058,"376021446ddc86e99acacd795182bbef903e61d33b76b9d8b359c2b0865bd992")
    };
    public static async Task Ensure(string modelFolder,CancellationToken token=default)
    {
        using var cancellation=CancellationTokenSource.CreateLinkedTokenSource(token);
        ConsoleCancelEventHandler handler=(_,e)=>{e.Cancel=true;cancellation.Cancel();};Console.CancelKeyPress+=handler;
        using var client=new HttpClient{Timeout=TimeSpan.FromHours(1)};
        try
        {
            foreach(var pinned in Assets)
            {
                var asset=pinned with{Path=System.IO.Path.Combine(modelFolder,pinned.Path.Substring(7))};
                cancellation.Token.ThrowIfCancellationRequested();Directory.CreateDirectory(System.IO.Path.GetDirectoryName(asset.Path)!);
                if(File.Exists(asset.Path))await Verify(asset.Path,asset,cancellation.Token);
                else await ModelDownload.Fetch(client,asset.Url,asset.Path,asset.Bytes,asset.Sha256,Console.WriteLine,cancellation.Token);
                Console.WriteLine("Verified "+System.IO.Path.GetFileName(asset.Path));
            }
            Directory.CreateDirectory(modelFolder);
            File.WriteAllText(System.IO.Path.Combine(modelFolder,"gvhmr-models.json"),JsonSerializer.Serialize(new{
                provenance="Pinned GVHMR model mirrors and OpenCV Zoo MediaPipe person detector; assets remain local",assets=Assets,
                totalBytes=Assets.Sum(x=>x.Bytes),verifiedUtc=DateTime.UtcNow},new JsonSerializerOptions{WriteIndented=true}));
        }
        finally{Console.CancelKeyPress-=handler;}
    }
    static async Task Verify(string path,Asset asset,CancellationToken cancellation)
    {
        if(new FileInfo(path).Length!=asset.Bytes)throw new InvalidDataException("Unexpected model size: "+path);
        var hash=await Task.Run(()=>FileChecksum.Sha256(path),cancellation);
        if(!hash.Equals(asset.Sha256,StringComparison.OrdinalIgnoreCase))throw new InvalidDataException("Model checksum mismatch; original file preserved: "+path);
    }
}