Skip to content
项目
群组
代码片段
帮助
正在加载...
登录
切换导航
I
ils-common-video
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
分枝图
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
分枝图
统计图
创建新议题
提交
议题看板
打开侧边栏
OpsTeam
ils-common-video
Commits
e888e357
提交
e888e357
authored
8月 03, 2021
作者:
zw.wang
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat: [filter] 移动侦测过滤模块
上级
16780ab7
全部展开
显示空白字符变更
内嵌
并排
正在显示
11 个修改的文件
包含
359 行增加
和
11 行删除
+359
-11
__init__.py
ils_common_video/capability/__init__.py
+0
-0
movement_verify.py
ils_common_video/capability/movement_verify.py
+0
-0
state_mechine.py
ils_common_video/capability/state_mechine.py
+121
-0
const.py
ils_common_video/const.py
+2
-0
mysql.py
ils_common_video/db/mysql.py
+4
-2
merger.py
ils_common_video/eviz_video/merger.py
+1
-1
recorder.py
ils_common_video/isc_video/recorder.py
+58
-1
main.py
ils_common_video/main.py
+6
-1
video_file.py
ils_common_video/utils/video_file.py
+12
-6
__init__.py
ils_common_video/video_filter/__init__.py
+0
-0
filter.py
ils_common_video/video_filter/filter.py
+155
-0
没有找到文件。
ils_common_video/capability/__init__.py
0 → 100644
浏览文件 @
e888e357
ils_common_video/capability/movement_verify.py
0 → 100644
浏览文件 @
e888e357
差异被折叠。
点击展开。
ils_common_video/capability/state_mechine.py
0 → 100644
浏览文件 @
e888e357
class
FrameState
:
def
__init__
(
self
,
prestate
,
count_forward
,
count_backward
):
self
.
prestate
=
prestate
self
.
count_forward
=
count_forward
self
.
count_backward
=
count_backward
self
.
alarm_starttime
=
None
self
.
alarm_endtime
=
None
def
noalarm_to_prealarm
(
self
,
state
,
duration
):
# 未报警状态转移到预报警状态
"""
:param state: 前一帧状态,
:param duration:
:return:
"""
self
.
count_forward
+=
1
if
self
.
count_forward
==
duration
:
newState
=
'prealarm'
self
.
count_backward
=
0
self
.
count_forward
=
0
else
:
# 状态不变
newState
=
state
return
newState
def
prealarm_to_conalarm
(
self
,
state
,
duration
):
# 预报警状态转移到持续报警状态
self
.
count_forward
+=
1
if
self
.
count_forward
==
duration
:
newState
=
'conalarm'
self
.
count_backward
=
0
self
.
count_forward
=
0
else
:
# 状态不变
newState
=
state
return
newState
def
conalarm_to_noalarm
(
self
,
state
,
duration
):
# 持续报警状态转移到未报警状态
self
.
count_forward
+=
1
if
self
.
count_forward
==
duration
:
newState
=
'noalarm'
self
.
count_backward
=
0
self
.
count_forward
=
0
else
:
# 状态不变
newState
=
state
return
newState
def
prealarm_to_noalarm
(
self
,
state
,
duration
):
# 预报警状态转移到未报警状态
self
.
count_backward
+=
1
if
self
.
count_backward
==
duration
:
newState
=
'noalarm'
self
.
count_backward
=
0
self
.
count_forward
=
0
else
:
# 状态不变
newState
=
state
return
newState
def
conalarm_to_conalarm
(
self
,
state
,
duration
):
# 预报警状态转移到未报警状态
self
.
count_backward
+=
1
if
self
.
count_backward
==
duration
:
newState
=
'conalarm'
self
.
count_backward
=
0
self
.
count_forward
=
0
else
:
# 状态不变
newState
=
state
return
newState
if
__name__
==
"__main__"
:
frame_state
=
FrameState
(
'initial'
,
0
,
0
)
frame_color_list
=
[
'red'
,
'red'
,
'red'
,
'blue'
,
'blue'
,
'blue'
,
'blue'
,
'red'
,
'red'
,
'red'
,
'red'
,
'red'
,
'red'
,
'red'
,
'red'
,
'red'
,
'green'
,
'green'
,
'green'
,
'green'
]
for
i
in
frame_color_list
:
if
frame_state
.
prestate
==
'initial'
:
if
i
==
'red'
:
frame_state
.
prestate
=
frame_state
.
noalarm_to_prealarm
(
'initial'
,
1
)
print
(
"当前状态是 {}"
.
format
(
frame_state
.
prestate
))
else
:
pass
if
frame_state
.
prestate
==
'noalarm'
:
if
i
==
'red'
:
frame_state
.
prestate
=
frame_state
.
noalarm_to_prealarm
(
'noalarm'
,
3
)
print
(
"当前状态是 {}"
.
format
(
frame_state
.
prestate
))
else
:
pass
if
frame_state
.
prestate
==
'prealarm'
:
if
i
==
'red'
:
frame_state
.
prestate
=
frame_state
.
prealarm_to_conalarm
(
'prealarm'
,
3
)
print
(
"当前状态是 {}"
.
format
(
frame_state
.
prestate
))
else
:
frame_state
.
prestate
=
frame_state
.
prealarm_to_noalarm
(
'prealarm'
,
3
)
print
(
"当前状态是 {}"
.
format
(
frame_state
.
prestate
))
if
frame_state
.
prestate
==
'conalarm'
:
if
i
==
'red'
:
frame_state
.
prestate
=
frame_state
.
conalarm_to_conalarm
(
'conalarm'
,
3
)
print
(
"当前状态是 {}"
.
format
(
frame_state
.
prestate
))
else
:
frame_state
.
prestate
=
frame_state
.
conalarm_to_noalarm
(
'conalarm'
,
3
)
print
(
"当前状态是 {}"
.
format
(
frame_state
.
prestate
))
\ No newline at end of file
ils_common_video/const.py
浏览文件 @
e888e357
...
...
@@ -4,3 +4,5 @@ PROCESSING_TOTAL_KEY = 'hk_isc:recording:processing:total'
ACCOUNT_TOKEN_KEY
=
'eviz:account:app_key:{}:token'
DEVICE_SERIAL_KEY
=
'eviz:account:device:{}'
UNVERIFIED_EVENT_QUEUE
=
'UNVERIFIED_EVENT_QUEUE'
ils_common_video/db/mysql.py
浏览文件 @
e888e357
...
...
@@ -166,7 +166,7 @@ def insert_video_info(cursor, conn, db_table, device_code, start_time, end_time,
def
update_video_info
(
cursor
,
conn
,
db_table
,
video_id
,
status
,
file_name
=
None
,
video_url
=
None
,
video_resolution
=
None
,
recovered_time
=
None
,
retry_info
=
None
,
next_retry_time
=
None
,
remark
=
None
):
next_retry_time
=
None
,
remark
=
None
,
end_time
=
None
):
sql
=
'''
update {}
set status =
%
s, update_time = now() {}
...
...
@@ -183,6 +183,8 @@ def update_video_info(cursor, conn, db_table, video_id, status,
sub_set
+=
', next_retry_time="{}"'
.
format
(
next_retry_time
)
if
remark
:
sub_set
+=
', remark="{}"'
.
format
(
remark
)
if
end_time
:
sub_set
+=
', end_time="{}"'
.
format
(
end_time
)
log
.
info
(
sql
.
format
(
db_table
,
sub_set
),
status
,
video_id
)
cursor
.
execute
(
sql
.
format
(
db_table
,
sub_set
),
[
status
,
video_id
])
...
...
@@ -206,7 +208,7 @@ def get_untreated_events(cursor, conn, db_table, camera_code, status=3, order_by
recovered_time,
video_url,
file_name,
retry_info, next_retry_time, remark
retry_info, next_retry_time, remark
, biz_type, service_type
from {}
where device_code =
%
s
and create_time > date_sub(now(), interval 7 day)
...
...
ils_common_video/eviz_video/merger.py
浏览文件 @
e888e357
...
...
@@ -273,7 +273,7 @@ def send_unverified_file(pre_files, camera):
video_url
=
oss_upload_file
(
'isc_record/'
+
file_name
,
video_file
.
full_path
)
event_id
=
mysql
.
insert_video_info
(
camera
[
'db_table'
],
camera
[
'device_code'
],
video_file
.
start_time
,
video_file
.
end_time
,
camera
[
'biz_type'
],
camera
[
'service_type'
],
status
=
1
,
camera
[
'biz_type'
],
camera
[
'service_type'
],
status
=
5
,
# status = 5 待分析
file_name
=
file_name
,
video_url
=
video_url
,
video_resolution
=
video_file
.
resolution
,
recovered_time
=
video_file
.
end_time
)
...
...
ils_common_video/isc_video/recorder.py
浏览文件 @
e888e357
...
...
@@ -20,6 +20,7 @@ from ils_common_video.utils import aliyun_oss
from
ils_common_video.utils.record_utils
import
record_thread
,
get_video_duration
,
time_to_seconds
,
judge_video_error
from
ils_common_video.utils.alarm_utils
import
send_alarm_to_developer
from
ils_common_video.db
import
mysql
from
ils_common_video.utils.video_file
import
VideoFile
tz
=
pytz
.
timezone
(
'Asia/Shanghai'
)
...
...
@@ -238,7 +239,7 @@ class ProcessMessage:
remark
=
(
remark
or
'start'
)
+
'+offline'
elif
record_result
.
get
(
'is_completed'
):
log
.
info
(
'
%
s:
%
s is completed.'
,
event
[
'camera_code'
],
record_result
[
'file_name'
])
status
=
1
status
=
5
# 录制完成的视频状态为5
else
:
next_retry_time
=
now
+
timedelta
(
minutes
=
10
)
retry_count
+=
1
...
...
@@ -265,10 +266,66 @@ class ProcessMessage:
next_retry_time
=
next_retry_time
,
remark
=
remark
)
if
status
==
5
:
ProcessMessage
.
send_unverified_file
(
VideoFile
(
record_result
[
'file_name'
]),
event
[
'video_id'
],
url
,
event
[
'camera_code'
],
event
[
'biz_type'
],
event
[
'service_type'
],
body
[
'db_table'
])
log
.
info
(
'video_info:
%
s, url:
%
s, video_id:
%
s.
%
s'
,
video_info
,
url
,
body
[
'db_table'
],
event
[
'video_id'
])
return
True
@staticmethod
def
send_unverified_file
(
video_file
,
event_id
,
video_url
,
device_code
,
biz_type
,
service_type
,
db_table
,
detection_region
=
''
):
queue_name
=
'UNVERIFIED_EVENT_QUEUE'
connection
=
rabbitmq_connect
()
channel
=
connection
.
channel
()
channel
.
queue_declare
(
queue_name
,
durable
=
True
)
log
.
info
(
'添加待处理文件
%
s,到分析队列,开始时间
%
s, 结束时间
%
s'
,
video_file
.
file_name
,
video_file
.
start_time
,
video_file
.
end_time
)
log
.
info
(
'视频文件
%
s的网络质量为
%
s, 评级为
%
s'
,
video_file
.
file_name
,
video_file
.
network_quality
,
video_file
.
network_quality_grade
)
if
not
os
.
path
.
isfile
(
video_file
.
full_path
):
log
.
warning
(
'文件
%
s已经不存在!'
,
video_file
.
file_name
)
return
if
video_file
.
end_time
-
video_file
.
start_time
<
timedelta
(
seconds
=
2
):
log
.
warning
(
'视频文件
%
s播放时间较短认为有问题'
,
video_file
.
file_name
)
os
.
remove
(
video_file
.
full_path
)
return
video_data
=
{
'event_id'
:
event_id
,
'video_url'
:
video_url
,
# 视频播放地址
'sn'
:
device_code
,
'full_path'
:
video_file
.
full_path
,
'db_table'
:
db_table
,
'biz_type'
:
biz_type
,
'service_type'
:
service_type
,
'file_name'
:
video_file
.
file_name
,
'start_time'
:
video_file
.
start_time
.
strftime
(
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S'
),
'end_time'
:
video_file
.
end_time
.
strftime
(
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S'
),
'date'
:
video_file
.
date
,
'detection_region'
:
detection_region
,
'bitrate'
:
video_file
.
bitrate
,
'resolution'
:
video_file
.
resolution
,
'duration'
:
video_file
.
duration
,
'cloud_storage'
:
video_file
.
file_path
,
# 云存储key
'network_quality'
:
video_file
.
network_quality
,
'network_quality_grade'
:
video_file
.
network_quality_grade
,
}
# 发送mq信息
channel
.
basic_publish
(
exchange
=
''
,
routing_key
=
queue_name
,
body
=
json
.
dumps
(
video_data
,
ensure_ascii
=
False
))
connection
.
close
()
@staticmethod
def
stream_to_video
(
body
,
playback_stream
,
start_time
,
end_time
,
part_files_set
):
...
...
ils_common_video/main.py
浏览文件 @
e888e357
...
...
@@ -6,7 +6,7 @@ def get_parser():
parsers
=
argparse
.
ArgumentParser
(
description
=
'ISC motion detection playback video stream recording service.'
)
parsers
.
add_argument
(
'-e'
,
'--env'
,
choices
=
[
'isc'
,
'eviz'
,
'common'
],
type
=
str
,
parsers
.
add_argument
(
'-e'
,
'--env'
,
choices
=
[
'isc'
,
'eviz'
,
'common'
],
default
=
'common'
,
type
=
str
,
dest
=
'env'
,
help
=
'choices [isc, eviz]'
)
parsers
.
add_argument
(
'-w'
,
'--worker'
,
type
=
str
,
dest
=
'worker'
)
...
...
@@ -48,6 +48,11 @@ def command_line_runner():
elif
args
[
'worker'
]
==
'merger'
:
from
ils_common_video.eviz_video.merger
import
main
as
record_main
record_main
()
elif
args
[
'env'
]
==
'common'
:
if
args
[
'worker'
]
==
'filter'
:
from
ils_common_video.video_filter.filter
import
VideoFilterProcess
fp
=
VideoFilterProcess
()
fp
.
run
()
else
:
parser
.
print_help
()
...
...
ils_common_video/utils/video_file.py
浏览文件 @
e888e357
...
...
@@ -22,16 +22,22 @@ class VideoFile:
self
.
dir_path
,
self
.
file_path
=
os
.
path
.
split
(
full_path
)
self
.
file_name
,
self
.
postfix
=
self
.
file_path
.
rsplit
(
'.'
,
1
)
if
len
(
self
.
file_name
.
split
(
'_'
))
==
3
:
# 文件名格式不正确
self
.
sn
,
self
.
date
,
self
.
time
=
self
.
file_name
.
split
(
'_'
)
name_info
=
self
.
file_name
.
split
(
'_'
)
if
len
(
name_info
)
==
3
:
# 文件名格式如:G25597998_2021-08-03_11-01-52.mp4
self
.
sn
,
self
.
date
,
self
.
time
=
name_info
self
.
prefix
=
''
# HD文件名处理
self
.
sn
=
self
.
sn
[
3
:]
if
'HDV'
in
self
.
sn
else
self
.
sn
self
.
start_time
=
dateutil
.
parser
.
parse
(
' '
.
join
([
self
.
date
,
self
.
time
.
replace
(
'-'
,
':'
)]))
elif
len
(
name_info
)
==
4
:
# 文件名格式如: EVIZ_G25597998_20210803T110152_20210803T110334.mp4
self
.
prefix
,
self
.
sn
,
self
.
start_time_str
,
self
.
end_time_str
=
name_info
self
.
start_time
=
dateutil
.
parser
.
parse
(
self
.
start_time_str
)
else
:
self
.
sn
=
self
.
date
=
self
.
time
=
None
self
.
start_time
=
dateutil
.
parser
.
parse
(
' '
.
join
([
self
.
date
,
self
.
time
.
replace
(
'-'
,
':'
)]))
self
.
_duration
=
self
.
_bitrate
=
self
.
_resolution
=
self
.
_media_type
=
None
self
.
_end_time
=
None
self
.
_error_log
=
''
...
...
@@ -167,7 +173,7 @@ class VideoFile:
if
__name__
==
'__main__'
:
video_file
=
VideoFile
(
'/
tmp/videos/E82843165_2021-07-21_15-43-2
4.mp4'
)
video_file
=
VideoFile
(
'/
Users/wen/Downloads/EVIZ_G25597998_20210803T110152_20210803T11033
4.mp4'
)
# print(video_file.picture_path)
print
(
video_file
.
duration
)
print
(
video_file
.
size
)
...
...
ils_common_video/video_filter/__init__.py
0 → 100644
浏览文件 @
e888e357
ils_common_video/video_filter/filter.py
0 → 100644
浏览文件 @
e888e357
import
os
import
time
import
json
from
datetime
import
timedelta
from
intelab_python_sdk.ffmpeg.ffmpeg_prune
import
prune
from
intelab_python_sdk.logger
import
log
from
dynaconf
import
settings
from
oss2.exceptions
import
NoSuchKey
from
ils_common_video.db.rabbitmq
import
rabbitmq_connect
from
ils_common_video.db
import
mysql
from
ils_common_video.const
import
UNVERIFIED_EVENT_QUEUE
from
ils_common_video.capability.movement_verify
import
file_filter
from
ils_common_video.utils.aliyun_oss
import
oss_upload_file
,
oss_download_file
,
oss_delete_file
from
ils_common_video.utils.video_file
import
VideoFile
class
VideoFilterProcess
:
def
__init__
(
self
):
self
.
queue_name
=
UNVERIFIED_EVENT_QUEUE
self
.
connection
=
rabbitmq_connect
()
self
.
channel
=
self
.
connection
.
channel
()
def
run
(
self
):
os
.
makedirs
(
settings
.
get
(
'VIDEOS_PATH'
),
exist_ok
=
True
)
log
.
info
(
'启动分析进程'
)
log
.
info
(
'binding to queue {}'
.
format
(
self
.
queue_name
))
# 超时时间(如果5秒内没有收到消息,将不会夯住等待消息,取消接收消息)
# self.connection.call_later(5, lambda: self.channel.stop_consuming())
self
.
channel
.
queue_declare
(
queue
=
self
.
queue_name
,
durable
=
True
)
def
callback
(
ch
,
method
,
properties
,
body
):
log
.
info
(
'received MQ message {}'
.
format
(
body
))
try
:
body
=
json
.
loads
(
body
)
try
:
ret
=
self
.
process_message
(
body
)
except
Exception
as
e
:
log
.
error
(
'视频文件
%
s分析过程出错'
,
body
[
'full_path'
])
log
.
exception
(
e
)
ret
=
False
if
ret
:
log
.
info
(
'finished processing MQ message'
)
ch
.
basic_ack
(
delivery_tag
=
method
.
delivery_tag
)
else
:
ch
.
basic_nack
(
delivery_tag
=
method
.
delivery_tag
)
except
Exception
as
e
:
log
.
exception
(
e
)
log
.
error
(
'finished processing MQ message, error'
)
self
.
channel
.
basic_qos
(
prefetch_count
=
1
)
self
.
channel
.
basic_consume
(
on_message_callback
=
callback
,
queue
=
self
.
queue_name
)
log
.
info
(
' [*] Waiting for messages. To exit press CTRL+C'
)
try
:
self
.
channel
.
start_consuming
()
except
KeyboardInterrupt
:
log
.
info
(
'MQ connection closed by user'
)
except
Exception
as
e
:
log
.
exception
(
'MQ connection closed unexpectedly.
%
s'
,
e
)
raise
self
.
connection
.
close
()
@staticmethod
def
process_message
(
pre_video_file
):
# 下载云端视频
video_path
=
os
.
path
.
split
(
pre_video_file
.
get
(
'full_path'
))[
0
]
if
video_path
and
not
os
.
path
.
exists
(
video_path
):
os
.
makedirs
(
video_path
,
exist_ok
=
True
)
try
:
oss_download_file
(
pre_video_file
.
get
(
'video_url'
),
pre_video_file
.
get
(
'full_path'
))
if
not
os
.
path
.
isfile
(
pre_video_file
.
get
(
'full_path'
)):
log
.
warning
(
'下载文件
%
s失败'
,
pre_video_file
.
get
(
'video_url'
))
raise
FileNotFoundError
except
NoSuchKey
as
e
:
log
.
warning
(
e
)
return
True
video_file
=
VideoFile
(
pre_video_file
[
'full_path'
])
region
=
pre_video_file
.
get
(
'detection_region'
)
if
region
and
isinstance
(
region
,
str
):
region
=
json
.
loads
(
region
)
try
:
t
=
time
.
time
()
_
,
video_result
,
camera_seconds
=
file_filter
(
pre_video_file
.
get
(
'full_path'
),
region
)
time_consuming
=
time
.
time
()
-
t
log
.
info
(
'分析在
%
s视频文件的
%
s秒结束,耗时
%
s'
,
pre_video_file
.
get
(
'full_path'
),
camera_seconds
,
time_consuming
)
except
(
FileNotFoundError
,
NoSuchKey
):
log
.
warning
(
'视频文件
%
s不存在'
,
pre_video_file
.
get
(
'full_path'
))
return
True
if
video_result
!=
1
:
mysql
.
update_video_info
(
pre_video_file
[
'db_table'
],
pre_video_file
[
'event_id'
],
status
=-
2
)
elif
camera_seconds
<
20
:
mysql
.
update_video_info
(
pre_video_file
[
'db_table'
],
pre_video_file
[
'event_id'
],
status
=
1
)
else
:
invalid_part_url
=
valid_part_url
=
''
camera_seconds
-=
20
# 截取前段无效视频
invalid_part_name
=
video_file
.
gen_file_name
(
pre_video_file
[
'device_code'
],
start_time
=
video_file
.
start_time
,
end_time
=
video_file
.
start_time
+
timedelta
(
seconds
=
camera_seconds
),
)
prune
(
pre_video_file
.
get
(
'full_path'
),
invalid_part_name
,
duration
=
camera_seconds
)
if
os
.
path
.
isfile
(
invalid_part_name
):
invalid_part_url
=
oss_upload_file
(
'isc_record/'
+
invalid_part_name
,
invalid_part_name
)
mysql
.
update_video_info
(
pre_video_file
[
'db_table'
],
pre_video_file
[
'event_id'
],
status
=-
2
,
file_name
=
invalid_part_name
,
video_url
=
invalid_part_url
,
end_time
=
video_file
.
end_time
,
recovered_time
=
video_file
.
end_time
,
video_resolution
=
video_file
.
resolution
)
# 截取后段有意义视频
valid_part_name
=
video_file
.
gen_file_name
(
pre_video_file
[
'device_code'
],
start_time
=
video_file
.
start_time
+
timedelta
(
seconds
=
camera_seconds
),
end_time
=
video_file
.
end_time
)
prune
(
pre_video_file
.
get
(
'full_path'
),
valid_part_name
,
start_time
=
camera_seconds
)
if
os
.
path
.
isfile
(
valid_part_name
):
valid_part_video
=
VideoFile
(
valid_part_name
)
valid_part_url
=
oss_upload_file
(
'isc_record/'
+
valid_part_name
,
valid_part_name
)
video_id
=
mysql
.
insert_video_info
(
pre_video_file
[
'db_table'
],
pre_video_file
[
'device_code'
],
valid_part_video
.
start_time
,
valid_part_video
.
end_time
,
biz_type
=
pre_video_file
[
'biz_type'
],
service_type
=
pre_video_file
[
'service_type'
],
status
=
1
,
file_name
=
valid_part_video
.
file_name
,
video_url
=
valid_part_url
,
video_resolution
=
valid_part_video
.
resolution
)
log
.
info
(
'insert new video_id:
%
s, db_table:
%
s'
,
video_id
,
pre_video_file
[
'db_table'
])
if
invalid_part_url
and
valid_part_url
:
oss_delete_file
(
pre_video_file
.
get
(
'video_url'
))
os
.
remove
(
invalid_part_name
)
os
.
remove
(
valid_part_name
)
else
:
return
False
os
.
remove
(
pre_video_file
.
get
(
'full_path'
))
log
.
info
(
'file[
%
s], network_quality
%
s, grade
%
s'
,
video_file
.
file_name
,
pre_video_file
[
'network_quality'
],
pre_video_file
[
'network_quality_grade'
])
log
.
info
(
'视频文件
%
s的分析结果是
%
s'
,
pre_video_file
[
'full_path'
],
video_result
)
return
True
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论