-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathcuescanner.py
144 lines (111 loc) · 4.56 KB
/
cuescanner.py
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
# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright (C) 2019-2021 Patryk Obara <[email protected]>
"""
Simple introspection and filtering of .cue files.
"""
import os
import re
import winpathlib
def is_cue_file(path):
"""Simple heuristic to determine if a path points to .cue file"""
if not os.path.isfile(path):
return False
if os.stat(path).st_size > 4096:
return False
with open(path, 'r') as cue_file:
lines = cue_file.readlines(4096)
if len(lines) < 3:
return False
first_line = lines[0].strip()
return first_line.startswith('FILE ')
def list_file_entries(cue_path):
"""Return iterator over file entries"""
with open(cue_path, 'r') as cue_file:
pattern_1 = r' *FILE +"([^"]+)" +(.*)'
pattern_2 = r' *FILE +([^ ]+) +(.*)'
file_entry_1 = re.compile(pattern_1)
file_entry_2 = re.compile(pattern_2)
for line in cue_file:
match = file_entry_1.match(line) or file_entry_2.match(line)
if match:
file_path = match.group(1)
file_type = match.group(2)
yield file_path, file_type
# Temporary implementation to fix TR1; proper implementation
# involves implementing almost full cue parser, but TR1 is the only game with
# broken indexes we found so far.
#
def list_indexes(cue_path):
"""Return iterator over file entries"""
with open(cue_path, 'r') as cue_file:
pattern = r' *INDEX +(\d+) +.*'
index_entry = re.compile(pattern)
for line in cue_file:
match = index_entry.match(line)
if match:
index_num = match.group(1)
yield int(index_num)
def valid_cue_file_paths(cue_path):
"""Return true if all paths in a .cue file refer to existing files"""
cue_dir, _ = os.path.split(cue_path)
def is_file(file_entry):
path, _ = file_entry
ref_file = os.path.join(cue_dir, path)
return os.path.isfile(ref_file)
return all(map(is_file, list_file_entries(cue_path)))
def valid_indexes(cue_path):
"""Return true if all indexes in a .cue file are either 0 or 1"""
return all(map(lambda x: x in (0, 1), list_indexes(cue_path)))
def valid_cue_file(cue_path):
"""Return true if .cue file is valid according to our checks"""
return valid_cue_file_paths(cue_path) and valid_indexes(cue_path)
def rm_prefix(pfx, txt):
"""Remove a prefix from a string"""
if txt is None:
return None
if txt.startswith(pfx):
return txt[len(pfx):]
return txt
def fix_relative_path(pfx_win, pfx_lin, file_path):
"""Convert a path relative to a different directory"""
win_path = pfx_win + file_path
linux_path = winpathlib.to_posix_path(win_path)
return rm_prefix(pfx_lin, linux_path)
def fix_index_number(num):
"""Clamp index number in cue file to the only valid values."""
return '00' if num in ('0', '00') else '01'
def dir_prefixes(path):
"""Return dirname as pair of windows and posix paths"""
dir_path, _ = os.path.split(path)
pfx_win = dir_path + '\\' if dir_path else ''
pfx_lin = dir_path + '/' if dir_path else ''
return pfx_win, pfx_lin
def create_fixed_cue_file(cue_path, new_file):
"""Filter content of .cue file and save as fixed file"""
file_entry_1 = re.compile(r'( *)FILE +"([^"]+)" +(.*)')
file_entry_2 = re.compile(r'( *)FILE +([^ ]+) +(.*)')
index_entry = re.compile(r'( *)INDEX +(\d+) +(.*)')
pfx_win, pfx_lin = dir_prefixes(cue_path)
new_file_path = os.path.join(pfx_lin, new_file)
def fix_file_entry(indent, path, file_type):
return '{}FILE "{}" {}\n'.format(
indent, fix_relative_path(pfx_win, pfx_lin, path), file_type)
def fix_index_entry(indent, num, time_pos):
return '{}INDEX {} {}\n'.format(indent, fix_index_number(num),
time_pos)
with open(cue_path, 'r') as cue_file, open(new_file_path, 'w') as out_file:
for line in cue_file:
match = file_entry_1.match(line) or file_entry_2.match(line)
if match:
line = fix_file_entry(match.group(1), match.group(2),
match.group(3))
out_file.write(line)
continue
match = index_entry.match(line)
if match:
line = fix_index_entry(match.group(1), match.group(2),
match.group(3))
out_file.write(line)
continue
out_file.write(line)
return new_file_path