      
import requests
import json,glob
import os
import tarfile
import av
import io
import cv2
import numpy as np
import pandas as pd
from typing import Dict, List, Any
from enum import IntEnum
from datetime import datetime, timezone, timedelta
import multiprocessing
from multiprocessing import Pool

from dotenv import load_dotenv
load_dotenv()
from pdcl_dss import DSSClient, Raw, Group, Clip

from pdcl_pyclip.reader import ClipReader
from pdcl_pyclip.msg_camera import VideoMessage
from pdcl_pyclip.decoder_struct import StructDecoder
from pdcl_pyclip.decoder_protobuf import ProtobufDecoder

os.environ['STS_UID']           = 'dis-uploader'
os.environ['STS_SECRET_KEY']    = '277310cc09724d315514a79701fecb0f'

client = DSSClient()

GET_JSON_BY_UKEY_API_URL = (
    "https://d.minieye.tech/dis-api/dashboard/calib-file-manage/json-files-by-ukey"
)
BEIJING_TZ = timezone(timedelta(hours=8))

det_cls_map = { 'kVehicle':0,
                    'kPed':1,
                    'kBike':2,
                    'kCyclist':3, 
                    'kCone':4,
                    'kRoadBarrier':4,
                    'kPedHead':5,
                    'kSmallTrafficSign': 6,
                    'kWarningTriangle' :6,
                    'kBigTrafficSign':7,
                    'kVehiclePlate' :8,
                    'kVehicleWheel':9,     
                    'kTrafficLight':10,      
                    'kTrafficLightBulb' :11,
                    'kTrafficLightDigit':12
                    }
posecls = {
            'kInvalid' : 0,       # 背景
            'kLeftTail' : 1 ,     # 左侧同向车
            'kMidTail' : 2,       # 中间同向车
            'kRightTail' : 3,     # 右侧同向车
            'kLeftHead' : 4,      # 左侧对向车
            'kMidHead' : 5,      # 中间对向车
            'kRightHead' : 6,     # 右侧对向车
            'kLeftSide' : 7,      # 朝左横向车
            'kRightSide' : 8,     # 朝右横向车
            'kLeftCutIn' : 9,     # 左侧切入
            'kRightCutIn' : 10,   # 右侧切入
            'kLeftCutOut' : 11,   # 左侧切出
            'kRightCutOut' : 12,  # 右侧切出
            'kOccluded' : 13,     # 车头遮挡/车尾遮挡/横向遮挡 (deprecated)
            'kSide' : 14,         # 横向车(不知道朝左，还是朝右)
            'kOccludedTail' : 15, # 车尾遮挡
            'kOccludedHead' : 16, # 车头遮挡
            'kOccludedSide' : 17, # 横向遮挡
            'kUnknownPose' : 20  # unknown pose
            }
subcls = {
        'kNegative' : 0,        # 背景
        'kBus' : 1,             # 大巴
        'kCar' : 2,             # 小轿车,suv
        'kMiniBus' : 3,         # 面包车
        'kBucketTruck' : 4,     # 斗卡
        'kContainerTruck' : 5,  # 箱卡
        'kTricycle' : 6,        # 三轮车
        'kTanker' : 7,          # 油罐车,晒水车（车身带有圆形,椭圆形,半圆形的罐）
        'kCementTankTruck' : 8, # 水泥罐车
        'kPickup' : 9,          # 皮卡
        'kSedimentTruck' : 10,  # 渣土车
        'kIveco' : 11,          # 依维柯
        'kSpecialCar' : 12,     # 异型车
        'kCityAuto' : 13,       # 市政车
        'kVehicleUnknown' : 14, # 未知车辆
        'kWrecker' : 15,        # 小型拖车，清障车
        'kFlatTransporter' : 16,# 大型平板拖车
        'kTractorHead' : 17,    # 卡车车头,牵引车车头
        'kSpecialFlatTransporter' : 18,  # 大型特种拖车（相对于大型平板拖车会多挡板）
        'kCarCarrier' : 19,  # 轿运车
        # 盖布车（车身上的货物全部被布盖着，同时对车辆尾部进行了遮挡）
        'kTruckWithTrap' : 20,
        'kEngineeringVehicle' : 21,  # 工程车
        'kSweeper' : 22,             # 扫地车
        'kRoadServiceCar' : 23,      # 道路维修车
        'kSanitationTruck' : 24,     # 环卫车
        'kGarbageTruck' : 25,        # 垃圾车
}

