Package: bm-video-tools
bm-video-tools针对图片、视频、音频常见操作的简易封装快速开始1.依赖ffmpeg 4.4+python >= 3.82.安装pipinstallbm-video-tools3.基础使用(1) 操作类该类是对ffmpeg指令相关选项的封装frombm_video_toolsimportOperateop=Operate()裁剪方法：cut(width, height, x, y)参数：width: 裁剪宽度height: 裁剪高度x: 裁剪的坐标点（默认左上角）y: 裁剪的坐标点（默认左上角）返回：Self示例：op=Operate().cut(300,300)如果不指定高度但指定坐标点，则裁剪坐标点到图像右下角的区域op=Operate().cut(x=100,y=100)缩放方法：scale(width, height)参数：width: 缩放的宽度height: 缩放的高度返回：Self示例：op=Operate().scale(200,100)如果指定宽度和高度，则按照指定的值进行缩放如果指定其中一个值，另一个为-1，则按照指定值等比例缩放op=Operate().scale(200,-1)# 缩放宽度为200，高度等比例缩放旋转方法：rotate(unit)参数：unit: 旋转角度返回：Self示例：op=Operate().rotate(1)# 旋转90度op=Operate().rotate(2)# 旋转180度op=Operate().rotate(3)# 旋转270度op=Operate().rotate(-1)# 旋转270度(逆时针旋转90度)翻转方法：turn(h, v)参数：h: 水平翻转v: 垂直翻转返回：Self示例：op=Operate().turn(v=True)拆分方法：split(start, end, second)参数：start: 开始时间end: 结束时间second: 截取时长（秒）返回：Self示例：op=Operate().split(5,second=5)剔除方法：eliminate(a, v)参数：a: 剔除音频流v: 剔除视频流返回：Self示例：op=Operate().eliminate(a=True)倒放方法：reverse()参数：无返回：Self示例：op=Operate().reverse()倍速方法：speed(rate)参数：rate: 速率（范围0.5~2.0）返回：Self示例：op=Operate().speed(0.5)修改参数方法：params(...)参数：pix_fmt: 像素格式codec_v: 视频编码器fps: 视频帧速率frames_v: 视频总帧数codec_a: 音频编码器sample: 音频采样率frames_a: 音频总帧数返回：Self示例：op=Operate().params(fps=30)理论上，音视频流相关格式参数都可修改，当前只列举了一些常见的修改参数，该方法会根据后续实际业务不断更新注意：Operate的成员方法返回的都是实例对象自身，这意味着这些方法可以链式调用：op=Operate().rotate(2).turn(h=True).rotate(1).reverse()op=Operate().split(5,15).cut(200,200)但是，这些方法其实并没有实质性的作用，需与下面几个类共同使用(2) 图片frombm_video_toolsimportImageimg=Image(input_path)参数：input_path：文件输入路径执行基础操作方法：run(op, output_path)参数：op: Operation实例对象output_path：输出路径返回：Image示例：img=Image('xxx')op=Operate().cut(300,300,0,0)awaitimg.run(op,output_path='xxx')查看图片信息方法：get_info()参数：无返回：dict示例：info=awaitimg.get_info(){'streams':[// 媒体流{'index':0,// 媒体流索引'codec_name':'mjpeg',// 编码器名'codec_long_name':'MotionJPEG',// 编码器全名'profile':'Baseline',// 编码器配置文件'codec_type':'video',// 流的类型'codec_tag_string':'[0][0][0][0]',// 流使用的编码器的标识符字符串'codec_tag':'0x0000',// 流使用的编码器的标识符整数值'width':1024,// 流的宽度'height':782,// 流的高度'coded_width':1024,// 流编码时的宽度'coded_height':782,// 流编码时的高度'closed_captions':0,// 流是否包含了有关视频注释和字幕的信息(这个参数只对视频流有效)'film_grain':0,// 流是否添加了电影颗粒噪点'has_b_frames':0,// 流是否存在B帧'sample_aspect_ratio':'1:1',// 采样宽高比'display_aspect_ratio':'512:391',// 显示宽高比'pix_fmt':'yuvj420p',// 像素格式'level':-99,// 流的级别'color_range':'pc',// 颜色范围(tv: 表示颜色采用了 16-235 范围，pc: 表示颜色采用了 0-255 范围)'color_space':'bt470bg',// 颜色空间'chroma_location':'center',// 色度坐标的放置位置'refs':1,// 编码器中参考图像的数量'r_frame_rate':'25/1',// 码率的分子和分母'avg_frame_rate':'25/1',// 平均帧率'time_base':'1/25',// 时间戳的基本时间单位'start_pts':0,// 首个PTS'start_time':'0.000000',// 流的开始时间'duration_ts':1,// 流总时长的时间戳'duration':'0.040000',// 流总时长的秒数'bits_per_raw_sample':'8',// 原始采样数据每个样本所占用的位数(音频通常为16、24，视频通常为8、10、12)'disposition':{// 流状态信息'default':0,'dub':0,'original':0,'comment':0,'lyrics':0,'karaoke':0,'forced':0,'hearing_impaired':0,'visual_impaired':0,'clean_effects':0,'attached_pic':0,'timed_thumbnails':0,'captions':0,'descriptions':0,'metadata':0,'dependent':0,'still_image':0}}],'format':{// 容器格式信息'filename':'./asset/image1.jpg',// 输入文件的路径或网络URL地址'nb_streams':1,// 媒体文件中流的数量'nb_programs':0,// 媒体文件中程序的数量'format_name':'image2',// 媒体封装格式的名称'format_long_name':'image2sequence',// 媒体封装格式的全名'start_time':'0.000000',// 从媒体中哪个时间点开始进行分析'duration':'0.040000',// 媒体的总时长'size':'217901',// 媒体文件的大小'bit_rate':'43580200',// 媒体文件中平均每秒使用的总比特率'probe_score':50// 判定该媒体文件是正确格式的可能性得分(分值范围为 0~100)}}人物抠图方法：remove(output_path, model_name[, op])参数：output_path: 输出路径（可选）model_name: 模型名称（默认u2net）（可选）op: Operation实例对象返回：Image示例：img=Image('xxx')awaitimg.remove('xxx')人物抠图功能是基于backgroundremover项目实现的可选模型：u2netp、u2net、u2net_human_seg请从项目中下载模型至操作系统当前用户的.bm-video-tools文件夹，否则无法使用抠图功能有时候，我们需要对媒体资源进行一些前置操作，然后再执行相应方法，例如：img=Image('xxx')op=Operate().cut(200,200)# 使用临时文件执行操作，对操作后的临时文件进行抠图temp_file=tempfile.NamedTemporaryFile(delete=False,suffix='.png')temp_file_path=temp_file.name.replace("\\","/")temp_file.close()try:img_res=awaitimg.run(op,output_path=temp_file_path)awaitimg_res.remove('xxx')delimg_resfinally:ifos.path.exists(temp_file_path):os.remove(temp_file_path)但对于使用者来说，并不需要关注这些中间状态以及临时文件，上述代码等同于：img=Image('xxx')op=Operate().cut(200,200)awaitvideo.remove("xxx",op=op)# 注意: 该Operate实例对象是对输入文件进行处理，而非输出文件人脸检测方法：face_detection(model_name[, op])参数：（可选）model_name: 模型名称（默认haarcascade_frontalface_alt2）（可选）op: op: Operation实例对象返回：bool示例：img=Image(image1_path)awaitimg.face_detection()人脸检测使用的是OpenCV的检测方法可选模型：haarcascade_frontalface_alt2、haarcascade_fullbody等和人物抠图一样，需将xml文件保存至操作系统当前用户的.bm-video-tools文件夹，否则无法正常使用(3) 视频frombm_video_toolsimportVideovideo=Video(input_path)参数：input_path：文件输入路径执行基础操作方法：run(op, output_path)参数：op: Operation实例对象output_path：输出路径返回：Video示例：video=Video("xxx")op=Operate().turn(v=True)awaitvideo.run(op,output_path="xxx")查看视频信息方法：get_info()参数：无返回：dict示例：info=awaitvideo.get_info(){'streams':[{'index':0,'codec_name':'h264','codec_long_name':'H.264/AVC/MPEG-4AVC/MPEG-4part10','profile':'ConstrainedBaseline','codec_type':'video','codec_tag_string':'avc1','codec_tag':'0x31637661','width':320,'height':240,'coded_width':320,'coded_height':240,'closed_captions':0,'film_grain':0,'has_b_frames':0,'pix_fmt':'yuv420p','level':13,'color_range':'tv','color_space':'smpte170m','color_transfer':'bt709','color_primaries':'smpte170m','chroma_location':'left','field_order':'progressive','refs':1,'is_avc':'true','nal_length_size':'4','id':'0x1','r_frame_rate':'30000/1001','avg_frame_rate':'93500/3153','time_base':'1/90000','start_pts':0,'start_time':'0.000000','duration_ts':1135080,'duration':'12.612000','bit_rate':'80637','bits_per_raw_sample':'8','nb_frames':'374','extradata_size':40,'disposition':{'default':1,'dub':0,'original':0,'comment':0,'lyrics':0,'karaoke':0,'forced':0,'hearing_impaired':0,'visual_impaired':0,'clean_effects':0,'attached_pic':0,'timed_thumbnails':0,'captions':0,'descriptions':0,'metadata':0,'dependent':0,'still_image':0},'tags':{'creation_time':'2010-05-11T10:32:06.000000Z','language':'und','vendor_id':'[0][0][0][0]','encoder':'JVT/AVCCoding'}},{'index':1,'codec_name':'aac','codec_long_name':'AAC(AdvancedAudioCoding)','profile':'LC','codec_type':'audio','codec_tag_string':'mp4a','codec_tag':'0x6134706d','sample_fmt':'fltp','sample_rate':'48000','channels':2,'channel_layout':'stereo','bits_per_sample':0,'initial_padding':0,'id':'0x2','r_frame_rate':'0/0','avg_frame_rate':'0/0','time_base':'1/48000','start_pts':0,'start_time':'0.000000','duration_ts':605184,'duration':'12.608000','bit_rate':'115752','nb_frames':'591','extradata_size':2,'disposition':{'default':1,'dub':0,'original':0,'comment':0,'lyrics':0,'karaoke':0,'forced':0,'hearing_impaired':0,'visual_impaired':0,'clean_effects':0,'attached_pic':0,'timed_thumbnails':0,'captions':0,'descriptions':0,'metadata':0,'dependent':0,'still_image':0},'tags':{'creation_time':'2010-05-11T10:32:06.000000Z','language':'und','vendor_id':'[0][0][0][0]'}},{'index':2,'codec_name':'bin_data','codec_long_name':'binarydata','codec_type':'data','codec_tag_string':'text','codec_tag':'0x74786574','id':'0x3','r_frame_rate':'0/0','avg_frame_rate':'0/0','time_base':'1/90000','start_pts':0,'start_time':'0.000000','duration_ts':1135080,'duration':'12.612000','bit_rate':'8','nb_frames':'1','extradata_size':43,'disposition':{'default':0,'dub':0,'original':0,'comment':0,'lyrics':0,'karaoke':0,'forced':0,'hearing_impaired':0,'visual_impaired':0,'clean_effects':0,'attached_pic':0,'timed_thumbnails':0,'captions':0,'descriptions':0,'metadata':0,'dependent':0,'still_image':0},'tags':{'creation_time':'2010-05-11T10:32:06.000000Z','language':'und'}}],'format':{'filename':'./asset/video1.mp4','nb_streams':3,'nb_programs':0,'format_name':'mov,mp4,m4a,3gp,3g2,mj2','format_long_name':'QuickTime/MOV','start_time':'0.000000','duration':'12.612000','size':'318465','bit_rate':'202007','probe_score':100,'tags':{'major_brand':'mp42','minor_version':'0','compatible_brands':'mp42isomavc1','creation_time':'2010-05-11T10:32:06.000000Z','encoder':'HandBrake0.9.42009112300'}}}视频截图方法：screenshot(output_path[, op])参数：output_path：图片输出路径（可选）op: Operation实例对象返回：Image示例：img=awaitvideo.screenshot("xxx")人物抠图方法：remove(output_path, model_name, worker_nodes, gpu_batch_size, frame_limit, frame_rate[, op])参数：output_path: 输出路径（可选）model_name: 模型名称（可选）frame_limit: 总帧数（可选）frame_rate: 帧率（可选）timeout: 超时时间（秒）（可选）op: Operation实例对象返回：Video示例：video=Video(video3_path)awaitvideo.remove('xxx.webm')可选模型：u2netp、u2net、u2net_human_seg注意：一般情况下，frame_limit和frame_rate无需输入，除非无法正常获取视频总帧数和帧率，可手动指定工具会识别GPU是否可用，若可用，则使用GPU进行算法处理，否则使用CPU工具默认安装torch和torchvision版本如下：torch~=2.0.1
torchvision~=0.15.2请根据设备CUDA版本安装对应的依赖版本，否则可能识别不到CUDA当前仅允许输出mov和webm格式文件。若输出webm格式，请确认是否存在libvpx-vp9编码器：ffmpeg-codecs|grepvp9默认编译安装的版本可能缺少该编码器，可尝试如下安装：# 安装libvpxapt-getinstalllibvpx-dev# 下载ffmpeg安装包wgethttps://ffmpeg.org/releases/ffmpeg-4.4.tar.gz# 解压tar-xzvfffmpeg-4.4.tar.gz# 进入文件夹cdffmpeg-4.4# 配置编译并安装./configure--enable-libvpx&&make&&makeinstall推荐传入timeout以防止意外情况所产生的僵尸进程。该参数仅控制子进程运行的最大时间，并不能准确控制完整流程的超时时间人脸检测方法：face_detection(model_name, proportion, worker_nodes[, op])参数：（可选）model_name: 模型名称（默认haarcascade_frontalface_alt2）（可选）proportion: 人脸出现占比（0~1，默认0.9）（可选）timeout: 超时时间（秒）（可选）op: op: Operation实例对象返回：bool示例：video=Video("xxx")awaitvideo.face_detection()awaitvideo.face_detection(worker_nodes=2)可选模型：haarcascade_frontalface_alt2、haarcascade_fullbody等视频拼接方法：concat(output_path, video_list, audio, logger)参数：output_path: 输出路径video_list: 视频列表{"media":Video实例对象,"op":Operate实例对象（可选）}（可选）audio: 音频如果传递该参数，则使用该音频作为最终合成视频的音频流（可选）logger: 是否打印进度返回：Video示例：# 视频倒放+拼接video=Video("xxx")op=Operate().reverse()awaitVideo.concat('xxx',[{"media":video},{"media":video,"op":op}])注意：该方法为静态方法，使用Video.调用视频合成方法：composite(output_path, media_list, audio, canvas, logger)参数：output_path: 输出路径media_list: 视频/图片列表{"media":Video/Image实例对象,"op":Operate实例对象（可选）,"start":开始时间（可选）,"end":结束时间（可选）,"duration":持续时间（可选）,"x":在画布中的坐标点（可选）,"y":在画布中的坐标点（可选）}（可选）audio: 音频如果传递该参数，则使用该音频作为最终合成视频的音频流（可选）canvas: 画布大小在默认情况下，合成的视频和第一个剪辑的尺寸相同，但是如果你想让你的剪辑在更大的合成视频里浮动，可以使用该参数：composite(...,canvas=(1280,720))（可选）logger: 是否打印进度返回：Video示例：video1=Video(video1_path)video2=Video(video2_path)image1=Image(image1_path)awaitVideo.composite(f'{output_path}/video_composite.mp4',[{"media":video2},{"media":video1,"start":5},{"media":image1,"start":3,"x":50,"y":50}])注意：该方法为静态方法，使用Video.调用如果是Image实例对象，默认显示10s，若不满足要求，请指定该媒体对象的持续时间(4) 音频frombm_video_toolsimportAudioaudio=Audio(input_path)参数：input_path：文件输入路径执行基础操作方法：run(op, output_path)参数：op: Operation实例对象output_path：输出路径返回：Audio示例：audio=Audio("xxx")op=Operate().params(sample='44100')awaitaudio.run(op,output_path="xxx")查看音频信息方法：get_info()参数：无返回：dict示例：info=awaitaudio.get_info()音频拼接方法：concat(output_path, audio_list, logger)参数：output_path: 输出路径audio_list: 音频列表{"media":Audio实例对象,"op":Operate实例对象}（可选）logger: 是否打印进度返回：Audio示例：audio1=Audio(audio1_path)audio2=Audio(audio2_path)awaitAudio.concat(f'{output_path}/audio_concat.mp3',[{"media":audio1},{"media":audio2}])注意：该方法为静态方法，使用Audio.调用音频合成方法：composite(output_path, audio_list, logger)参数：output_path: 输出路径audio_list: 音频列表{"media":Audio实例对象,"op":Operate实例对象（可选）,"start":音频开始时间（可选）,"end":音频结束时间（可选）,"duration":持续时间（可选）,}（可选）logger: 是否打印进度返回：Audio示例：audio1=Audio(audio1_path)audio2=Audio(audio2_path)awaitAudio.composite(f'{output_path}/audio_composite.mp3',[{"media":audio1,"start":5},{"media":audio2}])注意：该方法为静态方法，使用Audio.调用4.异常类型异常类名称说明ToolsRuntimeError运行异常ToolsTimeoutError超时异常ToolsFileError文件读取异常ToolsValueError值异常引用方法：frombm_video_tools.errorimportToolsRuntimeError,ToolsValueError,ToolsTimeoutError,ToolsFileError