9 Commits
Author SHA1 Message Date
132nd-Professor 018717ead7 gitignore: added release/ 2021-06-08 22:24:16 +02:00
132nd-Professor 782ada131b replaced gnu-make with pyinvoke 2021-06-08 22:23:45 +02:00
132nd-Professor ab01a14719 zip files: use filename as provided by zip archive
We assumed that the txt file is always at the top level of the zip file
The tacview files from Hoggit have them nested under multiple directories
fixes #1
2021-06-08 21:30:38 +02:00
132nd-Professor 18329155b3 last cleanup, added some docstrings 2021-06-08 15:45:54 +02:00
132nd-Professor abc5584856 even more refactoring to improve code quality 2021-06-08 15:32:14 +02:00
132nd-Professor b42a711b0c removed unused constant 2021-06-07 23:29:15 +02:00
132nd-Professor 15fd64210f Pycharm: adjusted compatiblity checks 2021-06-07 23:23:03 +02:00
132nd-Professor 3b26c32922 major refactoring to improve readability 2021-06-07 23:21:59 +02:00
132nd-Professor 130ceb60d8 added simple Makefile 2021-06-07 20:19:00 +02:00
5 changed files with 246 additions and 133 deletions
+2
View File
@@ -1,6 +1,8 @@
*.zip.acmi
*.txt.acmi
release/
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
+1
View File
@@ -1,5 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="PROJECT_PROFILE" value="Default" />
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
+3
View File
@@ -1,4 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (tacview-splitter)" project-jdk-type="Python SDK" />
<component name="PythonCompatibilityInspectionAdvertiser">
<option name="version" value="3" />
</component>
</project>
+169 -109
View File
@@ -1,101 +1,48 @@
#!/usr/bin/env ipython
from __future__ import annotations
from dataclasses import dataclass
from os import listdir as os_listdir
import zipfile
from typing import Tuple
from zipfile import ZipFile, ZIP_DEFLATED
EXTENSION_TXT = '.txt.acmi'
EXTENSION_ZIP = '.zip.acmi'
is_zip = None
all_files = os_listdir('.')
for filename in all_files:
filename_lower = filename.lower()
if filename_lower.endswith(EXTENSION_ZIP):
is_zip = True
break
elif filename_lower.endswith(EXTENSION_TXT):
is_zip = False
break
else:
raise FileNotFoundError('Could not find a tacview file in this directory.')
filename_input = filename
def main():
filename_input, is_zip = find_input_file()
print('Processing ' + str(filename_input))
filenames = Filenames(filename_input, is_zip)
tacview_lines = read_data(filenames)
# set up all the file descriptors we will need
descriptors = Descriptors(filenames)
# replicate the header from the input file into the output files
tacview_lines_no_header = move_header_to_output_files(tacview_lines, descriptors)
undecided_ids = move_content_to_output_files(tacview_lines_no_header, descriptors)
descriptors.close()
# sanity check
if len(undecided_ids) != 0:
print('There were units that are neither BLUE, RED nor NEUTRAL. Please investigate.')
print(undecided_ids)
if is_zip:
filename_no_extension = filename_input.replace(EXTENSION_ZIP, '')
else:
filename_no_extension = filename_input.replace(EXTENSION_TXT, '')
filename_blue_no_extension, filename_red_no_extension, filename_violet_no_extension = \
(f'{filename_no_extension}_{color}' for color in ('blue', 'red', 'violet'))
if is_zip:
filename_blue_zip, filename_red_zip, filename_violet_zip = \
(f'{arg}{EXTENSION_ZIP}' for arg in
(filename_blue_no_extension, filename_red_no_extension, filename_violet_no_extension)
)
filename_blue_txt, filename_red_txt, filename_violet_txt = \
(f'{arg}{EXTENSION_TXT}' for arg in
(filename_blue_no_extension, filename_red_no_extension, filename_violet_no_extension)
)
if is_zip:
with zipfile.ZipFile(filename_input) as fd_zip:
with fd_zip.open(filename_no_extension + EXTENSION_TXT) as fd_tacview:
tacview_binary_lines = fd_tacview.readlines()
tacview_raw_lines = []
for line in tacview_binary_lines:
tacview_raw_lines.append(line.decode())
else:
with open(filename_input) as fd_tacview:
tacview_raw_lines = fd_tacview.readlines()
if is_zip:
# noinspection PyUnboundLocalVariable
fd_blue_zip = zipfile.ZipFile(filename_blue_zip, 'w', zipfile.ZIP_DEFLATED)
# noinspection PyUnboundLocalVariable
fd_red_zip = zipfile.ZipFile(filename_red_zip, 'w', zipfile.ZIP_DEFLATED)
# noinspection PyUnboundLocalVariable
fd_violet_zip = zipfile.ZipFile(filename_violet_zip, 'w', zipfile.ZIP_DEFLATED)
fd_blue_txt = fd_blue_zip.open(filename_blue_txt, 'w')
fd_red_txt = fd_red_zip.open(filename_red_txt, 'w')
fd_violet_txt = fd_violet_zip.open(filename_violet_txt, 'w')
else:
fd_blue_txt = open(filename_blue_txt, 'w')
fd_red_txt = open(filename_red_txt, 'w')
fd_violet_txt = open(filename_violet_txt, 'w')
for i, line in enumerate(tacview_raw_lines):
if is_zip:
line_header = line.encode()
else:
line_header = line
if not line[0] == '#': # header is everything before the first '#'
fd_blue_txt.write(line_header)
fd_red_txt.write(line_header)
fd_violet_txt.write(line_header)
else:
break
else:
raise IOError('Tacview file seems to be empty')
tacview_raw_lines = tacview_raw_lines[i:] # remove the header, we don't need it anymore
blue_ids = []
red_ids = []
# violet = neutral faction, used for chaffs, flares, decoys and shrapnel
# we can't decide easily which faction they belong to
# we would need to find the blue or red object with the least distance
# to violet objects around their spawn time
violet_ids = []
undecided_ids = []
def move_content_to_output_files(tacview_lines_no_header: list[str], descriptors: Descriptors) -> list[str]:
"""
core routine: process all telemetry lines, put them in the correct output file
for lines that are time stamps, we put these in all output files
:param tacview_lines_no_header: tacview telemetry, header removed
:param descriptors: holding all the opened descriptors
:return: list of unit ids that belong to neither blue, red, nor violet
"""
#
blue_ids, red_ids, violet_ids, undecided_ids = (list() for _ in range(4))
# violet = neutral faction, used for chaffs, flares, decoys and shrapnel. we can't decide easily which faction
# they belong to. we would need to find the blue or red object with the least distance to violet objects
# around their spawn time
continued = False
for line in tacview_raw_lines:
for line in tacview_lines_no_header:
# tacview introduced continued lines, signified by a single backslash at EOL
# example: DCS briefing is copied into tacview file (begins with `0,Briefing=`)
# if the line was not continued, we need to extract the unit ID from the line
@@ -118,39 +65,152 @@ for line in tacview_raw_lines:
elif 'Color=' in line:
undecided_ids.append(id_)
if is_zip:
if descriptors.filenames.input.is_zip:
line_output = line.encode()
else:
line_output = line
# code checker thinks that id_ can be unbound, which it cannot
# noinspection PyUnboundLocalVariable
if id_ in blue_ids:
fd_blue_txt.write(line_output)
elif id_ in red_ids:
fd_red_txt.write(line_output)
elif id_ in violet_ids:
fd_violet_txt.write(line_output)
else: # id_ == 'both', has timestamps
fd_blue_txt.write(line_output)
fd_red_txt.write(line_output)
fd_violet_txt.write(line_output)
if id_ in blue_ids or id_ == 'both':
descriptors.blue_txt.write(line_output)
if id_ in red_ids or id_ == 'both':
descriptors.red_txt.write(line_output)
if id_ in violet_ids or id_ == 'both':
descriptors.violet_txt.write(line_output)
if line.endswith('\\\n'):
continued = True
else:
continued = False
return undecided_ids
fd_blue_txt.close()
fd_red_txt.close()
fd_violet_txt.close()
def move_header_to_output_files(tacview_lines: list[str], descriptors: Descriptors) -> list[str]:
"""
finds the tacview header in tacview_lines and writes it to the three output files
afterwards removes the header from the input data and returns the remaining content (actual telemetry)
:param tacview_lines: content of tacview file with header
:param descriptors: object of Descriptor class
:return: content of tacview file without header
"""
for i, line in enumerate(tacview_lines):
if descriptors.filenames.input.is_zip:
line_header = line.encode()
else:
line_header = line
if not line[0] == '#': # header is everything before the first '#'
descriptors.blue_txt.write(line_header)
descriptors.red_txt.write(line_header)
descriptors.violet_txt.write(line_header)
else:
break
else:
raise IOError('Tacview file seems to be empty')
return tacview_lines[i:]
def read_data(filenames: Filenames) -> list[str]:
"""
get the tacview data out of the file
:param filenames: object of class Filenames
:return: file content as list of strings, one line per item
"""
if filenames.input.is_zip:
with ZipFile(filenames.input.zip) as fd_zip:
with fd_zip.open(fd_zip.filelist[0].filename) as fd_tacview:
tacview_lines_binary = fd_tacview.readlines()
tacview_lines_ascii = []
for line in tacview_lines_binary:
tacview_lines_ascii.append(line.decode())
else:
with open(filenames.input.txt) as fd_tacview:
tacview_lines_ascii = fd_tacview.readlines()
return tacview_lines_ascii
def find_input_file() -> Tuple[str, bool]:
all_files = os_listdir('.')
for filename in all_files:
filename_lower = filename.lower()
if filename_lower.endswith(EXTENSION_ZIP):
is_zip = True
break
elif filename_lower.endswith(EXTENSION_TXT):
is_zip = False
break
else:
raise FileNotFoundError('Could not find a tacview file in this directory.')
return filename, is_zip
@dataclass
class Filenames:
@dataclass
class _Input:
def __init__(self, filename_input: str, is_zip: bool):
self.zip: str = ''
self.txt: str = ''
self.no_extension: str = ''
self.is_zip: bool = is_zip
if is_zip:
# noinspection PyUnboundLocalVariable
fd_blue_zip.close()
# noinspection PyUnboundLocalVariable
fd_red_zip.close()
# noinspection PyUnboundLocalVariable
fd_violet_zip.close()
self.zip = filename_input
self.txt = filename_input.replace(EXTENSION_ZIP, EXTENSION_TXT)
self.no_extension = filename_input.replace(EXTENSION_ZIP, '')
else:
self.zip = ''
self.txt = filename_input
self.no_extension = filename_input.replace(EXTENSION_TXT, '')
if len(undecided_ids) != 0:
print('There were units that are neither BLUE, RED nor NEUTRAL. Please investigate.')
print(undecided_ids)
@dataclass
class _Output:
@dataclass
class _Coalition:
def __init__(self, filename_input_no_extension: str, color: str):
self.no_extension: str = f'{filename_input_no_extension}_{color}'
self.zip: str = f'{self.no_extension}{EXTENSION_ZIP}'
self.txt: str = f'{self.no_extension}{EXTENSION_TXT}'
def __init__(self, filename_input_no_extension: str):
self.blue = self._Coalition(filename_input_no_extension, 'blue')
self.red = self._Coalition(filename_input_no_extension, 'red')
self.violet = self._Coalition(filename_input_no_extension, 'violet')
def __init__(self, filename_input: str, is_zip: bool):
self.input = self._Input(filename_input, is_zip)
self.output = self._Output(self.input.no_extension)
@dataclass
class Descriptors:
def __init__(self, filenames: Filenames):
self.filenames = filenames
if filenames.input.is_zip:
self._blue_zip = ZipFile(filenames.output.blue.zip, 'w', ZIP_DEFLATED)
self._red_zip = ZipFile(filenames.output.red.zip, 'w', ZIP_DEFLATED)
self._violet_zip = ZipFile(filenames.output.violet.zip, 'w', ZIP_DEFLATED)
self.blue_txt = self._blue_zip.open(filenames.output.blue.txt, 'w')
self.red_txt = self._red_zip.open(filenames.output.blue.txt, 'w')
self.violet_txt = self._violet_zip.open(filenames.output.blue.txt, 'w')
else:
self._blue_zip = None
self._red_zip = None
self._violet_zip = None
self.blue_txt = open(filenames.output.blue.txt, 'w')
self.red_txt = open(filenames.output.red.txt, 'w')
self.violet_txt = open(filenames.output.violet.txt, 'w')
def close(self):
self.blue_txt.close()
self.red_txt.close()
self.violet_txt.close()
if self.filenames.input.is_zip:
self._blue_zip.close()
self._red_zip.close()
self._violet_zip.close()
if __name__ == '__main__':
main()
+47
View File
@@ -0,0 +1,47 @@
from invoke import task
@task
def build(c):
c.run("pyinstaller --onefile tacview-splitter.py")
@task
def clean(c):
c.run("rm -rf build __pycache__ tacview-splitter.spec")
@task(pre=[clean])
def distclean(c):
c.run("rm -rf dist release")
@task(pre=[build])
def release(c, version):
c.run(f"""
mkdir -p release/linux/tacview-splitter-{version}
mkdir -p release/win/tacview-splitter-{version}
cd release/linux/tacview-splitter-{version}
cp ../../../README.md .
cp ../../../LICENSE .
cp ../../../dist/tacview-splitter .
cd ..
tar -cvzf tacview-splitter-linux_x86-64.tar.gz tacview-splitter-{version}
mv tacview-splitter-linux_x86-64.tar.gz ..
cd ../win/tacview-splitter-{version}
cp ../../../README.md .
cp ../../../LICENSE LICENSE.txt
echo Copy the windows release file into the following directory:
pwd
echo -n then press enter ...
read
cd ..
7z a tacview-splitter-win_x86-64.zip tacview-splitter-{version}
mv tacview-splitter-win_x86-64.zip ..
cd ..
rm -f sha256sums.txt
sha256sum tacview-splitter-win_x86-64.zip tacview-splitter-linux_x86-64.tar.gz >> sha256sums.txt
sha256sum --check sha256sums.txt
""")