def timestamp_to_ymd(timestamp_ms: int) -> str:
    """
    将毫秒时间戳转换为北京时间 YYYYMMDD 格式
    例如：1712345678900 -> '20250406'
    """
    try:
        timestamp_s = timestamp_ms / 1000
        utc_time = datetime.fromtimestamp(timestamp_s, tz=timezone.utc)
        beijing_time = utc_time.astimezone(BEIJING_TZ)
        return beijing_time.strftime('%Y%m%d%H%M%S')
    except Exception as e:
        raise ValueError(f"无效毫秒时间戳 {timestamp_ms!r}: {e}") from e

def exist_product_in_group(group_meta, product_type):
    '''
    判断group是否存在某种真值产物
    '''
    if group_meta['failReason'] == '' and group_meta['products'] is not None:
        with Group(group_meta['id']) as group:
            if group.get_product(product_type) is not None:
                return True
    return False

def plane_to_ndarray(plane):
    """
    将 VideoPlane 对象转换为 NumPy 数组
    """
    # 获取平面的步长（stride）和行数
    stride = plane.line_size
    height = plane.height
    width = plane.width
    
    # 根据步长创建数组
    # 如果步长等于宽度，可以直接重塑
    if stride == width:
        array = np.frombuffer(plane, dtype=np.uint8).reshape(height, width)
    else:
        # 如果步长不等于宽度，需要处理填充字节
        array = np.frombuffer(plane, dtype=np.uint8)
        # 取每行的前width个字节
        array = array.reshape(height, stride)[:, :width]
    
    return array

def yuvj420p_to_nv12(y_plane, u_plane, v_plane):
    """
    将 YUVJ420P（3个平面）转换为 NV12（2个平面）格式。

    参数:
        y_plane (np.ndarray): 亮度Y平面，形状为 (height, width), dtype=uint8
        u_plane (np.ndarray): 色度U平面，形状为 (height\\\\2, width\\\\2), dtype=uint8  
        v_plane (np.ndarray): 色度V平面，形状为 (height\\\\2, width\\\\2), dtype=uint8

    返回:
        tuple: (y_plane_nv12, uv_plane_nv12)
            - y_plane_nv12: 亮度平面，与输入相同
            - uv_plane_nv12: 交织的UV平面，形状为 (height\\\\2, width)
    """
    # 1. Y平面直接复制（保持不变）
    y_plane_nv12 = y_plane.copy()
    
    # 2. 获取尺寸信息
    uv_height = u_plane.shape[0]  # height \\\\ 2
    uv_width = u_plane.shape[1]   # width \\\\ 2
    
    # 3. 创建目标UV平面，宽度是原UV宽度的2倍（因为要交错存放U和V）
    uv_plane_nv12 = np.zeros((uv_height, uv_width * 2), dtype=np.uint8)
    
    # 4. 将U和V平面数据交错地填入目标UV平面
    # UV平面的偶数索引位置放U，奇数索引位置放V
    uv_plane_nv12[:, 0::2] = u_plane  # 所有行，第0,2,4,...列 = U
    uv_plane_nv12[:, 1::2] = v_plane  # 所有行，第1,3,5,...列 = V
    
    return y_plane_nv12, uv_plane_nv12

def nv12_to_bgr_manual(y_plane, uv_plane):
    """
    手动将 NV12 转换为 BGR
    """
    yuv_image = np.concatenate((y_plane, uv_plane), axis=0)
    bgr_image = cv2.cvtColor(yuv_image, cv2.COLOR_YUV2BGR_NV12)  # 使用OpenCV进行YUV到BGR的转换
    
    return bgr_image

