@@ -6,8 +6,17 @@ | |||
<component name="ChangeListManager"> | |||
<list default="true" id="4f7dccd9-8f92-4a6e-90cc-33890d102263" name="Changes" comment="Changes"> | |||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> | |||
<change beforePath="$PROJECT_DIR$/common/Constant.py" beforeDir="false" afterPath="$PROJECT_DIR$/common/Constant.py" afterDir="false" /> | |||
<change beforePath="$PROJECT_DIR$/util/ModelUtils.py" beforeDir="false" afterPath="$PROJECT_DIR$/util/ModelUtils.py" afterDir="false" /> | |||
<change beforePath="$PROJECT_DIR$/concurrency/FileUploadThread.py" beforeDir="false" afterPath="$PROJECT_DIR$/concurrency/FileUploadThread.py" afterDir="false" /> | |||
<change beforePath="$PROJECT_DIR$/concurrency/IntelligentRecognitionProcess.py" beforeDir="false" afterPath="$PROJECT_DIR$/concurrency/IntelligentRecognitionProcess.py" afterDir="false" /> | |||
<change beforePath="$PROJECT_DIR$/concurrency/PullVideoStreamProcess.py" beforeDir="false" afterPath="$PROJECT_DIR$/concurrency/PullVideoStreamProcess.py" afterDir="false" /> | |||
<change beforePath="$PROJECT_DIR$/dsp_master.py" beforeDir="false" afterPath="$PROJECT_DIR$/dsp_master.py" afterDir="false" /> | |||
<change beforePath="$PROJECT_DIR$/enums/ExceptionEnum.py" beforeDir="false" afterPath="$PROJECT_DIR$/enums/ExceptionEnum.py" afterDir="false" /> | |||
<change beforePath="$PROJECT_DIR$/service/Dispatcher.py" beforeDir="false" afterPath="$PROJECT_DIR$/service/Dispatcher.py" afterDir="false" /> | |||
<change beforePath="$PROJECT_DIR$/test/ffmpeg11/aa.py" beforeDir="false" afterPath="$PROJECT_DIR$/test/ffmpeg11/aa.py" afterDir="false" /> | |||
<change beforePath="$PROJECT_DIR$/test/日志/test.py" beforeDir="false" afterPath="$PROJECT_DIR$/test/日志/test.py" afterDir="false" /> | |||
<change beforePath="$PROJECT_DIR$/test/线程/Test.py" beforeDir="false" afterPath="$PROJECT_DIR$/test/线程/Test.py" afterDir="false" /> | |||
<change beforePath="$PROJECT_DIR$/util/AliyunSdk.py" beforeDir="false" afterPath="$PROJECT_DIR$/util/AliyunSdk.py" afterDir="false" /> | |||
<change beforePath="$PROJECT_DIR$/util/Cv2Utils.py" beforeDir="false" afterPath="$PROJECT_DIR$/util/Cv2Utils.py" afterDir="false" /> | |||
</list> | |||
<option name="SHOW_DIALOG" value="false" /> | |||
<option name="HIGHLIGHT_CONFLICTS" value="true" /> | |||
@@ -136,11 +145,12 @@ | |||
"WebServerToolWindowPanel.toolwindow.show.date": "false", | |||
"WebServerToolWindowPanel.toolwindow.show.permissions": "false", | |||
"WebServerToolWindowPanel.toolwindow.show.size": "false", | |||
"last_opened_file_path": "D:/tuoheng/fanbojiaoyu", | |||
"last_opened_file_path": "D:/tuoheng/codenew/tuoheng_alg/test/元类", | |||
"node.js.detected.package.eslint": "true", | |||
"node.js.detected.package.tslint": "true", | |||
"node.js.selected.package.eslint": "(autodetect)", | |||
"node.js.selected.package.tslint": "(autodetect)", | |||
"nodejs_package_manager_path": "npm", | |||
"project.structure.last.edited": "SDK", | |||
"project.structure.proportion": "0.15", | |||
"project.structure.side.proportion": "0.2816092", | |||
@@ -150,21 +160,22 @@ | |||
}]]></component> | |||
<component name="RecentsManager"> | |||
<key name="CopyFile.RECENT_KEYS"> | |||
<recent name="D:\tuoheng\codenew\tuoheng_alg\test\元类" /> | |||
<recent name="D:\tuoheng\codenew\tuoheng_alg" /> | |||
<recent name="D:\tuoheng\codenew\tuoheng_alg\test\color" /> | |||
<recent name="D:\tuoheng\codenew\tuoheng_alg\test\cuda" /> | |||
<recent name="D:\tuoheng\codenew\tuoheng_alg\util" /> | |||
<recent name="D:\tuoheng\codenew\tuoheng_alg\test" /> | |||
<recent name="D:\tuoheng\codenew\tuoheng_alg\test\aliyun" /> | |||
</key> | |||
<key name="MoveFile.RECENT_KEYS"> | |||
<recent name="D:\tuoheng\codenew\tuoheng_alg\test\设计模式\单例" /> | |||
<recent name="D:\tuoheng\codenew\tuoheng_alg\font" /> | |||
<recent name="D:\work\alg_new\tuoheng_alg\test\image" /> | |||
<recent name="D:\work\alg\tuoheng_alg\test\水印" /> | |||
<recent name="D:\work\alg\tuoheng_alg\image" /> | |||
</key> | |||
</component> | |||
<component name="RunManager" selected="Python.color_test"> | |||
<configuration name="IntelligentRecognitionProcess" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true"> | |||
<component name="RunManager" selected="Python.demo1"> | |||
<configuration name="demo" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true"> | |||
<module name="tuoheng_alg" /> | |||
<option name="INTERPRETER_OPTIONS" value="" /> | |||
<option name="PARENT_ENVS" value="true" /> | |||
@@ -172,12 +183,12 @@ | |||
<env name="PYTHONUNBUFFERED" value="1" /> | |||
</envs> | |||
<option name="SDK_HOME" value="" /> | |||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/concurrency" /> | |||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/test/设计模式/简单工厂模式" /> | |||
<option name="IS_MODULE_SDK" value="true" /> | |||
<option name="ADD_CONTENT_ROOTS" value="true" /> | |||
<option name="ADD_SOURCE_ROOTS" value="true" /> | |||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" /> | |||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/concurrency/IntelligentRecognitionProcess.py" /> | |||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/test/设计模式/简单工厂模式/demo.py" /> | |||
<option name="PARAMETERS" value="" /> | |||
<option name="SHOW_COMMAND_LINE" value="false" /> | |||
<option name="EMULATE_TERMINAL" value="false" /> | |||
@@ -186,7 +197,7 @@ | |||
<option name="INPUT_FILE" value="" /> | |||
<method v="2" /> | |||
</configuration> | |||
<configuration name="color_test" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true"> | |||
<configuration name="demo1" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true"> | |||
<module name="tuoheng_alg" /> | |||
<option name="INTERPRETER_OPTIONS" value="" /> | |||
<option name="PARENT_ENVS" value="true" /> | |||
@@ -194,12 +205,12 @@ | |||
<env name="PYTHONUNBUFFERED" value="1" /> | |||
</envs> | |||
<option name="SDK_HOME" value="" /> | |||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/test/color" /> | |||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/test/设计模式/简单工厂模式" /> | |||
<option name="IS_MODULE_SDK" value="true" /> | |||
<option name="ADD_CONTENT_ROOTS" value="true" /> | |||
<option name="ADD_SOURCE_ROOTS" value="true" /> | |||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" /> | |||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/test/color/color_test.py" /> | |||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/test/设计模式/简单工厂模式/demo1.py" /> | |||
<option name="PARAMETERS" value="" /> | |||
<option name="SHOW_COMMAND_LINE" value="false" /> | |||
<option name="EMULATE_TERMINAL" value="false" /> | |||
@@ -208,20 +219,20 @@ | |||
<option name="INPUT_FILE" value="" /> | |||
<method v="2" /> | |||
</configuration> | |||
<configuration name="editImage" type="PythonConfigurationType" factoryName="Python" nameIsGenerated="true"> | |||
<configuration name="demo3" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true"> | |||
<module name="tuoheng_alg" /> | |||
<option name="INTERPRETER_OPTIONS" value="" /> | |||
<option name="PARENT_ENVS" value="true" /> | |||
<envs> | |||
<env name="PYTHONUNBUFFERED" value="1" /> | |||
</envs> | |||
<option name="SDK_HOME" value="$PROJECT_DIR$/../../../software/anaconda/envs/test/python.exe" /> | |||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/test/editimage" /> | |||
<option name="IS_MODULE_SDK" value="false" /> | |||
<option name="SDK_HOME" value="" /> | |||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/test/元类" /> | |||
<option name="IS_MODULE_SDK" value="true" /> | |||
<option name="ADD_CONTENT_ROOTS" value="true" /> | |||
<option name="ADD_SOURCE_ROOTS" value="true" /> | |||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" /> | |||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/test/editimage/editImage.py" /> | |||
<option name="SCRIPT_NAME" value="D:\tuoheng\codenew\tuoheng_alg\test\设计模式\单例\demo3.py" /> | |||
<option name="PARAMETERS" value="" /> | |||
<option name="SHOW_COMMAND_LINE" value="false" /> | |||
<option name="EMULATE_TERMINAL" value="false" /> | |||
@@ -230,7 +241,7 @@ | |||
<option name="INPUT_FILE" value="" /> | |||
<method v="2" /> | |||
</configuration> | |||
<configuration name="mysqltest" type="PythonConfigurationType" factoryName="Python" nameIsGenerated="true"> | |||
<configuration name="demo4" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true"> | |||
<module name="tuoheng_alg" /> | |||
<option name="INTERPRETER_OPTIONS" value="" /> | |||
<option name="PARENT_ENVS" value="true" /> | |||
@@ -238,12 +249,12 @@ | |||
<env name="PYTHONUNBUFFERED" value="1" /> | |||
</envs> | |||
<option name="SDK_HOME" value="" /> | |||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/test" /> | |||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/test/元类" /> | |||
<option name="IS_MODULE_SDK" value="true" /> | |||
<option name="ADD_CONTENT_ROOTS" value="true" /> | |||
<option name="ADD_SOURCE_ROOTS" value="true" /> | |||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" /> | |||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/test/mysqltest.py" /> | |||
<option name="SCRIPT_NAME" value="D:\tuoheng\codenew\tuoheng_alg\test\设计模式\单例\demo4.py" /> | |||
<option name="PARAMETERS" value="" /> | |||
<option name="SHOW_COMMAND_LINE" value="false" /> | |||
<option name="EMULATE_TERMINAL" value="false" /> | |||
@@ -252,7 +263,7 @@ | |||
<option name="INPUT_FILE" value="" /> | |||
<method v="2" /> | |||
</configuration> | |||
<configuration name="test (2)" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true"> | |||
<configuration name="demo5" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true"> | |||
<module name="tuoheng_alg" /> | |||
<option name="INTERPRETER_OPTIONS" value="" /> | |||
<option name="PARENT_ENVS" value="true" /> | |||
@@ -260,12 +271,12 @@ | |||
<env name="PYTHONUNBUFFERED" value="1" /> | |||
</envs> | |||
<option name="SDK_HOME" value="" /> | |||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/test/进程" /> | |||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/test/元类" /> | |||
<option name="IS_MODULE_SDK" value="true" /> | |||
<option name="ADD_CONTENT_ROOTS" value="true" /> | |||
<option name="ADD_SOURCE_ROOTS" value="true" /> | |||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" /> | |||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/test/进程/test.py" /> | |||
<option name="SCRIPT_NAME" value="D:\tuoheng\codenew\tuoheng_alg\test\设计模式\单例\demo5.py" /> | |||
<option name="PARAMETERS" value="" /> | |||
<option name="SHOW_COMMAND_LINE" value="false" /> | |||
<option name="EMULATE_TERMINAL" value="false" /> | |||
@@ -274,20 +285,20 @@ | |||
<option name="INPUT_FILE" value="" /> | |||
<method v="2" /> | |||
</configuration> | |||
<configuration name="test" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true"> | |||
<configuration name="editImage" type="PythonConfigurationType" factoryName="Python" nameIsGenerated="true"> | |||
<module name="tuoheng_alg" /> | |||
<option name="INTERPRETER_OPTIONS" value="" /> | |||
<option name="PARENT_ENVS" value="true" /> | |||
<envs> | |||
<env name="PYTHONUNBUFFERED" value="1" /> | |||
</envs> | |||
<option name="SDK_HOME" value="" /> | |||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/test/集合" /> | |||
<option name="IS_MODULE_SDK" value="true" /> | |||
<option name="SDK_HOME" value="$PROJECT_DIR$/../../../software/anaconda/envs/test/python.exe" /> | |||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/test/editimage" /> | |||
<option name="IS_MODULE_SDK" value="false" /> | |||
<option name="ADD_CONTENT_ROOTS" value="true" /> | |||
<option name="ADD_SOURCE_ROOTS" value="true" /> | |||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" /> | |||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/test/集合/test.py" /> | |||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/test/editimage/editImage.py" /> | |||
<option name="PARAMETERS" value="" /> | |||
<option name="SHOW_COMMAND_LINE" value="false" /> | |||
<option name="EMULATE_TERMINAL" value="false" /> | |||
@@ -296,7 +307,7 @@ | |||
<option name="INPUT_FILE" value="" /> | |||
<method v="2" /> | |||
</configuration> | |||
<configuration name="test1" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true"> | |||
<configuration name="mysqltest" type="PythonConfigurationType" factoryName="Python" nameIsGenerated="true"> | |||
<module name="tuoheng_alg" /> | |||
<option name="INTERPRETER_OPTIONS" value="" /> | |||
<option name="PARENT_ENVS" value="true" /> | |||
@@ -304,12 +315,35 @@ | |||
<env name="PYTHONUNBUFFERED" value="1" /> | |||
</envs> | |||
<option name="SDK_HOME" value="" /> | |||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/test/cuda" /> | |||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/test" /> | |||
<option name="IS_MODULE_SDK" value="true" /> | |||
<option name="ADD_CONTENT_ROOTS" value="true" /> | |||
<option name="ADD_SOURCE_ROOTS" value="true" /> | |||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" /> | |||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/test/cuda/test1.py" /> | |||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/test/mysqltest.py" /> | |||
<option name="PARAMETERS" value="" /> | |||
<option name="SHOW_COMMAND_LINE" value="false" /> | |||
<option name="EMULATE_TERMINAL" value="false" /> | |||
<option name="MODULE_MODE" value="false" /> | |||
<option name="REDIRECT_INPUT" value="false" /> | |||
<option name="INPUT_FILE" value="" /> | |||
<method v="2" /> | |||
</configuration> | |||
<configuration name="test" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true"> | |||
<module name="tuoheng_alg" /> | |||
<option name="INTERPRETER_OPTIONS" value="" /> | |||
<option name="PARENT_ENVS" value="true" /> | |||
<envs> | |||
<env name="PYTHONUNBUFFERED" value="1" /> | |||
</envs> | |||
<option name="SDK_HOME" value="B:\software\conda\envs\test\python.exe" /> | |||
<option name="SDK_NAME" value="Python 3.8 (test)" /> | |||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/test/类型标注" /> | |||
<option name="IS_MODULE_SDK" value="false" /> | |||
<option name="ADD_CONTENT_ROOTS" value="true" /> | |||
<option name="ADD_SOURCE_ROOTS" value="true" /> | |||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" /> | |||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/test/类型标注/test.py" /> | |||
<option name="PARAMETERS" value="" /> | |||
<option name="SHOW_COMMAND_LINE" value="false" /> | |||
<option name="EMULATE_TERMINAL" value="false" /> | |||
@@ -321,19 +355,19 @@ | |||
<list> | |||
<item itemvalue="Python.editImage" /> | |||
<item itemvalue="Python.mysqltest" /> | |||
<item itemvalue="Python.color_test" /> | |||
<item itemvalue="Python.test (2)" /> | |||
<item itemvalue="Python.IntelligentRecognitionProcess" /> | |||
<item itemvalue="Python.test" /> | |||
<item itemvalue="Python.test1" /> | |||
<item itemvalue="Python.demo" /> | |||
<item itemvalue="Python.demo1" /> | |||
<item itemvalue="Python.demo3" /> | |||
<item itemvalue="Python.demo4" /> | |||
<item itemvalue="Python.demo5" /> | |||
</list> | |||
<recent_temporary> | |||
<list> | |||
<item itemvalue="Python.color_test" /> | |||
<item itemvalue="Python.test (2)" /> | |||
<item itemvalue="Python.IntelligentRecognitionProcess" /> | |||
<item itemvalue="Python.test" /> | |||
<item itemvalue="Python.test1" /> | |||
<item itemvalue="Python.demo1" /> | |||
<item itemvalue="Python.demo" /> | |||
<item itemvalue="Python.demo5" /> | |||
<item itemvalue="Python.demo4" /> | |||
<item itemvalue="Python.demo3" /> | |||
</list> | |||
</recent_temporary> | |||
</component> | |||
@@ -489,7 +523,19 @@ | |||
<workItem from="1683506530261" duration="919000" /> | |||
<workItem from="1683507482567" duration="15434000" /> | |||
<workItem from="1683591783960" duration="1186000" /> | |||
<workItem from="1683677260592" duration="8827000" /> | |||
<workItem from="1683677260592" duration="21750000" /> | |||
<workItem from="1683762579964" duration="23871000" /> | |||
<workItem from="1683851036596" duration="51000" /> | |||
<workItem from="1683851900729" duration="83000" /> | |||
<workItem from="1683851995142" duration="24789000" /> | |||
<workItem from="1684110880642" duration="5895000" /> | |||
<workItem from="1684197638479" duration="9103000" /> | |||
<workItem from="1684284520362" duration="13345000" /> | |||
<workItem from="1684379357818" duration="22600000" /> | |||
<workItem from="1684456296559" duration="11147000" /> | |||
<workItem from="1684653340859" duration="1199000" /> | |||
<workItem from="1684715657250" duration="6747000" /> | |||
<workItem from="1684801865053" duration="7552000" /> | |||
</task> | |||
<servers /> | |||
</component> | |||
@@ -539,53 +585,63 @@ | |||
</component> | |||
<component name="com.intellij.coverage.CoverageDataManagerImpl"> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$color_test.coverage" NAME="color_test 覆盖结果" MODIFIED="1683683775604" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/color" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$demo1.coverage" NAME="demo1 覆盖结果" MODIFIED="1680162882599" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/demo" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$demo1.coverage" NAME="demo1 覆盖结果" MODIFIED="1684807129961" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/元类" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$ffmpeg33.coverage" NAME="ffmpeg33 覆盖结果" MODIFIED="1670489109246" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/ffmpeg11" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$demo4.coverage" NAME="demo4 覆盖结果" MODIFIED="1684809818971" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/元类" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$minio.coverage" NAME="minio 覆盖结果" MODIFIED="1667465702864" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/minio1" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$3.coverage" NAME="视频添加文字水印3 Coverage Results" MODIFIED="1661906152928" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$ffmpeg12.coverage" NAME="ffmpeg12 覆盖结果" MODIFIED="1675391366890" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/ffmpeg11" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$Test__2_.coverage" NAME="Test (2) 覆盖结果" MODIFIED="1681796501563" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/路径" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$test1.coverage" NAME="test1 覆盖结果" MODIFIED="1681988279624" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/cuda" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$ossdemo.coverage" NAME="ossdemo 覆盖结果" MODIFIED="1681715255761" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/aliyun" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$test__1_.coverage" NAME="test (1) 覆盖结果" MODIFIED="1681969578447" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/cuda" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$test__1_.coverage" NAME="test (1) 覆盖结果" MODIFIED="1684480106837" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/日志" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$aa1.coverage" NAME="aa1 覆盖结果" MODIFIED="1667351136888" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/ffmpeg11" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg___$test.coverage" NAME="test 覆盖结果" MODIFIED="1668577200259" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/while" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$editImage.coverage" NAME="editImage 覆盖结果" MODIFIED="1678348350574" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/editimage" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$2.coverage" NAME="协程2 覆盖结果" MODIFIED="1668066168428" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="/opt/tuo_heng/algSch/test/协程/" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$ImgBaiduSdk.coverage" NAME="ImgBaiduSdk 覆盖结果" MODIFIED="1678355024003" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/util" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$ImageUtils.coverage" NAME="ImageUtils Coverage Results" MODIFIED="1663499421253" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/util" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$demo2.coverage" NAME="demo2 覆盖结果" MODIFIED="1684808407865" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/元类" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$dsp_master.coverage" NAME="dsp_master 覆盖结果" MODIFIED="1680503755624" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$test.coverage" NAME="test 覆盖结果" MODIFIED="1682582986112" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/集合" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$IntelligentRecognitionProcess.coverage" NAME="IntelligentRecognitionProcess 覆盖结果" MODIFIED="1682651444560" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/concurrency" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$Test.coverage" NAME="Test 覆盖结果" MODIFIED="1681810213173" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/序列化" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$test.coverage" NAME="test 覆盖结果" MODIFIED="1684742307978" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/opencv" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$demo3.coverage" NAME="demo3 覆盖结果" MODIFIED="1684809071819" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/元类" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$Test.coverage" NAME="Test 覆盖结果" MODIFIED="1683802532361" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/序列化" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$mysqltest.coverage" NAME="mysqltest Coverage Results" MODIFIED="1660868712851" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$asnyc__1_.coverage" NAME="asnyc (1) Coverage Results" MODIFIED="1663458917599" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$cv2test1.coverage" NAME="cv2test1 覆盖结果" MODIFIED="1665738045603" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="/home/DATA/chenyukun/algSch/test/" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$test2.coverage" NAME="test2 覆盖结果" MODIFIED="1669178077956" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/str" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$aa.coverage" NAME="aa 覆盖结果" MODIFIED="1670490313339" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="/home/chenyukun/algSch/test/ffmpeg11" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$ffmpeg22.coverage" NAME="aa 覆盖结果" MODIFIED="1667350492259" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="/opt/tuo_heng" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$aa.coverage" NAME="aa 覆盖结果" MODIFIED="1684461916527" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/ffmpeg11" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$KafkaUtils__1_.coverage" NAME="KafkaUtils (1) Coverage Results" MODIFIED="1663464961001" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/util" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$voddemo.coverage" NAME="voddemo 覆盖结果" MODIFIED="1681722102430" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/aliyun" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg___$producer_start.coverage" NAME="producer_start 覆盖结果" MODIFIED="1668522825199" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="/home/thsw/chenyukun/algSch" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg___$producer_start1.coverage" NAME="producer_start1 覆盖结果" MODIFIED="1668437822632" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="/home/thsw/chenyukun/algSch/test/kafka" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$re.coverage" NAME="re 覆盖结果" MODIFIED="1684221962919" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/正则" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$producer_start__1_.coverage" NAME="producer_start (1) 覆盖结果" MODIFIED="1665832569996" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg___$ffmpeg11.coverage" NAME="ffmpeg11 覆盖结果" MODIFIED="1668410004435" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/ffmpeg11" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$demo.coverage" NAME="demo 覆盖结果" MODIFIED="1684810722320" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/设计模式/简单工厂模式" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$producer_start.coverage" NAME="producer_start1 覆盖结果" MODIFIED="1670999187123" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/kafka" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$test__3_.coverage" NAME="test (3) 覆盖结果" MODIFIED="1684802056733" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/设计模式/单例" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$numpy_test.coverage" NAME="numpy_test 覆盖结果" MODIFIED="1684205019028" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/numpy" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$.coverage" NAME="协程笔记 覆盖结果" MODIFIED="1680926972744" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/协程" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$4.coverage" NAME="视频添加图片水印4 Coverage Results" MODIFIED="1661874731395" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$gputest.coverage" NAME="gputest 覆盖结果" MODIFIED="1681950938970" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/gpu" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$1.coverage" NAME="协程1 覆盖结果" MODIFIED="1667866542122" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/协程" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg___$3.coverage" NAME="协程3 覆盖结果" MODIFIED="1668147029048" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/协程" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$AliyunSdk.coverage" NAME="AliyunSdk 覆盖结果" MODIFIED="1683803902993" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$asnyc.coverage" NAME="asnyc Coverage Results" MODIFIED="1663459033435" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$5.coverage" NAME="视频添加图片水印5 Coverage Results" MODIFIED="1661905982885" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$read.coverage" NAME="read Coverage Results" MODIFIED="1663640070956" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$cv2test1__1_.coverage" NAME="cv2test1 覆盖结果" MODIFIED="1665820653649" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$TimeUtils.coverage" NAME="TimeUtils Coverage Results" MODIFIED="1661222768678" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/util" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$producer_start1.coverage" NAME="producer_start1 覆盖结果" MODIFIED="1671428635702" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/kafka" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$demo5.coverage" NAME="demo5 覆盖结果" MODIFIED="1684810002359" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/元类" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg___$producer_stop.coverage" NAME="producer_stop 覆盖结果" MODIFIED="1668522920533" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="/home/thsw/chenyukun/algSch" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$Test__1_.coverage" NAME="Test (1) 覆盖结果" MODIFIED="1681199611277" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/线程" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$Test__1_.coverage" NAME="Test (1) 覆盖结果" MODIFIED="1683865962957" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/线程" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$pa.coverage" NAME="pa 覆盖结果" MODIFIED="1684217734590" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/pachong" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$ffmpeg13.coverage" NAME="ffmpeg13 覆盖结果" MODIFIED="1675394160900" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/ffmpeg11" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$KafkaUtils.coverage" NAME="KafkaUtils Coverage Results" MODIFIED="1663465345491" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/util" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$test__2_.coverage" NAME="test (2) 覆盖结果" MODIFIED="1683355406740" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/进程" /> | |||
<SUITE FILE_PATH="coverage/tuoheng_alg$test__2_.coverage" NAME="test (2) 覆盖结果" MODIFIED="1684743889711" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/test/类型标注" /> | |||
</component> | |||
</project> |
@@ -4,18 +4,19 @@ from loguru import logger | |||
class Common(Thread): | |||
def __init__(self, content, func, args=()): | |||
def __init__(self, content, func, param1, param2): | |||
super(Common, self).__init__() | |||
self.content = content | |||
self.func = func | |||
self.args = args | |||
self.param1 = param1 | |||
self.param2 = param2 | |||
self.result = None | |||
def get_result(self): | |||
self.join(60 * 60 * 3) | |||
self.join(60 * 60 * 12) | |||
return self.result | |||
def run(self): | |||
logger.info("开始执行线程!") | |||
self.result = self.func(self.args) | |||
self.result = self.func(self.param1, self.param2) | |||
logger.info("线程停止完成!") |
@@ -1,4 +1,5 @@ | |||
import copy | |||
import time | |||
from concurrent.futures import ThreadPoolExecutor, as_completed | |||
from threading import Thread | |||
from loguru import logger | |||
@@ -113,7 +114,7 @@ class ImageFileUpload(FileUpload): | |||
def run(self): | |||
logger.info("启动图片上传线程, requestId:{}", self.msg.get("request_id")) | |||
# 初始化oss客户端 | |||
aliyunOssSdk = AliyunOssSdk(self.content, logger, self.msg.get("request_id")) | |||
aliyunOssSdk = AliyunOssSdk(self.content, self.msg.get("request_id")) | |||
aliyunOssSdk.get_oss_bucket() | |||
high_score_image = {} | |||
with ThreadPoolExecutor(max_workers=5) as t: | |||
@@ -165,8 +166,10 @@ class ImageFileUpload(FileUpload): | |||
thread_result.result() | |||
for msg in msg_list: | |||
self.sendResult(msg) | |||
else: | |||
time.sleep(1) | |||
except Exception as e: | |||
logger.exception("图片上传异常:{}, requestId:{}", e, self.msg.get("request_id")) | |||
logger.exception("图片上传异常:{}, requestId:{}", str(e), self.msg.get("request_id")) | |||
finally: | |||
high_score_image.clear() | |||
@@ -4,7 +4,7 @@ import json | |||
import os | |||
import time | |||
import copy | |||
from concurrent.futures import ThreadPoolExecutor, as_completed | |||
from concurrent.futures import ThreadPoolExecutor, wait, ALL_COMPLETED | |||
import cv2 | |||
import numpy as np | |||
@@ -13,6 +13,7 @@ from common import Constant | |||
from multiprocessing import Process, Queue | |||
from loguru import logger | |||
from concurrency.CommonThread import Common | |||
from concurrency.PullStreamThread import RecordingPullStreamThread | |||
from concurrency.PullVideoStreamProcess import OnlinePullVideoStreamProcess, OfflinePullVideoStreamProcess | |||
from concurrency.RecordingHeartbeatThread import RecordingHeartbeat | |||
@@ -250,8 +251,12 @@ class IntelligentRecognitionProcess(Process): | |||
for mod in frame[1]: | |||
result = frame[3].submit(self.analyze, mod, or_frame, frame[2].w) | |||
analyze_result.append(result) | |||
results = wait(analyze_result, timeout=60, return_when=ALL_COMPLETED) | |||
completed_futures = results.done | |||
det_xywh = {} | |||
for r in as_completed(analyze_result): | |||
for r in completed_futures: | |||
if r.exception(): | |||
raise r.exception() | |||
p_result, timeOut, code, allowedList, label_arraylist, rainbows = r.result() | |||
if allowedList is not None: | |||
is_call_model = True | |||
@@ -317,20 +322,25 @@ class OnlineIntelligentRecognitionProcess(IntelligentRecognitionProcess): | |||
self.pull_stream_timeout = int(self.content["service"]["cv2_pull_stream_timeout"]) | |||
# 停止任务方法 | |||
def stop_task(self, cv2tool, pullProcess, snalysisStatus, t): | |||
def stop_task(self, cv2tool, pullProcess, snalysisStatus): | |||
cv2tool.close() | |||
pullProcess.sendCommand({"command": "stop_pull_stream"}) | |||
if not os.path.exists(self.orFilePath) or not os.path.exists(self.aiFilePath): | |||
logger.error("原视频或AI视频不存在!requestId:{}", self.msg.get("request_id")) | |||
raise ServiceException(ExceptionType.PUSH_STREAM_TIME_EXCEPTION.value[0], | |||
ExceptionType.PUSH_STREAM_TIME_EXCEPTION.value[1]) | |||
aliyunVodSdk = ThAliyunVodSdk(self.content, logger, self.msg.get("request_id")) | |||
upload_video_thread_or = t.submit(aliyunVodSdk.get_play_url, self.orFilePath, "orOnLineVideo") | |||
upload_video_thread_ai = t.submit(aliyunVodSdk.get_play_url, self.aiFilePath, "aiOnLineVideo") | |||
url_result = [] | |||
for url in as_completed([upload_video_thread_or, upload_video_thread_ai]): | |||
url_result.append(url.result()) | |||
if len(url_result) != 2: | |||
aliyunVodSdk = ThAliyunVodSdk(self.content, self.msg.get("request_id")) | |||
upload_video_thread_or = Common(self.content, aliyunVodSdk.get_play_url, self.orFilePath, | |||
"or_online_%s" % self.msg.get("request_id")) | |||
upload_video_thread_ai = Common(self.content, aliyunVodSdk.get_play_url, self.aiFilePath, | |||
"ai_online_%s" % self.msg.get("request_id")) | |||
upload_video_thread_or.setDaemon(True) | |||
upload_video_thread_ai.setDaemon(True) | |||
upload_video_thread_or.start() | |||
upload_video_thread_ai.start() | |||
or_url = upload_video_thread_or.get_result() | |||
ai_url = upload_video_thread_ai.get_result() | |||
if or_url is None or ai_url is None: | |||
logger.error("原视频或AI视频播放上传VOD失败!, requestId: {}", self.msg.get("request_id")) | |||
raise ServiceException(ExceptionType.GET_VIDEO_URL_EXCEPTION.value[0], | |||
ExceptionType.GET_VIDEO_URL_EXCEPTION.value[1]) | |||
@@ -339,8 +349,8 @@ class OnlineIntelligentRecognitionProcess(IntelligentRecognitionProcess): | |||
self.sendResult({"feedback": message_feedback(self.msg.get("request_id"), snalysisStatus, | |||
self.analyse_type, | |||
progress=Constant.success_progess, | |||
original_url=url_result[0], | |||
sign_url=url_result[1], | |||
original_url=or_url, | |||
sign_url=ai_url, | |||
analyse_time=TimeUtils.now_date_to_str())}) | |||
def start_pull_stream(self): | |||
@@ -351,7 +361,7 @@ class OnlineIntelligentRecognitionProcess(IntelligentRecognitionProcess): | |||
return pullProcess | |||
def checkPullProcess(self, pullProcess): | |||
if not pullProcess.is_alive(): | |||
if pullProcess is not None and not pullProcess.is_alive(): | |||
logger.info("拉流进程停止异常, requestId: {}", self.msg.get("request_id")) | |||
raise Exception("拉流进程异常停止") | |||
@@ -372,7 +382,7 @@ class OnlineIntelligentRecognitionProcess(IntelligentRecognitionProcess): | |||
task_frame = None | |||
with ThreadPoolExecutor(max_workers=10) as t: | |||
with ThreadPoolExecutor(max_workers=4) as tt: | |||
with ThreadPoolExecutor(max_workers=5) as ttt: | |||
with ThreadPoolExecutor(max_workers=10) as ttt: | |||
while True: | |||
self.checkPullProcess(pullProcess) | |||
eBody = self.getEvent() | |||
@@ -399,8 +409,11 @@ class OnlineIntelligentRecognitionProcess(IntelligentRecognitionProcess): | |||
frame_all.get("frame")) | |||
write_ai_video_result = tt.submit(cv2tool.video_ai_write, frame_merge) | |||
res = [push_stream_result, write_or_video_result, write_ai_video_result] | |||
for re in as_completed(res): | |||
re.result() | |||
completed_results = wait(res, timeout=600, return_when=ALL_COMPLETED) | |||
completed_futures = completed_results.done | |||
for r in completed_futures: | |||
if r.exception(): | |||
raise r.exception() | |||
det_xywh = frame_all.get("det_xywh") | |||
if len(det_xywh) > 0: | |||
self.imageQueue.put({"image": frame_all}) | |||
@@ -409,17 +422,17 @@ class OnlineIntelligentRecognitionProcess(IntelligentRecognitionProcess): | |||
if status.get("status") == "1": | |||
raise ServiceException(status.get("error").get("code"), status.get("error").get("msg")) | |||
elif status.get("status") == "3": | |||
self.stop_task(cv2tool, pullProcess, AnalysisStatus.TIMEOUT.value, t) | |||
self.stop_task(cv2tool, pullProcess, AnalysisStatus.TIMEOUT.value) | |||
break | |||
elif status.get("status") == "9": | |||
logger.info("实时任务正常结束:requestId: {}", self.msg.get("request_id")) | |||
self.stop_task(cv2tool, pullProcess, AnalysisStatus.SUCCESS.value, t) | |||
self.stop_task(cv2tool, pullProcess, AnalysisStatus.SUCCESS.value) | |||
break | |||
else: | |||
raise Exception("未知拉流状态异常!") | |||
logger.info("实时进程任务完成,requestId:{}", self.msg.get("request_id")) | |||
except ServiceException as s: | |||
logger.error("服务异常,异常编号:{}, 异常描述:{}, requestId: {}", s.code, s.msg, self.msg.get("request_id")) | |||
logger.exception("服务异常,异常编号:{}, 异常描述:{}, requestId: {}", s.code, s.msg, self.msg.get("request_id")) | |||
feedback = {"feedback": message_feedback(self.msg.get("request_id"), AnalysisStatus.FAILED.value, | |||
self.analyse_type, | |||
s.code, | |||
@@ -470,16 +483,19 @@ class OfflineIntelligentRecognitionProcess(IntelligentRecognitionProcess): | |||
self.aiFilePath = "%s%s%s%s%s" % (self.content["video"]["file_path"], random_time, "_on_ai_", | |||
self.msg.get("request_id"), ".mp4") | |||
def stop_task(self, cv2tool, pullProcess, analysisStatus, t): | |||
def stop_task(self, cv2tool, pullProcess, analysisStatus): | |||
cv2tool.close() | |||
pullProcess.sendCommand({"command": "stop_pull_stream"}) | |||
if not os.path.exists(self.aiFilePath): | |||
logger.error("AI视频不存在!requestId:{}", self.msg.get("request_id")) | |||
raise ServiceException(ExceptionType.PUSH_STREAM_TIME_EXCEPTION.value[0], | |||
ExceptionType.PUSH_STREAM_TIME_EXCEPTION.value[1]) | |||
aliyunVodSdk = ThAliyunVodSdk(self.content, logger, self.msg.get("request_id")) | |||
upload_video_thread_ai = t.submit(aliyunVodSdk.get_play_url, self.aiFilePath, "aiOnLineVideo") | |||
ai_play_url = upload_video_thread_ai.result() | |||
aliyunVodSdk = ThAliyunVodSdk(self.content, self.msg.get("request_id")) | |||
upload_video_thread_ai = Common(self.content, aliyunVodSdk.get_play_url, self.aiFilePath, | |||
"ai_offLine_%s" % self.msg.get("request_id")) | |||
upload_video_thread_ai.setDaemon(True) | |||
upload_video_thread_ai.start() | |||
ai_play_url = upload_video_thread_ai.get_result() | |||
if ai_play_url is None: | |||
logger.error("原视频或AI视频播放上传VOD失败!, requestId: {}", self.msg.get("request_id")) | |||
raise ServiceException(ExceptionType.GET_VIDEO_URL_EXCEPTION.value[0], | |||
@@ -514,9 +530,9 @@ class OfflineIntelligentRecognitionProcess(IntelligentRecognitionProcess): | |||
task_frame = None | |||
with ThreadPoolExecutor(max_workers=10) as t: | |||
with ThreadPoolExecutor(max_workers=3) as tt: | |||
with ThreadPoolExecutor(max_workers=5) as ttt: | |||
with ThreadPoolExecutor(max_workers=10) as ttt: | |||
while True: | |||
if not pullProcess.is_alive(): | |||
if pullProcess is not None and not pullProcess.is_alive(): | |||
logger.info("拉流进程停止异常, requestId: {}", self.msg.get("request_id")) | |||
raise Exception("拉流进程异常停止") | |||
# 检查是否获取到视频信息 | |||
@@ -541,8 +557,11 @@ class OfflineIntelligentRecognitionProcess(IntelligentRecognitionProcess): | |||
push_stream_result = tt.submit(cv2tool.push_stream, frame_merge) | |||
write_ai_video_result = tt.submit(cv2tool.video_ai_write, frame_merge) | |||
res = [push_stream_result, write_ai_video_result] | |||
for re in as_completed(res): | |||
re.result() | |||
completed_results = wait(res, timeout=600, return_when=ALL_COMPLETED) | |||
completed_futures = completed_results.done | |||
for r in completed_futures: | |||
if r.exception(): | |||
raise r.exception() | |||
det_xywh = frame_all.get("det_xywh") | |||
if len(det_xywh) > 0: | |||
self.imageQueue.put({"image": frame_all}) | |||
@@ -558,14 +577,14 @@ class OfflineIntelligentRecognitionProcess(IntelligentRecognitionProcess): | |||
if status.get("status") == "1": | |||
raise ServiceException(status.get("error").get("code"), status.get("error").get("msg")) | |||
elif status.get("status") == "2": | |||
self.stop_task(cv2tool, pullProcess, AnalysisStatus.SUCCESS.value, t) | |||
self.stop_task(cv2tool, pullProcess, AnalysisStatus.SUCCESS.value) | |||
break | |||
elif status.get("status") == "3": | |||
self.stop_task(cv2tool, pullProcess, AnalysisStatus.TIMEOUT.value, t) | |||
self.stop_task(cv2tool, pullProcess, AnalysisStatus.TIMEOUT.value) | |||
break | |||
elif status.get("status") == "9": | |||
logger.info("离线任务正常结束:requestId: {}", self.msg.get("request_id")) | |||
self.stop_task(cv2tool, pullProcess, AnalysisStatus.SUCCESS.value, t) | |||
self.stop_task(cv2tool, pullProcess, AnalysisStatus.SUCCESS.value) | |||
break | |||
logger.info("离线进程任务完成,requestId:{}", self.msg.get("request_id")) | |||
except ServiceException as s: | |||
@@ -787,8 +806,11 @@ class PhotosIntelligentRecognitionProcess(IntelligentRecognitionProcess): | |||
for imageUrl in imageUrls: | |||
obj = tt.submit(self.epidemic_prevention, imageUrl, mod, orc, model_type_code) | |||
obj_list.append(obj) | |||
for future in as_completed(obj_list): | |||
future.result() | |||
completed_results = wait(obj_list, timeout=120, return_when=ALL_COMPLETED) | |||
completed_futures = completed_results.done | |||
for r in completed_futures: | |||
if r.exception(): | |||
raise r.exception() | |||
def image_recognition(self, imageUrl, mod, model_type_code, aliyunOssSdk): | |||
try: | |||
@@ -865,8 +887,11 @@ class PhotosIntelligentRecognitionProcess(IntelligentRecognitionProcess): | |||
for imageUrl in imageUrls: | |||
obj = tt.submit(self.image_recognition, imageUrl, mod, model_type_code, aliyunOssSdk) | |||
obj_list.append(obj) | |||
for future in as_completed(obj_list): | |||
future.result() | |||
completed_results = wait(obj_list, timeout=120, return_when=ALL_COMPLETED) | |||
completed_futures = completed_results.done | |||
for r in completed_futures: | |||
if r.exception(): | |||
raise r.exception() | |||
''' | |||
1. imageUrls: 图片url数组,多张图片 | |||
@@ -881,8 +906,11 @@ class PhotosIntelligentRecognitionProcess(IntelligentRecognitionProcess): | |||
for imageUrl in imageUrls: | |||
obj = tt.submit(self.baidu_recognition, imageUrl, mod, model_type_code, aliyunOssSdk, ttt) | |||
obj_list.append(obj) | |||
for future in as_completed(obj_list): | |||
future.result() | |||
completed_results = wait(obj_list, timeout=120, return_when=ALL_COMPLETED) | |||
completed_futures = completed_results.done | |||
for r in completed_futures: | |||
if r.exception(): | |||
raise r.exception() | |||
def baidu_recognition(self, imageUrl, mod, model_type_code, aliyunOssSdk, ttt): | |||
try: | |||
@@ -906,8 +934,11 @@ class PhotosIntelligentRecognitionProcess(IntelligentRecognitionProcess): | |||
reuslt = ttt.submit(self.baidu_method, mod, target, imageUrl, img, aliyunOssSdk, model_type_code, | |||
label_array, rainbows, person_label) | |||
obj_list.append(reuslt) | |||
for future in as_completed(obj_list): | |||
future.result() | |||
completed_results = wait(obj_list, timeout=120, return_when=ALL_COMPLETED) | |||
completed_futures = completed_results.done | |||
for r in completed_futures: | |||
if r.exception(): | |||
raise r.exception() | |||
except ServiceException as s: | |||
raise s | |||
except Exception as e: | |||
@@ -1034,14 +1065,14 @@ class PhotosIntelligentRecognitionProcess(IntelligentRecognitionProcess): | |||
def run(self): | |||
with ThreadPoolExecutor(max_workers=5) as t: | |||
with ThreadPoolExecutor(max_workers=5) as tt: | |||
with ThreadPoolExecutor(max_workers=10) as tt: | |||
with ThreadPoolExecutor(max_workers=5) as ttt: | |||
try: | |||
# 初始化日志 | |||
LogUtils.init_log(self.content) | |||
model_array = self.get_model() | |||
imageUrls = self.msg.get("image_urls") | |||
aliyunOssSdk = AliyunOssSdk(self.content, logger, self.msg.get('request_id')) | |||
aliyunOssSdk = AliyunOssSdk(self.content, self.msg.get('request_id')) | |||
aliyunOssSdk.get_oss_bucket() | |||
task_list = [] | |||
for model in model_array: | |||
@@ -1065,8 +1096,11 @@ class PhotosIntelligentRecognitionProcess(IntelligentRecognitionProcess): | |||
aliyunOssSdk, tt) | |||
task_list.append(result) | |||
if len(task_list) > 0: | |||
for future in as_completed(task_list): | |||
future.result() | |||
completed_results = wait(task_list, timeout=120, return_when=ALL_COMPLETED) | |||
completed_futures = completed_results.done | |||
for r in completed_futures: | |||
if r.exception(): | |||
raise r.exception() | |||
logger.info("图片进程任务完成,requestId:{}", self.msg.get("request_id")) | |||
self.sendResult( | |||
{"feedback": message_feedback(self.msg.get("request_id"), AnalysisStatus.SUCCESS.value, | |||
@@ -1167,9 +1201,12 @@ class ScreenRecordingProcess(Process): | |||
logger.error("原视频不存在!requestId:{}", self.msg.get("request_id")) | |||
raise ServiceException(ExceptionType.OR_VIDEO_DO_NOT_EXEIST_EXCEPTION.value[0], | |||
ExceptionType.OR_VIDEO_DO_NOT_EXEIST_EXCEPTION.value[1]) | |||
aliyunVodSdk = ThAliyunVodSdk(self.content, logger, self.msg.get("request_id")) | |||
upload_video_thread_or = t.submit(aliyunVodSdk.get_play_url, self.orFilePath, "orOnLineVideo") | |||
or_play_url = upload_video_thread_or.result() | |||
aliyunVodSdk = ThAliyunVodSdk(self.content, self.msg.get("request_id")) | |||
upload_video_thread_or = Common(self.content, aliyunVodSdk.get_play_url, self.orFilePath, | |||
"or_recording_%s" % self.msg.get("request_id")) | |||
upload_video_thread_or.setDaemon(True) | |||
upload_video_thread_or.start() | |||
or_play_url = upload_video_thread_or.get_result() | |||
if or_play_url is None: | |||
logger.error("原视频上传VOD失败!原视频播放地址:{}, requestId: {}", or_play_url, self.msg.get("request_id")) | |||
raise ServiceException(ExceptionType.GET_VIDEO_URL_EXCEPTION.value[0], | |||
@@ -1244,7 +1281,7 @@ class ScreenRecordingProcess(Process): | |||
raise ServiceException(status.get("error").get("code"), status.get("error").get("msg")) | |||
elif status.get("status") == "2": | |||
cv2tool.close() | |||
self.stop_task(hb, RecordingStatus.RECORDING_SUCCESS.value[0], t) | |||
self.stop_task(hb, RecordingStatus.RECORDING_SUCCESS.value[0]) | |||
pullThread.join(10) | |||
break | |||
else: |
@@ -48,13 +48,41 @@ class PullVideoStreamProcess(Process): | |||
imageFileUpload = ImageFileUpload(self.fbQueue, self.content, self.msg, self.imageQueue, self.analyse_type) | |||
imageFileUpload.setDaemon(True) | |||
imageFileUpload.start() | |||
return imageFileUpload | |||
start_time = time.time() | |||
while True: | |||
if imageFileUpload.is_alive(): | |||
return imageFileUpload | |||
if not imageFileUpload.is_alive(): | |||
logger.warning("图片上传线程异常等待中, requestId:{}", self.msg.get("request_id")) | |||
if time.time() - start_time <= 3: | |||
continue | |||
elif int(time.time() - start_time) <= 5: | |||
logger.warning("图片上传线程异常重启中, requestId:{}", self.msg.get("request_id")) | |||
imageFileUpload.start() | |||
time.sleep(1) | |||
continue | |||
elif int(time.time() - start_time) > 5: | |||
raise Exception("图片上传线程启动异常") | |||
def start_heartbeat(self): | |||
hb = Heartbeat(self.fbQueue, self.hbQueue, self.msg.get("request_id"), self.analyse_type) | |||
hb.setDaemon(True) | |||
hb.start() | |||
return hb | |||
start_time = time.time() | |||
while True: | |||
if hb.is_alive(): | |||
return hb | |||
if not hb.is_alive(): | |||
logger.warning("心跳线程异常等待中, requestId:{}", self.msg.get("request_id")) | |||
if time.time() - start_time <= 3: | |||
continue | |||
elif int(time.time() - start_time) <= 5: | |||
logger.warning("心跳线程异常重启中, requestId:{}", self.msg.get("request_id")) | |||
hb.start() | |||
time.sleep(1) | |||
continue | |||
elif int(time.time() - start_time) > 5: | |||
raise Exception("心跳线程启动异常") | |||
def check(self, start_time, imageFileUpload, hb): | |||
create_task_time = time.time() - start_time | |||
@@ -63,11 +91,11 @@ class PullVideoStreamProcess(Process): | |||
raise ServiceException(ExceptionType.ANALYSE_TIMEOUT_EXCEPTION.value[0], | |||
ExceptionType.ANALYSE_TIMEOUT_EXCEPTION.value[1]) | |||
# 检测图片上传线程是否正常运行 | |||
if not imageFileUpload.is_alive(): | |||
if imageFileUpload is not None and not imageFileUpload.is_alive(): | |||
logger.error("未检测到图片上传线程活动,图片上传线程可能出现异常, reuqestId:{}", self.msg.get("request_id")) | |||
raise Exception("未检测到图片上传线程活动,图片上传线程可能出现异常!") | |||
# 检测心跳线程是否正常运行 | |||
if not hb.is_alive(): | |||
if hb is not None and not hb.is_alive(): | |||
logger.error("未检测到心跳线程活动,心跳线程可能出现异常, reuqestId:{}", self.msg.get("request_id")) | |||
raise Exception("未检测到心跳线程活动,心跳线程可能出现异常!") | |||
@@ -128,7 +156,7 @@ class OnlinePullVideoStreamProcess(PullVideoStreamProcess): | |||
raise ServiceException(ExceptionType.PULLSTREAM_TIMEOUT_EXCEPTION.value[0], | |||
ExceptionType.PULLSTREAM_TIMEOUT_EXCEPTION.value[1]) | |||
cv2_init_num += 1 | |||
time.sleep(0.5) | |||
time.sleep(1) | |||
cv2tool.get_video_info() | |||
continue | |||
pull_stream_start_time = time.time() | |||
@@ -144,8 +172,8 @@ class OnlinePullVideoStreamProcess(PullVideoStreamProcess): | |||
stop_pull_stream_step = True | |||
cv2tool.close() | |||
continue | |||
cv2tool.close() | |||
init_pull_num += 1 | |||
time.sleep(0.1) | |||
continue | |||
init_pull_num = 1 | |||
pull_stream_read_start_time = time.time() | |||
@@ -161,9 +189,10 @@ class OnlinePullVideoStreamProcess(PullVideoStreamProcess): | |||
"all_frame": cv2tool.all_frames}) | |||
concurrent_frame += 1 | |||
except ServiceException as s: | |||
logger.exception("实时拉流异常: {}, requestId:{}", s.msg, self.msg.get("request_id")) | |||
self.sendPullQueue({"status": "1", "error": {"code": s.code, "msg": s.msg}}) | |||
except Exception as e: | |||
logger.exception("实时拉流异常: {}, requestId:{}", e, self.msg.get("request_id")) | |||
logger.exception("实时拉流异常: {}, requestId:{}", str(e), self.msg.get("request_id")) | |||
self.sendPullQueue({"status": "1", "error": {"code": ExceptionType.SERVICE_INNER_EXCEPTION.value[0], | |||
"msg": ExceptionType.SERVICE_INNER_EXCEPTION.value[1]}}) | |||
finally: |
@@ -13,4 +13,4 @@ if __name__ == '__main__': | |||
# 获取主程序执行根路径 | |||
base_dir = os.path.dirname(os.path.realpath(sys.argv[0])) | |||
torch.multiprocessing.set_start_method('spawn') | |||
Dispatcher.DispatcherService(base_dir).start_service() | |||
Dispatcher.DispatcherService(base_dir).start_service() |
@@ -79,7 +79,7 @@ class BaiduSdkErrorEnum(Enum): | |||
IMAGE_SPLIT_LIMIT_REACHED = (282101, "image split limit reached", "长图片切分数量超限!", 1, 1) | |||
TARGET_DETECT_ERROR = (282102, "target detect error", "未检测到图片中识别目标!", 2, 1) # | |||
TARGET_DETECT_ERROR = (282102, "target detect error", "未检测到图片中识别目标!", 2, 1) | |||
TARGET_RECOGNIZE_ERROR = (282103, "target recognize error", "图片目标识别错误!", 2, 1) | |||
@@ -21,7 +21,7 @@ class ExceptionType(Enum): | |||
PUSH_STREAM_URL_EXCEPTION = ("SP007", "推流地址不能为空!") | |||
PUSH_STREAM_TIME_EXCEPTION = ("SP008", "推流时间或原视频时间太短, 未生成分析结果, 建议延长推流时间或原视频时间!") | |||
PUSH_STREAM_TIME_EXCEPTION = ("SP008", "未生成本地视频地址!") | |||
AI_MODEL_MATCH_EXCEPTION = ("SP009", "未匹配到对应的AI模型!") | |||
@@ -61,6 +61,8 @@ class ExceptionType(Enum): | |||
COORDINATE_ACQUISITION_FAILED = ("SP027", "飞行坐标识别异常!") | |||
PUSH_STREAM_EXCEPTION = ("SP028", "推流异常!") | |||
SERVICE_COMMON_EXCEPTION = ("SP997", "公共服务异常!") | |||
NO_GPU_RESOURCES = ("SP998", "暂无GPU资源可以使用,请稍后再试!") |
@@ -35,16 +35,16 @@ class DispatcherService: | |||
raise Exception("cuda不在活动状态, 请检测显卡驱动是否正常!!!!") | |||
# 初始化alg相关配置 ###################################################################### | |||
self.base_dir = base_dir # 根路径 | |||
self.context = YmlUtils.getConfigs(base_dir) # 获取alg需要使用的配置 | |||
self.context[YmlConstant.BASE_DIR] = base_dir # 将根路径设置到上下文中 | |||
self.feedbackThread = None # 初始化反馈线程对象 | |||
self.__base_dir = base_dir # 根路径 | |||
self.__context = YmlUtils.getConfigs(base_dir) # 获取alg需要使用的配置 | |||
self.__context[YmlConstant.BASE_DIR] = base_dir # 将根路径设置到上下文中 | |||
self.__feedbackThread = None # 初始化反馈线程对象 | |||
# 初始化日志框架 ######################################################################### | |||
LogUtils.init_log(self.context) # 初始化日志框架 | |||
LogUtils.init_log(self.__context) # 初始化日志框架 | |||
# 初始化视频保存文件夹 ##################################################################### | |||
FileUtils.create_dir_not_exist(YmlConstant.get_file_path(self.context)) # 创建文件夹 | |||
FileUtils.create_dir_not_exist(YmlConstant.get_file_path(self.__context)) # 创建文件夹 | |||
# 创建任务记录字典 ######################################################################## | |||
self.onlineProcesses = {} # 记录当前正在执行的实时流分析任务 | |||
@@ -58,10 +58,10 @@ class DispatcherService: | |||
self.fbQueue = Queue() | |||
# 监听topic信息 ########################################################################## | |||
self.online_topic = YmlConstant.get_online_tasks_topic(self.context) | |||
self.offline_topic = YmlConstant.get_offline_tasks_topic(self.context) | |||
self.image_topic = YmlConstant.get_image_tasks_topic(self.context) | |||
self.recording_task_topic = YmlConstant.get_recording_tasks_topic(self.context) | |||
self.online_topic = YmlConstant.get_online_tasks_topic(self.__context) | |||
self.offline_topic = YmlConstant.get_offline_tasks_topic(self.__context) | |||
self.image_topic = YmlConstant.get_image_tasks_topic(self.__context) | |||
self.recording_task_topic = YmlConstant.get_recording_tasks_topic(self.__context) | |||
self.topics = [self.online_topic, self.offline_topic, self.image_topic, self.recording_task_topic] | |||
self.analysisType = { | |||
self.online_topic: (AnalysisType.ONLINE.value, lambda x, y: self.online(x, y), | |||
@@ -79,16 +79,16 @@ class DispatcherService: | |||
gpu_codes = YmlConstant.GPU_CODES | |||
gpu_array = [g for g in gpu_codes if g in gpu_name] | |||
if len(gpu_array) > 0: | |||
self.context[YmlConstant.GPU_NAME] = gpu_array[0] | |||
self.__context[YmlConstant.GPU_NAME] = gpu_array[0] | |||
if gpu_array[0] == YmlConstant.GPU_2080: | |||
self.context[YmlConstant.GPU_NAME] = YmlConstant.GPU_2080_Ti | |||
self.__context[YmlConstant.GPU_NAME] = YmlConstant.GPU_2080_Ti | |||
else: | |||
raise Exception("GPU资源不在提供的模型所支持的范围内!请先提供对应的GPU模型!") | |||
# 服务调用启动方法 | |||
def start_service(self): | |||
# 初始化kafka监听者 | |||
customerKafkaConsumer = KafkaUtils.CustomerKafkaConsumer(self.context, topics=self.topics) | |||
customerKafkaConsumer = KafkaUtils.CustomerKafkaConsumer(self.__context, topics=self.topics) | |||
logger.info("(♥◠‿◠)ノ゙ DSP【算法调度服务】启动成功 ლ(´ڡ`ლ)゙") | |||
# 循环消息处理 | |||
while True: | |||
@@ -191,7 +191,7 @@ class DispatcherService: | |||
if self.onlineProcesses.get(msg.get(YmlConstant.REQUEST_ID)): | |||
logger.warning("重复任务,请稍后再试!requestId:{}", msg.get(YmlConstant.REQUEST_ID)) | |||
return | |||
cfg = {YmlConstant.FBQUEUE: self.fbQueue, YmlConstant.CONTEXT: self.context, YmlConstant.MSG: msg, | |||
cfg = {YmlConstant.FBQUEUE: self.fbQueue, YmlConstant.CONTEXT: self.__context, YmlConstant.MSG: msg, | |||
YmlConstant.GPU_IDS: gpu_ids, YmlConstant.ANALYSE_TYPE: analysisType} | |||
# 创建在线识别进程并启动 | |||
oirp = OnlineIntelligentRecognitionProcess(cfg) | |||
@@ -222,7 +222,7 @@ class DispatcherService: | |||
if self.offlineProcesses.get(msg.get(YmlConstant.REQUEST_ID)): | |||
logger.warning("重复任务,请稍后再试!requestId:{}", msg.get(YmlConstant.REQUEST_ID)) | |||
return | |||
cfg = {YmlConstant.FBQUEUE: self.fbQueue, YmlConstant.CONTEXT: self.context, YmlConstant.MSG: msg, | |||
cfg = {YmlConstant.FBQUEUE: self.fbQueue, YmlConstant.CONTEXT: self.__context, YmlConstant.MSG: msg, | |||
YmlConstant.GPU_IDS: gpu_ids, YmlConstant.ANALYSE_TYPE: analysisType} | |||
ofirp = OfflineIntelligentRecognitionProcess(cfg) | |||
ofirp.start() | |||
@@ -242,7 +242,7 @@ class DispatcherService: | |||
if pp is not None: | |||
logger.warning("重复任务,请稍后再试!requestId:{}", msg.get(YmlConstant.REQUEST_ID)) | |||
return | |||
cfg = {YmlConstant.FBQUEUE: self.fbQueue, YmlConstant.CONTEXT: self.context, YmlConstant.MSG: msg, | |||
cfg = {YmlConstant.FBQUEUE: self.fbQueue, YmlConstant.CONTEXT: self.__context, YmlConstant.MSG: msg, | |||
YmlConstant.GPU_IDS: gpu_ids, YmlConstant.ANALYSE_TYPE: analysisType} | |||
# 创建在线识别进程并启动 | |||
imagep = PhotosIntelligentRecognitionProcess(cfg) | |||
@@ -275,11 +275,44 @@ class DispatcherService: | |||
''' | |||
def start_feedback_thread(self): | |||
if self.__feedbackThread is None: | |||
self.__feedbackThread = FeedbackThread(self.fbQueue, self.__context) | |||
self.__feedbackThread.setDaemon(True) | |||
self.__feedbackThread.start() | |||
start_time = time.time() | |||
while True: | |||
if self.__feedbackThread.is_alive(): | |||
return | |||
if not self.__feedbackThread.is_alive(): | |||
logger.warning("反馈线程异常等待中") | |||
if time.time() - start_time <= 3: | |||
continue | |||
elif int(time.time() - start_time) <= 5: | |||
logger.warning("反馈线程异常重启中") | |||
self.__feedbackThread.start() | |||
time.sleep(2) | |||
continue | |||
elif int(time.time() - start_time) > 5: | |||
raise Exception("反馈线程程启动异常") | |||
# 如果反馈线程为空,启动反馈线程,如果反馈线程意外停止,再次启动反馈线程 | |||
if self.feedbackThread is None or not self.feedbackThread.is_alive(): | |||
self.feedbackThread = FeedbackThread(self.fbQueue, self.context) | |||
self.feedbackThread.setDaemon(True) | |||
self.feedbackThread.start() | |||
if self.__feedbackThread is not None and not self.__feedbackThread.is_alive(): | |||
start_time_1 = time.time() | |||
while True: | |||
if self.__feedbackThread.is_alive(): | |||
return | |||
if not self.__feedbackThread.is_alive(): | |||
logger.warning("反馈线程异常等待中") | |||
if time.time() - start_time_1 <= 3: | |||
continue | |||
elif time.time() - start_time_1 <= 5: | |||
logger.warning("反馈线程异常重启中") | |||
self.__feedbackThread.start() | |||
time.sleep(1) | |||
continue | |||
elif time.time() - start_time_1 > 5: | |||
raise Exception("反馈线程程启动异常") | |||
''' | |||
在线分析逻辑 | |||
@@ -287,7 +320,7 @@ class DispatcherService: | |||
def online(self, message, analysisType): | |||
if YmlConstant.START == message.get(YmlConstant.COMMAND): | |||
gpu_ids = GPUtils.check_gpu_resource(self.context) | |||
gpu_ids = GPUtils.check_gpu_resource(self.__context) | |||
self.startOnlineProcess(message, gpu_ids, analysisType) | |||
elif YmlConstant.STOP == message.get(YmlConstant.COMMAND): | |||
self.stopOnlineProcess(message) | |||
@@ -296,7 +329,7 @@ class DispatcherService: | |||
def offline(self, message, analysisType): | |||
if YmlConstant.START == message.get(YmlConstant.COMMAND): | |||
gpu_ids = GPUtils.check_gpu_resource(self.context) | |||
gpu_ids = GPUtils.check_gpu_resource(self.__context) | |||
self.startOfflineProcess(message, gpu_ids, analysisType) | |||
elif YmlConstant.STOP == message.get(YmlConstant.COMMAND): | |||
self.stopOfflineProcess(message) | |||
@@ -305,7 +338,7 @@ class DispatcherService: | |||
def image(self, message, analysisType): | |||
if YmlConstant.START == message.get(YmlConstant.COMMAND): | |||
gpu_ids = GPUtils.check_gpu_resource(self.context) | |||
gpu_ids = GPUtils.check_gpu_resource(self.__context) | |||
self.startImageProcess(message, gpu_ids, analysisType) | |||
else: | |||
pass | |||
@@ -324,7 +357,7 @@ class DispatcherService: | |||
if self.recordingProcesses.get(msg.get(YmlConstant.REQUEST_ID)): | |||
logger.warning("重复任务,请稍后再试!requestId:{}", msg.get(YmlConstant.REQUEST_ID)) | |||
return | |||
cfg = {YmlConstant.FBQUEUE: self.fbQueue, YmlConstant.CONTEXT: self.context, YmlConstant.MSG: msg, | |||
cfg = {YmlConstant.FBQUEUE: self.fbQueue, YmlConstant.CONTEXT: self.__context, YmlConstant.MSG: msg, | |||
YmlConstant.ANALYSE_TYPE: analysisType} | |||
srp = ScreenRecordingProcess(cfg) | |||
srp.start() |
@@ -4,9 +4,9 @@ import traceback | |||
from aliyunsdkcore.client import AcsClient | |||
from aliyunsdkvod.request.v20170321 import CreateUploadVideoRequest | |||
from aliyunsdkvod.request.v20170321 import GetPlayInfoRequest | |||
from voduploadsdk.AliyunVodUtils import * | |||
from voduploadsdk.AliyunVodUploader import AliyunVodUploader | |||
from voduploadsdk.UploadVideoRequest import UploadVideoRequest | |||
from vodsdk.AliyunVodUtils import * | |||
from vodsdk.AliyunVodUploader import AliyunVodUploader | |||
from vodsdk.UploadVideoRequest import UploadVideoRequest | |||
# # # 填入AccessKey信息 | |||
def init_vod_client(accessKeyId, accessKeySecret): |
@@ -10,9 +10,9 @@ from alibabacloud_darabonba_env.client import Client as EnvClient | |||
from alibabacloud_vod20170321 import models as vod_20170321_models | |||
from alibabacloud_tea_console.client import Client as ConsoleClient | |||
from alibabacloud_tea_util.client import Client as UtilClient | |||
from voduploadsdk.AliyunVodUtils import * | |||
from voduploadsdk.AliyunVodUploader import AliyunVodUploader | |||
from voduploadsdk.UploadVideoRequest import UploadVideoRequest | |||
from vodsdk.AliyunVodUtils import * | |||
from vodsdk.AliyunVodUploader import AliyunVodUploader | |||
from vodsdk.UploadVideoRequest import UploadVideoRequest | |||
class Sample: | |||
def __init__(self): | |||
@@ -133,9 +133,9 @@ import traceback | |||
from aliyunsdkcore.client import AcsClient | |||
from aliyunsdkvod.request.v20170321 import CreateUploadVideoRequest | |||
from aliyunsdkvod.request.v20170321 import GetPlayInfoRequest | |||
from voduploadsdk.AliyunVodUtils import * | |||
from voduploadsdk.AliyunVodUploader import AliyunVodUploader | |||
from voduploadsdk.UploadVideoRequest import UploadVideoRequest | |||
from vodsdk.AliyunVodUtils import * | |||
from vodsdk.AliyunVodUploader import AliyunVodUploader | |||
from vodsdk.UploadVideoRequest import UploadVideoRequest | |||
# 获取播放地址 | |||
def init_vod_client(accessKeyId, accessKeySecret): | |||
regionId = 'cn-shanghai' # 点播服务接入地域 |
@@ -6,9 +6,9 @@ import json | |||
from aliyunsdkcore.client import AcsClient | |||
from aliyunsdkvod.request.v20170321 import GetPlayInfoRequest | |||
from voduploadsdk.AliyunVodUtils import * | |||
from voduploadsdk.AliyunVodUploader import AliyunVodUploader | |||
from voduploadsdk.UploadVideoRequest import UploadVideoRequest | |||
from vodsdk.AliyunVodUtils import * | |||
from vodsdk.AliyunVodUploader import AliyunVodUploader | |||
from vodsdk.UploadVideoRequest import UploadVideoRequest | |||
''' | |||
视频上传使用vod |
@@ -3,9 +3,9 @@ import traceback | |||
from aliyunsdkcore.client import AcsClient | |||
from aliyunsdkvod.request.v20170321 import CreateUploadVideoRequest | |||
from aliyunsdkvod.request.v20170321 import GetPlayInfoRequest | |||
from voduploadsdk.AliyunVodUtils import * | |||
from voduploadsdk.AliyunVodUploader import AliyunVodUploader | |||
from voduploadsdk.UploadVideoRequest import UploadVideoRequest | |||
from vodsdk.AliyunVodUtils import * | |||
from vodsdk.AliyunVodUploader import AliyunVodUploader | |||
from vodsdk.UploadVideoRequest import UploadVideoRequest | |||
# 获取播放地址 | |||
def init_vod_client(accessKeyId, accessKeySecret): | |||
regionId = 'cn-shanghai' # 点播服务接入地域 |
@@ -49,9 +49,7 @@ def get_video_info(in_file): | |||
if __name__ == '__main__': | |||
file_path = 'https://vod.play.t-aaron.com/customerTrans/edc96ea2115a0723a003730956208134/55547af9-184f0827dae-0004-f90c-f2c-7ec68.mp4' | |||
#file_path = 'https://vod.play.t-aaron.com/customerTrans/edc96ea2115a0723a003730956208134/40b416f7-183b57f6be0-0004-f90c-f2c-7ec68.mp4' | |||
#file_path = 'https://vod.play.t-aaron.com/3301fc8e166f45be88f2214e7a8f4a9d/e29535365b54434d9ed2e8c3b0a175da-fba35541b31a1049ca05b145a283c33a-hd.mp4' | |||
file_path = r'D:\shipin\777.mp4' | |||
video_info = get_video_info(file_path) | |||
print(json.dumps(video_info)) | |||
# total_frames = int(video_info['nb_frames']) | |||
@@ -62,47 +60,32 @@ if __name__ == '__main__': | |||
# image_array = numpy.asarray(bytearray(out), dtype="uint8") | |||
# image = cv2.imdecode(image_array, cv2.IMREAD_COLOR) | |||
# kwargs={'fflags': 'nobuffer', 'flags': 'low_delay'} | |||
kwargs={ | |||
# "hwaccel": "nvdec", | |||
# "vcodec": "h264_cuvid", | |||
# "c:v": "h264_cuvid" | |||
} | |||
output_args = { | |||
# "vcodec": "hevc_nvenc", | |||
# "c:v": "hevc_nvenc", | |||
# "preset": "fast", | |||
} | |||
# i = 1 | |||
# process1 = ( | |||
# ffmpeg | |||
# .input(file_path, **kwargs) | |||
# .output('pipe:', format='rawvideo', pix_fmt='rgb24', **output_args) | |||
# # .global_args("-an") | |||
# .overwrite_output() | |||
# .run_async(pipe_stdout=True, pipe_stderr=True) | |||
# ) | |||
width = int(video_info['width']) | |||
height = int(video_info['height']) | |||
width_2_1 = int(width/2) | |||
height_2_1 = int(height/2) | |||
print("长:", width, "宽:", height) | |||
text = '' | |||
command = ['ffmpeg', | |||
# '-hwaccel', 'cuvid', | |||
'-c:v', 'h264_cuvid', | |||
# '-hwaccel_output_format', 'cuda', | |||
'-resize', '%sx%s' % (width_2_1, height_2_1), | |||
# '-resize', '%sx%s' % (width_2_1, height_2_1), | |||
'-i', file_path, | |||
# '-c:v', 'hevc_nvenc', | |||
# '-pix_fmt', 'yuv420p', | |||
'-f', 'rawvideo', | |||
# '-pix_fmt', 'bgr24', | |||
'-pix_fmt', 'bgr24', | |||
'-an', | |||
'-'] | |||
p = sp.Popen(command, stdout=sp.PIPE) | |||
# ai_video_file = cv2.VideoWriter(r"C:\Users\chenyukun\Desktop\shipin\aa.mp4", cv2.VideoWriter_fourcc(*'mp4v'), 30, | |||
# (width, height)) | |||
# for line in p.stderr: | |||
# # print(line.strip()) | |||
# for line in p.stdout: | |||
# print(text) | |||
command1 = ['ffmpeg', | |||
'-report', | |||
# '-loglevel', 'debug', | |||
'-y', # 不经过确认,输出时直接覆盖同名文件。 | |||
'-f', 'rawvideo', | |||
@@ -112,7 +95,7 @@ if __name__ == '__main__': | |||
# '-c:v', 'h264_cuvid', | |||
# '-hwaccel_output_format', 'cuda', | |||
# '-s', "{}x{}".format(self.width * 2, self.height), | |||
'-s', "{}x{}".format(width_2_1, height_2_1), | |||
'-s', "{}x{}".format(width, height), | |||
'-r', str(25), | |||
'-i', '-', # 指定输入文件 | |||
'-g', str(5), | |||
@@ -141,30 +124,37 @@ if __name__ == '__main__': | |||
'rtmp://live.push.t-aaron.com/live/THSAr'] | |||
# # 管道配置 | |||
p1 = sp.Popen(command1, stdin=sp.PIPE) | |||
p1 = sp.Popen(command1, stdin=sp.PIPE, stdout=sp.PIPE, stderr=sp.PIPE, ) | |||
start1 = time.time() | |||
num = 0 | |||
while True: | |||
num += 1 | |||
# if num ==100: | |||
# print(time.time()-start1) | |||
# break | |||
start = time.time() | |||
in_bytes = p.stdout.read(int(width * height*3//8)) | |||
# print(type(in_bytes)) | |||
# img = (np.frombuffer(in_bytes, np.uint8)).reshape((height*3//4, width//2)) | |||
# result = cv2.cvtColor(img, cv2.COLOR_YUV2BGR_NV12) | |||
# result = cv2.resize(result, (int(width / 2), int(height / 2)), interpolation=cv2.INTER_LINEAR) | |||
# result = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) | |||
if not in_bytes: | |||
print(in_bytes) | |||
# ai_video_file.release() | |||
p.stdout.close() | |||
p.wait() | |||
break | |||
print(num) | |||
time.sleep(0.001) | |||
# p1.stdin.write(result.tostring()) | |||
# num += 1 | |||
# # if num ==100: | |||
# # print(time.time()-start1) | |||
# # break | |||
# start = time.time() | |||
in_bytes = p.stdout.read(int(width * height*3)) | |||
# # print(type(in_bytes)) | |||
img = (np.frombuffer(in_bytes, np.uint8)).reshape((height, width, 3)) | |||
# # result = cv2.cvtColor(img, cv2.COLOR_YUV2BGR_NV12) | |||
# # result = cv2.resize(result, (int(width / 2), int(height / 2)), interpolation=cv2.INTER_LINEAR) | |||
# # result = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) | |||
for line in iter(p1.stdout.readline, b''): | |||
print(line) | |||
# if in_bytes: | |||
# p1.stdin.write(img.tostring()) | |||
# print(p1.stderr.read().decode('utf-8')) | |||
# for line in iter(p1.std | |||
# | |||
# | |||
# out.readline, ''): | |||
# line = line.decode('utf-8') | |||
# print(line) | |||
# print(num) | |||
# time.sleep(0.001) | |||
# p1.stdin.write(in_frame.tostring()) | |||
# frame | |||
# .astype(np.uint8) | |||
@@ -182,4 +172,4 @@ if __name__ == '__main__': | |||
# break | |||
# cv2.imshow('frame', frame) | |||
# time.sleep(1111) | |||
p.kill() | |||
# p.kill() |
@@ -0,0 +1,108 @@ | |||
import numpy as np | |||
# # 使用标量类型 | |||
# dt = np.dtype(np.int32) | |||
# print(dt) | |||
# | |||
# # int8, int16, int32, int64 四种数据类型可以使用字符串 'i1', 'i2','i4','i8' 代替 | |||
# dt = np.dtype('i4') | |||
# print(dt) | |||
# | |||
# # 字节顺序标注 | |||
# dt = np.dtype('<i4') | |||
# print(dt) | |||
# | |||
# dt = np.dtype([('age', np.int8)]) | |||
# print(dt) | |||
# | |||
# dt = np.dtype([('age', np.int8)]) | |||
# a = np.array([(10,), (20,), (30,)], dtype=dt) | |||
# print(a) | |||
# print(a['age']) | |||
# | |||
# student = np.dtype([('name', 'S20'), ('age', 'i1'), ('marks', 'f4')]) | |||
# print(student) | |||
# | |||
# a = np.array([('abc', 21, 50), ('xyz', 18, 75)], dtype=student) | |||
# print(a) | |||
# | |||
# a = np.arange(32) | |||
# b = a.reshape(2, 4, 4) | |||
# print(b.ndim) | |||
# print(b.shape) | |||
# | |||
# a = np.array([[1, 2, 3], [4, 5, 6]]) | |||
# a.shape = (3, 2) | |||
# print(a) | |||
# | |||
# a = np.array([[1, 2, 3], [4, 5, 6]]) | |||
# b = a.reshape(3, 2) | |||
# print(b) | |||
# x = np.array([1, 2, 3, 4, 5], dtype=np.int8) | |||
# print(x.itemsize) | |||
# y = np.array([1, 2, 3, 4, 5], dtype=np.float64) | |||
# print(y.itemsize) | |||
# x = np.empty([3, 2], dtype=int) | |||
# print(x) | |||
# | |||
# # 默认为浮点数 | |||
# x = np.zeros(5) | |||
# print(x) | |||
# # 设置类型为整数 | |||
# y = np.zeros((5,), dtype=int) | |||
# print(y) | |||
# | |||
# # 自定义类型 | |||
# z = np.zeros((2, 2), dtype=[('x', 'i4'), ('y', 'i4')]) | |||
# print(z) | |||
# | |||
# # 默认为浮点数 | |||
# x = np.ones(5) | |||
# print(x) | |||
# | |||
# x = np.ones([2, 2], dtype=int) | |||
# print(x) | |||
# | |||
# s = b'Hello World' | |||
# a = np.frombuffer(s, dtype='S1') | |||
# print(a) | |||
# | |||
# arr1 = np.array([1, 2, 3, 4, 5]) | |||
# arr2 = np.frombuffer(arr1, dtype=int) | |||
# print(arr2) # [1 2 3 4 5] | |||
# | |||
# | |||
# list=range(5) | |||
# it=iter(list) | |||
# a = [1,2,3,4,5] | |||
# # 使用迭代器创建 ndarray | |||
# x=np.fromiter(arr1, dtype=float) | |||
# print(x) | |||
# aa = [ | |||
# [1, 2, 3], | |||
# [4, 5, 6], | |||
# [7, 8, 9] | |||
# ] | |||
# a = np.array(aa) | |||
# # b = a[1:3, 1:3] | |||
# # c = a[1:3, [1, 2]] | |||
# d = a[...,1:] | |||
# # print(b) | |||
# # print(c) | |||
# print(d) | |||
# a = np.array([[0, 0, 0], | |||
# [10, 10, 10], | |||
# [20, 20, 20], | |||
# [30, 30, 30]]) | |||
# b = np.array([1, 2, 3]) | |||
# bb = np.tile(b, (4, 1)) # 重复 b 的各个维度 | |||
# print(bb) | |||
# # print(a + bb) | |||
a = np.arange(6).reshape(2, 3) | |||
print(a) | |||
print("========================") | |||
print(a.T) |
@@ -0,0 +1,198 @@ | |||
""" | |||
1.色彩空间:[BGR] | |||
(1) HSV: 更类似于人类感觉颜色的方式。H:色相(Hue) S: 饱和度(Saturation0) V: 亮度(Value) | |||
(2) YUV: Y: 亮度信号 U/V: 两个色彩信号 -> 色彩的饱和度 | |||
(3) Lab: 有国际照明委员会建立。 L: 整张图到的明亮度 a\b: 负责颜色的多少 | |||
""" | |||
import cv2 | |||
import numpy as np | |||
from matplotlib import pyplot as plt | |||
# img = cv2.imread(r'C:\Users\chenyukun\Pictures\R.png') | |||
# cv2.imshow("img", img) | |||
# HSV | |||
# hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) | |||
# cv2.imshow("hsv", hsv) | |||
# YUV | |||
# yuv = cv2.cvtColor(img, cv2.COLOR_BGR2YUV) | |||
# cv2.imshow("yuv", yuv) | |||
# LAB | |||
# lab = cv2.cvtColor(img, cv2.COLOR_BGR2Lab) | |||
# cv2.imshow("lab", lab) | |||
# 分离颜色通道 | |||
# print(img) | |||
# b, g, r = cv2.split(img) | |||
# h, w = np.shape(b) | |||
# # 建立空白数组 | |||
# hest = np.zeros([256], dtype=np.int32) | |||
# print(hest) | |||
# # 遍历图片 | |||
# for i in [b, g, r]: | |||
# for row in range(h): | |||
# for col in range(w): | |||
# pv = i[row, col] | |||
# hest[pv] += 1 | |||
# plt.plot(hest, color='r') | |||
# plt.show() | |||
# cv2.waitKey(100000) | |||
# array1 = np.random.randint(0, 255, (300, 150, 3), dtype=np.uint8) | |||
# array2 = np.random.randint(200, 255, (300, 150, 3), dtype=np.uint8) | |||
# new_array = np.hstack([array1, array2]) | |||
# cv2.imshow("new_array", new_array) | |||
# cv2.waitKey(100000) | |||
# 彩色图片 -> BGR | |||
# 查看B | |||
# img_B = img.copy() | |||
# img_B[:, :, 1] = 0 | |||
# img_B[:, :, 2] = 0 | |||
# cv2.imshow("img_B", img_B) | |||
# cv2.waitKey(100000) | |||
# 查看G | |||
# img_G = img.copy() | |||
# img_G[:, :, 0] = 0 | |||
# img_G[:, :, 2] = 0 | |||
# cv2.imshow("img_G", img_G) | |||
# cv2.waitKey(100000) | |||
# 查看G | |||
# img_R = img.copy() | |||
# img_R[:, :, 0] = 0 | |||
# img_R[:, :, 1] = 0 | |||
# cv2.imshow("img_R", img_R) | |||
# cv2.waitKey(100000) | |||
# 深入研究图片的读取 | |||
""" | |||
cv2.imread(path, way) | |||
0: 读取灰度图片 | |||
1: 读取彩色图片 | |||
-1: 读取图片,加载Alpha通道 | |||
Alpha通道: | |||
指一张图片的透明与不透明度 | |||
""" | |||
img_color = cv2.imread(r'C:\Users\chenyukun\Pictures\R.png') | |||
img_gray = cv2.imread(r'C:\Users\chenyukun\Pictures\R.png', 0) | |||
img_transparent = cv2.imread(r'C:\Users\chenyukun\Pictures\R.png', -1) | |||
""" | |||
彩色图像:三维 B G R -> (373*490) | |||
灰度图像:二维:-> (373*490) | |||
推断出的公式: Y = (B+G+R)/3 | |||
官方给出的公式: Y = 0.299R + 0.587G + 0.114B | |||
cv.imshow() -> 不仅仅是array()必须是uint8这种类型的。 | |||
""" | |||
# 三通道分离 | |||
# b, g, r = cv2.split(img_color) | |||
# new_img = (b+g+r)/3 | |||
# new_img = 0.299*r + 0.587*g + 0.114*b | |||
# new_img = new_img.astype('uint8') | |||
# cv2.imshow('new_img', new_img) | |||
# 官方的填充方式: 以初始的灰度二维矩阵进行RGB通道的填充 | |||
# new_image = cv2.merge([img_gray,img_gray,img_gray]) | |||
# cv2.imshow('new_img', new_image) | |||
# cv2.waitKey(100000) | |||
# 图像的加法 -> 矩阵 | |||
# 加法 【前提:必须是一样大小的矩阵:对应的元素求和】 | |||
""" | |||
像素点: [0,255]超过255怎么办?【取余】 | |||
150+150=300 | |||
300%255 | |||
""" | |||
""" | |||
图片融合 | |||
addWeighted(src1, alpha, src2, beta, gamma) | |||
src1\src2: 第一张和第二张图片 | |||
alpha\beta: 第一张图片的权重、第二张图片的权重 | |||
gamma: 亮度的调节 | |||
""" | |||
# result = cv2.addWeighted(img_color, 0.6, img_color, 0.3, 0) | |||
# cv2.imshow('new_img', result) | |||
# cv2.waitKey(100000) | |||
# | |||
# # 图像的类型转换 | |||
# # 原始图片BGR -> RGB | |||
# cv2.cvtColor(img_color, cv2.COLOR_BGR2RGB) | |||
""" | |||
1.沿着x轴进行翻转 | |||
2.沿着y轴进行翻转 | |||
3.同时沿着x轴和y进行翻转 | |||
cv2.flip(src, flipCode) | |||
src: 源图像 | |||
flipCode: 翻转形式 | |||
0:沿着x轴进行翻转 | |||
1(大于等于1): 沿着y轴进行翻转 | |||
-1(小于等于-1): 同时沿着x轴和y进行翻转 | |||
""" | |||
""" | |||
图像阈值化处理 | |||
ret, dst = cv2.threshold(src, thresh, maxval, type) | |||
ret: 阈值返回值(阈值设定的是多少) | |||
dst: 输出的图像 | |||
src: 源图像 需要阈值化处理的图像 | |||
thresh: 人为指定的阈值 | |||
maxval: 当像素超过了阈值,(小于等于阈值)所赋予的值,否则取0 | |||
type: | |||
(1) cv2.THRESH_BINARY: 当像素点大于阈值时,取指定255,小于等于阈值时,取0 | |||
(2) cv2.THRESH_BINARY_INV: 当像素点大于阈值时,取0。小于等于阈值时,取255 | |||
(3) cv2.THRESH_TRUNC: 超过阈值取阈值,低于阈值取自身 | |||
(4) cv2.THRESH_TOZERO: 超过阈值不变,低于阈值取0 | |||
(5) cv2.THRESH_TOZERO_INV: 超过阈值变为0, 低于阈值不变 | |||
注意:当阈值处理彩色图像时,出现粉色等颜色的原因在于: BGR三通道的叠加 | |||
""" | |||
# ret, dst = cv2.threshold(img_color, 127, 255, cv2.THRESH_BINARY) | |||
# cv2.imshow('new_img', dst) | |||
# cv2.waitKey(100000) | |||
""" | |||
均值滤波 | |||
cv2.blur(src, kernel) | |||
src: 源图像 | |||
kernel: 大小(选择多大的矩阵进行平移[3*3最常见]) | |||
""" | |||
# new_img = cv2.blur(img_color, (3, 3)) | |||
# cv2.imshow('new_img', new_img) | |||
# cv2.waitKey(100000) | |||
""" | |||
方框滤波 | |||
cv2.boxFilter(src,depth, ksize, normalize) | |||
src: 源图像 | |||
depth: 图像的深度 填-1就ok, 表示与源图像深度相同 | |||
ksize: 核大小(3*3)(5*5) | |||
normalize: 是否进行归一化 | |||
0:false 不进行归一下(求和,像素点溢出,超过255取255) | |||
1:True 进行归一化,也就是均值滤波 | |||
""" | |||
""" | |||
高斯滤波【考虑了权重问题】 | |||
cv2.GaussianBlur(src, ksize, sigmaX, sigmaY) | |||
src:源图像 | |||
ksize: (3*3)(5*5) 必须为奇数 | |||
sigmaX, sigmaY: 高斯核函数在x或y方向上的标准偏差【控制权重】 | |||
sigmaX = 0, sigmaX = 0.3 * ((ksize -1) * 0.5 -1) + 0.8 | |||
""" | |||
''' | |||
中值滤波 | |||
cv2.medianBlur(src, ksize) | |||
src: 源图像 | |||
ksize:核大小, 只能传递int(1,3,5,7,9)【(3, 3)(5, 5)】 | |||
''' | |||
new_img = cv2.medianBlur(img_color, 5) | |||
cv2.imshow('new_img', new_img) | |||
cv2.waitKey(100000) |
@@ -0,0 +1,24 @@ | |||
import requests | |||
# url = 'https://www.baidu.com' | |||
# response = requests.get(url) | |||
# print(response) | |||
# print(type(response)) | |||
# # 返回的页面是requests库猜测的编码方式,有误 | |||
# print(response.text) | |||
# content = response.content.decode('utf-8') | |||
# print(content) | |||
url = 'https://www.baidu.com/s' | |||
kw = {"wd": "猫"} | |||
headers = { | |||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36" | |||
} | |||
response = requests.get(url, params=kw, headers=headers) | |||
# 打印requests库猜测的解码方式 | |||
print(response.encoding) | |||
content = response.content.decode('utf-8') | |||
print(content) | |||
# 查看状态码 | |||
print(response.status_code) | |||
@@ -0,0 +1,121 @@ | |||
# -*- coding: utf-8 -*- | |||
class Test: | |||
pass | |||
print(type(Test)) | |||
print(type(int)) | |||
# 结果都为<class 'type'>,type就是内置的元类,class关键字定义的所有的类以及内置的类都是由元类type实例化产生。 | |||
""" | |||
由于在python3中没有经典类和新式类的区别,因此python3中object是所有类的基类,那内置元类type又是什么? | |||
type是object类的类型,总结来说就是type是object的类型,同时,object又是type的基类,这句话看起来就有问题, | |||
到底是先有type还是先有object呢?这个问题有点类似于先有鸡还是先有蛋,我们可以通过代码简单分析一下 | |||
在python3中类和类型是同一种东西,因此对象.__class__和type(对象)得到的结果是一致的,object的基类为空, | |||
但是type的基类为object,但是object的类型又是type。 | |||
""" | |||
print(type(object)) | |||
print(object.__class__) | |||
print(type(type)) | |||
print(type.__class__) | |||
print(object.__bases__) | |||
print(type.__bases__) | |||
# python字符串 | |||
strs = """ | |||
global name | |||
global age | |||
name = 'py' | |||
age = 18 | |||
addr = 'xx' | |||
""" | |||
# 定义全局作用域中的名字和值 | |||
globals = { | |||
'a': '1', | |||
'b': '2' | |||
} | |||
# 定义局部作用域中的名字和值 | |||
locals = { | |||
'x': 3, | |||
'y': 4 | |||
} | |||
exec(strs, globals, locals) | |||
print(globals['name']) | |||
print(locals) | |||
# 了解了exec的作用之后,就可以分析class关键字如何借助type元类产生类的步骤: | |||
# 1 定义类名 | |||
class_name = 'Test' | |||
# 2 定义类的基类(父类) | |||
class_bases = (object,) | |||
# 3 执行类体代码拿到类的名称空间 | |||
class_dic = {} | |||
# 4 定义类体代码(本质是字符串) | |||
class_body = """ | |||
def __init__(self,name,age): | |||
self.name=name | |||
self.age=age | |||
def test(self): | |||
print('%s:%s' %(self.name,self.name)) | |||
""" | |||
# 5 将字符串转为python能识别的语法:将class_body运行时产生的名字存入class_dic中 | |||
exec(class_body, {}, class_dic) | |||
# 查看类的名称空间 | |||
print(class_dic) | |||
# 6 调用元类产生类 | |||
Test = type(class_name, class_bases, class_dic) | |||
# 7 调用类产生对象 | |||
t = Test('python', '12') | |||
t.test() | |||
# 自定义元类 | |||
class MyMeta(type): # 自定义元类必须继承type,否则就是普通的类 | |||
''' | |||
早于__init__方法执行,必须返回空对象,由于该方法是调用类后第一个运行的方法, | |||
此时并没有对象产生,因此该方法的第一个参数必须是类本身(MyMeta),*args, **kwargs | |||
用来接收调用元类产生对象所需的参数(类名 类的基类 名称空间) | |||
''' | |||
def __new__(cls, *args, **kwargs): | |||
return type.__new__(cls, *args, **kwargs) # 直接调用父类type中的__new__方法 | |||
''' | |||
通过__init__控制类的产生,调用自定义元类与调用内置元类type的方式相同, | |||
需要传入类名、类的父类们、类体代码的名称空间,__init__方法中第一个参数来自于__new__方法产生的空对象。 | |||
''' | |||
def __init__(self, class_name, class_bases, class_dict): | |||
''' | |||
在该方法内可以控制类的产生 | |||
''' | |||
if not class_name.istitle(): # 实现类名首字母必须大写,否则抛出异常 | |||
raise NameError('类名的首字母必须大写') | |||
if '__doc__' not in class_dict or len(class_dict['__doc__'].strip())==0: # 实现类必须有文档注释,否则抛出异常 | |||
raise TypeError('必须有文档注释') | |||
def __call__(self, *args, **kwargs): | |||
print(self) | |||
print(args) | |||
print(kwargs) | |||
return 'test' | |||
class Test(metaclass=MyMeta): | |||
''' | |||
我是文档注释 | |||
''' | |||
def __init__(self): | |||
self.name = 'python' | |||
def test(self): | |||
print('test') | |||
t = Test() |
@@ -1,21 +1,23 @@ | |||
from loguru import logger | |||
import pickle | |||
from loguru import logger | |||
# 定义一个类 | |||
class Person: | |||
def __init__(self, name, age): | |||
self.name = name | |||
self.age = age | |||
# 创建一个Person实例 | |||
person = Person("Alice", 25) | |||
# 使用Loguru的serialize方法将Person实例序列化为字节字符串 | |||
serialized_person = logger.serialize(person) | |||
print(serialized_person) | |||
# 使用pickle库将字节字符串反序列化为Python对象 | |||
deserialized_person = pickle.loads(serialized_person) | |||
# 输出反序列化后的对象属性 | |||
print(deserialized_person.name) # Alice | |||
print(deserialized_person.age) # 25 | |||
# class Person: | |||
# def __init__(self, name, age): | |||
# self.name = name | |||
# self.age = age | |||
# | |||
# # 创建一个Person实例 | |||
# person = Person("Alice", 25) | |||
# | |||
# # 使用Loguru的serialize方法将Person实例序列化为字节字符串 | |||
# serialized_person = logger.serialize(person) | |||
# print(serialized_person) | |||
# # 使用pickle库将字节字符串反序列化为Python对象 | |||
# deserialized_person = pickle.loads(serialized_person) | |||
# | |||
# # 输出反序列化后的对象属性 | |||
# print(deserialized_person.name) # Alice | |||
# print(deserialized_person.age) # 25 | |||
aa = {"name": "11111"} | |||
logger.info(aa) |
@@ -0,0 +1,5 @@ | |||
from os import environ | |||
print("LOGURU_TRACE_NO" not in environ) |
@@ -0,0 +1,145 @@ | |||
import re | |||
''' | |||
1.匹配某个字符串 | |||
match():只能匹配某个 | |||
''' | |||
# text = 'python python' | |||
# result = re.match('py', text) | |||
# print(result) | |||
# print(result.group()) | |||
''' | |||
2.点 | |||
匹配任意的某个字符【无法匹配换行符】【必须从开头开始匹配】 | |||
''' | |||
# text = 'python' | |||
# result = re.match('.', text) | |||
# print(result) | |||
# print(result.group()) | |||
''' | |||
3.\d | |||
匹配任意的数字【除了数字外均无法匹配】 | |||
''' | |||
# text = '1python' | |||
# result = re.match('\d', text) | |||
# print(result) | |||
# print(result.group()) | |||
''' | |||
4.\D | |||
除了数字外均可匹配【数字均无法匹配】 | |||
''' | |||
# text = 'python' | |||
# result = re.match('\D', text) | |||
# print(result) | |||
# print(result.group()) | |||
''' | |||
5.\s | |||
匹配空白字符【\n、 \t、 \r 、空格】 | |||
''' | |||
# text = '\npython' | |||
# result = re.match('\s', text) | |||
# print(result) | |||
# print(result.group()) | |||
''' | |||
6.\w | |||
匹配小写的a-z,大写的A-Z,数字和下划线 | |||
''' | |||
# text = 'python' | |||
# result = re.match('\w', text) | |||
# print(result) | |||
# print(result.group()) | |||
''' | |||
7.\W | |||
匹配除小写\w之外的所有字符 | |||
''' | |||
# text = '\npython' | |||
# result = re.match('\W', text) | |||
# print(result) | |||
# print(result.group()) | |||
''' | |||
8.[] ->> 组合的方式 | |||
只要在中括号内的内容均可匹配 | |||
''' | |||
# text = '\npython' | |||
# result = re.match('[\n_]', text) | |||
# print(result) | |||
# print(result.group()) | |||
''' | |||
9.星号 * | |||
匹配零个或者多个字符 | |||
''' | |||
# text = '139-1234-5678' | |||
# result = re.match('[-\d]*', text) | |||
# print(result) | |||
# print(result.group()) | |||
''' | |||
10.加号 + | |||
匹配1个或者多个字符 | |||
''' | |||
# text = '139-1234-5678' | |||
# result = re.match('[-\d]+', text) | |||
# print(result) | |||
# print(result.group()) | |||
''' | |||
11.问号 ? | |||
匹配0个或者匹配1个 | |||
''' | |||
# text = '139-1234-5678' | |||
# result = re.match('[-\d]?', text) | |||
# print(result) | |||
# print(result.group()) | |||
''' | |||
12. {m,n} 匹配m到n个 | |||
匹配0个或者匹配1个 | |||
''' | |||
# text = '139-1234-5678' | |||
# result = re.match('[-\d]{1,5}', text) | |||
# print(result) | |||
# print(result.group()) | |||
''' | |||
13. 匹配所有的数字 | |||
''' | |||
# text = '139-1234-5678' | |||
# result = re.match('[-0-9]*', text) | |||
# print(result) | |||
# print(result.group()) | |||
''' | |||
14. 匹配所有的非数字 | |||
''' | |||
# text = '139-1234-5678' | |||
# result = re.match('[^0-9]*', text) | |||
# print(result) | |||
# print(result.group()) | |||
# re.match() 必须从字符串开头进行匹配 | |||
# re.search() 从左到右进行字符串的遍历,找到就返回 | |||
# text = 'aapython' | |||
# result = re.match('py', text) | |||
# print(result) | |||
# print(result.group()) | |||
# text = 'aapython' | |||
# result = re.search('py', text) | |||
# print(result) | |||
# print(result.group()) | |||
""" | |||
1. 贪婪模式: 正则表达式会尽可能多地匹配字符【默认就是贪婪模式】 | |||
2. 非贪婪模式: 正则表达式会尽可能少地匹配字符【?】 | |||
转义字符 \ | |||
""" | |||
@@ -0,0 +1,18 @@ | |||
from typing import Optional, Union, Any, Set | |||
a: int = 8 | |||
b: bool = True | |||
c: str = 'ok' | |||
d: Optional[Union[int, float]] = None | |||
e: float = 9.8 | |||
f: bytes = b'32' | |||
d = 5 | |||
d = 9.8 | |||
d = None | |||
s: Set[int] = {1, 2, '3'} | |||
for ss in s: | |||
print(ss) |
@@ -1,7 +1,7 @@ | |||
# -*- coding: utf-8 -*- | |||
import threading | |||
import time | |||
from concurrent.futures import ThreadPoolExecutor | |||
from concurrent.futures import ThreadPoolExecutor, ALL_COMPLETED, wait | |||
class Test(object): | |||
@@ -24,21 +24,25 @@ class Test(object): | |||
def bb(): | |||
print("!1111111111") | |||
def aa(t): | |||
while True: | |||
t.submit(bb) | |||
def aa(aa): | |||
print(aa) | |||
time.sleep(5) | |||
return "1111" | |||
# test = Test() | |||
# test.process() | |||
# print(3//2) | |||
# with ThreadPoolExecutor(max_workers=10) as t: | |||
# t.submit(aa, t) | |||
# time.sleep(1000) | |||
# codeArray=[''] | |||
# codeStr = ','.join(codeArray) | |||
# print(codeStr) | |||
aa={'aaaa': []} | |||
aa["aaaa"].append("1111111") | |||
aa["aaaa"].append("1111111") | |||
aa["aaaa"].append("1111111") | |||
print(aa) | |||
with ThreadPoolExecutor(max_workers=10) as t: | |||
aa = t.submit(aa, "aaa") | |||
results = wait([aa], timeout=60, return_when=ALL_COMPLETED) | |||
completed_futures = results.done | |||
for f in completed_futures: | |||
if f.exception(): | |||
raise f.exception() | |||
else: | |||
print(f"Task {f.result()} succeeded") | |||
dsp算法交互 V 2.7.4转测 | |||
1. 修复算法交互分析后上传大视频失败问题bug | |||
2. 修改百度图片子线程异常信息父级线程不打印问题bug |
@@ -0,0 +1,24 @@ | |||
# -*- coding: utf-8 -*- | |||
from threading import RLock | |||
class SingletonType(type): | |||
single_lock = RLock() | |||
def __call__(cls, *args, **kwargs): # 创建cls的对象时候调用 | |||
with SingletonType.single_lock: | |||
if not hasattr(cls, "_instance"): | |||
cls._instance = super(SingletonType, cls).__call__(*args, **kwargs) # 创建cls的对象 | |||
return cls._instance | |||
class Singleton(metaclass=SingletonType): | |||
def __init__(self, name): | |||
self.name = name | |||
single_1 = Singleton('第1次创建') | |||
single_2 = Singleton('第2次创建') | |||
print(single_1.name, single_2.name) # 第1次创建 第1次创建 | |||
print(single_1 is single_2) |
@@ -0,0 +1,28 @@ | |||
# -*- coding: utf-8 -*- | |||
from threading import RLock | |||
single_lock = RLock() | |||
def Singleton(cls): | |||
instance = {} | |||
def _singleton_wrapper(*args, **kargs): | |||
with single_lock: | |||
if cls not in instance: | |||
instance[cls] = cls(*args, **kargs) | |||
return instance[cls] | |||
return _singleton_wrapper | |||
@Singleton | |||
class SingletonTest(object): | |||
def __init__(self, name): | |||
self.name = name | |||
slt_1 = SingletonTest('第1次创建') | |||
print(slt_1.name) | |||
slt_2 = SingletonTest('第2次创建') | |||
print(slt_1.name, slt_2.name) | |||
print(slt_1 is slt_2) |
@@ -0,0 +1,18 @@ | |||
from threading import RLock | |||
class Singleton(object): | |||
single_lock = RLock() | |||
def __init__(self, name): | |||
self.name = name | |||
@classmethod | |||
def instance(cls, *args, **kwargs): | |||
with Singleton.single_lock: | |||
if not hasattr(Singleton, "_instance"): | |||
Singleton._instance = Singleton(*args, **kwargs) | |||
return Singleton._instance | |||
single_1 = Singleton.instance('第1次创建') | |||
single_2 = Singleton.instance('第2次创建') | |||
print(single_1 is single_2) # True |
@@ -0,0 +1,22 @@ | |||
from threading import RLock | |||
class Singleton(object): | |||
single_lock = RLock() | |||
def __init__(self, name): | |||
if hasattr(self, 'name'): | |||
return | |||
self.name = name | |||
def __new__(cls, *args, **kwargs): | |||
with Singleton.single_lock: | |||
if not hasattr(Singleton, "_instance"): | |||
Singleton._instance = object.__new__(cls) | |||
return Singleton._instance | |||
single_1 = Singleton('第1次创建') | |||
single_2 = Singleton('第2次创建') | |||
print(single_1.name, single_2.name) # 第2次创建 第2次创建 | |||
print(single_1 is single_2) # True |
@@ -0,0 +1,15 @@ | |||
class DbSingleton(): | |||
def __init__(self, host, port, username, password): | |||
self.host = host | |||
self.port = port | |||
self.username = username | |||
self.password = password | |||
self.pool = None # 连接池 | |||
def connect(self): | |||
print("建立连接") | |||
db_singleton = DbSingleton('host', 'port', 'username', 'password') |
@@ -0,0 +1,7 @@ | |||
from single import db_singleton | |||
if __name__ == '__main__': | |||
print(id(db_singleton)) | |||
print(id(db_singleton)) | |||
print(id(db_singleton)) |
@@ -0,0 +1,36 @@ | |||
from abc import ABC, abstractmethod | |||
class Fruit(ABC): | |||
def __init__(self, name): | |||
self.name = name | |||
@abstractmethod | |||
def make_juice(self): | |||
pass | |||
class Apple(Fruit): | |||
def make_juice(self): | |||
print(f"制作{self.name}汁") | |||
class Grape(Fruit): | |||
def make_juice(self): | |||
print(f"制作{self.name}酒") | |||
class FruitFactory(): | |||
@classmethod | |||
def create_fruit(cls, name): | |||
if name == 'apple': | |||
return Apple('苹果') | |||
elif name == 'grape': | |||
return Grape("葡萄") | |||
fruit1 = FruitFactory.create_fruit('apple') | |||
fruit1.make_juice() | |||
fruit2 = FruitFactory.create_fruit('grape') | |||
fruit2.make_juice() |
@@ -0,0 +1,44 @@ | |||
from abc import ABC, abstractmethod | |||
class Fruit(ABC): | |||
def __init__(self, name): | |||
self.name = name | |||
@abstractmethod | |||
def make_juice(self): | |||
pass | |||
class Apple(Fruit): | |||
def make_juice(self): | |||
print(f"制作{self.name}汁") | |||
class Grape(Fruit): | |||
def make_juice(self): | |||
print(f"制作{self.name}酒") | |||
class AbcFruitFactory(ABC): | |||
@abstractmethod | |||
def create_fruit(self): | |||
pass | |||
class AppleFactory(AbcFruitFactory): | |||
def create_fruit(self): | |||
return Apple('苹果') | |||
class OrangeFactory(AbcFruitFactory): | |||
def create_fruit(self): | |||
return Grape('葡萄') | |||
fruit1 = AppleFactory().create_fruit() | |||
fruit1.make_juice() # 制作苹果汁 | |||
fruit2 = OrangeFactory().create_fruit() | |||
fruit2.make_juice() |
@@ -1,31 +1,33 @@ | |||
# -*- coding: utf-8 -*- | |||
import oss2 | |||
import time | |||
from aliyunsdkvod.request.v20170321.GetPlayInfoRequest import GetPlayInfoRequest | |||
from loguru import logger | |||
from common import YmlConstant | |||
from exception.CustomerException import ServiceException | |||
from enums.ExceptionEnum import ExceptionType | |||
import json | |||
from aliyunsdkcore.client import AcsClient | |||
from aliyunsdkvod.request.v20170321 import GetPlayInfoRequest | |||
from voduploadsdk.AliyunVodUtils import * | |||
from voduploadsdk.AliyunVodUploader import AliyunVodUploader | |||
from voduploadsdk.UploadVideoRequest import UploadVideoRequest | |||
from util import LogUtils | |||
from vodsdk.AliyunVodUploader import AliyunVodUploader | |||
from vodsdk.UploadVideoRequest import UploadVideoRequest | |||
class AliyunOssSdk: | |||
def __init__(self, context, log, requestId): | |||
def __init__(self, context, requestId): | |||
# LogUtils.init_log(context) | |||
self.__context = context | |||
self.bucket = None | |||
self.__logger = log | |||
self.__requestId = requestId | |||
def get_oss_bucket(self): | |||
if self.bucket is None: | |||
self.__logger.info("初始化oss桶, requestId:{}", self.__requestId) | |||
logger.info("初始化oss桶, requestId:{}", self.__requestId) | |||
auth = oss2.Auth(YmlConstant.get_aliyun_access_key(self.__context), | |||
YmlConstant.get_aliyun_access_secret(self.__context)) | |||
self.bucket = oss2.Bucket(auth, YmlConstant.get_aliyun_oss_endpoint(self.__context), | |||
@@ -33,29 +35,29 @@ class AliyunOssSdk: | |||
connect_timeout=YmlConstant.get_aliyun_oss_connect_timeout(self.__context)) | |||
def sync_upload_file(self, updatePath, fileByte): | |||
self.__logger.info("开始上传文件到oss, requestId:{}", self.__requestId) | |||
logger.info("开始上传文件到oss, requestId:{}", self.__requestId) | |||
self.get_oss_bucket() | |||
MAX_RETRIES = 3 | |||
retry_count = 0 | |||
while True: | |||
try: | |||
self.bucket.put_object(updatePath, fileByte) | |||
self.__logger.info("上传文件到oss成功! requestId:{}", self.__requestId) | |||
logger.info("上传文件到oss成功! requestId:{}", self.__requestId) | |||
break | |||
except Exception as e: | |||
retry_count += 1 | |||
time.sleep(1) | |||
self.__logger.info("上传文件到oss失败, 重试次数:{}, requestId:{}", retry_count, self.__requestId) | |||
logger.info("上传文件到oss失败, 重试次数:{}, requestId:{}", retry_count, self.__requestId) | |||
if retry_count > MAX_RETRIES: | |||
self.__logger.exception("上传文件到oss重试失败:{}, requestId:{}", e, self.__requestId) | |||
logger.exception("上传文件到oss重试失败:{}, requestId:{}", e, self.__requestId) | |||
raise e | |||
class ThAliyunVodSdk: | |||
def __init__(self, context, log, requestId): | |||
def __init__(self, context, requestId): | |||
# LogUtils.init_log(context) | |||
self.__context = context | |||
self.__logger = log | |||
self.__requestId = requestId | |||
def init_vod_client(self, accessKeyId, accessKeySecret): | |||
@@ -63,7 +65,7 @@ class ThAliyunVodSdk: | |||
return AcsClient(accessKeyId, accessKeySecret, regionId, auto_retry=True, max_retry_time=3, timeout=30) | |||
def get_play_info(self, videoId): | |||
self.__logger.info("开始获取视频地址,videoId:{}, requestId:{}", videoId, self.__requestId) | |||
logger.info("开始获取视频地址,videoId:{}, requestId:{}", videoId, self.__requestId) | |||
start = time.time() | |||
while True: | |||
try: | |||
@@ -75,33 +77,33 @@ class ThAliyunVodSdk: | |||
request.set_AuthTimeout(3600 * 5) | |||
response = json.loads(clt.do_action_with_exception(request)) | |||
play_url = response["PlayInfoList"]["PlayInfo"][0]["PlayURL"] | |||
self.__logger.info("获取视频地址成功,视频地址: {}, requestId: {}", play_url, self.__requestId) | |||
logger.info("获取视频地址成功,视频地址: {}, requestId: {}", play_url, self.__requestId) | |||
return play_url | |||
except Exception as e: | |||
self.__logger.error("获取视频地址失败,5秒后重试, requestId: {}", self.__requestId) | |||
logger.info("获取视频地址失败,5秒后重试, requestId: {}", self.__requestId) | |||
time.sleep(5) | |||
current_time = time.time() | |||
if "HTTP Status: 403" not in str(e): | |||
self.__logger.exception("获取视频地址失败: {}, requestId: {}", e, self.__requestId) | |||
logger.error("获取视频地址失败: {}, requestId: {}", str(e), self.__requestId) | |||
raise ServiceException(ExceptionType.GET_VIDEO_URL_EXCEPTION.value[0], | |||
ExceptionType.GET_VIDEO_URL_EXCEPTION.value[1]) | |||
if "HTTP Status: 403" in str(e) and ("UploadFail" in str(e) or "TranscodeFail" in str(e)): | |||
self.__logger.exception("获取视频地址失败: {}, requestId: {}", e, self.__requestId) | |||
logger.error("获取视频地址失败: {}, requestId: {}", str(e), self.__requestId) | |||
raise ServiceException(ExceptionType.GET_VIDEO_URL_EXCEPTION.value[0], | |||
ExceptionType.GET_VIDEO_URL_EXCEPTION.value[1]) | |||
diff_time = current_time - start | |||
if diff_time > 60 * 60 * 2: | |||
self.__logger.exception("获取视频地址失败超时异常: {},超时时间:{}, requestId: {}", e, diff_time, | |||
self.__requestId) | |||
if diff_time > 60 * 60 * 5: | |||
logger.error("获取视频地址失败超时异常: {},超时时间:{}, requestId: {}", str(e), diff_time, | |||
self.__requestId) | |||
raise ServiceException(ExceptionType.GET_VIDEO_URL_TIMEOUT_EXCEPTION.value[0], | |||
ExceptionType.GET_VIDEO_URL_TIMEOUT_EXCEPTION.value[1]) | |||
def upload_local_video(self, filePath, file_title): | |||
self.__logger.info("开始执行vod视频上传, filePath: {}, requestId: {}", filePath, self.__requestId) | |||
logger.info("开始执行vod视频上传, filePath: {}, requestId: {}", filePath, self.__requestId) | |||
uploader = AliyunVodUploader(YmlConstant.get_aliyun_access_key(self.__context), | |||
YmlConstant.get_aliyun_access_secret(self.__context)) | |||
YmlConstant.get_aliyun_access_secret(self.__context), self.__requestId) | |||
uploadVideoRequest: UploadVideoRequest = UploadVideoRequest(filePath, file_title) | |||
self.__logger.info("视频分类:{}", YmlConstant.get_aliyun_vod_cateId(self.__context)) | |||
logger.info("视频分类:{}", YmlConstant.get_aliyun_vod_cateId(self.__context)) | |||
uploadVideoRequest.setCateId(YmlConstant.get_aliyun_vod_cateId(self.__context)) | |||
# 可以设置视频封面,如果是本地或网络图片可使用UploadImageRequest上传图片到视频点播,获取到ImageURL | |||
# ImageURL示例:https://example.com/sample-****.jpg | |||
@@ -113,14 +115,13 @@ class ThAliyunVodSdk: | |||
while True: | |||
try: | |||
result = uploader.uploadLocalVideo(uploadVideoRequest) | |||
self.__logger.info("vod视频上传成功, videoId:{}, requestId:{}", result.get("VideoId"), self.__requestId) | |||
logger.info("vod视频上传成功, videoId:{}, requestId:{}", result.get("VideoId"), self.__requestId) | |||
return result.get("VideoId") | |||
except AliyunVodException as e: | |||
except Exception as e: | |||
retry_count += 1 | |||
time.sleep(3) | |||
self.__logger.error("vod视频上传失败,重试次数:{}, requestId:{}", retry_count, self.__requestId) | |||
time.sleep(1) | |||
logger.error("vod视频上传失败:{},重试次数:{}, requestId:{}", str(e), retry_count, self.__requestId) | |||
if retry_count >= MAX_RETRIES: | |||
self.__logger.exception("vod视频上传重试失败: {}, requestId:{}", e.message, self.__requestId) | |||
raise ServiceException(ExceptionType.SERVICE_INNER_EXCEPTION.value[0], | |||
ExceptionType.SERVICE_INNER_EXCEPTION.value[1]) | |||
@@ -129,3 +130,21 @@ class ThAliyunVodSdk: | |||
if videoId is None or len(videoId) == 0: | |||
return None | |||
return self.get_play_info(videoId) | |||
# if __name__ == "__main__": | |||
# with open('/home/th/tuo_heng/prod/tuoheng_alg/dsp_application.yml', 'r', encoding='"utf-8"') as f: | |||
# file_content = f.read() | |||
# context = yaml.load(file_content, yaml.FullLoader) | |||
# aliyunVodSdk = ThAliyunVodSdk(context, logger, "11111111111") | |||
# aliyunVodSdk = ThAliyunVodSdk(context, logger, "11111111111") | |||
# | |||
# upload_video_thread_or = Common(context, aliyunVodSdk.get_play_url, '/home/th/tuo_heng/prod/dsp/video1/20230510185733460569_on_ai_592c3dd7eb404af9a744c5543e0e006a.mp4', "orOnLineVideo") | |||
# upload_video_thread_ai = Common(context, aliyunVodSdk.get_play_url, '/home/th/tuo_heng/prod/dsp/video1/20230510185733460569_on_or_592c3dd7eb404af9a744c5543e0e006a.mp4', "aiOnLineVideo") | |||
# upload_video_thread_or.setDaemon(True) | |||
# upload_video_thread_ai.setDaemon(True) | |||
# upload_video_thread_or.start() | |||
# upload_video_thread_ai.start() | |||
# or_url = upload_video_thread_or.get_result() | |||
# ai_url = upload_video_thread_ai.get_result() | |||
# print(or_url) | |||
# print(ai_url) |
@@ -48,8 +48,8 @@ class Cv2Util(): | |||
self.width = width | |||
self.height = height | |||
if width > Constant.width: | |||
self.h = int(self.height//2) | |||
self.w = int(self.width//2) | |||
self.h = int(self.height // 2) | |||
self.w = int(self.width // 2) | |||
else: | |||
self.h = int(self.height) | |||
self.w = int(self.width) | |||
@@ -94,8 +94,8 @@ class Cv2Util(): | |||
self.height = int(height) | |||
self.wh = self.width * self.height * 3 | |||
if width > Constant.width: | |||
self.h = int(self.height//2) | |||
self.w = int(self.width//2) | |||
self.h = int(self.height // 2) | |||
self.w = int(self.width // 2) | |||
else: | |||
self.h = int(self.height) | |||
self.w = int(self.width) | |||
@@ -106,8 +106,9 @@ class Cv2Util(): | |||
# if duration: | |||
# self.duration = float(video_stream['duration']) | |||
# self.bit_rate = int(bit_rate) / 1000 | |||
self.__logger.info("视频信息, width:{}|height:{}|fps:{}|all_frames:{}|bit_rate:{}, requestId:{}", self.width, | |||
self.height, self.fps, self.all_frames, self.bit_rate, self.requestId) | |||
self.__logger.info("视频信息, width:{}|height:{}|fps:{}|all_frames:{}|bit_rate:{}, requestId:{}", | |||
self.width, | |||
self.height, self.fps, self.all_frames, self.bit_rate, self.requestId) | |||
except ServiceException as s: | |||
self.__logger.error("获取视频信息异常: {}, requestId:{}", s.msg, self.requestId) | |||
self.clear_video_info() | |||
@@ -119,6 +120,7 @@ class Cv2Util(): | |||
''' | |||
录屏任务获取视频信息 | |||
''' | |||
def get_recording_video_info(self): | |||
try: | |||
video_info = 'ffprobe -show_format -show_streams -of json %s' % self.pullUrl | |||
@@ -149,7 +151,7 @@ class Cv2Util(): | |||
up, down = str(fps).split('/') | |||
self.fps = int(eval(up) / eval(down)) | |||
self.__logger.info("视频信息, width:{}|height:{}|fps:{}|all_frames:{}, requestId:{}", self.width, | |||
self.height, self.fps, self.all_frames, self.requestId) | |||
self.height, self.fps, self.all_frames, self.requestId) | |||
except ServiceException as s: | |||
self.__logger.error("获取视频信息异常: {}, requestId:{}", s.msg, self.requestId) | |||
self.clear_video_info() | |||
@@ -166,6 +168,7 @@ class Cv2Util(): | |||
''' | |||
录屏拉取视频 | |||
''' | |||
def recording_pull_p(self): | |||
try: | |||
# 如果视频信息不存在, 不初始化拉流 | |||
@@ -308,25 +311,30 @@ class Cv2Util(): | |||
return result | |||
def close(self): | |||
self.clear_video_info() | |||
if self.pull_p: | |||
if self.pull_p.stdout: | |||
self.pull_p.stdout.close() | |||
self.pull_p.terminate() | |||
self.pull_p.wait() | |||
self.pull_p = None | |||
self.__logger.info("关闭拉流管道完成, requestId:{}", self.requestId) | |||
if self.p: | |||
if self.p.stdin: | |||
self.p.stdin.close() | |||
self.p.terminate() | |||
self.p.wait() | |||
self.p = None | |||
# self.p.communicate() | |||
# self.p.kill() | |||
self.__logger.info("关闭管道完成, requestId:{}", self.requestId) | |||
if self.or_video_file: | |||
self.or_video_file.release() | |||
self.or_video_file = None | |||
self.__logger.info("关闭原视频写入流完成, requestId:{}", self.requestId) | |||
if self.ai_video_file: | |||
self.ai_video_file.release() | |||
self.ai_video_file = None | |||
self.__logger.info("关闭AI视频写入流完成, requestId:{}", self.requestId) | |||
# 构建 cv2 | |||
@@ -402,7 +410,7 @@ class Cv2Util(): | |||
'-r', str(self.fps), | |||
'-i', '-', # 指定输入文件 | |||
'-g', str(self.fps), | |||
'-maxrate', '8000k', | |||
'-maxrate', '6000k', | |||
# '-profile:v', 'high', | |||
'-b:v', '5000k', | |||
# '-crf', '18', | |||
@@ -423,7 +431,8 @@ class Cv2Util(): | |||
'-tune', 'll', | |||
'-f', 'flv', | |||
self.pushUrl] | |||
self.__logger.info("fps:{}|height:{}|width:{}|requestId:{}", self.fps, self.height, self.width, self.requestId) | |||
self.__logger.info("fps:{}|height:{}|width:{}|requestId:{}", self.fps, self.height, self.width, | |||
self.requestId) | |||
self.p = sp.Popen(command, stdin=sp.PIPE, shell=False) | |||
except ServiceException as s: | |||
if self.p: | |||
@@ -442,112 +451,127 @@ class Cv2Util(): | |||
self.__logger.exception("初始化p管道异常:{}, requestId:{}", e, self.requestId) | |||
def push_stream(self, frame): | |||
try: | |||
if self.p is None: | |||
self.build_p() | |||
self.p.stdin.write(frame.tostring()) | |||
except ServiceException as s: | |||
raise s | |||
except Exception as ex: | |||
self.__logger.exception("推流进管道异常:{}, requestId: {}", ex, self.requestId) | |||
current_retry_num = 0 | |||
while True: | |||
try: | |||
time.sleep(1) | |||
self.p_push_retry_num += 1 | |||
current_retry_num += 1 | |||
if current_retry_num > 3 or self.p_push_retry_num > 600: | |||
raise ServiceException(ExceptionType.PUSH_STREAMING_CHANNEL_IS_OCCUPIED.value[0], | |||
ExceptionType.PUSH_STREAMING_CHANNEL_IS_OCCUPIED.value[1]) | |||
current_retry_num = 0 | |||
while True: | |||
try: | |||
if self.p is None: | |||
self.build_p() | |||
self.p.stdin.write(frame.tostring()) | |||
self.__logger.info("构建p管道重试成功, 当前重试次数: {}, requestId: {}", current_retry_num, | |||
self.requestId) | |||
except ServiceException as ss: | |||
raise ss | |||
except Exception as e: | |||
self.__logger.exception("构建p管道异常:{}, 开始重试, 当前重试次数:{}, requestId: {}", e, | |||
current_retry_num, self.requestId) | |||
self.p.stdin.write(frame.tostring()) | |||
self.p_push_retry_num == 0 | |||
break | |||
except ServiceException as s: | |||
raise s | |||
except Exception as ex: | |||
if self.p: | |||
if self.p.stdin: | |||
self.p.stdin.close() | |||
self.p.terminate() | |||
self.p.wait() | |||
self.p = None | |||
time.sleep(0.1) | |||
self.p_push_retry_num += 1 | |||
current_retry_num += 1 | |||
if self.p_push_retry_num > 500: | |||
self.__logger.exception("推流进管道异常:{}, requestId: {}", ex, self.requestId) | |||
raise ServiceException(ExceptionType.PUSH_STREAMING_CHANNEL_IS_OCCUPIED.value[0], | |||
ExceptionType.PUSH_STREAMING_CHANNEL_IS_OCCUPIED.value[1]) | |||
if current_retry_num > 3: | |||
self.__logger.exception("推流进管道异常:{}, requestId: {}", ex, self.requestId) | |||
raise ServiceException(ExceptionType.PUSH_STREAM_EXCEPTION.value[0], | |||
ExceptionType.PUSH_STREAM_EXCEPTION.value[1]) | |||
def build_or_write(self): | |||
try: | |||
if self.orFilePath is not None and self.or_video_file is None: | |||
self.or_video_file = cv2.VideoWriter(self.orFilePath, cv2.VideoWriter_fourcc(*'mp4v'), self.fps, | |||
(self.w, self.h)) | |||
# self.or_video_file.set(cv2.CAP_PROP_BITRATE, 5000) | |||
if self.or_video_file is None: | |||
self.__logger.error("or_video_file为空, requestId:{}", self.requestId) | |||
raise ServiceException(ExceptionType.SERVICE_INNER_EXCEPTION.value[0], | |||
ExceptionType.SERVICE_INNER_EXCEPTION.value[1]) | |||
except ServiceException as s: | |||
if self.or_video_file: | |||
self.or_video_file.release() | |||
self.or_video_file = None | |||
self.__logger.exception("构建OR文件写对象异常: {}, requestId:{}", s.msg, self.requestId) | |||
raise s | |||
except Exception as e: | |||
if self.or_video_file: | |||
self.or_video_file.release() | |||
self.or_video_file = None | |||
self.__logger.exception("构建OR文件写对象异常: {}, requestId:{}", e, self.requestId) | |||
raise e | |||
except: | |||
if self.or_video_file: | |||
self.or_video_file.release() | |||
self.or_video_file = None | |||
self.__logger.exception("构建OR文件写对象异常, requestId:{}", self.requestId) | |||
raise Exception("构建OR文件写对象异常") | |||
def build_ai_write(self): | |||
try: | |||
if self.aiFilePath is not None and self.ai_video_file is None: | |||
self.ai_video_file = cv2.VideoWriter(self.aiFilePath, cv2.VideoWriter_fourcc(*'mp4v'), self.fps, | |||
(self.w * 2, self.h)) | |||
# self.ai_video_file.set(cv2.CAP_PROP_BITRATE, 5000) | |||
if self.ai_video_file is None: | |||
self.__logger.error("ai_video_file为空, requestId:{}", self.requestId) | |||
raise ServiceException(ExceptionType.SERVICE_INNER_EXCEPTION.value[0], | |||
ExceptionType.SERVICE_INNER_EXCEPTION.value[1]) | |||
except ServiceException as s: | |||
if self.ai_video_file: | |||
self.ai_video_file.release() | |||
self.ai_video_file = None | |||
self.__logger.exception("构建AI文件写对象异常: {}, requestId:{}", s.msg, self.requestId) | |||
raise s | |||
except Exception as e: | |||
if self.ai_video_file: | |||
self.ai_video_file.release() | |||
self.ai_video_file = None | |||
self.__logger.exception("构建AI文件写对象异常: {}, requestId:{}", e, self.requestId) | |||
raise e | |||
except: | |||
if self.ai_video_file: | |||
self.ai_video_file.release() | |||
self.ai_video_file = None | |||
self.__logger.exception("构建AI文件写对象异常, requestId:{}", self.requestId) | |||
raise Exception("构建AI文件写对象异常") | |||
def video_or_write(self, frame): | |||
try: | |||
if self.or_video_file is None: | |||
self.build_or_write() | |||
self.or_video_file.write(frame) | |||
except ServiceException as s: | |||
raise s | |||
except Exception as ex: | |||
ai_retry_num = 0 | |||
while True: | |||
try: | |||
ai_retry_num += 1 | |||
if ai_retry_num > 3: | |||
self.__logger.exception("重新写入原视频视频到本地,重试失败, requestId: {}", self.requestId) | |||
raise ServiceException(ExceptionType.SERVICE_INNER_EXCEPTION.value[0], | |||
ExceptionType.SERVICE_INNER_EXCEPTION.value[1]) | |||
self.or_video_file.write(frame) | |||
self.__logger.info("重新写入原视频视到本地, 当前重试次数: {}, requestId: {}", ai_retry_num, | |||
self.requestId) | |||
break | |||
except Exception as e: | |||
self.__logger.exception("重新写入原视频视到本地:{}, 开始重试, 当前重试次数:{}, requestId: {}", e, | |||
ai_retry_num, self.requestId) | |||
ai_retry_num = 0 | |||
while True: | |||
try: | |||
if self.or_video_file is None: | |||
self.build_or_write() | |||
self.or_video_file.write(frame) | |||
break | |||
except ServiceException as s: | |||
raise s | |||
except Exception as ex: | |||
if ai_retry_num > 3: | |||
self.__logger.exception("重新写入原视频视频到本地, 重试失败, requestId: {}", self.requestId) | |||
raise ex | |||
finally: | |||
ai_retry_num += 1 | |||
def video_ai_write(self, frame): | |||
try: | |||
if self.ai_video_file is None: | |||
self.build_ai_write() | |||
self.ai_video_file.write(frame) | |||
except ServiceException as s: | |||
raise s | |||
except Exception as ex: | |||
ai_retry_num = 0 | |||
while True: | |||
try: | |||
ai_retry_num += 1 | |||
if ai_retry_num > 3: | |||
self.__logger.exception("重新写入分析后的视频到本地,重试失败, requestId: {}", self.requestId) | |||
raise ServiceException(ExceptionType.SERVICE_INNER_EXCEPTION.value[0], | |||
ExceptionType.SERVICE_INNER_EXCEPTION.value[1]) | |||
self.ai_video_file.write(frame) | |||
self.__logger.info("重新写入分析后的视频到本地, 当前重试次数: {}, requestId: {}", ai_retry_num, | |||
self.requestId) | |||
break | |||
except Exception as e: | |||
self.__logger.exception("重新写入分析后的视频到本地:{}, 开始重试, 当前重试次数:{}, requestId: {}", e, | |||
ai_retry_num, self.requestId) | |||
ai_retry_num = 0 | |||
while True: | |||
try: | |||
if self.ai_video_file is None: | |||
self.build_ai_write() | |||
self.ai_video_file.write(frame) | |||
break | |||
except ServiceException as s: | |||
raise s | |||
except Exception as ex: | |||
if ai_retry_num > 3: | |||
self.__logger.exception("重新写入分析后的视频到本地,重试失败, requestId: {}", self.requestId) | |||
raise ex | |||
finally: | |||
ai_retry_num += 1 | |||
def video_merge(self, frame1, frame2): | |||
# frameLeft = cv2.resize(frame1, (int(self.width / 2), int(self.height / 2)), interpolation=cv2.INTER_LINEAR) |
@@ -320,6 +320,8 @@ class RiverModel: | |||
return AI_process([frame], self.model, self.segmodel, self.names, self.label_arraylist, | |||
self.rainbows, objectPar=self.objectPar, font=self.digitFont, segPar=self.segPar, | |||
mode=self.mode, postPar=copy.deepcopy(self.postPar)) | |||
except ServiceException as s: | |||
raise s | |||
except Exception as ee: | |||
# self.num += 1 | |||
# cv2.imwrite('/home/th/tuo_heng/dev/img%s.jpg' % str(self.num), frame) | |||
@@ -466,6 +468,8 @@ class HighWayModel: | |||
self.rainbows, objectPar=copy.deepcopy(self.objectPar), font=self.digitFont, | |||
segPar=copy.deepcopy(self.segPar), | |||
mode=self.mode, postPar=copy.deepcopy(self.postPar)) | |||
except ServiceException as s: | |||
raise s | |||
except Exception as ee: | |||
# self.num += 1 | |||
@@ -570,6 +574,8 @@ class ForestModel: | |||
return AI_process_forest([frame], self.model, self.segmodel, self.names, self.label_arraylist, | |||
self.rainbows, self.half, self.device, self.conf_thres, self.iou_thres, | |||
[], font=self.digitFont, trtFlag_det=self.trtFlag_det) | |||
except ServiceException as s: | |||
raise s | |||
except Exception as ee: | |||
# self.num += 1 | |||
# cv2.imwrite('/home/th/tuo_heng/dev/img%s.jpg' % str(self.num), frame) | |||
@@ -671,6 +677,8 @@ class VehicleModel: | |||
return AI_process_forest([frame], self.model, self.segmodel, self.names, self.label_arraylist, | |||
self.rainbows, self.half, self.device, self.conf_thres, self.iou_thres, | |||
[], font=self.digitFont, trtFlag_det=self.trtFlag_det) | |||
except ServiceException as s: | |||
raise s | |||
except Exception as ee: | |||
# self.num += 1 | |||
# cv2.imwrite('/home/th/tuo_heng/dev/img%s.jpg' % str(self.num), frame) | |||
@@ -774,6 +782,8 @@ class PedestrianModel: | |||
return AI_process_forest([frame], self.model, self.segmodel, self.names, self.label_arraylist, | |||
self.rainbows, self.half, self.device, self.conf_thres, self.iou_thres, | |||
[], font=self.digitFont, trtFlag_det=self.trtFlag_det) | |||
except ServiceException as s: | |||
raise s | |||
except Exception as ee: | |||
# self.num += 1 | |||
# cv2.imwrite('/home/th/tuo_heng/dev/img%s.jpg' % str(self.num), frame) | |||
@@ -876,6 +886,8 @@ class SmogfireModel: | |||
return AI_process_forest([frame], self.model, self.segmodel, self.names, self.label_arraylist, | |||
self.rainbows, self.half, self.device, self.conf_thres, self.iou_thres, | |||
[], font=self.digitFont, trtFlag_det=self.trtFlag_det) | |||
except ServiceException as s: | |||
raise s | |||
except Exception as ee: | |||
# self.num += 1 | |||
# cv2.imwrite('/home/th/tuo_heng/dev/img%s.jpg' % str(self.num), frame) | |||
@@ -979,6 +991,8 @@ class AnglerSwimmerModel: | |||
return AI_process_forest([frame], self.model, self.segmodel, self.names, self.label_arraylist, | |||
self.rainbows, self.half, self.device, self.conf_thres, self.iou_thres, | |||
[], font=self.digitFont, trtFlag_det=self.trtFlag_det) | |||
except ServiceException as s: | |||
raise s | |||
except Exception as ee: | |||
# self.num += 1 | |||
# cv2.imwrite('/home/th/tuo_heng/dev/img%s.jpg' % str(self.num), frame) | |||
@@ -1083,6 +1097,8 @@ class CountryRoadModel: | |||
return AI_process_forest([frame], self.model, self.segmodel, self.names, self.label_arraylist, | |||
self.rainbows, self.half, self.device, self.conf_thres, self.iou_thres, | |||
[], font=self.digitFont, trtFlag_det=self.trtFlag_det) | |||
except ServiceException as s: | |||
raise s | |||
except Exception as ee: | |||
# self.num += 1 | |||
# cv2.imwrite('/home/th/tuo_heng/dev/img%s.jpg' % str(self.num), frame) | |||
@@ -1187,6 +1203,8 @@ class ChannelEmergencyModel: | |||
return AI_process_forest([frame], self.model, self.segmodel, self.names, self.label_arraylist, | |||
self.rainbows, self.half, self.device, self.conf_thres, self.iou_thres, | |||
[], font=self.digitFont, trtFlag_det=self.trtFlag_det) | |||
except ServiceException as s: | |||
raise s | |||
except Exception as ee: | |||
# self.num += 1 | |||
# cv2.imwrite('/home/th/tuo_heng/dev/img%s.jpg' % str(self.num), frame) | |||
@@ -1269,6 +1287,8 @@ class ShipModel: | |||
fontpath=self.fontPath) | |||
self.label_arraylist = self.par["label_array"] | |||
return OBB_infer(self.model, frame, self.par) | |||
except ServiceException as s: | |||
raise s | |||
except Exception as ee: | |||
# self.num += 1 | |||
# cv2.imwrite('/home/th/tuo_heng/dev/img%s.jpg' % str(self.num), frame) | |||
@@ -1318,6 +1338,8 @@ class IMModel: | |||
iou_thres=self.par['iou_thres'], nc=self.par[self.img_type]['nc']) # 后处理 | |||
dataBack = get_return_data(frame, boxes, modelType=self.img_type, plate_dilate=self.par['plate_dilate']) | |||
return dataBack | |||
except ServiceException as s: | |||
raise s | |||
except Exception as ee: | |||
self.logger.exception("算法模型分析异常:{}, requestId:{}", ee, self.requestId) | |||
raise ServiceException(ExceptionType.MODEL_ANALYSE_EXCEPTION.value[0], | |||
@@ -1380,6 +1402,8 @@ class OCR_Model: | |||
gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) | |||
par = [gray_frame, self.engine, self.context, self.converter, self.AlignCollate_normal, self.device] | |||
return ocr_process(par) | |||
except ServiceException as s: | |||
raise s | |||
except Exception as ee: | |||
self.__logger.exception("ocr坐标识别异常:{}, requestId:{}", ee, self.requestId) | |||
raise ServiceException(ExceptionType.COORDINATE_ACQUISITION_FAILED.value[0], | |||
@@ -1416,6 +1440,8 @@ class BaiduAiImageModel: | |||
+ " target: " + target) | |||
return baiduEnum.value[2](self.__aipImageClassifyClient, self.__aipBodyAnalysisClient, url, | |||
self.__requestId) | |||
except ServiceException as s: | |||
raise s | |||
except Exception as ee: | |||
self.logger.exception("算法模型分析异常:{}, requestId:{}", ee, self.requestId) | |||
raise ServiceException(ExceptionType.MODEL_ANALYSE_EXCEPTION.value[0], |
@@ -0,0 +1,730 @@ | |||
# -*- coding: UTF-8 -*- | |||
import json | |||
import oss2 | |||
import base64 | |||
import time | |||
from aliyunsdkcore import client | |||
from aliyunsdkvod.request.v20170321 import CreateUploadVideoRequest | |||
from aliyunsdkvod.request.v20170321 import RefreshUploadVideoRequest | |||
from aliyunsdkvod.request.v20170321 import CreateUploadImageRequest | |||
from aliyunsdkvod.request.v20170321 import CreateUploadAttachedMediaRequest | |||
from vodsdk.AliyunVodUtils import * | |||
from loguru import logger | |||
VOD_MAX_TITLE_LENGTH = 128 | |||
VOD_MAX_DESCRIPTION_LENGTH = 1024 | |||
class AliyunVodUploader: | |||
def __init__(self, accessKeyId, accessKeySecret, requestId, ecsRegionId=None): | |||
""" | |||
constructor for VodUpload | |||
:param accessKeyId: string, access key id | |||
:param accessKeySecret: string, access key secret | |||
:param ecsRegion: string, 部署迁移脚本的ECS所在的Region,详细参考:https://help.aliyun.com/document_detail/40654.html,如:cn-beijing | |||
:return | |||
""" | |||
self.__requestId = requestId | |||
# LogUtils.init_log(context) | |||
self.__accessKeyId = accessKeyId | |||
self.__accessKeySecret = accessKeySecret | |||
self.__ecsRegion = ecsRegionId | |||
self.__vodApiRegion = None | |||
self.__connTimeout = 60 | |||
self.__bucketClient = None | |||
self.__maxRetryTimes = 5 | |||
self.__vodClient = None | |||
self.__EnableCrc = True | |||
# 分片上传参数 | |||
self.__multipartThreshold = 10 * 1024 * 1024 # 分片上传的阈值,超过此值开启分片上传 | |||
self.__multipartPartSize = 10 * 1024 * 1024 # 分片大小,单位byte | |||
self.__multipartThreadsNum = 3 # 分片上传时并行上传的线程数,暂时为串行上传,不支持并行,后续会支持。 | |||
# 设置apiRegion为cn-shanghai, 初始化客户端self.__vodClient | |||
self.setApiRegion('cn-shanghai') | |||
logger.info("初始化阿里云视频上传sdk,连接超时时间:{}, 重试次数:{}, requestId:{}", self.__connTimeout, | |||
self.__maxRetryTimes, requestId) | |||
def setApiRegion(self, apiRegion): | |||
""" | |||
设置VoD的接入地址,中国大陆为cn-shanghai,海外支持ap-southeast-1(新加坡)等区域,详情参考:https://help.aliyun.com/document_detail/98194.html | |||
:param apiRegion: 接入地址的Region英文表示 | |||
:return: | |||
""" | |||
self.__vodApiRegion = apiRegion | |||
self.__vodClient = self.__initVodClient() | |||
def __initVodClient(self): | |||
return client.AcsClient(self.__accessKeyId, self.__accessKeySecret, self.__vodApiRegion, | |||
auto_retry=True, max_retry_time=self.__maxRetryTimes, timeout=self.__connTimeout) | |||
def setMultipartUpload(self, multipartThreshold=10 * 1024 * 1024, multipartPartSize=10 * 1024 * 1024, | |||
multipartThreadsNum=1): | |||
if multipartThreshold > 0: | |||
self.__multipartThreshold = multipartThreshold | |||
if multipartPartSize > 0: | |||
self.__multipartPartSize = multipartPartSize | |||
if multipartThreadsNum > 0: | |||
self.__multipartThreadsNum = multipartThreadsNum | |||
def setEnableCrc(self, isEnable=False): | |||
self.__EnableCrc = True if isEnable else False | |||
@catch_error | |||
def uploadLocalVideo(self, uploadVideoRequest, startUploadCallback=None): | |||
""" | |||
上传本地视频或音频文件到点播,最大支持48.8TB的单个文件,暂不支持断点续传 | |||
:param uploadVideoRequest: UploadVideoRequest类的实例,注意filePath为本地文件的绝对路径 | |||
:param startUploadCallback为获取到上传地址和凭证(uploadInfo)后开始进行文件上传时的回调,可用于记录上传日志等;uploadId为设置的上传ID,可用于关联导入视频。 | |||
:return | |||
""" | |||
uploadInfo = self.__createUploadVideo(uploadVideoRequest) | |||
if startUploadCallback: | |||
startUploadCallback(uploadVideoRequest.uploadId, uploadInfo) | |||
headers = self.__getUploadHeaders(uploadVideoRequest) | |||
self.__uploadOssObjectWithRetry(uploadVideoRequest.filePath, uploadInfo['UploadAddress']['FileName'], | |||
uploadInfo, headers) | |||
return uploadInfo | |||
@catch_error | |||
def uploadWebVideo(self, uploadVideoRequest, startUploadCallback=None): | |||
""" | |||
上传网络视频或音频文件到点播,最大支持48.8TB的单个文件(需本地磁盘空间足够);会先下载到本地临时目录,再上传到点播存储 | |||
:param uploadVideoRequest: UploadVideoRequest类的实例,注意filePath为网络文件的URL地址 | |||
:return | |||
""" | |||
# 下载文件 | |||
uploadVideoRequest = self.__downloadWebMedia(uploadVideoRequest) | |||
# 上传到点播 | |||
uploadInfo = self.__createUploadVideo(uploadVideoRequest) | |||
if startUploadCallback: | |||
startUploadCallback(uploadVideoRequest.uploadId, uploadInfo) | |||
headers = self.__getUploadHeaders(uploadVideoRequest) | |||
self.__uploadOssObjectWithRetry(uploadVideoRequest.filePath, uploadInfo['UploadAddress']['FileName'], | |||
uploadInfo, headers) | |||
# 删除本地临时文件 | |||
os.remove(uploadVideoRequest.filePath) | |||
return uploadInfo['VideoId'] | |||
@catch_error | |||
def uploadLocalM3u8(self, uploadVideoRequest, sliceFilePaths=None): | |||
""" | |||
上传本地m3u8视频或音频文件到点播,m3u8文件和分片文件默认在同一目录 | |||
:param uploadVideoRequest: UploadVideoRequest类的实例,注意filePath为本地m3u8索引文件的绝对路径, | |||
且m3u8文件的分片信息必须是相对地址,不能含有URL或本地绝对路径 | |||
:param sliceFilePaths: list, 分片文件的本地路径列表,例如:['/opt/m3u8_video/sample_001.ts', '/opt/m3u8_video/sample_002.ts'] | |||
sliceFilePaths为None时,会按照同一目录去解析分片地址;如不在同一目录等原因导致解析有误,可自行组装分片地址 | |||
:return | |||
""" | |||
if sliceFilePaths is None: | |||
sliceFilePaths = self.parseLocalM3u8(uploadVideoRequest.filePath) | |||
if (not isinstance(sliceFilePaths, list)) or len(sliceFilePaths) <= 0: | |||
raise AliyunVodException('InvalidM3u8SliceFile', 'M3u8 slice files invalid', | |||
'sliceFilePaths invalid or m3u8 index file error') | |||
# 上传到点播的m3u8索引文件会重写,以此确保分片地址都为相对地址 | |||
downloader = AliyunVodDownloader() | |||
m3u8LocalDir = downloader.getSaveLocalDir() + '/' + AliyunVodUtils.getStringMd5(uploadVideoRequest.fileName) | |||
downloader.setSaveLocalDir(m3u8LocalDir) | |||
m3u8LocalPath = m3u8LocalDir + '/' + os.path.basename(uploadVideoRequest.fileName) | |||
self.__rewriteM3u8File(uploadVideoRequest.filePath, m3u8LocalPath, True) | |||
# 获取上传凭证 | |||
uploadVideoRequest.setFilePath(m3u8LocalPath) | |||
uploadInfo = self.__createUploadVideo(uploadVideoRequest) | |||
uploadAddress = uploadInfo['UploadAddress'] | |||
headers = self.__getUploadHeaders(uploadVideoRequest) | |||
# 依次上传分片文件 | |||
for sliceFilePath in sliceFilePaths: | |||
tempFilePath, sliceFileName = AliyunVodUtils.getFileBriefPath(sliceFilePath) | |||
self.__uploadOssObjectWithRetry(sliceFilePath, uploadAddress['ObjectPrefix'] + sliceFileName, uploadInfo, | |||
headers) | |||
# 上传m3u8文件 | |||
self.__uploadOssObjectWithRetry(m3u8LocalPath, uploadAddress['FileName'], uploadInfo, headers) | |||
# 删除重写到本地的m3u8文件 | |||
if os.path.exists(m3u8LocalPath): | |||
os.remove(m3u8LocalPath) | |||
if not os.listdir(m3u8LocalDir): | |||
os.rmdir(m3u8LocalDir) | |||
return uploadInfo['VideoId'] | |||
@catch_error | |||
def uploadWebM3u8(self, uploadVideoRequest, sliceFileUrls=None): | |||
""" | |||
上传网络m3u8视频或音频文件到点播,需本地磁盘空间足够,会先下载到本地临时目录,再上传到点播存储 | |||
:param uploadVideoRequest: UploadVideoRequest类的实例,注意filePath为m3u8网络文件的URL地址 | |||
:param sliceFileUrls: list, 分片文件的url,例如:['http://host/sample_001.ts', 'http://host/sample_002.ts'] | |||
sliceFileUrls为None时,会按照同一前缀解析分片地址;如分片路径和m3u8索引文件前缀不同等原因导致解析有误,可自行组装分片地址 | |||
:return | |||
""" | |||
if sliceFileUrls is None: | |||
sliceFileUrls = self.parseWebM3u8(uploadVideoRequest.filePath) | |||
if (not isinstance(sliceFileUrls, list)) or len(sliceFileUrls) <= 0: | |||
raise AliyunVodException('InvalidM3u8SliceFile', 'M3u8 slice urls invalid', | |||
'sliceFileUrls invalid or m3u8 index file error') | |||
# 下载m3u8文件和所有ts分片文件到本地;上传到点播的m3u8索引文件会重写,以此确保分片地址都为相对地址 | |||
downloader = AliyunVodDownloader() | |||
m3u8LocalDir = downloader.getSaveLocalDir() + '/' + AliyunVodUtils.getStringMd5(uploadVideoRequest.fileName) | |||
downloader.setSaveLocalDir(m3u8LocalDir) | |||
m3u8LocalPath = m3u8LocalDir + '/' + os.path.basename(uploadVideoRequest.fileName) | |||
self.__rewriteM3u8File(uploadVideoRequest.filePath, m3u8LocalPath, False) | |||
sliceList = [] | |||
for sliceFileUrl in sliceFileUrls: | |||
tempFilePath, sliceFileName = AliyunVodUtils.getFileBriefPath(sliceFileUrl) | |||
err, sliceLocalPath = downloader.downloadFile(sliceFileUrl, sliceFileName) | |||
if sliceLocalPath is None: | |||
raise AliyunVodException('FileDownloadError', 'Download M3u8 File Error', '') | |||
sliceList.append((sliceLocalPath, sliceFileName)) | |||
# 获取上传凭证 | |||
uploadVideoRequest.setFilePath(m3u8LocalPath) | |||
uploadInfo = self.__createUploadVideo(uploadVideoRequest) | |||
uploadAddress = uploadInfo['UploadAddress'] | |||
headers = self.__getUploadHeaders(uploadVideoRequest) | |||
# 依次上传分片文件 | |||
for sliceFile in sliceList: | |||
self.__uploadOssObjectWithRetry(sliceFile[0], uploadAddress['ObjectPrefix'] + sliceFile[1], uploadInfo, | |||
headers) | |||
# 上传m3u8文件 | |||
self.__uploadOssObjectWithRetry(m3u8LocalPath, uploadAddress['FileName'], uploadInfo, headers) | |||
# 删除下载到本地的m3u8文件和分片文件 | |||
if os.path.exists(m3u8LocalPath): | |||
os.remove(m3u8LocalPath) | |||
for sliceFile in sliceList: | |||
if os.path.exists(sliceFile[0]): | |||
os.remove(sliceFile[0]) | |||
if not os.listdir(m3u8LocalDir): | |||
os.rmdir(m3u8LocalDir) | |||
return uploadInfo['VideoId'] | |||
@catch_error | |||
def uploadImage(self, uploadImageRequest, isLocalFile=True): | |||
""" | |||
上传图片文件到点播,不支持断点续传;该接口可支持上传本地图片或网络图片 | |||
:param uploadImageRequest: UploadImageRequest,注意filePath为本地文件的绝对路径或网络文件的URL地址 | |||
:param isLocalFile: bool, 是否为本地文件。True:本地文件,False:网络文件 | |||
:return | |||
""" | |||
# 网络图片需要先下载到本地 | |||
if not isLocalFile: | |||
uploadImageRequest = self.__downloadWebMedia(uploadImageRequest) | |||
# 上传到点播 | |||
uploadInfo = self.__createUploadImage(uploadImageRequest) | |||
self.__uploadOssObject(uploadImageRequest.filePath, uploadInfo['UploadAddress']['FileName'], uploadInfo, None) | |||
# 删除本地临时文件 | |||
if not isLocalFile: | |||
os.remove(uploadImageRequest.filePath) | |||
return uploadInfo['ImageId'], uploadInfo['ImageURL'] | |||
@catch_error | |||
def uploadAttachedMedia(self, uploadAttachedRequest, isLocalFile=True): | |||
""" | |||
上传辅助媒资文件(如水印、字幕文件)到点播,不支持断点续传;该接口可支持上传本地或网络文件 | |||
:param uploadAttachedRequest: UploadAttachedMediaRequest,注意filePath为本地文件的绝对路径或网络文件的URL地址 | |||
:param isLocalFile: bool, 是否为本地文件。True:本地文件,False:网络文件 | |||
:return | |||
""" | |||
# 网络文件需要先下载到本地 | |||
if not isLocalFile: | |||
uploadAttachedRequest = self.__downloadWebMedia(uploadAttachedRequest) | |||
# 上传到点播 | |||
uploadInfo = self.__createUploadAttachedMedia(uploadAttachedRequest) | |||
self.__uploadOssObject(uploadAttachedRequest.filePath, uploadInfo['UploadAddress']['FileName'], uploadInfo, | |||
None) | |||
# 删除本地临时文件 | |||
if not isLocalFile: | |||
os.remove(uploadAttachedRequest.filePath) | |||
result = {'MediaId': uploadInfo['MediaId'], 'MediaURL': uploadInfo['MediaURL'], | |||
'FileURL': uploadInfo['FileURL']} | |||
return result | |||
@catch_error | |||
def parseWebM3u8(self, m3u8FileUrl): | |||
""" | |||
解析网络m3u8文件得到所有分片文件地址,原理是将m3u8地址前缀拼接ts分片名称作为后者的下载url,适用于url不带签名或分片与m3u8文件签名相同的情况 | |||
本函数解析时会默认分片文件和m3u8文件位于同一目录,如不是则请自行拼接分片文件的地址列表 | |||
:param m3u8FileUrl: string, m3u8网络文件url,例如:http://host/sample.m3u8 | |||
:return sliceFileUrls | |||
""" | |||
sliceFileUrls = [] | |||
res = requests.get(m3u8FileUrl) | |||
res.raise_for_status() | |||
for line in res.iter_lines(): | |||
if line.startswith('#'): | |||
continue | |||
sliceFileUrl = AliyunVodUtils.replaceFileName(m3u8FileUrl, line.strip()) | |||
sliceFileUrls.append(sliceFileUrl) | |||
return sliceFileUrls | |||
@catch_error | |||
def parseLocalM3u8(self, m3u8FilePath): | |||
""" | |||
解析本地m3u8文件得到所有分片文件地址,原理是将m3u8地址前缀拼接ts分片名称作为后者的本地路径 | |||
本函数解析时会默认分片文件和m3u8文件位于同一目录,如不是则请自行拼接分片文件的地址列表 | |||
:param m3u8FilePath: string, m3u8本地文件路径,例如:/opt/videos/sample.m3u8 | |||
:return sliceFilePaths | |||
""" | |||
sliceFilePaths = [] | |||
m3u8FilePath = AliyunVodUtils.toUnicode(m3u8FilePath) | |||
for line in open(m3u8FilePath): | |||
if line.startswith('#'): | |||
continue | |||
sliceFileName = line.strip() | |||
sliceFilePath = AliyunVodUtils.replaceFileName(m3u8FilePath, sliceFileName) | |||
sliceFilePaths.append(sliceFilePath) | |||
return sliceFilePaths | |||
# 定义进度条回调函数;consumedBytes: 已经上传的数据量,totalBytes:总数据量 | |||
def uploadProgressCallback(self, consumedBytes, totalBytes): | |||
try: | |||
if totalBytes: | |||
rate = int(100 * (float(consumedBytes) / float(totalBytes))) | |||
else: | |||
rate = 0 | |||
logger.info('视频上传中: {} bytes, percent:{}{}, requestId:{}', consumedBytes, format(rate), '%', | |||
self.__requestId) | |||
except Exception as e: | |||
logger.exception("打印视频上传进度回调方法异常: {}", e) | |||
# print("[%s]uploaded %s bytes, percent %s%s" % ( | |||
# AliyunVodUtils.getCurrentTimeStr(), consumedBytes, format(rate), '%')) | |||
# sys.stdout.flush() | |||
def __downloadWebMedia(self, request): | |||
# 下载媒体文件到本地临时目录 | |||
downloader = AliyunVodDownloader() | |||
localFileName = "%s.%s" % (AliyunVodUtils.getStringMd5(request.fileName), request.mediaExt) | |||
fileUrl = request.filePath | |||
err, localFilePath = downloader.downloadFile(fileUrl, localFileName) | |||
if err < 0: | |||
raise AliyunVodException('FileDownloadError', 'Download File Error', '') | |||
# 重新设置上传请求对象 | |||
request.setFilePath(localFilePath) | |||
return request | |||
def __rewriteM3u8File(self, srcM3u8File, dstM3u8File, isSrcLocal=True): | |||
newM3u8Text = '' | |||
if isSrcLocal: | |||
for line in open(AliyunVodUtils.toUnicode(srcM3u8File)): | |||
item = self.__processM3u8Line(line) | |||
if item is not None: | |||
newM3u8Text += item + "\n" | |||
else: | |||
res = requests.get(srcM3u8File) | |||
res.raise_for_status() | |||
for line in res.iter_lines(): | |||
item = self.__processM3u8Line(line) | |||
if item is not None: | |||
newM3u8Text += item + "\n" | |||
AliyunVodUtils.mkDir(dstM3u8File) | |||
with open(dstM3u8File, 'w') as f: | |||
f.write(newM3u8Text) | |||
def __processM3u8Line(self, line): | |||
item = line.strip() | |||
if len(item) <= 0: | |||
return None | |||
if item.startswith('#'): | |||
return item | |||
tempFilePath, fileName = AliyunVodUtils.getFileBriefPath(item) | |||
return fileName | |||
def __requestUploadInfo(self, request, mediaType): | |||
request.set_accept_format('JSON') | |||
result = json.loads(self.__vodClient.do_action_with_exception(request).decode('utf-8')) | |||
result['OriUploadAddress'] = result['UploadAddress'] | |||
result['OriUploadAuth'] = result['UploadAuth'] | |||
result['UploadAddress'] = json.loads(base64.b64decode(result['OriUploadAddress']).decode('utf-8')) | |||
result['UploadAuth'] = json.loads(base64.b64decode(result['OriUploadAuth']).decode('utf-8')) | |||
result['MediaType'] = mediaType | |||
if mediaType == 'video': | |||
result['MediaId'] = result['VideoId'] | |||
elif mediaType == 'image': | |||
result['MediaId'] = result['ImageId'] | |||
result['MediaURL'] = result['ImageURL'] | |||
return result | |||
# 获取视频上传地址和凭证 | |||
def __createUploadVideo(self, uploadVideoRequest): | |||
request = CreateUploadVideoRequest.CreateUploadVideoRequest() | |||
title = AliyunVodUtils.subString(uploadVideoRequest.title, VOD_MAX_TITLE_LENGTH) | |||
request.set_Title(title) | |||
request.set_FileName(uploadVideoRequest.fileName) | |||
if uploadVideoRequest.description: | |||
description = AliyunVodUtils.subString(uploadVideoRequest.description, VOD_MAX_DESCRIPTION_LENGTH) | |||
request.set_Description(description) | |||
if uploadVideoRequest.coverURL: | |||
request.set_CoverURL(uploadVideoRequest.coverURL) | |||
if uploadVideoRequest.tags: | |||
request.set_Tags(uploadVideoRequest.tags) | |||
if uploadVideoRequest.cateId: | |||
request.set_CateId(uploadVideoRequest.cateId) | |||
if uploadVideoRequest.templateGroupId: | |||
request.set_TemplateGroupId(uploadVideoRequest.templateGroupId) | |||
if uploadVideoRequest.storageLocation: | |||
request.set_StorageLocation(uploadVideoRequest.storageLocation) | |||
if uploadVideoRequest.userData: | |||
request.set_UserData(uploadVideoRequest.userData) | |||
if uploadVideoRequest.appId: | |||
request.set_AppId(uploadVideoRequest.appId) | |||
if uploadVideoRequest.workflowId: | |||
request.set_WorkflowId(uploadVideoRequest.workflowId) | |||
# 根据request发送请求阿里云 | |||
result = self.__requestUploadInfo(request, 'video') | |||
# logger.info("CreateUploadVideo, 获取响应体: {}, requestId:{}", result, self.__requestId) | |||
logger.info("CreateUploadVideo, FilePath: {}, VideoId: {}, requestId:{}", uploadVideoRequest.filePath, | |||
result['VideoId'], self.__requestId) | |||
return result | |||
# 刷新上传凭证 | |||
def __refresh_upload_video(self, videoId): | |||
request = RefreshUploadVideoRequest.RefreshUploadVideoRequest(); | |||
request.set_VideoId(videoId) | |||
result = self.__requestUploadInfo(request, 'video') | |||
logger.info("RefreshUploadVideo, VideoId:{}, requestId:{}", result['VideoId'], self.__requestId) | |||
return result | |||
# 获取图片上传地址和凭证 | |||
def __createUploadImage(self, uploadImageRequest): | |||
request = CreateUploadImageRequest.CreateUploadImageRequest() | |||
request.set_ImageType(uploadImageRequest.imageType) | |||
request.set_ImageExt(uploadImageRequest.imageExt) | |||
if uploadImageRequest.title: | |||
title = AliyunVodUtils.subString(uploadImageRequest.title, VOD_MAX_TITLE_LENGTH) | |||
request.set_Title(title) | |||
if uploadImageRequest.description: | |||
description = AliyunVodUtils.subString(uploadImageRequest.description, VOD_MAX_DESCRIPTION_LENGTH) | |||
request.set_Description(description) | |||
if uploadImageRequest.tags: | |||
request.set_Tags(uploadImageRequest.tags) | |||
if uploadImageRequest.cateId: | |||
request.set_CateId(uploadImageRequest.cateId) | |||
if uploadImageRequest.storageLocation: | |||
request.set_StorageLocation(uploadImageRequest.storageLocation) | |||
if uploadImageRequest.userData: | |||
request.set_UserData(uploadImageRequest.userData) | |||
if uploadImageRequest.appId: | |||
request.set_AppId(uploadImageRequest.appId) | |||
if uploadImageRequest.workflowId: | |||
request.set_WorkflowId(uploadImageRequest.workflowId) | |||
result = self.__requestUploadInfo(request, 'image') | |||
logger.info("CreateUploadImage, FilePath: %s, ImageId: %s, ImageUrl: %s" % ( | |||
uploadImageRequest.filePath, result['ImageId'], result['ImageURL'])) | |||
return result | |||
def __createUploadAttachedMedia(self, uploadAttachedRequest): | |||
request = CreateUploadAttachedMediaRequest.CreateUploadAttachedMediaRequest() | |||
request.set_BusinessType(uploadAttachedRequest.businessType) | |||
request.set_MediaExt(uploadAttachedRequest.mediaExt) | |||
if uploadAttachedRequest.title: | |||
title = AliyunVodUtils.subString(uploadAttachedRequest.title, VOD_MAX_TITLE_LENGTH) | |||
request.set_Title(title) | |||
if uploadAttachedRequest.description: | |||
description = AliyunVodUtils.subString(uploadAttachedRequest.description, VOD_MAX_DESCRIPTION_LENGTH) | |||
request.set_Description(description) | |||
if uploadAttachedRequest.tags: | |||
request.set_Tags(uploadAttachedRequest.tags) | |||
if uploadAttachedRequest.cateId: | |||
request.set_CateId(uploadAttachedRequest.cateId) | |||
if uploadAttachedRequest.storageLocation: | |||
request.set_StorageLocation(uploadAttachedRequest.storageLocation) | |||
if uploadAttachedRequest.userData: | |||
request.set_UserData(uploadAttachedRequest.userData) | |||
if uploadAttachedRequest.appId: | |||
request.set_AppId(uploadAttachedRequest.appId) | |||
if uploadAttachedRequest.workflowId: | |||
request.set_WorkflowId(uploadAttachedRequest.workflowId) | |||
result = self.__requestUploadInfo(request, 'attached') | |||
logger.info("CreateUploadImage, FilePath: %s, MediaId: %s, MediaURL: %s" % ( | |||
uploadAttachedRequest.filePath, result['MediaId'], result['MediaURL'])) | |||
return result | |||
def __getUploadHeaders(self, uploadVideoRequest): | |||
if uploadVideoRequest.isShowWatermark is None: | |||
return None | |||
else: | |||
userData = "{\"Vod\":{\"UserData\":{\"IsShowWaterMark\": \"%s\"}}}" % (uploadVideoRequest.isShowWatermark) | |||
return {'x-oss-notification': base64.b64encode(userData, 'utf-8')} | |||
# uploadType,可选:multipart, put, web | |||
def __uploadOssObjectWithRetry(self, filePath, object, uploadInfo, headers=None): | |||
retryTimes = 0 | |||
while retryTimes < self.__maxRetryTimes: | |||
try: | |||
return self.__uploadOssObject(filePath, object, uploadInfo, headers) | |||
except OssError as e: | |||
# 上传凭证过期需要重新获取凭证 | |||
if e.code == 'SecurityTokenExpired' or e.code == 'InvalidAccessKeyId': | |||
uploadInfo = self.__refresh_upload_video(uploadInfo['MediaId']) | |||
except Exception as e: | |||
raise e | |||
except: | |||
raise AliyunVodException('UnkownError', repr(e), traceback.format_exc()) | |||
finally: | |||
retryTimes += 1 | |||
else: | |||
raise Exception("重试超过限制") | |||
def __uploadOssObject(self, filePath, object, uploadInfo, headers=None): | |||
self.__createOssClient(uploadInfo['UploadAuth'], uploadInfo['UploadAddress']) | |||
""" | |||
p = os.path.dirname(os.path.realpath(__file__)) | |||
store = os.path.dirname(p) + '/osstmp' | |||
return oss2.resumable_upload(self.__bucketClient, object, filePath, | |||
store=oss2.ResumableStore(root=store), headers=headers, | |||
multipart_threshold=self.__multipartThreshold, part_size=self.__multipartPartSize, | |||
num_threads=self.__multipartThreadsNum, progress_callback=self.uploadProgressCallback) | |||
""" | |||
uploader = _VodResumableUploader(self.__bucketClient, filePath, object, uploadInfo, headers, | |||
self.uploadProgressCallback, self.__refreshUploadAuth, | |||
requestId=self.__requestId) | |||
uploader.setMultipartInfo(self.__multipartThreshold, self.__multipartPartSize, self.__multipartThreadsNum) | |||
uploader.setClientId(self.__accessKeyId) | |||
res = uploader.upload() | |||
uploadAddress = uploadInfo['UploadAddress'] | |||
bucketHost = uploadAddress['Endpoint'].replace('://', '://' + uploadAddress['Bucket'] + ".") | |||
logger.info("UploadFile {} Finish, MediaId: {}, FilePath: {}, Destination: {}/{}, requestId:{}", | |||
uploadInfo['MediaType'], uploadInfo['MediaId'], filePath, bucketHost, object, self.__requestId) | |||
return res | |||
# 使用上传凭证和地址信息初始化OSS客户端(注意需要先Base64解码并Json Decode再传入) | |||
# 如果上传的ECS位于点播相同的存储区域(如上海),则可以指定internal为True,通过内网上传更快且免费 | |||
def __createOssClient(self, uploadAuth, uploadAddress): | |||
auth = oss2.StsAuth(uploadAuth['AccessKeyId'], uploadAuth['AccessKeySecret'], uploadAuth['SecurityToken']) | |||
endpoint = AliyunVodUtils.convertOssInternal(uploadAddress['Endpoint'], self.__ecsRegion) | |||
self.__bucketClient = oss2.Bucket(auth, endpoint, uploadAddress['Bucket'], | |||
connect_timeout=self.__connTimeout, enable_crc=self.__EnableCrc) | |||
return self.__bucketClient | |||
def __refreshUploadAuth(self, videoId): | |||
uploadInfo = self.__refresh_upload_video(videoId) | |||
uploadAuth = uploadInfo['UploadAuth'] | |||
uploadAddress = uploadInfo['UploadAddress'] | |||
return self.__createOssClient(uploadAuth, uploadAddress) | |||
from oss2 import SizedFileAdapter, determine_part_size | |||
from oss2.models import PartInfo | |||
from aliyunsdkcore.utils import parameter_helper as helper | |||
class _VodResumableUploader: | |||
def __init__(self, bucket, filePath, object, uploadInfo, headers, progressCallback, refreshAuthCallback, | |||
requestId=None): | |||
self.__bucket = bucket | |||
self.__filePath = filePath | |||
self.__object = object | |||
self.__uploadInfo = uploadInfo | |||
self.__totalSize = None | |||
self.__headers = headers | |||
self.__mtime = os.path.getmtime(filePath) | |||
self.__progressCallback = progressCallback | |||
self.__refreshAuthCallback = refreshAuthCallback | |||
self.__threshold = None | |||
self.__partSize = None | |||
self.__threadsNum = None | |||
self.__uploadId = 0 | |||
self.__record = {} | |||
self.__finishedSize = 0 | |||
self.__finishedParts = [] | |||
self.__filePartHash = None | |||
self.__clientId = None | |||
self.__requestId = requestId | |||
def setMultipartInfo(self, threshold, partSize, threadsNum): | |||
self.__threshold = threshold | |||
self.__partSize = partSize | |||
self.__threadsNum = threadsNum | |||
def setClientId(self, clientId): | |||
self.__clientId = clientId | |||
def upload(self): | |||
self.__totalSize = os.path.getsize(self.__filePath) | |||
logger.info("上传视频路径: {}, 视频大小: {}, requestId:{}", self.__filePath, self.__totalSize, self.__requestId) | |||
if self.__threshold and self.__totalSize <= self.__threshold: | |||
return self.simpleUpload() | |||
else: | |||
return self.multipartUpload() | |||
def simpleUpload(self): | |||
with open(AliyunVodUtils.toUnicode(self.__filePath), 'rb') as f: | |||
result = self.__bucket.put_object(self.__object, f, headers=self.__headers, progress_callback=None) | |||
if self.__uploadInfo['MediaType'] == 'video': | |||
self.__reportUploadProgress('put', 1, self.__totalSize) | |||
return result | |||
def multipartUpload(self): | |||
psize = oss2.determine_part_size(self.__totalSize, preferred_size=self.__partSize) | |||
# 初始化分片 | |||
self.__uploadId = self.__bucket.init_multipart_upload(self.__object).upload_id | |||
startTime = time.time() | |||
expireSeconds = 2500 # 上传凭证有效期3000秒,提前刷新 | |||
# 逐个上传分片 | |||
with open(AliyunVodUtils.toUnicode(self.__filePath), 'rb') as fileObj: | |||
partNumber = 1 | |||
offset = 0 | |||
while offset < self.__totalSize: | |||
uploadSize = min(psize, self.__totalSize - offset) | |||
# logger.info("UploadPart, FilePath: %s, VideoId: %s, UploadId: %s, PartNumber: %s, PartSize: %s" % (self.__fileName, self.__videoId, self.__uploadId, partNumber, uploadSize)) | |||
result = self.__upload_part(partNumber, fileObj, uploadSize) | |||
# print(result.request_id) | |||
self.__finishedParts.append(PartInfo(partNumber, result.etag)) | |||
offset += uploadSize | |||
partNumber += 1 | |||
# 上传进度回调 | |||
self.__progressCallback(offset, self.__totalSize) | |||
if self.__uploadInfo['MediaType'] == 'video': | |||
# 上报上传进度 | |||
self.__reportUploadProgress('multipart', partNumber - 1, offset) | |||
# 检测上传凭证是否过期 | |||
nowTime = time.time() | |||
if nowTime - startTime >= expireSeconds: | |||
self.__bucket = self.__refreshAuthCallback(self.__uploadInfo['MediaId']) | |||
startTime = nowTime | |||
# 完成分片上传 | |||
self.__complete_multipart_upload() | |||
return result | |||
def __upload_part(self, partNumber, fileObj, uploadSize): | |||
retry_num = 0 | |||
while True: | |||
try: | |||
return self.__bucket.upload_part(self.__object, self.__uploadId, partNumber, | |||
SizedFileAdapter(fileObj, uploadSize)) | |||
except Exception as e: | |||
logger.error("阿里云分片上传异常报错: {}, 当前重试次数:{} requestId:{}", str(e), retry_num + 1, self.__requestId) | |||
if retry_num > 3: | |||
raise Exception("阿里云分片上传异常") | |||
except: | |||
logger.error("阿里云完成分片上传异常报错, 当前重试次数:{}, requestId:{}", retry_num + 1, self.__requestId) | |||
if retry_num > 3: | |||
raise Exception("阿里云分片上传异常") | |||
finally: | |||
retry_num += 1 | |||
time.sleep(1) | |||
def __complete_multipart_upload(self): | |||
retry_num = 0 | |||
while True: | |||
try: | |||
self.__bucket.complete_multipart_upload(self.__object, self.__uploadId, self.__finishedParts, | |||
headers=self.__headers) | |||
break | |||
except Exception as e: | |||
logger.error("阿里云完成分片上传异常报错: {}, 当前重试次数:{}, requestId:{}", str(e), retry_num + 1, self.__requestId) | |||
if retry_num > 5: | |||
raise Exception("阿里云完成分片上传异常") | |||
except: | |||
logger.error("阿里云完成分片上传异常报错, 当前重试次数:{}, requestId:{}", retry_num + 1, self.__requestId) | |||
if retry_num > 5: | |||
raise Exception("阿里云完成分片上传异常") | |||
finally: | |||
time.sleep(1) | |||
retry_num += 1 | |||
def __reportUploadProgress(self, uploadMethod, donePartsCount, doneBytes): | |||
retry_num = 5 | |||
current_num = 0 | |||
while True: | |||
try: | |||
reportHost = 'vod.cn-shanghai.aliyuncs.com' | |||
sdkVersion = '1.3.1' | |||
reportKey = 'HBL9nnSwhtU2$STX' | |||
uploadPoint = {'upMethod': uploadMethod, 'partSize': self.__partSize, 'doneBytes': doneBytes} | |||
timestamp = int(time.time()) | |||
authInfo = AliyunVodUtils.getStringMd5("%s|%s|%s" % (self.__clientId, reportKey, timestamp)) | |||
fields = {'Action': 'ReportUploadProgress', 'Format': 'JSON', 'Version': '2017-03-21', | |||
'Timestamp': helper.get_iso_8061_date(), 'SignatureNonce': helper.get_uuid(), | |||
'VideoId': self.__uploadInfo['MediaId'], 'Source': 'PythonSDK', 'ClientId': self.__clientId, | |||
'BusinessType': 'UploadVideo', 'TerminalType': 'PC', 'DeviceModel': 'Server', | |||
'AppVersion': sdkVersion, 'AuthTimestamp': timestamp, 'AuthInfo': authInfo, | |||
'FileName': self.__filePath, | |||
'FileHash': self.__getFilePartHash(self.__clientId, self.__filePath, self.__totalSize), | |||
'FileSize': self.__totalSize, 'FileCreateTime': timestamp, 'UploadRatio': 0, | |||
'UploadId': self.__uploadId, | |||
'DonePartsCount': donePartsCount, 'PartSize': self.__partSize, | |||
'UploadPoint': json.dumps(uploadPoint), | |||
'UploadAddress': self.__uploadInfo['OriUploadAddress'] | |||
} | |||
requests.post('http://' + reportHost, fields, timeout=30) | |||
break | |||
except Exception as e: | |||
current_num += 1 | |||
time.sleep(1) | |||
logger.error("vod上报视频进度异常: {}, 当前重试次数:{}, requestId:{}", repr(e), current_num, | |||
self.__requestId) | |||
if current_num > retry_num: | |||
logger.error("vod上报视频重试失败 {}, requestId:{}", repr(e), self.__requestId) | |||
raise e | |||
def __getFilePartHash(self, clientId, filePath, fileSize): | |||
if self.__filePartHash: | |||
return self.__filePartHash | |||
length = 1 * 1024 * 1024 | |||
if fileSize < length: | |||
length = fileSize | |||
try: | |||
fp = open(AliyunVodUtils.toUnicode(filePath), 'rb') | |||
strVal = fp.read(length) | |||
self.__filePartHash = AliyunVodUtils.getStringMd5(strVal, False) | |||
fp.close() | |||
except: | |||
self.__filePartHash = "%s|%s|%s" % (clientId, filePath, self.__mtime) | |||
return self.__filePartHash |
@@ -0,0 +1,327 @@ | |||
# -*- coding: UTF-8 -*- | |||
import os, sys | |||
import hashlib | |||
import datetime | |||
import functools | |||
import logging | |||
from loguru import logger | |||
from oss2.exceptions import OssError | |||
from aliyunsdkcore.acs_exception.exceptions import ServerException | |||
from aliyunsdkcore.acs_exception.exceptions import ClientException | |||
import traceback | |||
import requests | |||
if sys.version_info[0] == 3: | |||
import urllib.parse | |||
else: | |||
from urllib import unquote | |||
VOD_PRINT_INFO_LOG_SWITCH = 1 | |||
class AliyunVodLog: | |||
""" | |||
VOD日志类,基于logging实现 | |||
""" | |||
@staticmethod | |||
def printLogStr(msg, *args, **kwargs): | |||
if VOD_PRINT_INFO_LOG_SWITCH: | |||
print("[%s]%s" % (AliyunVodUtils.getCurrentTimeStr(), msg)) | |||
@staticmethod | |||
def info(msg, *args, **kwargs): | |||
logging.info(msg, *args, **kwargs) | |||
AliyunVodLog.printLogStr(msg, *args, **kwargs) | |||
@staticmethod | |||
def error(msg, *args, **kwargs): | |||
logging.error(msg, *args, **kwargs) | |||
AliyunVodLog.printLogStr(msg, *args, **kwargs) | |||
@staticmethod | |||
def warning(msg, *args, **kwargs): | |||
logging.warning(msg, *args, **kwargs) | |||
AliyunVodLog.printLogStr(msg, *args, **kwargs) | |||
class AliyunVodUtils: | |||
""" | |||
VOD上传SDK的工具类,提供截取字符串、获取扩展名、获取文件名等静态函数 | |||
""" | |||
# 截取字符串,在不超过最大字节数前提下确保中文字符不被截断出现乱码(先转换成unicode,再取子串,然后转换成utf-8) | |||
@staticmethod | |||
def subString(strVal, maxBytes, charSet='utf-8'): | |||
i = maxBytes | |||
if sys.version_info[0] == 3: | |||
while len(strVal.encode(charSet)) > maxBytes: | |||
if i < 0: | |||
return '' | |||
strVal = strVal[:i] | |||
i -= 1 | |||
else: | |||
while len(strVal) > maxBytes: | |||
if i < 0: | |||
return '' | |||
strVal = strVal.decode(charSet)[:i].encode(charSet) | |||
i -= 1 | |||
return strVal | |||
@staticmethod | |||
def getFileExtension(fileName): | |||
end = fileName.rfind('?') | |||
if end <= 0: | |||
end = len(fileName) | |||
i = fileName.rfind('.') | |||
if i >= 0: | |||
return fileName[i + 1:end].lower() | |||
else: | |||
return None | |||
# urldecode | |||
@staticmethod | |||
def urlDecode(fileUrl): | |||
if sys.version_info[0] == 3: | |||
return urllib.parse.unquote(fileUrl) | |||
else: | |||
return unquote(fileUrl) | |||
# urlencode | |||
@staticmethod | |||
def urlEncode(fileUrl): | |||
if sys.version_info[0] == 3: | |||
return urllib.parse.urlencode(fileUrl) | |||
else: | |||
return urllib.urlencode(fileUrl) | |||
# 获取Url的摘要地址(去除?后的参数,如果有)以及文件名 | |||
@staticmethod | |||
def getFileBriefPath(fileUrl): | |||
# fileUrl = AliyunVodUtils.urlDecode(fileUrl) | |||
i = fileUrl.rfind('?') | |||
if i > 0: | |||
briefPath = fileUrl[:i] | |||
else: | |||
briefPath = fileUrl | |||
briefName = os.path.basename(briefPath) | |||
return briefPath, AliyunVodUtils.urlDecode(briefName) | |||
@staticmethod | |||
def getStringMd5(strVal, isEncode=True): | |||
m = hashlib.md5() | |||
m.update(strVal.encode('utf-8') if isEncode else strVal) | |||
return m.hexdigest() | |||
@staticmethod | |||
def getCurrentTimeStr(): | |||
now = datetime.datetime.now() | |||
return now.strftime("%Y-%m-%d %H:%M:%S") | |||
# 将oss地址转换为内网地址(如果脚本部署的ecs与oss bucket在同一区域) | |||
@staticmethod | |||
def convertOssInternal(ossUrl, ecsRegion=None, isVpc=False): | |||
if (not ossUrl) or (not ecsRegion): | |||
return ossUrl | |||
availableRegions = ['cn-qingdao', 'cn-beijing', 'cn-zhangjiakou', 'cn-huhehaote', 'cn-hangzhou', 'cn-shanghai', | |||
'cn-shenzhen', | |||
'cn-hongkong', 'ap-southeast-1', 'ap-southeast-2', 'ap-southeast-3', | |||
'ap-northeast-1', 'us-west-1', 'us-east-1', 'eu-central-1', 'me-east-1'] | |||
if ecsRegion not in availableRegions: | |||
return ossUrl | |||
ossUrl = ossUrl.replace("https:", "http:") | |||
if isVpc: | |||
return ossUrl.replace("oss-%s.aliyuncs.com" % (ecsRegion), "vpc100-oss-%s.aliyuncs.com" % (ecsRegion)) | |||
else: | |||
return ossUrl.replace("oss-%s.aliyuncs.com" % (ecsRegion), "oss-%s-internal.aliyuncs.com" % (ecsRegion)) | |||
# 把输入转换为unicode | |||
@staticmethod | |||
def toUnicode(data): | |||
if isinstance(data, bytes): | |||
return data.decode('utf-8') | |||
else: | |||
return data | |||
# 替换路径中的文件名;考虑分隔符为"/" 或 "\"(windows) | |||
@staticmethod | |||
def replaceFileName(filePath, replace): | |||
if len(filePath) <= 0 or len(replace) <= 0: | |||
return filePath | |||
filePath = AliyunVodUtils.urlDecode(filePath) | |||
separator = '/' | |||
start = filePath.rfind(separator) | |||
if start < 0: | |||
separator = '\\' | |||
start = filePath.rfind(separator) | |||
if start < 0: | |||
return None | |||
result = "%s%s%s" % (filePath[0:start], separator, replace) | |||
return result | |||
# 创建文件中的目录 | |||
@staticmethod | |||
def mkDir(filePath): | |||
if len(filePath) <= 0: | |||
return -1 | |||
separator = '/' | |||
i = filePath.rfind(separator) | |||
if i < 0: | |||
separator = '\\' | |||
i = filePath.rfind(separator) | |||
if i < 0: | |||
return -2 | |||
dirs = filePath[:i] | |||
if os.path.exists(dirs) and os.path.isdir(dirs): | |||
return 0 | |||
os.makedirs(dirs) | |||
return 1 | |||
class AliyunVodException(Exception): | |||
""" | |||
VOD上传SDK的异常类,做统一的异常处理,外部捕获此异常即可 | |||
""" | |||
def __init__(self, type, code, msg, http_status=None, request_id=None): | |||
Exception.__init__(self) | |||
self.type = type or 'UnkownError' | |||
self.code = code | |||
self.message = msg | |||
self.http_status = http_status or 'NULL' | |||
self.request_id = request_id or 'NULL' | |||
def __str__(self): | |||
return "Type: %s, Code: %s, Message: %s, HTTPStatus: %s, RequestId: %s" % ( | |||
self.type, self.code, self.message, str(self.http_status), self.request_id) | |||
def catch_error(method): | |||
""" | |||
装饰器,将内部异常转换成统一的异常类AliyunVodException | |||
""" | |||
@functools.wraps(method) | |||
def wrapper(self, *args, **kwargs): | |||
try: | |||
return method(self, *args, **kwargs) | |||
except ServerException as e: | |||
logger.error("阿里云ServerException异常, error_code: {}, error_msg:{}, status:{}, requestId:{}", | |||
e.get_error_code(), e.get_error_msg(), e.get_http_status(), self.__requestId) | |||
# 可能原因:AK错误、账号无权限、参数错误等 | |||
raise AliyunVodException('ServerException', e.get_error_code(), e.get_error_msg(), e.get_http_status(), | |||
e.get_request_id()) | |||
except ClientException as e: | |||
logger.error("阿里云ClientException异常, error_code: {}, error_msg:{}, requestId:{}", e.get_error_code(), | |||
e.get_error_msg(), self.__requestId) | |||
# 可能原因:本地网络故障(如不能连接外网)等 | |||
raise AliyunVodException('ClientException', e.get_error_code(), e.get_error_msg()) | |||
except OssError as e: | |||
logger.error("阿里云OssError异常, error_code: {}, error_msg:{}, status:{}, requestId:{}", e.code, e.message, | |||
e.status, self.__requestId) | |||
# 可能原因:上传凭证过期等 | |||
raise AliyunVodException('OssError', e.code, e.message, e.status, e.request_id) | |||
except IOError as e: | |||
logger.error("阿里云IOError异常: {}, requestId:{}", traceback.format_exc(), self.__requestId) | |||
# 可能原因:文件URL不能访问、本地文件无法读取等 | |||
raise AliyunVodException('IOError', repr(e), traceback.format_exc()) | |||
except OSError as e: | |||
logger.error("阿里云OSError异常: {}, requestId:{}", traceback.format_exc(), self.__requestId) | |||
# 可能原因:本地文件不存在等 | |||
raise AliyunVodException('OSError', repr(e), traceback.format_exc()) | |||
except AliyunVodException as e: | |||
logger.error("阿里云VodException异常: {}, requestId:{}", e, self.__requestId) | |||
# 可能原因:参数错误 | |||
raise e | |||
except Exception as e: | |||
logger.error("阿里云UnkownException异常: {}, requestId:{}", traceback.format_exc(), self.__requestId) | |||
raise AliyunVodException('UnkownException', repr(e), traceback.format_exc()) | |||
except: | |||
logger.error("阿里云UnkownError异常: {}, requestId:{}", traceback.format_exc(), self.__requestId) | |||
raise AliyunVodException('UnkownError', 'UnkownError', traceback.format_exc()) | |||
return wrapper | |||
class AliyunVodDownloader: | |||
""" | |||
VOD网络文件的下载类,上传网络文件时会先下载到本地临时目录,再上传到点播 | |||
""" | |||
def __init__(self, localDir=None): | |||
if localDir: | |||
self.__localDir = localDir | |||
else: | |||
p = os.path.dirname(os.path.realpath(__file__)) | |||
self.__localDir = os.path.dirname(p) + '/dlfiles' | |||
def setSaveLocalDir(self, localDir): | |||
self.__localDir = localDir | |||
def getSaveLocalDir(self): | |||
return self.__localDir | |||
def downloadFile(self, fileUrl, localFileName, fileSize=None): | |||
localPath = self.__localDir + '/' + localFileName | |||
logger.info("Download %s To %s" % (fileUrl, localPath)) | |||
try: | |||
lsize = self.getFileSize(localPath) | |||
if fileSize and lsize == fileSize: | |||
logger.info('Download OK, File Exists') | |||
return 0, localPath | |||
AliyunVodUtils.mkDir(self.__localDir) | |||
err, webPage = self.__openWebFile(fileUrl, lsize) | |||
if err == 0: | |||
logger.info('Download OK, File Exists') | |||
webPage.close() | |||
return 0, localPath | |||
fileObj = open(localPath, 'ab+') | |||
for chunk in webPage.iter_content(chunk_size=8 * 1024): | |||
if chunk: | |||
fileObj.write(chunk) | |||
except Exception as e: | |||
logger.error("Download fail: %s" % (e)) | |||
return -1, None | |||
fileObj.close() | |||
webPage.close() | |||
logger.info('Download OK') | |||
return 1, localPath | |||
def getFileSize(self, filePath): | |||
try: | |||
lsize = os.stat(filePath).st_size | |||
except: | |||
lsize = 0 | |||
return lsize | |||
def __openWebFile(self, fileUrl, offset): | |||
webPage = None | |||
try: | |||
headers = {'Range': 'bytes=%d-' % offset} | |||
webPage = requests.get(fileUrl, stream=True, headers=headers, timeout=120, verify=False) | |||
status_code = webPage.status_code | |||
err = -1 | |||
if status_code in [200, 206]: | |||
err = 1 | |||
elif status_code == 416: | |||
err = 0 | |||
else: | |||
logger.error("Download offset %s fail, invalid url, status: %s" % (offset, status_code)) | |||
except Exception as e: | |||
logger.error("Download offset %s fail: %s" % (offset, e)) | |||
err = -2 | |||
finally: | |||
return err, webPage |
@@ -0,0 +1,87 @@ | |||
# -*- coding: UTF-8 -*- | |||
""" | |||
# Class UploadAttachedMediaRequest | |||
# | |||
# Aliyun VoD's Upload Attached Media(such as watermark,subtitle files) Request class, which wraps parameters to upload an media file into VoD. | |||
# Users could pass parameters to AliyunVodUploader, including File Path,Title,etc. via an UploadAttachedMediaRequest instance. | |||
# For more details, please check out the VoD API document: https://help.aliyun.com/document_detail/98467.html | |||
""" | |||
from vodsdk.AliyunVodUtils import * | |||
class UploadAttachedMediaRequest: | |||
def __init__(self, filePath, businessType, title=None, fileExt=None): | |||
""" | |||
constructor for UploadAttachedMediaRequest | |||
:param filePath: string, 文件的绝对路径,或者网络文件的URL,必须含有扩展名 | |||
:return | |||
""" | |||
self.businessType = businessType | |||
self.filePath = None | |||
self.fileName = None | |||
self.mediaExt = None | |||
self.title = None | |||
self.setFilePath(filePath, title, fileExt) | |||
self.fileSize = None | |||
self.cateId = None | |||
self.tags = None | |||
self.description = None | |||
self.userData = None | |||
self.storageLocation = None | |||
self.appId = None | |||
self.workflowId = None | |||
def setFilePath(self, filePath, title=None, fileExt=None): | |||
if fileExt is None: | |||
fileExt = AliyunVodUtils.getFileExtension(filePath) | |||
if not fileExt: | |||
raise AliyunVodException('ParameterError', 'InvalidParameter', 'filePath has no Extension') | |||
fileExt = fileExt.lstrip('.') | |||
self.mediaExt = fileExt | |||
self.filePath = AliyunVodUtils.toUnicode(filePath) | |||
briefPath, briefName = AliyunVodUtils.getFileBriefPath(self.filePath) | |||
self.fileName = briefPath | |||
if fileExt and (not self.fileName.endswith('.' + fileExt)): | |||
self.fileName = self.fileName + '.' + fileExt | |||
if title: | |||
self.title = title | |||
else: | |||
if self.title is None: | |||
self.title = briefName | |||
def setBusinessType(self, businessType): | |||
self.businessType = businessType | |||
def setTitle(self, title): | |||
self.title = title | |||
def setFileSize(self, fileSize): | |||
self.fileSize = fileSize | |||
def setCateId(self, cateId): | |||
self.cateId = cateId | |||
def setTags(self, tags): | |||
self.tags = tags | |||
def setDescription(self, description): | |||
self.description = description | |||
def setStorageLocation(self, storageLocation): | |||
self.storageLocation = storageLocation | |||
def setUserData(self, userData): | |||
self.userData = userData | |||
def setAppId(self, appId): | |||
self.appId = appId | |||
def setWorkflowId(self, workflowId): | |||
self.workflowId = workflowId | |||
@@ -0,0 +1,84 @@ | |||
# -*- coding: UTF-8 -*- | |||
""" | |||
# Class UploadImageRequest | |||
# | |||
# Aliyun VoD's Upload Image Request class, which wraps parameters to upload an image into VoD. | |||
# Users could pass parameters to AliyunVodUploader, including File Path,Title,etc. via an UploadImageRequest instance. | |||
# For more details, please check out the VoD API document: https://help.aliyun.com/document_detail/55619.html | |||
""" | |||
from vodsdk.AliyunVodUtils import * | |||
class UploadImageRequest: | |||
def __init__(self, filePath, title=None, fileExt=None): | |||
""" | |||
constructor for UploadVideoRequest | |||
:param filePath: string, 文件的绝对路径,或者网络文件的URL,必须含有扩展名 | |||
:param title: string, 图片标题 | |||
:return | |||
""" | |||
self.filePath = None | |||
self.fileName = None | |||
self.imageExt = None | |||
self.mediaExt = None | |||
self.title = None | |||
self.setFilePath(filePath, title, fileExt) | |||
self.imageType = 'default' | |||
self.cateId = None | |||
self.tags = None | |||
self.description = None | |||
self.userData = None | |||
self.storageLocation = None | |||
self.appId = None | |||
self.workflowId = None | |||
def setFilePath(self, filePath, title=None, fileExt=None): | |||
if fileExt is None: | |||
fileExt = AliyunVodUtils.getFileExtension(filePath) | |||
if not fileExt: | |||
raise AliyunVodException('ParameterError', 'InvalidParameter', 'filePath has no Extension') | |||
fileExt = fileExt.lstrip('.') | |||
self.imageExt = fileExt | |||
self.mediaExt = fileExt | |||
self.filePath = AliyunVodUtils.toUnicode(filePath) | |||
briefPath, briefName = AliyunVodUtils.getFileBriefPath(self.filePath) | |||
self.fileName = briefPath | |||
if fileExt and (not self.fileName.endswith('.' + fileExt)): | |||
self.fileName = self.fileName + '.' + fileExt | |||
if title: | |||
self.title = title | |||
else: | |||
if self.title is None: | |||
self.title = briefName | |||
def setImageType(self, imageType): | |||
self.imageType = imageType | |||
def setTitle(self, title): | |||
self.title = title | |||
def setCateId(self, cateId): | |||
self.cateId = cateId | |||
def setTags(self, tags): | |||
self.tags = tags | |||
def setDescription(self, description): | |||
self.description = description | |||
def setStorageLocation(self, storageLocation): | |||
self.storageLocation = storageLocation | |||
def setUserData(self, userData): | |||
self.userData = userData | |||
def setAppId(self, appId): | |||
self.appId = appId | |||
def setWorkflowId(self, workflowId): | |||
self.workflowId = workflowId |
@@ -0,0 +1,86 @@ | |||
# -*- coding: UTF-8 -*- | |||
""" | |||
# Class UploadVideoRequest | |||
# | |||
# Aliyun VoD's Upload Video Request class, which wraps parameters to upload a video into VoD. | |||
# Users could pass parameters to AliyunVodUploader, including File Path,Title,etc. via an UploadVideoRequest instance. | |||
# For more details, please check out the VoD API document: https://help.aliyun.com/document_detail/55407.html | |||
""" | |||
from vodsdk.AliyunVodUtils import * | |||
class UploadVideoRequest: | |||
def __init__(self, filePath, title=None, fileExt=None): | |||
""" | |||
constructor for UploadVideoRequest | |||
:param filePath: string, 文件的绝对路径,或者网络文件的URL,必须含有扩展名 | |||
:param title: string, 视频标题,最长128字节,不传则使用文件名为标题 | |||
:return | |||
""" | |||
self.filePath = None | |||
self.fileName = None | |||
self.mediaExt = None | |||
self.title = None | |||
self.setFilePath(filePath, title, fileExt) | |||
self.cateId = None | |||
self.tags = None | |||
self.description = None | |||
self.coverURL = None | |||
self.templateGroupId = None | |||
self.isShowWatermark = None | |||
self.userData = None | |||
self.storageLocation = None | |||
self.uploadId = None | |||
self.appId = None | |||
self.workflowId = None | |||
def setFilePath(self, filePath, title=None, fileExt=None): | |||
if fileExt is None: | |||
fileExt = AliyunVodUtils.getFileExtension(filePath) | |||
if not fileExt: | |||
raise AliyunVodException('ParameterError', 'InvalidParameter', 'filePath has no Extension') | |||
fileExt = fileExt.lstrip('.') | |||
self.mediaExt = fileExt | |||
self.filePath = AliyunVodUtils.toUnicode(filePath) | |||
briefPath, briefName = AliyunVodUtils.getFileBriefPath(self.filePath) | |||
self.fileName = briefPath | |||
if fileExt and (not self.fileName.endswith('.' + fileExt)): | |||
self.fileName = self.fileName + '.' + fileExt | |||
if title: | |||
self.title = title | |||
else: | |||
if self.title is None: | |||
self.title = briefName | |||
def setCateId(self, cateId): | |||
self.cateId = cateId | |||
def setTags(self, tags): | |||
self.tags = tags | |||
def setDescription(self, description): | |||
self.description = description | |||
def setCoverURL(self, coverURL): | |||
self.coverURL = coverURL | |||
def setTemplateGroupId(self, templateGroupId): | |||
self.templateGroupId = templateGroupId | |||
# 关闭水印,仅用于配置全局水印且转码模板开启水印后,单次上传时关闭水印 | |||
def shutdownWatermark(self): | |||
self.isShowWatermark = False | |||
# 设置上传ID,可用于关联导入视频 | |||
def setUploadId(self, uploadId): | |||
self.uploadId = uploadId | |||
def setAppId(self, appId): | |||
self.appId = appId | |||
def setWorkflowId(self, workflowId): | |||
self.workflowId = workflowId |
@@ -0,0 +1,3 @@ | |||
__version__ = '1.3.1' | |||