MP4转live图批量多线程版windows

查看 41|回复 4
作者:thisbug   
[Python] 纯文本查看 复制代码# mp4_to_live_photo.py
import os
import sys
import argparse
import logging
import concurrent.futures
from pathlib import Path
from datetime import datetime
import cv2
import imageio
from PIL import Image
import subprocess
import msvcrt
# 修复Windows中文路径问题
sys.getfilesystemencoding = lambda: 'utf-8'
# 日志配置
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] %(message)s',
    handlers=[
        logging.FileHandler('conversion.log', encoding='utf-8'),
        logging.StreamHandler()
    ]
)
def win_path_convert(path):
    """Windows路径兼容性处理"""
    return Path(path).resolve().as_posix()
def validate_environment():
    """环境检查与硬件加速支持检测"""
    try:
        result = subprocess.run(
            ['ffmpeg', '-hwaccels'],
            capture_output=True,
            text=True,
            check=True
        )
        if 'dxva2' not in result.stdout.lower():
            logging.warning(" 未检测到DXVA2硬件加速支持,性能将受影响")
    except FileNotFoundError:
        raise RuntimeError("请先安装FFmpeg并添加至PATH环境变量")
def generate_output_paths(src, output_root, platform):
    """生成标准化输出路径(含时间戳防重复)"""
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    src_stem = Path(src).stem[:30]  # 限制文件名长度
    base_name = f"{src_stem}_{timestamp}"
    output_root = Path(output_root) / platform
    output_root.mkdir(parents=True, exist_ok=True)
    if platform == 'ios':
        return (
            output_root / f"{base_name}.jpeg",
            output_root / f"{base_name}.mov"
        )
    else:
        return (
            output_root / f"{base_name}.jpg",
            output_root / f"{base_name}.jpg"
        )
def process_single_video(src, cover_path, video_path, platform):
    """单文件处理核心逻辑"""
    try:
        # 硬件加速解码配置
        ffmpeg_base = [
            'ffmpeg', '-hwaccel', 'dxva2',
            '-hwaccel_output_format', 'dxva2_vld',
            '-y', '-i', win_path_convert(src)
        ]
        # 关键帧提取
        cap = cv2.VideoCapture(win_path_convert(src))
        cap.set(cv2.CAP_PROP_POS_MSEC, 2000)  # 取2秒位置
        ret, frame = cap.read()
        if not ret:
            raise ValueError("视频关键帧提取失败")
        # 封面保存(带EXIF)
        with Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)) as img:
            exif_data = img.info.get('exif', b'')
            img.save(
                win_path_convert(cover_path),
                quality=95,
                exif=exif_data,
                subsampling=0  # 关闭色度抽样保持最佳质量
            )
        # 视频转码
        ffmpeg_cmd = ffmpeg_base + [
            '-ss', '0', '-t', '15',  # 截取前15秒
            '-vcodec', 'hevc', '-tag:v', 'hvc1',
            '-acodec', 'aac', '-b:a', '192k',
            '-vf', 'scale=ceil(iw/2)*2:ceil(ih/2)*2',  # 确保分辨率合规
            '-movflags', '+faststart',
            win_path_convert(video_path)
        ]
        subprocess.run(
            ffmpeg_cmd,
            check=True,
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL
        )
        # Android特殊封装
        if platform == 'android':
            with open(video_path, 'rb') as f:
                video_data = f.read()
            with Image.open(cover_path) as img:
                img.save(
                    video_path,
                    'jpeg',
                    quality=95,
                    save_all=True,
                    append_images=[Image.new('RGB', (1, 1))],
                    append_images_data=[video_data]
                )
        logging.info(f" 成功处理:{Path(src).name}")
        return True
    except Exception as e:
        logging.error(f" 处理失败:{Path(src).name} - {str(e)}")
        return False
def batch_convert(input_path, output_root, platform='ios', max_workers=4):
    """批处理主程序"""
    validate_environment()
    # 构建任务列表
    input_path = Path(input_path)
    if input_path.is_file():
        task_list = [input_path]
    else:
        task_list = list(input_path.rglob('*.mp4')) + list(input_path.rglob('*.mov'))
        # 进度跟踪
    total = len(task_list)
    success = 0
    # 多线程处理
    with concurrent.futures.ThreadPoolExecutor(
            max_workers=min(max_workers, os.cpu_count() * 2),
            thread_name_prefix='ConvWorker'
    ) as executor:
        futures = []
        for src in task_list:
            cover_path, video_path = generate_output_paths(src, output_root, platform)
            futures.append(
                executor.submit(
                    process_single_video,
                    str(src),
                    str(cover_path),
                    str(video_path),
                    platform
                )
            )
        for future in concurrent.futures.as_completed(futures):
            if future.result():
                success += 1
                # 生成报告
    logging.info("\n===  转换完成 ===")
    logging.info(f" 总处理文件:{total} 个")
    logging.info(f" 成功转换:{success} 个 ({success / total:.1%})")
    logging.info(f" 输出目录:{Path(output_root).resolve()}")
'''
# 安装依赖(管理员权限运行)
pip install opencv-python imageio Pillow
scoop install ffmpeg
# 转换单个文件(iOS)
python mp4_to_live_photo.py  "D:\My Videos\test.mp4"  -o "C:\LivePhotos"
# 批量转换目录(Android平台/8线程)
python mp4_to_live_photo.py  "E:\Camera" -p android -j 8
'''
if __name__ == "__main__":
    # 命令行参数解析
    parser = argparse.ArgumentParser(
        description='MP4批量转LivePhoto工具(Windows优化版)',
        formatter_class=argparse.ArgumentDefaultsHelpFormatter
    )
    parser.add_argument('input', help='输入文件/目录路径')
    parser.add_argument('-o', '--output', default='./LiveOutput',
                        help='输出目录路径')
    parser.add_argument('-p', '--platform', choices=['ios', 'android'],
                        default='ios', help='目标平台')
    parser.add_argument('-j', '--jobs', type=int,
                        default=os.cpu_count(), help='并行任务数')
    args = parser.parse_args()
    try:
        batch_convert(
            win_path_convert(args.input),
            win_path_convert(args.output),
            args.platform,
            args.jobs
        )
    except KeyboardInterrupt:
        logging.warning(" 用户中断操作!")
    except Exception as e:
        logging.critical(f" 致命错误:{str(e)}")

路径, 目录

童童1993   

虽说看不懂吧,但是还是要支持一下
wpdzdx   

MP4和图片有什么关联吗
sonny   


wpdzdx 发表于 2025-6-2 15:56
MP4和图片有什么关联吗

视频转实况图
lupaf   

搞个成品,期待
您需要登录后才可以回帖 登录 | 立即注册

返回顶部