def cam_img_kb(x,y,z,cam_intrinsic,distort_param):
    # objp=np.array([x/z,y/z,1]).reshape(1,-1,3)
    # rvec = np.array([[[0., 0., 0.]]])
    # tvec = np.array([[[0., 0., 0.]]])
    # image_coord, _ = cv2.fisheye.projectPoints(objp, rvec, tvec,cam_intrinsic, distort_param)
    # image_coord = image_coord.reshape(-1)
    # return image_coord
    camxyz = np.array([x/z, y/z]).reshape(-1,2)
    a = camxyz[...,0]
    b = camxyz[...,1] 
    r = np.sqrt(a * a + b * b)
    theta = np.arctan(r)
    k1 = distort_param[0]
    k2 = distort_param[1]
    k3 = distort_param[2]
    k4 = distort_param[3]
    thetaD = theta * (1 + k1 * theta**2 + k2 * theta**4 + k3 * theta**6 + k4 * theta**8)
    xp = thetaD / r * a
    yp = thetaD / r * b
    fx = cam_intrinsic[0,0]
    fy = cam_intrinsic[1,1]
    cx = cam_intrinsic[0,2]
    cy = cam_intrinsic[1,2]
    imgx = (fx * xp + cx)
    imgy = (fy * yp + cy)
    # mask_2d = (imgx>=0)&(imgx<3840)&(imgy>=0)&(imgy<2160)
    pix_point = np.round(np.concatenate((imgx.reshape((-1,1)),imgy.reshape((-1,1))),axis=-1))
    # pix_point = pix_point[mask_2d].astype(np.int32)
    pix_point = pix_point.astype(np.int32)
    return pix_point.reshape(-1)
def xyz_xy(x,y,distort_param,K):
    r = np.sqrt(x * x + y * y)
    r_2 = r * r
    r_4 = r_2 * r_2
    r_6 = r_4 * r_2
    m_distort_coeffs = distort_param
    coeffs = np.zeros(12,dtype=np.float32)
    for i in range(min(m_distort_coeffs.shape[0],12)):
        coeffs[i] = m_distort_coeffs[i]

    radial_ratio = (1 + coeffs[0] * r_2 + coeffs[1] * r_4 + coeffs[4] * r_6) / (1 + coeffs[5] * r_2 + coeffs[6] * r_4 + coeffs[7] * r_6)
    res_x = x * radial_ratio + 2 * coeffs[2] * x * y + coeffs[3] * (r_2 + 2 * pow(x, 2)) + coeffs[8] * r_2 + coeffs[9] * r_4
    res_y = y * radial_ratio + 2 * coeffs[2] * (r_2 + 2 * pow(y, 2)) + coeffs[3] * x * y + coeffs[10] * r_2 + coeffs[11] * r_4
    image_coord = np.dot(K,np.array([res_x,res_y,1])).T
    return image_coord

def Lidar2OrigImg(pts3d, lidar_to_cam, distort_param, K, origW, origH):
    pts3d_cam = np.dot(lidar_to_cam[:3, :3],[pts3d[0],pts3d[1],pts3d[2]]) + lidar_to_cam[:3, 3]
    if len(distort_param)==5:
        pts2d_cam = xyz_xy(pts3d_cam[0] /pts3d_cam[2], pts3d_cam[1] / pts3d_cam[2], distort_param, K)
    else:
        pts2d_cam = cam_img_kb(pts3d_cam[0],pts3d_cam[1],pts3d_cam[2],K,distort_param)
    # pts2d_cam = cam_img_kb(pts3d_cam[0],pts3d_cam[1],pts3d_cam[2],K,distort_param)
    # pts2d_cam = xyz_xy(pts3d_cam[0] / pts3d_cam[2],pts3d_cam[1] / pts3d_cam[2],distort_param,K)
    pts2d_cam = pts2d_cam[0:2]
    pts2d_cam[0] = np.clip(pts2d_cam[0],0,origW-1)
    pts2d_cam[1] = np.clip(pts2d_cam[1],0,origH-1)
    return pts2d_cam, pts3d_cam

def show_group_uuid(raw_id: str) -> None:
    '''
    通过 raw_id 列出所有通过清洗的 group_uuid
    '''
    with Raw(raw_id) as raw:
        group_uuid_list = raw.list_group_ukeys()
        for group_uuid in group_uuid_list:
            print(group_uuid)


def call_api(body: Dict[str, str], url: str) -> Dict[str, Any]:
    """
    调用标定文件管理API

    Args:
        body: 请求体参数，包含plate_number
        url: API接口地址

    Returns:
        API返回的完整响应数据
    """
    headers = {
        "User-Agent": "Apifox/1.0.0 (https://apifox.com)",
        "Content-Type": "application/json",
        "Accept": "*/*",
        "Host": "d.minieye.tech",
        "Connection": "keep-alive",
    }

    try:
        response = requests.post(url, headers=headers, json=body)
        response.raise_for_status()  # 检查HTTP错误
        return response.json()
    except requests.exceptions.RequestException as e:
        print(f"请求失败: {e}")
        return {}
    except json.JSONDecodeError as e:
        print(f"JSON解析失败: {e}")
        return {}
    
    
