-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathale_to_csv.py
More file actions
154 lines (138 loc) · 5.85 KB
/
Copy pathale_to_csv.py
File metadata and controls
154 lines (138 loc) · 5.85 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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
from libale import ALELib
from pathlib import Path
from typing import Iterable, Generator, Union
import argparse
import csv
import io
import logging
logger = logging.getLogger(__name__)
def is_ale_file_ext(filename: str | Path):
return str(filename).lower()[-3:] == 'ale'
def get_ale_filepaths(
input: Union[str, Iterable],
allow_all: bool = False,
recurse: bool = False,
) -> Generator[Path, None, None]:
"""
Return Path()s of files and folders that are recognised media files, determined by file extension
:param input: Path()s in a string or Iterable
:param allow_all: Skip file extension check and pass all items. Default is False
:param recurse: deeply recurse all folders found and return all items beneath this path in the filesystem.
Default is False and will just return files that are immediate children
[Originally from get_media_duration]
"""
def _iterate(filepath: Path):
if filepath.is_file():
if is_ale_file_ext(filepath) or allow_all:
yield filepath
elif filepath.is_dir():
for child in filepath.iterdir():
childpath = Path(child)
if is_ale_file_ext(childpath) or allow_all:
yield childpath
if recurse is True:
if childpath.is_dir():
yield from _iterate(childpath)
if isinstance(input, str):
filepath = Path(input)
yield from _iterate(filepath)
elif isinstance(input, Iterable):
for input_item in input:
filepath = Path(input_item)
yield from _iterate(filepath)
def parse(items: list, user_map: list = None):
def _build(clip: dict, mappings: list[dict]) -> Generator:
for mapping in mappings:
ale_col = mapping.get('ale_col')
csv_col = mapping.get('csv_col')
value = clip.get(ale_col)
if csv_col and value:
# Good column name and good value
yield csv_col, value
elif csv_col and not value:
# Good column name but no result in the source
yield csv_col, None
else:
logger.debug(f'Skipped during mapping - ALE Col: {ale_col} - CSV Col: {csv_col} - Value: {value}')
mappings = []
user_mapped_columns = False
if user_map:
user_mapped_columns = True
logger.debug(f'Input mapping: {user_map}')
for index, mapping_raw in enumerate(user_map):
try:
ale_col, csv_col = mapping_raw.split(':')
mappings.append(
dict(
ale_col = ale_col,
csv_col = csv_col,
)
)
except (
ValueError,
AttributeError,
) as e:
raise Exception(f'Error interpreting this map item #{index + 1}: {mapping_raw}. Ensure it is using the syntax: ALEColumnName:CSVColumnName')
if not user_map:
# Map will be created during ALE parsing
pass
logger.debug(f'Mappings: {mappings}')
logger.debug(f'Input items: {items}')
ale_files = list( get_ale_filepaths(items, recurse=True) )
ale_files.sort()
entries = []
for ale_file in ale_files:
logger.debug(f'Parsing {ale_file.name}')
with open(ale_file, 'r') as f:
ale = ALELib.parse(f.read())
if user_mapped_columns is False:
# If no user map specified, pass all ALE columns through to the CSV unaltered
mappings_for_this_ale = [ { 'ale_col': col, 'csv_col': col } for col in ale.columns ]
mappings.extend(mappings_for_this_ale)
for index, clip in enumerate(ale.clips):
# Look up the values per column
try:
subtable = dict( _build(clip, mappings) )
except Exception as e:
logger.debug(f'Exception {type(e)} on ALE file {str(ale_file)}: Clip #{index + 1}')
logger.debug(e, exc_info=1)
continue
entries.append(subtable)
# Process columns
columns = [ mapping['csv_col'] for mapping in mappings ]
logger.debug(f'Columns: {columns}')
return entries, columns
def write_csv(file_target, entries: list, columns: list):
output_csv_file = csv.DictWriter(
file_target,
delimiter = ',',
fieldnames = columns,
)
output_csv_file.writeheader()
for item in entries:
output_csv_file.writerow(item)
return output_csv_file
def main():
parser = argparse.ArgumentParser()
parser.add_argument('items', help='folders or ALE files', nargs='+', action='append')
parser.add_argument('--map', help='map of ALE column names to CSV column names. Format is ale_col:csv_col, space separated. Escape spaces in the column names with a backslash or quotes. Example: Name:name_csv "Start:Start TC"', nargs='+', action='append')
parser.add_argument('--debug', help='include debug output', action='store_true', default=False)
parser.add_argument('-o', help='output to CSV file, otherwise prints to stdout', type=str, required=False)
args = parser.parse_args()
if args.debug:
logging.basicConfig(level=logging.DEBUG)
# Parse
# args.items and args.map are returned as double lists - work around this with first index only
entries, columns = parse(args.items[0], args.map[0])
# Output
if args.o:
logger.debug(f'Writing CSV output to file: {args.o}')
file_target = open(args.o, 'w', encoding='utf-8')
write_csv(file_target, entries, columns)
logger.debug(f'Done.')
else:
file_target = io.StringIO()
write_csv(file_target, entries, columns)
print( file_target.getvalue().strip() )
if __name__ == '__main__':
main()