diff --git a/pyproject.toml b/pyproject.toml index 0551082..35c9c7a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,12 +65,8 @@ indent-width = 4 [tool.ruff.lint] fixable = ['ALL'] ignore = [ - 'FA100', #Add `from __future__ import annotations` to simplify `typing.Optional` - # 'UP022', # Prefer `capture_output` over sending `stdout` and `stderr` to `PIPE` - # 'SIM115', # Use a context manager for opening files + # 'FA100', #Add `from __future__ import annotations` to simplify `typing.Optional` 'UP031', #Use format specifiers instead of percent format - # 'C408', # Unnecessary `dict()` call (rewrite as a literal) - # 'C417' # Unnecessary `map()` usage (rewrite using a generator expression) ] diff --git a/src/wavinfo/__main__.py b/src/wavinfo/__main__.py index 43001a4..2a1f3d8 100644 --- a/src/wavinfo/__main__.py +++ b/src/wavinfo/__main__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import datetime import importlib.metadata import json @@ -8,7 +10,6 @@ from cmd import Cmd from enum import Enum from optparse import OptionParser from shlex import split -from typing import Dict, List, Union from . import WavInfoReader @@ -30,8 +31,8 @@ class MissingDataError(RuntimeError): class MetaBrowser(Cmd): prompt = "(wavinfo) " - metadata: Union[List, Dict] - path: List[str] + metadata: list | dict + path: list[str] def preloop(self) -> None: self.path = [] @@ -39,7 +40,7 @@ class MetaBrowser(Cmd): @property def cwd(self): - root: List | Dict = self.metadata + root: list | dict = self.metadata for key in self.path: if isinstance(root, list): root = root[int(key)] diff --git a/src/wavinfo/rf64_parser.py b/src/wavinfo/rf64_parser.py index 92d3937..c8d249e 100644 --- a/src/wavinfo/rf64_parser.py +++ b/src/wavinfo/rf64_parser.py @@ -1,14 +1,14 @@ -import struct +from __future__ import annotations -# from collections import namedtuple -from typing import Dict, NamedTuple +import struct +from typing import NamedTuple from . import riff_parser class RF64Context(NamedTuple): sample_count: int - bigchunk_table: Dict[str, int] + bigchunk_table: dict[str, int] def parse_rf64(stream, signature=b"RF64") -> RF64Context: diff --git a/src/wavinfo/riff_parser.py b/src/wavinfo/riff_parser.py index ab317b3..08e5a3d 100644 --- a/src/wavinfo/riff_parser.py +++ b/src/wavinfo/riff_parser.py @@ -1,6 +1,7 @@ -# from optparse import Option +from __future__ import annotations + import struct -from typing import List, NamedTuple, Optional, Union +from typing import NamedTuple from .rf64_parser import RF64Context, parse_rf64 @@ -13,14 +14,14 @@ class WavInfoEOFError(EOFError): class ListChunkDescriptor(NamedTuple): signature: bytes - children: List[Union["ChunkDescriptor", "ListChunkDescriptor"]] + children: list[ChunkDescriptor | ListChunkDescriptor] class ChunkDescriptor(NamedTuple): ident: bytes start: int length: int - rf64_context: Optional[RF64Context] + rf64_context: RF64Context | None def read_data(self, from_stream) -> bytes: from_stream.seek(self.start) diff --git a/src/wavinfo/wave_adm_reader.py b/src/wavinfo/wave_adm_reader.py index 7de36b4..aa3d3d4 100644 --- a/src/wavinfo/wave_adm_reader.py +++ b/src/wavinfo/wave_adm_reader.py @@ -1,11 +1,11 @@ """ ADM Reader """ +from __future__ import annotations from collections import namedtuple from io import BytesIO from struct import calcsize, unpack, unpack_from -from typing import Optional from lxml import etree as ET @@ -101,7 +101,7 @@ class WavADMReader: return ret_dict - def track_info(self, index) -> Optional[dict]: + def track_info(self, index) -> dict | None: """ Information about a track in the WAV file. diff --git a/src/wavinfo/wave_bext_reader.py b/src/wavinfo/wave_bext_reader.py index 56e7112..7520b07 100644 --- a/src/wavinfo/wave_bext_reader.py +++ b/src/wavinfo/wave_bext_reader.py @@ -1,7 +1,8 @@ +from __future__ import annotations + import struct # from .umid_parser import UMIDParser -from typing import Optional class WavBextReader: @@ -58,22 +59,22 @@ class WavBextReader: #: SMPTE 330M UMID of this audio file, 64 bytes are allocated though #: the UMID may only be 32 bytes long. - self.umid: Optional[bytes] = None + self.umid: bytes | None = None #: EBU R128 Integrated loudness, in LUFS. - self.loudness_value: Optional[float] = None + self.loudness_value: float | None = None #: EBU R128 Loudness range, in LUFS. - self.loudness_range: Optional[float] = None + self.loudness_range: float | None = None #: True peak level, in dBFS TP - self.max_true_peak: Optional[float] = None + self.max_true_peak: float | None = None #: EBU R128 Maximum momentary loudness, in LUFS - self.max_momentary_loudness: Optional[float] = None + self.max_momentary_loudness: float | None = None #: EBU R128 Maximum short-term loudness, in LUFS. - self.max_shortterm_loudness: Optional[float] = None + self.max_shortterm_loudness: float | None = None if self.version > 0: self.umid = unpacked[7] diff --git a/src/wavinfo/wave_cues_reader.py b/src/wavinfo/wave_cues_reader.py index 317f8b5..4938a39 100644 --- a/src/wavinfo/wave_cues_reader.py +++ b/src/wavinfo/wave_cues_reader.py @@ -8,9 +8,11 @@ IBM Corporation and Microsoft Corporation https://www.aelius.com/njh/wavemetatools/doc/riffmci.pdf """ +from __future__ import annotations + from dataclasses import dataclass from struct import calcsize, unpack -from typing import Any, Dict, Generator, List, NamedTuple, Optional, Tuple +from typing import Any, Generator, NamedTuple from .riff_parser import ChunkDescriptor @@ -120,7 +122,7 @@ class CueEntry(NamedTuple): return calcsize(cls.Format) @classmethod - def read(cls, data: bytes) -> "CueEntry": + def read(cls, data: bytes) -> CueEntry: assert len(data) == cls.format_size(), ( f"cue data size incorrect, expected {calcsize(cls.Format)} " "found {len(data)}" @@ -196,27 +198,27 @@ class RangeLabel(NamedTuple): @dataclass class WavCuesReader: #: Every ``cue`` entry in the file - cues: List[CueEntry] + cues: list[CueEntry] #: Every ``labl`` in the file - labels: List[LabelEntry] + labels: list[LabelEntry] #: Every ``ltxt`` in the file - ranges: List[RangeLabel] + ranges: list[RangeLabel] #: Every ``note`` in the file - notes: List[NoteEntry] + notes: list[NoteEntry] @classmethod def read_all( cls, f, - cues: Optional[ChunkDescriptor], - labls: List[ChunkDescriptor], - ltxts: List[ChunkDescriptor], - notes: List[ChunkDescriptor], + cues: ChunkDescriptor | None, + labls: list[ChunkDescriptor], + ltxts: list[ChunkDescriptor], + notes: list[ChunkDescriptor], fallback_encoding: str, - ) -> "WavCuesReader": + ) -> WavCuesReader: cue_list = [] if cues is not None: cues_data = cues.read_data(f) @@ -251,7 +253,7 @@ class WavCuesReader: cues=cue_list, labels=label_list, ranges=range_list, notes=note_list ) - def each_cue(self) -> Generator[Tuple[int, int], None, None]: + def each_cue(self) -> Generator[tuple[int, int], None, None]: """ Iterate through each cue. @@ -260,7 +262,7 @@ class WavCuesReader: for cue in self.cues: yield (cue.name, cue.sample_offset) - def label_and_note(self, cue_ident: int) -> Tuple[Optional[str], Optional[str]]: + def label_and_note(self, cue_ident: int) -> tuple[str | None, str | None]: """ Get the label and note (extended comment) for a cue. @@ -274,7 +276,7 @@ class WavCuesReader: note = next((n.text for n in self.notes if n.name == cue_ident), None) return (label, note) - def range(self, cue_ident: int) -> Optional[int]: + def range(self, cue_ident: int) -> int | None: """ Get the length of the time range for a cue, if it has one. @@ -283,7 +285,7 @@ class WavCuesReader: """ return next((r.length for r in self.ranges if r.name == cue_ident), None) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: retval = {} for n, t in self.each_cue(): diff --git a/src/wavinfo/wave_dbmd_reader.py b/src/wavinfo/wave_dbmd_reader.py index f9ff8d5..2cb473a 100644 --- a/src/wavinfo/wave_dbmd_reader.py +++ b/src/wavinfo/wave_dbmd_reader.py @@ -7,11 +7,13 @@ Unless otherwise stated, all ยง references here are to .. _EBU Tech 3285 Supplement 6: https://tech.ebu.ch/docs/tech/tech3285s6.pdf """ +from __future__ import annotations + from dataclasses import asdict, dataclass from enum import Enum, IntEnum from io import BytesIO from struct import unpack -from typing import Any, List, Tuple, Union +from typing import Any class SegmentType(IntEnum): @@ -339,8 +341,10 @@ class DolbyDigitalPlusMetadata: @staticmethod def load(buffer: bytes): - assert len(buffer) == 96, "Dolby Digital Plus segment incorrect size, " - "expected 96 got %i" % len(buffer) + assert len(buffer) == 96, ( + "Dolby Digital Plus segment incorrect size, " + "expected 96 got %i" % len(buffer) + ) def program_id(b) -> int: return b @@ -409,7 +413,7 @@ class DolbyDigitalPlusMetadata: return DolbyDigitalPlusMetadata.RFCompressionProfile(b) def dynrng1(b): - DolbyDigitalPlusMetadata.RFCompressionProfile(b) + return DolbyDigitalPlusMetadata.RFCompressionProfile(b) def ddplus_reserved3(_): pass @@ -504,7 +508,7 @@ class DolbyAtmosMetadata: NOT_INDICATED = 0x04 tool_name: str - tool_version: Tuple[int, int, int] + tool_version: tuple[int, int, int] warp_mode: WarpMode SEGMENT_LENGTH = 248 @@ -555,8 +559,8 @@ class DolbyAtmosSupplementalMetadata: NOT_INDICATED = 0x04 object_count: int - render_modes: List["DolbyAtmosSupplementalMetadata.BinauralRenderMode"] - trim_modes: List[int] + render_modes: list[DolbyAtmosSupplementalMetadata.BinauralRenderMode] + trim_modes: list[int] MAGIC = 0xF8726FBD TRIM_CONFIG_COUNT = 9 @@ -603,9 +607,9 @@ class WavDolbyMetadataReader: #: indicating if the segment's checksum was valid, and the #: segment's parsed dataclass (or a `bytes` array if it was #: not recognized). - segment_list: List[Tuple[Union[SegmentType, int], bool, Any]] + segment_list: list[tuple[SegmentType | int, bool, Any]] - version: Tuple[int, int, int, int] + version: tuple[int, int, int, int] @staticmethod def segment_checksum(bs: bytes, size: int): @@ -654,7 +658,7 @@ class WavDolbyMetadataReader: (stype, checksum == expected_checksum, segment) ) - def dolby_digital_plus(self) -> List[DolbyDigitalPlusMetadata]: + def dolby_digital_plus(self) -> list[DolbyDigitalPlusMetadata]: """ Every valid Dolby Digital Plus metadata segment in the file. """ @@ -664,7 +668,7 @@ class WavDolbyMetadataReader: if x[0] == SegmentType.DolbyDigitalPlus and x[1] ] - def dolby_atmos(self) -> List[DolbyAtmosMetadata]: + def dolby_atmos(self) -> list[DolbyAtmosMetadata]: """ Every valid Dolby Atmos metadata segment in the file. """ @@ -672,7 +676,7 @@ class WavDolbyMetadataReader: x[2] for x in self.segment_list if x[0] == SegmentType.DolbyAtmos and x[1] ] - def dolby_atmos_supplemental(self) -> List[DolbyAtmosSupplementalMetadata]: + def dolby_atmos_supplemental(self) -> list[DolbyAtmosSupplementalMetadata]: """ Every valid Dolby Atmos Supplemental metadata segment in the file. """ @@ -687,4 +691,4 @@ class WavDolbyMetadataReader: atmos = [asdict(x) for x in self.dolby_atmos()] # atmos_sup = map(lambda x: asdict(x), self.dolby_atmos_supplemental()) - return {'dolby_digital_plus': ddp, 'dolby_atmos': atmos} + return {"dolby_digital_plus": ddp, "dolby_atmos": atmos} diff --git a/src/wavinfo/wave_info_reader.py b/src/wavinfo/wave_info_reader.py index 9a9679d..b8a22ee 100644 --- a/src/wavinfo/wave_info_reader.py +++ b/src/wavinfo/wave_info_reader.py @@ -1,4 +1,4 @@ -from typing import Optional +from __future__ import annotations from .riff_parser import ListChunkDescriptor, parse_chunk @@ -22,40 +22,40 @@ class WavInfoChunkReader: ) #: 'ICOP' Copyright - self.copyright: Optional[str] = self._get_field(f, b"ICOP") + self.copyright: str | None = self._get_field(f, b"ICOP") #: 'IPRD' Product - self.product: Optional[str] = self._get_field(f, b"IPRD") - self.album: Optional[str] = self.product + self.product: str | None = self._get_field(f, b"IPRD") + self.album: str | None = self.product #: 'IGNR' Genre - self.genre: Optional[str] = self._get_field(f, b"IGNR") + self.genre: str | None = self._get_field(f, b"IGNR") #: 'ISBJ' Subject - self.subject: Optional[str] = self._get_field(f, b"ISBJ") + self.subject: str | None = self._get_field(f, b"ISBJ") #: 'IART' Artist, composer, author - self.artist: Optional[str] = self._get_field(f, b"IART") + self.artist: str | None = self._get_field(f, b"IART") #: 'ICMT' Comment - self.comment: Optional[str] = self._get_field(f, b"ICMT") + self.comment: str | None = self._get_field(f, b"ICMT") #: 'ISFT' Software, encoding application - self.software: Optional[str] = self._get_field(f, b"ISFT") + self.software: str | None = self._get_field(f, b"ISFT") #: 'ICRD' Created date - self.created_date: Optional[str] = self._get_field(f, b"ICRD") + self.created_date: str | None = self._get_field(f, b"ICRD") #: 'IENG' Engineer - self.engineer: Optional[str] = self._get_field(f, b"IENG") + self.engineer: str | None = self._get_field(f, b"IENG") #: 'ITCH' Technician - self.technician: Optional[str] = self._get_field(f, b"ITCH") + self.technician: str | None = self._get_field(f, b"ITCH") #: 'IKEY' Keywords, keyword list - self.keywords: Optional[str] = self._get_field(f, b"IKEY") + self.keywords: str | None = self._get_field(f, b"IKEY") #: 'INAM' Name, title - self.title: Optional[str] = self._get_field(f, b"INAM") + self.title: str | None = self._get_field(f, b"INAM") #: 'ISRC' Source - self.source: Optional[str] = self._get_field(f, b"ISRC") + self.source: str | None = self._get_field(f, b"ISRC") #: 'TAPE' Tape - self.tape: Optional[str] = self._get_field(f, b"TAPE") + self.tape: str | None = self._get_field(f, b"TAPE") #: 'IARL' Archival Location - self.archival_location: Optional[str] = self._get_field(f, b"IARL") + self.archival_location: str | None = self._get_field(f, b"IARL") #: 'ICSM' Commissioned - self.commissioned: Optional[str] = self._get_field(f, b"ICMS") + self.commissioned: str | None = self._get_field(f, b"ICMS") - def _get_field(self, f, field_ident) -> Optional[str]: + def _get_field(self, f, field_ident) -> str | None: search = next( ( (chunk.start, chunk.length)