def wirite_calib_by_ukey(ukey: str, output_path: str) -> None:
    """
    通过 ukey 获取标定信息并写入本地文件
    """
    full_info = call_api(ukey, GET_JSON_BY_UKEY_API_URL)["json_files_content"]
    for key, value in full_info.items():
        result_dir = os.path.join(output_path, os.path.dirname(key))
        os.makedirs(result_dir, exist_ok=True)
        with open(os.path.join(output_path, key), 'w') as fw:
            json.dump(value, fw, indent=4)


def get_calib_info(group_uuid: str, output_path: str) -> None:
    '''
    通过 group_uuid 获取标定信息
    '''
    with Group(group_uuid) as group: 
        calib_ukey = group.meta.get('calib_filepath', None)
    if calib_ukey is None:
        print(f"未找到标定信息 for group_uuid: {group_uuid}")
        return
    ukey = {"ukey": calib_ukey}
    wirite_calib_by_ukey(ukey, output_path)

def getRoadCurve(textpath):
    Curuuids = []
    textlists = open(textpath,'r').readlines()
    for text in textlists:
        context = text.strip('\n').split(' ')
        uuid = context[0]
        frameid = context[1]
        couvature = float(context[2])
        if abs(couvature) < 150:
            if uuid not in Curuuids:
                Curuuids.append(uuid)
    return Curuuids
       
def find_index_by_timestamp(df: pd.DataFrame, camera_id: str, timestamp_ns: int) -> int:
        json_index = df[abs(df[f'{camera_id}'] - timestamp_ns) < 1e-3].index[0]
        return json_index


def parse_video(car_name: str, date_name: str, groupuuid: str, json_list: List, clip_paths: List, camera_id: str, df: pd.DataFrame, output_path: str) -> Dict:
    '''
    解析视频流，保存为图片
    '''
    print(f"  开始解析视频并保存图片...")

    def h265_to_image(frame):
        container = av.open(io.BytesIO(frame))
        for frame_yuvj420p in container.decode(video=0):
            y_plane = plane_to_ndarray(frame_yuvj420p.planes[0])
            u_plane = plane_to_ndarray(frame_yuvj420p.planes[1])
            v_plane = plane_to_ndarray(frame_yuvj420p.planes[2])

            # 转换为 NV12
            y_nv12, uv_nv12 = yuvj420p_to_nv12(y_plane, u_plane, v_plane)

            frame = nv12_to_bgr_manual(y_nv12, uv_nv12)
            return frame
        raise ValueError("解码失败")

    image_path = os.path.join(output_path, 'images')
    os.makedirs(image_path, exist_ok=True)

    struct_decoder = StructDecoder()
    saved_count = 0
    for clip_path in clip_paths:
        reader = ClipReader(clip_path)
        for schema, channel, msg in reader.iter_messages(topics=[camera_id]):
            if schema.encoding == "struct":
                data = struct_decoder.decode(schema, channel, msg)
                if not isinstance(data, VideoMessage):
                    continue
                camera_ts_ns = msg.log_time
                json_index = find_index_by_timestamp(df, camera_id, camera_ts_ns)
                if json_list is not None:
                    if json_index not in json_list:
                        continue
                frame = data.payload
                img = h265_to_image(frame)
                # filepath = os.path.join(image_path, f"{json_index:06d}.jpg")

                filepath = image_path + '/' + '%s_%s_%s_%06d_%d.png'%(car_name, date_name, groupuuid, json_index,df['camera4_frame_id'][json_index])

                # 使用OpenCV保存图片
                cv2.imwrite(str(filepath), img)
                saved_count += 1

    print(f"  保存了 {saved_count} 张图片到 {image_path}")
                
                
def get_clip_paths(group_uuid: str) -> List[str]:
    '''
    获取 group 下的所有 clip 路径
    '''
    clip_path_list = []
    with Group(group_uuid) as group:
        for clip_ukey in group.list_clip_ukeys():
            clip = Clip(clip_ukey)
            files, raw_files = clip.list_files()
            cache_file_path = clip.get_cache_path(files[0])
            clip_path_list.append(cache_file_path)
    return clip_path_list
                

class NumericType(IntEnum):
    UNKNOWN = 0
    UINT8 = 1
    INT8 = 2
    UINT16 = 3
    INT16 = 4
    UINT32 = 5
    INT32 = 6
    FLOAT32 = 7
    FLOAT64 = 8


