-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfit_to_csv.py
More file actions
137 lines (109 loc) · 3.53 KB
/
fit_to_csv.py
File metadata and controls
137 lines (109 loc) · 3.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import argparse
import csv
import glob
import os
from datetime import timezone
from typing import Any
from zoneinfo import ZoneInfo
import fitparse
FIELDS_ALLOWED = [
"timestamp",
"position_lat",
"position_long",
"distance",
"enhanced_altitude",
"altitude",
"enhanced_speed",
"speed",
"avg_heart_rate",
"heart_rate",
"cadence",
"fractional_cadence",
]
FIELDS_REQUIRED = ["timestamp", "position_lat", "position_long"]
UTC = timezone.utc
TZ = ZoneInfo("US/Pacific")
def write_to_csv(data: list[dict[str, Any]], output_path: str) -> None:
"""Write extracted data fields from the .fit messages to file
Args:
data (list[dict[str, Any]]): Data from messages
output_path (str): Output path
"""
# write to csv
with open(output_path, "w") as f:
writer = csv.writer(f)
writer.writerow(FIELDS_ALLOWED)
for entry in data:
writer.writerow([str(entry.get(k, "")) for k in FIELDS_ALLOWED])
print("wrote %s" % output_path)
def collect_data(filepath: str, tz: ZoneInfo = TZ) -> list[dict[str, Any]]:
"""Collects data from the .fit file at filepath
Args:
filepath (str): Path to .fit file
tz (ZoneInfo, optional): Timezone identifier. Defaults to TZ.
Returns:
list[dict[str, Any]]: List of dicts containing relevant data from each message in the .fit
"""
# Parse the .fit file
fitfile = fitparse.FitFile(
filepath, data_processor=fitparse.StandardUnitsDataProcessor()
)
data = []
messages = fitfile.messages
for m in messages:
skip = False
if not hasattr(m, "fields"):
continue
fields = m.fields
# check for desired data and collect it
mdata = {}
for field in fields:
if field.name in FIELDS_ALLOWED:
if field.name == "timestamp":
timestamp_value = field.value
if timestamp_value.tzinfo is None:
timestamp_value = timestamp_value.replace(tzinfo=UTC)
mdata[field.name] = timestamp_value.astimezone(tz)
else:
mdata[field.name] = field.value
for required_field in FIELDS_REQUIRED:
if required_field not in mdata:
skip = True
if not skip:
data.append(mdata)
return data
def parse_args() -> argparse.Namespace:
args = argparse.ArgumentParser(description="Convert .fit to .csv")
args.add_argument(
"--dir",
help="Path to directory containing .fit files",
type=str,
default=os.getcwd(),
)
args.add_argument(
"--timezone",
help="Timezone for timestamps, e.g. 'US/Pacific'",
default="US/Pacific",
)
args.add_argument(
"--overwrite",
help="Overwrite any .csv files already converted from .fit",
action="store_true",
)
return args.parse_args()
def main():
args = parse_args()
# Identify .fit files
fit_files = glob.glob(args.dir + "/*.fit")
for file in fit_files:
# Use the same filename, just change extension to .csv
base_filename = file.removesuffix(".fit")
new_filename = base_filename + ".csv"
if not args.overwrite and os.path.exists(new_filename):
continue
print("converting %s" % file)
data = collect_data(file, tz=ZoneInfo(args.timezone))
write_to_csv(data, new_filename)
print("finished conversions")
if __name__ == "__main__":
main()