【Python文本处理】基于运动路线记录GPX的文件解析,GPX转SRT字幕文件(不需要安装三方库)
解析和转换
GPX文件格式
GPX文件本身其实就是坐标、海拔、时间、心率等综合性的xml文件
如图:
海拔:ele
时间:time
心率:heartrate
功率:power
踏频:cadence
距离:distance
一般不用距离distance 但可以根据距离求瞬时速度(前提是时间间隔均匀 最小精度不低于1s) 不过如果距离和坐标之差相差太远 则不太好确定 Strava等软件通过这个来计算瞬时速度和距离 但不用于计算赛段速度(赛段时间)
某一时刻的数据就看trkpt部分
以trkpt 为始 到 /trkpt 为止
比如:
<trkpt lat="30.3940883" lon="112.2400167">
<ele>34</ele>
<time>2023-05-12T12:26:13Z</time>
<extensions>
<heartrate>167</heartrate>
<distance>26698</distance>
</extensions>
</trkpt>
SRT基本格式
SRT字幕通常以srt作为后缀,作为外挂字幕,多数主流播放器都支持直接加载并显示SRT字幕,具体细节看参考SubRip (.SRT) subtitles support in players。
该格式是基于纯文本的格式,使用CR+LF作为换行符(Windows下常用换行符,*nix使用LF作为换行符)。每个SRT文件包含至少一个字幕段。
每个字幕段有四部分构成:
字幕序号
字幕显示的起始时间
字幕内容(可多行)
空白行(表示本字幕段的结束)
其中字幕序号一般是顺序增加的,表示字幕是一系列连续的序列。但该数值在字幕显示中不起任何作用,只是起着标记和标识的作用,方便分配翻译行数用。字幕序号的值可以随意,1和100都一样,并不会影响字幕的显示。但字幕序号也是字幕段的一部分,所以不能没有或者删去,否则在播放时,将出现错误。
字幕显示起始时间的格式如下:
hour:minute:second.millisecond --> hour:minute:second.millisecond 或
hour:minute:second,millisecond --> hour:minute:second,millisecond
后面还可以附加用于指定字幕显示位置的信息,以像素为单位,格式如下: X1:number Y1:number X2:number Y2:number。
一个典型的SRT文件如下(截取自阿凡达中英字幕):
3
00:00:39,770 --> 00:00:41,880
在经历了一场人生巨变之后
When I was lying there in the VA hospital ...
4
00:00:42,550 --> 00:00:44,690
我被送进了退伍军人管理局医院
... with a big hole blown through the middle of my life,
5
00:00:45,590 --> 00:00:48,120
那段时间我经常会梦到自己在飞翔
... I started having these dreams of flying.
6
00:00:49,740 --> 00:00:51,520
终获自由
I was free.
7
00:00:54,620 --> 00:00:55,830
而不幸的是
Sooner or later though, ...
SRT格式化设置
多数SRT支持一些特定格式化,比如斜体、粗体、下划线以及字体颜色。使用时需要基于HTML的标签,具体用法如下:
颜色
字体斜体
字体下加划线
换行
字体加粗
这些HTML可嵌套。
<font color=red>颜色</font>
<i>字体斜体</i>
<u>字体下加划线</u>
<br>换行
<b>字体加粗</b>
当然某些播放器还对SRT做了扩展,可以支持ASS/SSA中部分格式化代码。
代码实现
首先需要获取第一次开始的时间参数:
try:
ti=str((gpx[i].split("<time>")[1]).split("</time>")[0])
now_time = time.mktime(time.strptime(ti, "%Y-%m-%dT%H:%M:%SZ"))
first_time=now_time
srt_time=0
str_now_time=time.strftime("<i>%Y-%m-%d %H:%M:%S</i>\n",time.localtime(now_time))
srt=str(j)+"\n00:00:00,000 --> 00:00:01,000\n"+str_now_time+"<u>BEGIN</u>\n\n"
srt_list.append(srt)
break
except:
pass
以及每个时刻的时间参数:
try:
ti=str((gpx[i].split("<time>")[1]).split("</time>")[0])
now_time = time.mktime(time.strptime(ti, "%Y-%m-%dT%H:%M:%SZ"))
srt_time=now_time-first_time
h=int(srt_time/3600)
m=int(srt_time/60)
s=int(srt_time%60)
s2=s+1
h=trans_time(h)
m=trans_time(m)
s=trans_time(s)
s2=trans_time(s2)
srt_str_time=str(h+":"+m+":"+s+",000 --> "+h+":"+m+":"+s2+",000\n")
str_now_time=time.strftime("<i>%Y-%m-%d %H:%M:%S</i>\n",time.localtime(now_time))
srt=srt+str_now_time
except:
pass
这里用到了一个时间格式转换:
def trans_time(st):
if st<10 and st>=0:
st="0"+str(st)
else:
st=str(st)
return str(st)
在解析时 用trkpt_flag状态机表示某一时刻的数据有效性状态:
if gpx[i].count('<trkpt'):
trkpt_flag=1
trkpt_first_flag=1
j=j+1
同理 还有trkpt_first_flag 表示第一次获取到trkpt
检测到/trkpt时表示该时刻数据获取结束
if gpx[i].count('</trkpt>'):
trkpt_flag=0
检测到/trkseg时 表示所有数据获取结束
if gpx[i].count('</trkseg>'):
j=j+1
trkpt_first_flag=2
当trkpt_first_flag 为1 trkpt_flag为0时 进行数据保存:
if trkpt_first_flag==1 and trkpt_flag==0:
if not srt=="":
last_srt_time=int(srt_time)
last_distance=int(dat)
srt=str(j)+"\n"+srt_str_time+srt+"\n"
srt_list.append(srt)
srt=""
last_distance 和 last_srt_time用于配合distance计算瞬时速度
了解这些以后 我们再来进行数据解析
经纬度获取:
if gpx[i].count('<trkpt'):
srt=srt+"lat:"+gpx[i].split('"')[1]+" lon:"+gpx[i].split('"')[3]+"\n"
海拔
try:
el=str((gpx[i].split("<ele>")[1]).split("</ele>")[0])
srt=srt+"ele:"+el+"\n"
except:
pass
关键词获取 其中 当获取到distance时 进行瞬时速度计算
for key in keywords_list:
try:
dat=str((gpx[i].split("<"+key+">")[1]).split("</"+key+">")[0])
srt=srt+"<i>"+key+":"+dat+"</i>\n"
if key=="distance":
velocity_time=srt_time-last_srt_time
velocity=(int(dat)-last_distance)/velocity_time*3.6
srt=srt+"<b>"+"velocity"+":"+str(velocity)+"</b>\n"
except:
pass
最后一个时刻后 再加一行字幕
if trkpt_first_flag==2:
h=int((last_srt_time+1)/3600)
m=int((last_srt_time+1)/60)
s=int((last_srt_time+1)%60)
s2=s+1
h=trans_time(h)
m=trans_time(m)
s=trans_time(s)
s2=trans_time(s2)
srt_str_time=str(h+":"+m+":"+s+",000 --> "+h+":"+m+":"+s2+",000\n")
srt=str(j)+"\n"+srt_str_time+"<u>END</u>\n"+"\n"
srt_list.append(srt)
srt=""
break
整体代码:
# -*- coding: utf-8 -*-
"""
Created on Thu Jun 1 14:23:10 2023
@author: ZHOU
"""
import time
'''data keywords
心率 "heartrate"
踏频 "cadence"
距离 "distance"
功率 "power"
'''
def trans_time(st):
if st<10 and st>=0:
st="0"+str(st)
else:
st=str(st)
return str(st)
def gpx_to_srt(gpx):
keywords_list=["heartrate","cadence","power","distance"]
srt_list=[]
first_time=0
srt_time=0
srt=""
j=1
trkpt_flag=0
trkpt_first_flag=0
for i in range(len(gpx)):
try:
ti=str((gpx[i].split("<time>")[1]).split("</time>")[0])
now_time = time.mktime(time.strptime(ti, "%Y-%m-%dT%H:%M:%SZ"))
first_time=now_time
srt_time=0
str_now_time=time.strftime("<i>%Y-%m-%d %H:%M:%S</i>\n",time.localtime(now_time))
srt=str(j)+"\n00:00:00,000 --> 00:00:01,000\n"+str_now_time+"<u>BEGIN</u>\n\n"
srt_list.append(srt)
break
except:
pass
srt=""
srt_str_time=""
last_distance=0
srt_time=0
last_srt_time=0
for i in range(len(gpx)):
if gpx[i].count('<trkpt'):
trkpt_flag=1
trkpt_first_flag=1
j=j+1
if trkpt_flag==1:
try:
ti=str((gpx[i].split("<time>")[1]).split("</time>")[0])
now_time = time.mktime(time.strptime(ti, "%Y-%m-%dT%H:%M:%SZ"))
srt_time=now_time-first_time
h=int(srt_time/3600)
m=int(srt_time/60)
s=int(srt_time%60)
s2=s+1
h=trans_time(h)
m=trans_time(m)
s=trans_time(s)
s2=trans_time(s2)
srt_str_time=str(h+":"+m+":"+s+",000 --> "+h+":"+m+":"+s2+",000\n")
str_now_time=time.strftime("<i>%Y-%m-%d %H:%M:%S</i>\n",time.localtime(now_time))
srt=srt+str_now_time
except:
pass
if gpx[i].count('<trkpt'):
srt=srt+"lat:"+gpx[i].split('"')[1]+" lon:"+gpx[i].split('"')[3]+"\n"
try:
el=str((gpx[i].split("<ele>")[1]).split("</ele>")[0])
srt=srt+"ele:"+el+"\n"
except:
pass
for key in keywords_list:
try:
dat=str((gpx[i].split("<"+key+">")[1]).split("</"+key+">")[0])
srt=srt+"<i>"+key+":"+dat+"</i>\n"
if key=="distance":
velocity_time=srt_time-last_srt_time
velocity=(int(dat)-last_distance)/velocity_time*3.6
srt=srt+"<b>"+"velocity"+":"+str(velocity)+"</b>\n"
except:
pass
if gpx[i].count('</trkpt>'):
trkpt_flag=0
if trkpt_first_flag==1 and trkpt_flag==0:
if not srt=="":
last_srt_time=int(srt_time)
last_distance=int(dat)
srt=str(j)+"\n"+srt_str_time+srt+"\n"
srt_list.append(srt)
srt=""
if gpx[i].count('</trkseg>'):
j=j+1
trkpt_first_flag=2
if trkpt_first_flag==2:
h=int((last_srt_time+1)/3600)
m=int((last_srt_time+1)/60)
s=int((last_srt_time+1)%60)
s2=s+1
h=trans_time(h)
m=trans_time(m)
s=trans_time(s)
s2=trans_time(s2)
srt_str_time=str(h+":"+m+":"+s+",000 --> "+h+":"+m+":"+s2+",000\n")
srt=str(j)+"\n"+srt_str_time+"<u>END</u>\n"+"\n"
srt_list.append(srt)
srt=""
break
return srt_list
def save_lines(lines,path):
try:
f=open(path, 'w', encoding="utf-8")
except:
f=open(path, 'a', encoding="utf-8")
for i in lines:
f.write(i)
f.close()
if __name__ == '__main__':
path="./1.gpx"
f=open(path, 'r', encoding="utf-8")
gpx=f.readlines()
f.close()
gpx=gpx_to_srt(gpx)
save_lines(gpx,"srt_test.srt")
若有不明白的 可以联系上下文一起看
GUI界面编程
py打包
Pyinstaller打包exe(包括打包资源文件 绝不出错版)
依赖包及其对应的版本号
PyQt5 5.10.1
PyQt5-Qt5 5.15.2
PyQt5-sip 12.9.0
pyinstaller 4.5.1
pyinstaller-hooks-contrib 2021.3
Pyinstaller -F setup.py 打包exe
Pyinstaller -F -w setup.py 不带控制台的打包
Pyinstaller -F -i xx.ico setup.py 打包指定exe图标打包
打包exe参数说明:
-F:打包后只生成单个exe格式文件;
-D:默认选项,创建一个目录,包含exe文件以及大量依赖文件;
-c:默认选项,使用控制台(就是类似cmd的黑框);
-w:不使用控制台;
-p:添加搜索路径,让其找到对应的库;
-i:改变生成程序的icon图标。
如果要打包资源文件
则需要对代码中的路径进行转换处理
另外要注意的是 如果要打包资源文件 则py程序里面的路径要从./xxx/yy换成xxx/yy 并且进行路径转换
但如果不打包资源文件的话 最好路径还是用作./xxx/yy 并且不进行路径转换
def get_resource_path(relative_path):
if hasattr(sys, '_MEIPASS'):
return os.path.join(sys._MEIPASS, relative_path)
return os.path.join(os.path.abspath("."), relative_path)
而后再spec文件中的datas部分加入目录
如:
a = Analysis(['cxk.py'],
pathex=['D:\\Python Test\\cxk'],
binaries=[],
datas=[('root','root')],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
而后直接Pyinstaller -F setup.spec即可
如果打包的文件过大则更改spec文件中的excludes 把不需要的库写进去(但是已经在环境中安装了的)就行
这些不要了的库在上一次编译时的shell里面输出
比如:
然后用pyinstaller --clean -F 某某.spec