def get_frame_timestamps_data_from_tar(group_uuid: str) -> pd.DataFrame:
    with Group(group_uuid) as group:
        # 获取时间同步表
        with group.open('clean_data_archive.tar', mode="rb") as f:
            tar = tarfile.open(fileobj=f)
            csv_obj = tar.extractfile(tar.getmember("frame_timestamps.csv"))
            df = pd.read_csv(csv_obj)
    return df


def load_point_cloud(json_index, df, clip_path_list):
    # 类型映射表
    type_mapping = {
        NumericType.FLOAT32: 'f4', NumericType.FLOAT64: 'f8',
        NumericType.UINT8: 'u1', NumericType.UINT16: 'u2', NumericType.UINT32: 'u4',
        NumericType.INT8: 'i1', NumericType.INT16: 'i2', NumericType.INT32: 'i4'
    }
    
    # 根据时间戳搜索点云数据
    protobuf_decoder = ProtobufDecoder()
    lidar_ts_ns = df.loc[json_index, 'Pandar128']
    start_ts_ns = lidar_ts_ns - 1e9
    end_ts_ns = lidar_ts_ns + 1e9
    for clip_path in clip_path_list:
        reader = ClipReader(clip_path)
        for schema, channel, msg in reader.iter_messages(topics=['Pandar128'], start_time=start_ts_ns, end_time=end_ts_ns):
            if schema.encoding == "protobuf":
                data = protobuf_decoder.decode(schema, channel, msg)
                frame_ts = msg.log_time
                if abs(frame_ts -  lidar_ts_ns) < 1e3:
                    fields = data.fields
                    
                    # 1. 计算每个点的长度和类型
                    stride = 0
                    dtype_list = []
                    for field in fields:
                        dtype = type_mapping[field.type]
                        field_size = np.dtype(dtype).itemsize
                        stride = max(stride, field.offset + field_size)
                        dtype_list.append((field.name, type_mapping[field.type]))
                        
                    # 2. 解析二进制数据
                    num_points = len(data.data) // stride
                    data = np.frombuffer(data.data, dtype=np.dtype(dtype_list), count=num_points)          
                    
                    return np.vstack([data['x'], data['y'], data['z'], data['intensity']]).T
    raise Exception("未搜索到点云")


