怎么做网站卖车,wordpress菜单导航代码,如何选择网站域名,网站建设 服务器第30章 素材获取服务
30.1 概述
素材获取服务是剪映小助手的基础功能模块#xff0c;主要负责获取各种媒体素材的信息#xff0c;包括音频时长、图片动画效果、文字动画效果等。该服务通过分析媒体文件的内容#xff0c;为视频编辑提供必要的素材信息支持。服务支持多种媒体…第30章 素材获取服务30.1 概述素材获取服务是剪映小助手的基础功能模块主要负责获取各种媒体素材的信息包括音频时长、图片动画效果、文字动画效果等。该服务通过分析媒体文件的内容为视频编辑提供必要的素材信息支持。服务支持多种媒体格式采用异步处理方式确保高效和稳定的性能表现。30.2 音频时长获取服务30.2.1 核心实现音频时长获取服务的核心实现位于src/service/get_audio_duration.py文件中asyncdefget_audio_duration(request:GetAudioDurationRequest)-GetAudioDurationResponse:获取音频时长logger.info(f获取音频时长:{request.mp3_url})# 参数验证ifnotrequest.mp3_url:raiseValueError(音频URL不能为空)# 下载音频文件audio_fileawaitdownload_audio_file(str(request.mp3_url))ifnotaudio_file:raiseAUDIO_DOWNLOAD_FAILEDtry:# 获取音频时长durationawaitextract_audio_duration(audio_file)logger.info(f音频时长获取成功:{duration}微秒)returnGetAudioDurationResponse(durationduration)finally:# 清理临时文件ifos.path.exists(audio_file):os.remove(audio_file)logger.debug(f清理临时文件:{audio_file})30.2.2 音频文件下载下载音频文件到临时目录asyncdefdownload_audio_file(mp3_url:str)-str:下载音频文件try:# 创建临时文件temp_dirtempfile.gettempdir()file_extensionget_file_extension(mp3_url)temp_fileos.path.join(temp_dir,faudio_{uuid.uuid4().hex}.{file_extension})logger.info(f开始下载音频文件:{mp3_url})# 下载文件asyncwithaiohttp.ClientSession()assession:timeoutaiohttp.ClientTimeout(total30)asyncwithsession.get(mp3_url,timeouttimeout)asresponse:ifresponse.status!200:raiseException(f下载失败状态码:{response.status})# 写入临时文件withopen(temp_file,wb)asf:asyncforchunkinresponse.content.iter_chunked(8192):f.write(chunk)# 验证文件大小file_sizeos.path.getsize(temp_file)iffile_size0:raiseException(下载的文件为空)logger.info(f音频文件下载成功:{temp_file}, 大小:{file_size}bytes)returntemp_fileexceptExceptionase:logger.error(f音频文件下载失败:{str(e)})ifos.path.exists(temp_file):os.remove(temp_file)raiseException(f音频文件下载失败:{str(e)})30.2.3 音频时长提取使用ffprobe提取音频时长asyncdefextract_audio_duration(audio_file:str)-int:提取音频时长try:# 构建ffprobe命令cmd[ffprobe,-v,quiet,-print_format,json,-show_format,-show_streams,audio_file]logger.info(f执行ffprobe命令:{ .join(cmd)})# 执行命令processawaitasyncio.create_subprocess_exec(*cmd,stdoutasyncio.subprocess.PIPE,stderrasyncio.subprocess.PIPE)stdout,stderrawaitprocess.communicate()ifprocess.returncode!0:error_msgstderr.decode(utf-8,errorsignore)logger.error(fffprobe执行失败:{error_msg})# 尝试备用方法returnawaitextract_duration_fallback(audio_file)# 解析JSON输出try:probe_datajson.loads(stdout.decode(utf-8))exceptjson.JSONDecodeErrorase:logger.error(fffprobe输出解析失败:{str(e)})returnawaitextract_duration_fallback(audio_file)# 查找音频流durationNoneforstreaminprobe_data.get(streams,[]):ifstream.get(codec_type)audio:# 优先使用流的时长ifdurationinstream:durationfloat(stream[duration])break# 如果没有流时长使用格式时长elifdurationinprobe_data.get(format,{}):durationfloat(probe_data[format][duration])breakifdurationisNone:logger.error(未找到音频时长信息)returnawaitextract_duration_fallback(audio_file)# 转换为微秒duration_microsecondsint(duration*1000000)logger.info(f音频时长:{duration}秒 {duration_microseconds}微秒)returnduration_microsecondsexceptExceptionase:logger.error(f音频时长提取失败:{str(e)})raiseException(f音频时长提取失败:{str(e)})30.2.4 备用时长提取方法当ffprobe失败时的备用方法asyncdefextract_duration_fallback(audio_file:str)-int:备用时长提取方法try:# 使用ffprobe的简化模式cmd[ffprobe,-v,error,-show_entries,formatduration,-of,defaultnoprint_wrappers1:nokey1,audio_file]processawaitasyncio.create_subprocess_exec(*cmd,stdoutasyncio.subprocess.PIPE,stderrasyncio.subprocess.PIPE)stdout,stderrawaitprocess.communicate()ifprocess.returncode0:durationfloat(stdout.decode(utf-8).strip())duration_microsecondsint(duration*1000000)logger.info(f备用方法获取音频时长:{duration_microseconds}微秒)returnduration_microsecondselse:raiseException(f备用方法也失败:{stderr.decode()})exceptExceptionase:logger.error(f备用时长提取方法失败:{str(e)})raiseException(f无法获取音频时长:{str(e)})30.3 图片动画获取服务30.3.1 图片入场动画获取图片的入场动画效果asyncdefget_image_animations(request:GetImageAnimationsRequest)-GetImageAnimationsResponse:获取图片出入场动画logger.info(f获取图片动画类型:{request.type}, 模式:{request.mode})# 参数验证ifnotrequest.type:raiseValueError(动画类型不能为空)# 获取动画列表animationsawaitload_image_animations(request.type,request.mode)# 过滤和排序filtered_animationsfilter_animations(animations,request)returnGetImageAnimationsResponse(effectsjson.dumps([anim.dict()foraniminfiltered_animations],ensure_asciiFalse))30.3.2 动画数据加载从素材库加载动画数据asyncdefload_image_animations(animation_type:str,mode:int)-List[ImageAnimationItem]:加载图片动画数据try:# 构建查询条件filters{type:animation_type,material_type:sticker,platform:all}# 根据模式过滤ifmode1:# VIPfilters[is_vip]Trueelifmode2:# 免费filters[is_free]True# 从数据库或缓存获取动画数据animation_dataawaitget_animation_data(image,filters)# 转换为模型对象animations[]fordatainanimation_data:animationImageAnimationItem(resource_iddata[resource_id],typedata[type],category_iddata[category_id],category_namedata[category_name],durationdata[duration],iddata[id],namedata[name],icon_urldata[icon_url],material_typedata.get(material_type,sticker),paneldata.get(panel,),pathdata.get(path,),platformdata.get(platform,all))animations.append(animation)logger.info(f加载到{len(animations)}个图片动画)returnanimationsexceptExceptionase:logger.error(f加载图片动画失败:{str(e)})raiseException(f加载图片动画失败:{str(e)})30.4 文字动画获取服务30.4.1 文字出入场动画获取文字的出入场动画效果asyncdefget_text_animations(request:GetTextAnimationsRequest)-GetTextAnimationsResponse:获取文字出入场动画logger.info(f获取文字动画类型:{request.type}, 模式:{request.mode})# 参数验证ifnotrequest.type:raiseValueError(动画类型不能为空)# 获取动画列表animationsawaitload_text_animations(request.type,request.mode)# 过滤和排序filtered_animationsfilter_animations(animations,request)returnGetTextAnimationsResponse(effectsjson.dumps([anim.dict()foraniminfiltered_animations],ensure_asciiFalse))30.4.2 文字动画特性文字动画的特殊处理defprocess_text_animation(animation:TextAnimationItem)-TextAnimationItem:处理文字动画的特殊属性# 文字动画通常需要更短的持续时间ifanimation.duration2000000:# 超过2秒animation.duration1500000# 调整为1.5秒# 设置文字动画的默认缓动函数ifnotanimation.path:animation.pathease_in_out# 根据动画类型调整参数ifanimation.typein:# 入场动画从透明到不透明animation.start0elifanimation.typeout:# 出场动画从不透明到透明animation.start500000# 延迟0.5秒开始elifanimation.typeloop:# 循环动画持续进行animation.start0returnanimation30.5 数据结构定义30.5.1 音频时长数据结构classGetAudioDurationRequest(BaseModel):获取音频时长请求参数mp3_url:HttpUrlField(...,description音频文件URL支持mp3、wav、m4a等常见音频格式)classConfig:json_schema_extra{example:{mp3_url:https://www.soundjay.com/misc/sounds/bell-ringing-05.wav}}classGetAudioDurationResponse(BaseModel):获取音频时长响应参数duration:intField(...,description音频时长单位微秒,ge0)30.5.2 图片动画数据结构classGetImageAnimationsRequest(BaseModel):获取图片出入场动画的请求模型mode:intField(default0,description动画模式0所有1VIP2免费)type:Literal[in,out,loop]Field(...,description动画类型in入场out出场loop循环)classImageAnimationItem(BaseModel):单个图片动画项的数据模型resource_id:strField(...,description动画资源ID)type:strField(...,description动画类型)category_id:strField(...,description动画分类ID)category_name:strField(...,description动画分类名称)duration:intField(...,description动画时长微秒)id:strField(...,description动画唯一标识ID)name:strField(...,description动画名称)request_id:strField(default,description请求ID)start:intField(default0,description动画开始时间)icon_url:strField(...,description动画图标URL)material_type:strField(defaultsticker,description素材类型)panel:strField(default,description面板信息)path:strField(default,description路径信息)platform:strField(defaultall,description支持平台)classGetImageAnimationsResponse(BaseModel):获取图片出入场动画的响应模型effects:strField(...,description图片出入场动画数组的JSON字符串)30.5.3 文字动画数据结构classGetTextAnimationsRequest(BaseModel):获取文字出入场动画的请求模型mode:intField(default0,description动画模式0所有1VIP2免费)type:Literal[in,out,loop]Field(...,description动画类型in入场out出场loop循环)classTextAnimationItem(BaseModel):单个文字动画项的数据模型resource_id:strField(...,description动画资源ID)type:strField(...,description动画类型)category_id:strField(...,description动画分类ID)category_name:strField(...,description动画分类名称)duration:intField(...,description动画时长微秒)id:strField(...,description动画唯一标识ID)name:strField(...,description动画名称)request_id:strField(default,description请求ID)start:intField(default0,description动画开始时间)icon_url:strField(...,description动画图标URL)material_type:strField(defaultsticker,description素材类型)panel:strField(default,description面板信息)path:strField(default,description路径信息)platform:strField(defaultall,description支持平台)classGetTextAnimationsResponse(BaseModel):获取文字出入场动画的响应模型effects:strField(...,description文字出入场动画数组的JSON字符串)30.6 异常处理素材获取服务定义了完善的异常处理机制# 音频下载失败AUDIO_DOWNLOAD_FAILEDHTTPException(status_code400,detail音频文件下载失败)# 音频时长提取失败AUDIO_DURATION_EXTRACTION_FAILEDHTTPException(status_code500,detail音频时长提取失败)# 动画数据加载失败ANIMATION_DATA_LOAD_FAILEDHTTPException(status_code500,detail动画数据加载失败)# 不支持的音频格式UNSUPPORTED_AUDIO_FORMATHTTPException(status_code400,detail不支持的音频格式)30.7 API接口定义30.7.1 音频时长接口router.post(/getAudioDuration,response_modelGetAudioDurationResponse)asyncdefget_audio_duration_endpoint(request:GetAudioDurationRequest):获取音频时长try:returnawaitget_audio_duration(request)exceptExceptionase:logger.error(f获取音频时长失败:{str(e)})raiseAUDIO_DURATION_EXTRACTION_FAILED30.7.2 图片动画接口router.post(/getImageAnimations,response_modelGetImageAnimationsResponse)asyncdefget_image_animations_endpoint(request:GetImageAnimationsRequest):获取图片出入场动画try:returnawaitget_image_animations(request)exceptExceptionase:logger.error(f获取图片动画失败:{str(e)})raiseANIMATION_DATA_LOAD_FAILED30.7.3 文字动画接口router.post(/getTextAnimations,response_modelGetTextAnimationsResponse)asyncdefget_text_animations_endpoint(request:GetTextAnimationsRequest):获取文字出入场动画try:returnawaitget_text_animations(request)exceptExceptionase:logger.error(f获取文字动画失败:{str(e)})raiseANIMATION_DATA_LOAD_FAILED30.8 使用示例30.8.1 音频时长请求示例{mp3_url:https://example.com/audio/background-music.mp3}30.8.2 音频时长响应示例{duration:180000000}30.8.3 动画获取请求示例{type:in,mode:0}30.8.4 动画获取响应示例{effects:[{\resource_id\:\anim_fade_in\,\type\:\in\,\category_id\:\basic\,\category_name\:\基础\,\duration\:1000000,\id\:\fade_in_001\,\name\:\淡入\,\icon_url\:\https://example.com/icons/fade_in.png\,\material_type\:\sticker\,\platform\:\all\}]}30.9 性能优化素材获取服务采用了多种性能优化策略30.9.1 缓存优化# 使用Redis缓存动画数据importaioredisclassAnimationCache:def__init__(self):self.redisNoneasyncdefinit_cache(self):self.redisawaitaioredis.create_redis_pool(redis://localhost:6379,encodingutf-8)asyncdefget_animation_data(self,key:str):获取缓存的动画数据cached_dataawaitself.redis.get(key)ifcached_data:returnjson.loads(cached_data)returnNoneasyncdefset_animation_data(self,key:str,data:dict,expire:int3600):设置动画数据缓存awaitself.redis.setex(key,expire,json.dumps(data))30.9.2 连接池优化# 使用连接池管理HTTP连接classHttpConnectionPool:def__init__(self):self.connectoraiohttp.TCPConnector(limit100,limit_per_host30,ttl_dns_cache300,use_dns_cacheTrue,keepalive_timeout30)self.sessionaiohttp.ClientSession(connectorself.connector)asyncdefdownload_file(self,url:str,timeout:int30):下载文件timeout_configaiohttp.ClientTimeout(totaltimeout)asyncwithself.session.get(url,timeouttimeout_config)asresponse:ifresponse.status200:returnawaitresponse.read()else:raiseException(f下载失败:{response.status})30.9.3 异步处理优化# 使用Semaphore限制并发数classAsyncLimiter:def__init__(self,max_concurrent:int10):self.semaphoreasyncio.Semaphore(max_concurrent)asyncdefacquire(self):awaitself.semaphore.acquire()defrelease(self):self.semaphore.release()asyncdef__aenter__(self):awaitself.acquire()returnselfasyncdef__aexit__(self,exc_type,exc_val,exc_tb):self.release()# 使用示例limiterAsyncLimiter(max_concurrent5)asyncdeflimited_download(url:str):asyncwithlimiter:returnawaitdownload_file(url)30.10 扩展性设计素材获取服务具有良好的扩展性媒体格式扩展易于添加新的媒体格式支持动画类型扩展支持动态添加新的动画类型数据源扩展支持从多个数据源获取素材信息缓存策略扩展支持自定义缓存策略和过期时间附录代码仓库地址GitHub:https://github.com/Hommy-master/capcut-mateGitee:https://gitee.com/taohongmin-gitee/capcut-mate接口文档地址API文档地址:https://docs.jcaigc.cn