diff --git a/pyproject.toml b/pyproject.toml index 1c128da..65fef01 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,9 +10,9 @@ dependencies = [ "matplotlib==3.9.3", "plotly==5.24.1", "pyperclip==1.9.0", - "watchdog==5.0.3", + "watchdog==6.0.0", "requests==2.32.3", - "nicegui==2.7.0", + "nicegui==2.8.1", "librosa==0.10.2.post1", "soundfile==0.12.1", "pywin32==308; sys_platform=='win32'", diff --git a/src/synth_mapping_helper/__init__.py b/src/synth_mapping_helper/__init__.py index 71b97f3..b723599 100644 --- a/src/synth_mapping_helper/__init__.py +++ b/src/synth_mapping_helper/__init__.py @@ -1,4 +1,4 @@ -__version__ = "1.5.8" +__version__ = "1.5.9" from . import movement from . import pattern_generation diff --git a/src/synth_mapping_helper/synth_format.py b/src/synth_mapping_helper/synth_format.py index 546e0dd..11a3022 100644 --- a/src/synth_mapping_helper/synth_format.py +++ b/src/synth_mapping_helper/synth_format.py @@ -11,8 +11,9 @@ import re import time from typing import Any, Generator, Literal, Union -import zipfile +import struct import sys +import zipfile import numpy as np import pyperclip @@ -568,6 +569,21 @@ def encode(self, encoding: str = "utf-8", errors: str = "strict") -> bytes: raise UnicodeEncodeError(encoding, self, 0, 0, "dummy error to trick ZipFile._open_to_write") return super().encode(encoding=encoding, errors=errors) +def encode_extra_pkware_win32(timestamp: datetime.datetime) -> bytes: + # https://mdfs.net/Docs/Comp/Archiving/Zip/ExtraField -> 0x000A + # encode NTFS timestamp + ntfs_epoch = datetime.datetime(1601,1,1, 0,0,0,0, tzinfo=datetime.timezone.utc) + ts_bytes = int((timestamp - ntfs_epoch).total_seconds()*1e7).to_bytes(8, "little") + + return struct.pack( + " None: "Tags": [], # ? "BeatConverted": False, # ? "BeatModified": False, # ? - "ModifiedTime": now.timestamp(), + "ModifiedTime": int(now.timestamp()), "Explicit": self.meta.explicit, } @@ -829,7 +845,7 @@ def save_as(self, output_file: Union[Path, BytesIO]) -> None: meta_json = json.dumps({ "name": self.meta.name, "artist": self.meta.artist, - "duration": f"{self.audio.duration//60:.0f}:{self.audio.duration%60:02.0f}", + "duration": f"{self.audio.duration//60:02.0f}:{self.audio.duration%60:02.0f}", "coverImage": self.meta.cover_name, "audioFile": safe_audio_name, "supportedDifficulties": [ @@ -837,7 +853,7 @@ def save_as(self, output_file: Union[Path, BytesIO]) -> None: ], "bpm": self.bpm, "mapper": self.meta.mapper, - }, indent=2, allow_nan=False) + }, indent=4, allow_nan=False) with zipfile.ZipFile(out_buffer, "w") as outzip: def make_fileinfo(filename: str) -> zipfile.ZipInfo: @@ -846,8 +862,8 @@ def make_fileinfo(filename: str) -> zipfile.ZipInfo: # fake various file headers info.create_system = 0 info.create_version = zipfile.ZIP64_VERSION - # editor files also contain NTFS timestamps, see "PKWARE Win95/WinNT Extra Field (0x000a)"" at - # https://mdfs.net/Docs/Comp/Archiving/Zip/ExtraField + # editor files also contain NTFS timestamps + info.extra = encode_extra_pkware_win32(now) # trick header encoder info.external_attr = _TrueInt(0) @@ -857,7 +873,7 @@ def make_fileinfo(filename: str) -> zipfile.ZipInfo: outzip.writestr(make_fileinfo(BEATMAP_JSON_FILE), codecs.BOM_UTF8 + beatmap_json.encode("utf-8").replace(b"\n", b"\r\n")) outzip.writestr(make_fileinfo(safe_audio_name), self.audio.raw_data) outzip.writestr(make_fileinfo(self.meta.cover_name), self.meta.cover_data) - outzip.writestr(make_fileinfo(METADATA_JSON_FILE), codecs.BOM_UTF8 + meta_json.encode("utf-8").replace(b"\n", b"\r\n")) + outzip.writestr(make_fileinfo(METADATA_JSON_FILE), codecs.BOM_UTF16_LE + meta_json.encode("utf-16-le")) # write output zip if isinstance(output_file, BytesIO): output_file.seek(0)