def parse_truth_data(group_uuid: str, curvelists: list, output_path: str, car_name: str, date_name: str, df: pd.DataFrame) -> None:
    '''
    解析各类真值数据
    '''
    def untar_file(group_uuid: str, output_path: str, tar_filename: str, product_type: str, car_name: str, date_name: str, df: pd.DataFrame) -> None:
        with Group(group_uuid) as group:
            if group.get_product(product_type) is not None:
                with group.open(tar_filename, mode="rb") as f:
                    tar = tarfile.open(fileobj=f)
                    # 只提取JSON文件和calib文件，跳过depth_map_fg
                    for member in tar.getmembers():
                        # 跳过深度图文件夹
                        if 'depth_map_fg' in member.name:
                            continue

                        # 优先判断calib文件（因为calib文件也可能是.json格式）
                        if 'calib' in member.name:
                            # 修改提取路径：将calib文件放到calib/目录
                            calib_filename = os.path.basename(member.name)
                            calib_dir = os.path.join(output_path, 'calib')
                            os.makedirs(calib_dir, exist_ok=True)
                            # 提取文件内容并保存
                            file_obj = tar.extractfile(member)
                            if file_obj:
                                with open(os.path.join(calib_dir, calib_filename), 'wb') as f_out:
                                    f_out.write(file_obj.read())
                        # 提取annotations JSON文件到annotations文件夹
                        elif 'camera_radar_lidar_jsons' in member.name and member.name.endswith('.json'):
                            # 提取文件名中的json_index
                            json_filename = os.path.basename(member.name)
                            try:
                                # 从文件名中提取json_index (例如 "000001.json" -> 1)
                                json_index = int(json_filename[:-5])  # 去掉.json后缀
                                # 获取对应的frame_id
                                frame_id = df['camera4_frame_id'][json_index]
                                # 使用和图片相同的命名格式
                                new_filename = '%s_%s_%s_%06d_%d.json' % (car_name, date_name, group_uuid, json_index, frame_id)

                                annotations_dir = os.path.join(output_path, 'annotations')
                                os.makedirs(annotations_dir, exist_ok=True)
                                # 提取文件内容并保存
                                file_obj = tar.extractfile(member)
                                if file_obj:
                                    with open(os.path.join(annotations_dir, new_filename), 'wb') as f_out:
                                        f_out.write(file_obj.read())
                            except (ValueError, KeyError, IndexError) as e:
                                print(f"警告: 无法处理文件 {member.name}, 错误: {e}")
            else:
                print(f"Group {group_uuid} does not have product {product_type}.")
    
    def gen_valid_file(group_uuid: str, curvelists: list, output_path: str, tar_filename: str, product_type: str) -> None:
        valid_list = []
        with Group(group_uuid) as group:
            if group.get_product(product_type) is not None:
                with group.open(tar_filename, mode="rb") as f:
                    with tarfile.open(fileobj=f, mode="r|*") as tar:
                        for member in tar:
                            # print("读取", member.name)
                            if 'calib/calib_pandar128_to_cam.json'  in member.name:
                                fr = tar.extractfile(member)
                                calib_param = json.load(fr)
                                lidar_to_cam = np.array(calib_param['camera4']["Extrinsic"])
                                cam_intrinsic = np.array(calib_param['camera4']["Intrinsic"])
                                distortion_param = np.array(calib_param['camera4']["Distortion"])
                            if 'camera_radar_lidar_jsons' not in member.name or '.json' not in member.name:
                                continue
                            ##########
                            valid_list.append(member.name)
                            # ##############curve####
                            # if len(curvelists) != 0:
                            #     if  group_uuid in curvelists:
                            #         valid_list.append(member.name)
                            #         continue
                            # #############
                            # fr = tar.extractfile(member)
                            # objs = json.load(fr)['asso_list']
                            # valid_obj = 0
                            # for obj in objs:
                            #     label_2d = obj['camera_mea']
                            #     label_det_cls = det_cls_map[label_2d['cls']]
                            #     pose = posecls.get(label_2d['pose'])
                            #     sublabel = subcls.get(label_2d['subcls'])
                            #     if label_det_cls != 0:
                            #         continue
                            #     label_lidar = obj['lidar_mea']
                            #     if label_lidar is None:
                            #         continue
                            #     obj_lidar = [float(label_lidar[1]),float(label_lidar[2]),float(label_lidar[3]),float(label_lidar[4]),float(label_lidar[5]),float(label_lidar[6]),float(label_lidar[7])]
                            #     cam2d,cam3dpoint = Lidar2OrigImg(np.array([obj_lidar[0],obj_lidar[1],obj_lidar[2]]), lidar_to_cam, distortion_param, cam_intrinsic, 1920, 1080)
                            #     if abs(cam3dpoint[0]) < 15 and abs(cam3dpoint[2]) < 80 and obj_lidar[3] > 5.5: ####大车
                            #         valid_obj += 1
                            #         valid_list.append(member.name)
                            #         break ##
                            #     # elif abs(cam3dpoint[0]) < 8 and abs(cam3dpoint[2]) < 8 : ####大车切车
                            #     #     valid_obj += 1
                            #     #     valid_list.append(member.name)
                            #     #     break ##
            else:
                print(f"Group {group_uuid} does not have product {product_type}.")
        return valid_list
                
    # product_type_list = [
    #     'auto-2d-box-label',
    #     'auto-3d-box-label',
    #     '2d-3d-association',
    #     'static-map'
    # ]
    product_type_list = [
        '2d-3d-association'
    ]
    for product_type in product_type_list:
        tar_filename = f"{product_type}_archive.tar"
        valid_list = gen_valid_file(group_uuid, curvelists, output_path, tar_filename, product_type)
        if len(valid_list) != 0:
            untar_file(group_uuid, output_path, tar_filename, product_type, car_name, date_name, df)
    return valid_list

def gettraindatagroupuuid(path):
    used_uuids = []
    imglists = glob.glob(path + '*/*/images/*.png')
    imglists.sort()
    for img in imglists:
        name = os.path.basename(img)
        co = name.split('_')
        uuid = co[3]
        if uuid not in used_uuids:
            used_uuids.append(uuid)
    return used_uuids

