mirror of
https://github.com/iluvcapra/wavinfo.git
synced 2026-10-02 09:47:27 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d7612f2fdf | |||
| e57bcc30c9 | |||
| 39c3868b7f | |||
| 739e574a10 | |||
| b3d5377f7b | |||
| f3547c8bc5 | |||
| 5d481e2960 | |||
| 94aec13d00 | |||
| 0060212083 | |||
| 90d15c78c0 | |||
| ae79a32d4a |
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
[](https://github.com/iluvcapra/wavinfo/actions/workflows/python-package.yml)
|
[](https://github.com/iluvcapra/wavinfo/actions/workflows/python-package.yml)
|
||||||
[](https://github.com/iluvcapra/wavinfo/actions/workflows/python-ruff.yml)
|
[](https://github.com/iluvcapra/wavinfo/actions/workflows/python-ruff.yml)
|
||||||
[](https://codecov.io/gh/iluvcapra/wavinfo)
|
|
||||||
|
|
||||||
# wavinfo
|
# wavinfo
|
||||||
|
|
||||||
|
|||||||
+1
-17
@@ -4,7 +4,7 @@ build-backend = "uv_build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "wavinfo"
|
name = "wavinfo"
|
||||||
version = "4.0.0"
|
version = "4.0.1"
|
||||||
description = "Probe WAVE files for all metadata"
|
description = "Probe WAVE files for all metadata"
|
||||||
authors = [{ name = "Jamie Hardt", email = "jamiehardt@me.com"}]
|
authors = [{ name = "Jamie Hardt", email = "jamiehardt@me.com"}]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
@@ -65,23 +65,7 @@ indent-width = 4
|
|||||||
[tool.ruff.lint]
|
[tool.ruff.lint]
|
||||||
fixable = ['ALL']
|
fixable = ['ALL']
|
||||||
ignore = [
|
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
|
|
||||||
'UP031', #Use format specifiers instead of percent format
|
'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)
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# [tool.pylint]
|
|
||||||
# max-line-length = 88
|
|
||||||
# disable = [
|
|
||||||
# "C0103", # (invalid-name)
|
|
||||||
# "C0114", # (missing-module-docstring)
|
|
||||||
# "C0115", # (missing-class-docstring)
|
|
||||||
# "C0116", # (missing-function-docstring)
|
|
||||||
# "R0903", # (too-few-public-methods)
|
|
||||||
# "R0913", # (too-many-arguments)
|
|
||||||
# "W0105", # (pointless-string-statement)
|
|
||||||
# ]
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import importlib.metadata
|
import importlib.metadata
|
||||||
import json
|
import json
|
||||||
@@ -8,7 +10,6 @@ from cmd import Cmd
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from optparse import OptionParser
|
from optparse import OptionParser
|
||||||
from shlex import split
|
from shlex import split
|
||||||
from typing import Dict, List, Union
|
|
||||||
|
|
||||||
from . import WavInfoReader
|
from . import WavInfoReader
|
||||||
|
|
||||||
@@ -30,8 +31,8 @@ class MissingDataError(RuntimeError):
|
|||||||
class MetaBrowser(Cmd):
|
class MetaBrowser(Cmd):
|
||||||
prompt = "(wavinfo) "
|
prompt = "(wavinfo) "
|
||||||
|
|
||||||
metadata: Union[List, Dict]
|
metadata: list | dict
|
||||||
path: List[str]
|
path: list[str]
|
||||||
|
|
||||||
def preloop(self) -> None:
|
def preloop(self) -> None:
|
||||||
self.path = []
|
self.path = []
|
||||||
@@ -39,7 +40,7 @@ class MetaBrowser(Cmd):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def cwd(self):
|
def cwd(self):
|
||||||
root: List | Dict = self.metadata
|
root: list | dict = self.metadata
|
||||||
for key in self.path:
|
for key in self.path:
|
||||||
if isinstance(root, list):
|
if isinstance(root, list):
|
||||||
root = root[int(key)]
|
root = root[int(key)]
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import struct
|
from __future__ import annotations
|
||||||
|
|
||||||
# from collections import namedtuple
|
import struct
|
||||||
from typing import Dict, NamedTuple
|
from typing import NamedTuple
|
||||||
|
|
||||||
from . import riff_parser
|
from . import riff_parser
|
||||||
|
|
||||||
|
|
||||||
class RF64Context(NamedTuple):
|
class RF64Context(NamedTuple):
|
||||||
sample_count: int
|
sample_count: int
|
||||||
bigchunk_table: Dict[str, int]
|
bigchunk_table: dict[str, int]
|
||||||
|
|
||||||
|
|
||||||
def parse_rf64(stream, signature=b"RF64") -> RF64Context:
|
def parse_rf64(stream, signature=b"RF64") -> RF64Context:
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# from optparse import Option
|
from __future__ import annotations
|
||||||
|
|
||||||
import struct
|
import struct
|
||||||
from typing import List, NamedTuple, Optional, Union
|
from typing import NamedTuple
|
||||||
|
|
||||||
from .rf64_parser import RF64Context, parse_rf64
|
from .rf64_parser import RF64Context, parse_rf64
|
||||||
|
|
||||||
@@ -13,14 +14,14 @@ class WavInfoEOFError(EOFError):
|
|||||||
|
|
||||||
class ListChunkDescriptor(NamedTuple):
|
class ListChunkDescriptor(NamedTuple):
|
||||||
signature: bytes
|
signature: bytes
|
||||||
children: List[Union["ChunkDescriptor", "ListChunkDescriptor"]]
|
children: list[ChunkDescriptor | ListChunkDescriptor]
|
||||||
|
|
||||||
|
|
||||||
class ChunkDescriptor(NamedTuple):
|
class ChunkDescriptor(NamedTuple):
|
||||||
ident: bytes
|
ident: bytes
|
||||||
start: int
|
start: int
|
||||||
length: int
|
length: int
|
||||||
rf64_context: Optional[RF64Context]
|
rf64_context: RF64Context | None
|
||||||
|
|
||||||
def read_data(self, from_stream) -> bytes:
|
def read_data(self, from_stream) -> bytes:
|
||||||
from_stream.seek(self.start)
|
from_stream.seek(self.start)
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
"""
|
"""
|
||||||
ADM Reader
|
ADM Reader
|
||||||
"""
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from struct import calcsize, unpack, unpack_from
|
from struct import calcsize, unpack, unpack_from
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from lxml import etree as ET
|
from lxml import etree as ET
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ class WavADMReader:
|
|||||||
Read the ADM `audioProgramme` data structure and some of its reference
|
Read the ADM `audioProgramme` data structure and some of its reference
|
||||||
properties.
|
properties.
|
||||||
"""
|
"""
|
||||||
ret_dict = dict()
|
ret_dict = {}
|
||||||
|
|
||||||
nsmap = self.axml.getroot().nsmap
|
nsmap = self.axml.getroot().nsmap
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ class WavADMReader:
|
|||||||
ret_dict["contents"] = []
|
ret_dict["contents"] = []
|
||||||
|
|
||||||
for content_ref in program.findall("audioContentIDRef", namespaces=nsmap):
|
for content_ref in program.findall("audioContentIDRef", namespaces=nsmap):
|
||||||
content_dict = dict()
|
content_dict = {}
|
||||||
content_dict["content_id"] = cid = content_ref.text
|
content_dict["content_id"] = cid = content_ref.text
|
||||||
content = afext.find(
|
content = afext.find(
|
||||||
"audioContent[@audioContentID='%s']" % cid, namespaces=nsmap
|
"audioContent[@audioContentID='%s']" % cid, namespaces=nsmap
|
||||||
@@ -80,7 +80,7 @@ class WavADMReader:
|
|||||||
content_dict["objects"] = []
|
content_dict["objects"] = []
|
||||||
|
|
||||||
for object_ref in content.findall("audioObjectIDRef", namespaces=nsmap):
|
for object_ref in content.findall("audioObjectIDRef", namespaces=nsmap):
|
||||||
object_dict = dict()
|
object_dict = {}
|
||||||
object_dict["object_id"] = oid = object_ref.text
|
object_dict["object_id"] = oid = object_ref.text
|
||||||
object = afext.find(
|
object = afext.find(
|
||||||
"audioObject[@audioObjectID='%s']" % oid, namespaces=nsmap
|
"audioObject[@audioObjectID='%s']" % oid, namespaces=nsmap
|
||||||
@@ -101,7 +101,7 @@ class WavADMReader:
|
|||||||
|
|
||||||
return ret_dict
|
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.
|
Information about a track in the WAV file.
|
||||||
|
|
||||||
@@ -206,7 +206,7 @@ class WavADMReader:
|
|||||||
rd.update(self.track_info(channel_uid_rec.track_index))
|
rd.update(self.track_info(channel_uid_rec.track_index))
|
||||||
return rd
|
return rd
|
||||||
|
|
||||||
return dict(
|
return {
|
||||||
channel_entries=list(map(lambda z: make_entry(z), self.channel_uids)),
|
'channel_entries': [make_entry(z) for z in self.channel_uids],
|
||||||
programme=self.programme(),
|
'programme': self.programme(),
|
||||||
)
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
# from .umid_parser import UMIDParser
|
# from .umid_parser import UMIDParser
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
|
|
||||||
class WavBextReader:
|
class WavBextReader:
|
||||||
@@ -58,22 +59,22 @@ class WavBextReader:
|
|||||||
|
|
||||||
#: SMPTE 330M UMID of this audio file, 64 bytes are allocated though
|
#: SMPTE 330M UMID of this audio file, 64 bytes are allocated though
|
||||||
#: the UMID may only be 32 bytes long.
|
#: the UMID may only be 32 bytes long.
|
||||||
self.umid: Optional[bytes] = None
|
self.umid: bytes | None = None
|
||||||
|
|
||||||
#: EBU R128 Integrated loudness, in LUFS.
|
#: EBU R128 Integrated loudness, in LUFS.
|
||||||
self.loudness_value: Optional[float] = None
|
self.loudness_value: float | None = None
|
||||||
|
|
||||||
#: EBU R128 Loudness range, in LUFS.
|
#: EBU R128 Loudness range, in LUFS.
|
||||||
self.loudness_range: Optional[float] = None
|
self.loudness_range: float | None = None
|
||||||
|
|
||||||
#: True peak level, in dBFS TP
|
#: 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
|
#: 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.
|
#: 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:
|
if self.version > 0:
|
||||||
self.umid = unpacked[7]
|
self.umid = unpacked[7]
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ IBM Corporation and Microsoft Corporation
|
|||||||
https://www.aelius.com/njh/wavemetatools/doc/riffmci.pdf
|
https://www.aelius.com/njh/wavemetatools/doc/riffmci.pdf
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from struct import calcsize, unpack
|
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
|
from .riff_parser import ChunkDescriptor
|
||||||
|
|
||||||
@@ -120,7 +122,7 @@ class CueEntry(NamedTuple):
|
|||||||
return calcsize(cls.Format)
|
return calcsize(cls.Format)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def read(cls, data: bytes) -> "CueEntry":
|
def read(cls, data: bytes) -> CueEntry:
|
||||||
assert len(data) == cls.format_size(), (
|
assert len(data) == cls.format_size(), (
|
||||||
f"cue data size incorrect, expected {calcsize(cls.Format)} "
|
f"cue data size incorrect, expected {calcsize(cls.Format)} "
|
||||||
"found {len(data)}"
|
"found {len(data)}"
|
||||||
@@ -176,13 +178,15 @@ class RangeLabel(NamedTuple):
|
|||||||
parsed = unpack(leader_struct_fmt, data[0 : calcsize(leader_struct_fmt)])
|
parsed = unpack(leader_struct_fmt, data[0 : calcsize(leader_struct_fmt)])
|
||||||
text_data = data[calcsize(leader_struct_fmt) :]
|
text_data = data[calcsize(leader_struct_fmt) :]
|
||||||
|
|
||||||
|
purpose_str = parsed[2].decode('ascii')
|
||||||
|
|
||||||
if data[6] != 0:
|
if data[6] != 0:
|
||||||
fallback_encoding = f"cp{data[6]}"
|
fallback_encoding = f"cp{data[6]}"
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
name=parsed[0],
|
name=parsed[0],
|
||||||
length=parsed[1],
|
length=parsed[1],
|
||||||
purpose=parsed[2],
|
purpose=purpose_str,
|
||||||
country=parsed[3],
|
country=parsed[3],
|
||||||
language=parsed[4],
|
language=parsed[4],
|
||||||
dialect=parsed[5],
|
dialect=parsed[5],
|
||||||
@@ -194,27 +198,27 @@ class RangeLabel(NamedTuple):
|
|||||||
@dataclass
|
@dataclass
|
||||||
class WavCuesReader:
|
class WavCuesReader:
|
||||||
#: Every ``cue`` entry in the file
|
#: Every ``cue`` entry in the file
|
||||||
cues: List[CueEntry]
|
cues: list[CueEntry]
|
||||||
|
|
||||||
#: Every ``labl`` in the file
|
#: Every ``labl`` in the file
|
||||||
labels: List[LabelEntry]
|
labels: list[LabelEntry]
|
||||||
|
|
||||||
#: Every ``ltxt`` in the file
|
#: Every ``ltxt`` in the file
|
||||||
ranges: List[RangeLabel]
|
ranges: list[RangeLabel]
|
||||||
|
|
||||||
#: Every ``note`` in the file
|
#: Every ``note`` in the file
|
||||||
notes: List[NoteEntry]
|
notes: list[NoteEntry]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def read_all(
|
def read_all(
|
||||||
cls,
|
cls,
|
||||||
f,
|
f,
|
||||||
cues: Optional[ChunkDescriptor],
|
cues: ChunkDescriptor | None,
|
||||||
labls: List[ChunkDescriptor],
|
labls: list[ChunkDescriptor],
|
||||||
ltxts: List[ChunkDescriptor],
|
ltxts: list[ChunkDescriptor],
|
||||||
notes: List[ChunkDescriptor],
|
notes: list[ChunkDescriptor],
|
||||||
fallback_encoding: str,
|
fallback_encoding: str,
|
||||||
) -> "WavCuesReader":
|
) -> WavCuesReader:
|
||||||
cue_list = []
|
cue_list = []
|
||||||
if cues is not None:
|
if cues is not None:
|
||||||
cues_data = cues.read_data(f)
|
cues_data = cues.read_data(f)
|
||||||
@@ -249,7 +253,7 @@ class WavCuesReader:
|
|||||||
cues=cue_list, labels=label_list, ranges=range_list, notes=note_list
|
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.
|
Iterate through each cue.
|
||||||
|
|
||||||
@@ -258,7 +262,7 @@ class WavCuesReader:
|
|||||||
for cue in self.cues:
|
for cue in self.cues:
|
||||||
yield (cue.name, cue.sample_offset)
|
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.
|
Get the label and note (extended comment) for a cue.
|
||||||
|
|
||||||
@@ -272,7 +276,7 @@ class WavCuesReader:
|
|||||||
note = next((n.text for n in self.notes if n.name == cue_ident), None)
|
note = next((n.text for n in self.notes if n.name == cue_ident), None)
|
||||||
return (label, note)
|
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.
|
Get the length of the time range for a cue, if it has one.
|
||||||
|
|
||||||
@@ -281,11 +285,11 @@ class WavCuesReader:
|
|||||||
"""
|
"""
|
||||||
return next((r.length for r in self.ranges if r.name == cue_ident), None)
|
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 = dict()
|
retval = {}
|
||||||
|
|
||||||
for n, t in self.each_cue():
|
for n, t in self.each_cue():
|
||||||
retval[n] = dict()
|
retval[n] = {}
|
||||||
retval[n]["frame"] = t
|
retval[n]["frame"] = t
|
||||||
label, note = self.label_and_note(n)
|
label, note = self.label_and_note(n)
|
||||||
r = self.range(n)
|
r = self.range(n)
|
||||||
|
|||||||
@@ -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
|
.. _EBU Tech 3285 Supplement 6: https://tech.ebu.ch/docs/tech/tech3285s6.pdf
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import asdict, dataclass
|
from dataclasses import asdict, dataclass
|
||||||
from enum import Enum, IntEnum
|
from enum import Enum, IntEnum
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from struct import unpack
|
from struct import unpack
|
||||||
from typing import Any, List, Tuple, Union
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
class SegmentType(IntEnum):
|
class SegmentType(IntEnum):
|
||||||
@@ -339,8 +341,10 @@ class DolbyDigitalPlusMetadata:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load(buffer: bytes):
|
def load(buffer: bytes):
|
||||||
assert len(buffer) == 96, "Dolby Digital Plus segment incorrect size, "
|
assert len(buffer) == 96, (
|
||||||
"expected 96 got %i" % len(buffer)
|
"Dolby Digital Plus segment incorrect size, "
|
||||||
|
"expected 96 got %i" % len(buffer)
|
||||||
|
)
|
||||||
|
|
||||||
def program_id(b) -> int:
|
def program_id(b) -> int:
|
||||||
return b
|
return b
|
||||||
@@ -409,7 +413,7 @@ class DolbyDigitalPlusMetadata:
|
|||||||
return DolbyDigitalPlusMetadata.RFCompressionProfile(b)
|
return DolbyDigitalPlusMetadata.RFCompressionProfile(b)
|
||||||
|
|
||||||
def dynrng1(b):
|
def dynrng1(b):
|
||||||
DolbyDigitalPlusMetadata.RFCompressionProfile(b)
|
return DolbyDigitalPlusMetadata.RFCompressionProfile(b)
|
||||||
|
|
||||||
def ddplus_reserved3(_):
|
def ddplus_reserved3(_):
|
||||||
pass
|
pass
|
||||||
@@ -504,7 +508,7 @@ class DolbyAtmosMetadata:
|
|||||||
NOT_INDICATED = 0x04
|
NOT_INDICATED = 0x04
|
||||||
|
|
||||||
tool_name: str
|
tool_name: str
|
||||||
tool_version: Tuple[int, int, int]
|
tool_version: tuple[int, int, int]
|
||||||
warp_mode: WarpMode
|
warp_mode: WarpMode
|
||||||
|
|
||||||
SEGMENT_LENGTH = 248
|
SEGMENT_LENGTH = 248
|
||||||
@@ -555,8 +559,8 @@ class DolbyAtmosSupplementalMetadata:
|
|||||||
NOT_INDICATED = 0x04
|
NOT_INDICATED = 0x04
|
||||||
|
|
||||||
object_count: int
|
object_count: int
|
||||||
render_modes: List["DolbyAtmosSupplementalMetadata.BinauralRenderMode"]
|
render_modes: list[DolbyAtmosSupplementalMetadata.BinauralRenderMode]
|
||||||
trim_modes: List[int]
|
trim_modes: list[int]
|
||||||
|
|
||||||
MAGIC = 0xF8726FBD
|
MAGIC = 0xF8726FBD
|
||||||
TRIM_CONFIG_COUNT = 9
|
TRIM_CONFIG_COUNT = 9
|
||||||
@@ -603,9 +607,9 @@ class WavDolbyMetadataReader:
|
|||||||
#: indicating if the segment's checksum was valid, and the
|
#: indicating if the segment's checksum was valid, and the
|
||||||
#: segment's parsed dataclass (or a `bytes` array if it was
|
#: segment's parsed dataclass (or a `bytes` array if it was
|
||||||
#: not recognized).
|
#: 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
|
@staticmethod
|
||||||
def segment_checksum(bs: bytes, size: int):
|
def segment_checksum(bs: bytes, size: int):
|
||||||
@@ -654,7 +658,7 @@ class WavDolbyMetadataReader:
|
|||||||
(stype, checksum == expected_checksum, segment)
|
(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.
|
Every valid Dolby Digital Plus metadata segment in the file.
|
||||||
"""
|
"""
|
||||||
@@ -664,7 +668,7 @@ class WavDolbyMetadataReader:
|
|||||||
if x[0] == SegmentType.DolbyDigitalPlus and x[1]
|
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.
|
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]
|
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.
|
Every valid Dolby Atmos Supplemental metadata segment in the file.
|
||||||
"""
|
"""
|
||||||
@@ -683,8 +687,8 @@ class WavDolbyMetadataReader:
|
|||||||
]
|
]
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
def to_dict(self) -> dict:
|
||||||
ddp = map(lambda x: asdict(x), self.dolby_digital_plus())
|
ddp = [asdict(x) for x in self.dolby_digital_plus()]
|
||||||
atmos = map(lambda x: asdict(x), self.dolby_atmos())
|
atmos = [asdict(x) for x in self.dolby_atmos()]
|
||||||
# atmos_sup = map(lambda x: asdict(x), self.dolby_atmos_supplemental())
|
# atmos_sup = map(lambda x: asdict(x), self.dolby_atmos_supplemental())
|
||||||
|
|
||||||
return dict(dolby_digital_plus=list(ddp), dolby_atmos=list(atmos))
|
return {"dolby_digital_plus": ddp, "dolby_atmos": atmos}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import Optional
|
from __future__ import annotations
|
||||||
|
|
||||||
from .riff_parser import ListChunkDescriptor, parse_chunk
|
from .riff_parser import ListChunkDescriptor, parse_chunk
|
||||||
|
|
||||||
@@ -22,40 +22,40 @@ class WavInfoChunkReader:
|
|||||||
)
|
)
|
||||||
|
|
||||||
#: 'ICOP' Copyright
|
#: 'ICOP' Copyright
|
||||||
self.copyright: Optional[str] = self._get_field(f, b"ICOP")
|
self.copyright: str | None = self._get_field(f, b"ICOP")
|
||||||
#: 'IPRD' Product
|
#: 'IPRD' Product
|
||||||
self.product: Optional[str] = self._get_field(f, b"IPRD")
|
self.product: str | None = self._get_field(f, b"IPRD")
|
||||||
self.album: Optional[str] = self.product
|
self.album: str | None = self.product
|
||||||
#: 'IGNR' Genre
|
#: 'IGNR' Genre
|
||||||
self.genre: Optional[str] = self._get_field(f, b"IGNR")
|
self.genre: str | None = self._get_field(f, b"IGNR")
|
||||||
#: 'ISBJ' Subject
|
#: '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
|
#: '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
|
#: '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
|
#: '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
|
#: '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
|
#: 'IENG' Engineer
|
||||||
self.engineer: Optional[str] = self._get_field(f, b"IENG")
|
self.engineer: str | None = self._get_field(f, b"IENG")
|
||||||
#: 'ITCH' Technician
|
#: '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
|
#: '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
|
#: '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
|
#: 'ISRC' Source
|
||||||
self.source: Optional[str] = self._get_field(f, b"ISRC")
|
self.source: str | None = self._get_field(f, b"ISRC")
|
||||||
#: 'TAPE' Tape
|
#: '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
|
#: '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
|
#: '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(
|
search = next(
|
||||||
(
|
(
|
||||||
(chunk.start, chunk.length)
|
(chunk.start, chunk.length)
|
||||||
|
|||||||
+3
-5
@@ -15,8 +15,6 @@ class TestRf64(TestCase):
|
|||||||
|
|
||||||
def test_open(self):
|
def test_open(self):
|
||||||
for path in glob("tests/test_files/rf64/*.wav.gz"):
|
for path in glob("tests/test_files/rf64/*.wav.gz"):
|
||||||
gz = gzip.open(path)
|
with gzip.open(path) as gz:
|
||||||
wav_info = wavinfo.WavInfoReader(gz)
|
wav_info = wavinfo.WavInfoReader(gz)
|
||||||
|
self.assertIsNotNone(wav_info)
|
||||||
self.assertIsNotNone(wav_info)
|
|
||||||
# self.assertIsNotNone(wav_info.bext)
|
|
||||||
|
|||||||
+1
-2
@@ -1,14 +1,13 @@
|
|||||||
import json
|
import json
|
||||||
import os.path
|
import os.path
|
||||||
import subprocess
|
import subprocess
|
||||||
from subprocess import PIPE
|
|
||||||
|
|
||||||
FFPROBE = "ffprobe"
|
FFPROBE = "ffprobe"
|
||||||
|
|
||||||
|
|
||||||
def ffprobe(path):
|
def ffprobe(path):
|
||||||
arguments = [FFPROBE, "-of", "json", "-show_format", "-show_streams", path]
|
arguments = [FFPROBE, "-of", "json", "-show_format", "-show_streams", path]
|
||||||
process = subprocess.run(arguments, stdin=None, stdout=PIPE, stderr=PIPE, check=True)
|
process = subprocess.run(arguments, check=True, capture_output=True)
|
||||||
if process.returncode == 0:
|
if process.returncode == 0:
|
||||||
output_str = process.stdout.decode("utf-8")
|
output_str = process.stdout.decode("utf-8")
|
||||||
return json.loads(output_str)
|
return json.loads(output_str)
|
||||||
|
|||||||
Reference in New Issue
Block a user