def process_single_uuid(args):
    """
    封装单个UUID的完整处理流程，作为多进程执行的任务函数
    :param args: (group_uuid, uuid_date_dict, vehicle_number)
    :return: 处理结果（成功/失败），便于后续统计
    """
    group_uuid, uuid_date_dict, vehicle_number = args

    # existpath = '/mnt/hfs/xdzhu/G1M3_PDCL/0105_1616/'
    # used_uuids = os.listdir(existpath)
    # existpath2 = '/mnt/hfs/xdzhu/G1M3_PDCL/0105_1616_2/'
    # used_uuids2 = os.listdir(existpath2)
    # if group_uuid not in used_uuids and group_uuid not in used_uuids2:

    # 1. 初始化变量（原循环内的逻辑）
    curvelists = []  # 每个进程独立初始化，避免多进程共享冲突
    date = uuid_date_dict[group_uuid][0]  # 取该UUID的第一个日期
    output_path = os.path.join(output, group_uuid)  # 更规范的路径拼接

    # 2. 获取时间同步表和clip路径（需要在parse_truth_data之前获取df）
    df = get_frame_timestamps_data_from_tar(group_uuid)
    clip_path_list = get_clip_paths(group_uuid)

    # 3. 解析真值数据（传递vehicle_number, date, df参数以便重命名JSON文件）
    valid_list = parse_truth_data(group_uuid, curvelists, output_path, vehicle_number, date, df)

    # 4. 无有效数据则跳过
    if len(valid_list) == 0:
        print(f"UUID {group_uuid} 无有效数据，跳过")
        return (group_uuid, "success (no valid data)")
    
    # 5. 处理有效列表（修复原代码重复用i、追加元素的问题）
    valid_list.sort()
    valid_ids = []  # 单独定义ID列表，避免污染原valid_list
    for file_path in valid_list:
        name = os.path.basename(file_path)
        # 提取ID（假设文件名格式为 "xxx.id.json" 之类，-5 对应后缀长度）
        try:
            id = int(name[0:-5])
            valid_ids.append(id)
        except ValueError:
            print(f"UUID {group_uuid} 文件名 {name} 解析ID失败，跳过")
            continue
    
    # 6. 执行图像解码
    parse_video(vehicle_number, date, group_uuid, valid_ids, clip_path_list, 'camera4', df, output_path)
    
    print(f"UUID {group_uuid} 处理完成")
    return (group_uuid, "success")
    


if __name__  == "__main__":
    product_type = '2d-3d-association'  # 真值产物类型
    project_name = 'G1M3_0630'          # 项目名称

    CAR =  'G1M3_0630' #'G1M3_AFS1616' #'G1M3_0877' #'G1M3_AFS1616'  #'G1M3_FDL2232' 
    output = '/data/zschen/dm3d/'
    # output = '/home/xdzhu/Data/test0877/'
    
    groupuuidpath = '/data/zschen/dm3d/0630_total_20260111.txt'
    textlists = open(groupuuidpath,'r').readlines()
    textlists.sort()
    uuid_date = {}
    group_uuidlists = []
    for text in textlists:
        context = text.strip('\n').split(' ')
        date = context[1]
        # if date[0:8] == '20251129':
        #     continue
        if  context[0] not in group_uuidlists:
            group_uuidlists.append(context[0])
        if context[0] not in uuid_date:
            uuid_date[context[0]] = []
        uuid_date[context[0]].append(date)
    
    task_list = [(uuid, uuid_date, CAR) for uuid in group_uuidlists]

    cpu_count = multiprocessing.cpu_count()
    process_num = max(1, cpu_count - 2)  # 至少1个进程
    # process_num = 1
    
    print(f"开始多进程处理，共 {len(group_uuidlists)} 个UUID，进程数: {process_num}")
    
    # 创建进程池并执行
    with Pool(processes=process_num) as pool:
        # 映射任务到进程池，返回结果列表
        results = pool.map(process_single_uuid, task_list)
    
    # 3. 统计处理结果
    success_count = 0
    fail_count = 0
    try:
        for uuid, res in results:
            if res.startswith("success"):
                success_count += 1
            else:
                fail_count += 1
    except:
        pass
    
    print(f"\n处理完成！总计: {len(group_uuidlists)} | 成功: {success_count} | 失败: {fail_count}")
    # 可选：打印失败的UUID详情
    try:
        fail_details = [(uuid, res) for uuid, res in results if not res.startswith("success")]
        if fail_details:
            print("失败详情:")
            for uuid, res in fail_details:
                print(f"  {uuid}: {res}")
    except:
        pass

    