mirror of
https://github.com/iluvcapra/wavinfo.git
synced 2025-12-31 17:00:41 +00:00
Compare commits
12 Commits
feature-sm
...
v3.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94563f69a9 | ||
|
|
2830cb87a4 | ||
|
|
1c8581ff35 | ||
|
|
1d499d9741 | ||
|
|
8cabf948ff | ||
|
|
c13b07e4a3 | ||
|
|
ac37c14b3d | ||
|
|
36e4a02ab8 | ||
|
|
ffc0c48af7 | ||
|
|
206962b218 | ||
|
|
d560e5a9f0 | ||
|
|
98ca1ec462 |
@@ -6,89 +6,135 @@ from the command line and output metadata to stdout.
|
|||||||
|
|
||||||
.. code-block:: shell
|
.. code-block:: shell
|
||||||
|
|
||||||
$ wavinfo [--ixml | --adm] INFILE +
|
$ wavinfo [[-i] | [--ixml | --adm]] INFILE +
|
||||||
|
|
||||||
By default, `wavinfo` will output a JSON dictionary for each file argument.
|
|
||||||
|
|
||||||
|
|
||||||
Options
|
Options
|
||||||
-------
|
-------
|
||||||
|
|
||||||
Two option flags will change the behavior of the command:
|
By default, `wavinfo` will output a JSON dictionary for each file argument.
|
||||||
|
|
||||||
|
``-i``
|
||||||
|
`wavinfo` will run in `interactive mode`_.
|
||||||
|
|
||||||
|
Two option flags will change the behavior of the command in non-interactive
|
||||||
|
mode:
|
||||||
|
|
||||||
``--ixml``
|
``--ixml``
|
||||||
The *\-\-ixml* flag will cause `wavinfo` to output the iXML metadata payload
|
The *\-\-ixml* flag will cause `wavinfo` to output the iXML metadata
|
||||||
of each input wave file, or will emit an error message to stderr if iXML
|
payload of each input wave file, or will emit an error message to stderr if
|
||||||
metadata is not present.
|
iXML metadata is not present.
|
||||||
|
|
||||||
``--adm``
|
``--adm``
|
||||||
The *\-\-adm* flag will cause `wavinfo` to output the ADM XML metadata
|
The *\-\-adm* flag will cause `wavinfo` to output the ADM XML metadata
|
||||||
payload of each input wave file, or will emit an error message to stderr if
|
payload of each input wave file, or will emit an error message to stderr if
|
||||||
ADM XML metadata is not present.
|
ADM XML metadata is not present.
|
||||||
|
|
||||||
These options are mutually-exclusive, with `\-\-adm` taking precedence.
|
These options are mutually-exclusive, with `\-\-adm` taking precedence. The
|
||||||
|
``--ixml`` and ``--adm`` flags futher take precedence over ``-i``.
|
||||||
|
|
||||||
|
|
||||||
|
Interactive Mode
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
In interactive mode, `wavinfo` will present a command prompt which allows you
|
||||||
|
to query the files provided on the command line and explore the metadata tree
|
||||||
|
interactively. Each file on the command line is scanned and presented as a
|
||||||
|
tree of metadata records.
|
||||||
|
|
||||||
|
Commands include:
|
||||||
|
|
||||||
|
``ls``
|
||||||
|
List the available metadata keys at the current level.
|
||||||
|
|
||||||
|
``cd``
|
||||||
|
Traverse to a metadata key in the current level (or enter `..` to go up
|
||||||
|
to the prevvious level).
|
||||||
|
|
||||||
|
``bye``
|
||||||
|
Exit to the shell.
|
||||||
|
|
||||||
|
Type `help` or `?` at the prompt to get a full list of commands.
|
||||||
|
|
||||||
|
|
||||||
Example Output
|
Example Output
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
|
.. attention::
|
||||||
|
|
||||||
|
Metadata fields containing binary data, such as the Broadcast-WAV UMID, will
|
||||||
|
be included in the JSON output as a base-64 encoded string, preceded by the
|
||||||
|
marker "base64:".
|
||||||
|
|
||||||
.. code-block:: javascript
|
.. code-block:: javascript
|
||||||
|
|
||||||
{
|
{
|
||||||
"filename": "tests/test_files/sounddevices/A101_1.WAV",
|
"filename": "../tests/test_files/nuendo/wavinfo Test Project - Audio - 1OA.wav",
|
||||||
"run_date": "2022-11-26T17:56:38.342935",
|
"run_date": "2024-11-25T10:26:11.280053",
|
||||||
"application": "wavinfo 2.1.0",
|
"application": "wavinfo 3.0.0",
|
||||||
"scopes": {
|
"scopes": {
|
||||||
"fmt": {
|
"fmt": {
|
||||||
"audio_format": 1,
|
"audio_format": 65534,
|
||||||
"channel_count": 2,
|
"channel_count": 4,
|
||||||
"sample_rate": 48000,
|
"sample_rate": 48000,
|
||||||
"byte_rate": 288000,
|
"byte_rate": 576000,
|
||||||
"block_align": 6,
|
"block_align": 12,
|
||||||
"bits_per_sample": 24
|
"bits_per_sample": 24
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"byte_count": 576000,
|
||||||
|
"frame_count": 48000
|
||||||
|
},
|
||||||
|
"ixml": {
|
||||||
|
"track_list": [
|
||||||
|
{
|
||||||
|
"channel_index": "1",
|
||||||
|
"interleave_index": "1",
|
||||||
|
"name": "",
|
||||||
|
"function": "ACN0-FOA"
|
||||||
},
|
},
|
||||||
"data": {
|
{
|
||||||
"byte_count": 1441434,
|
"channel_index": "2",
|
||||||
"frame_count": 240239
|
"interleave_index": "2",
|
||||||
|
"name": "",
|
||||||
|
"function": "ACN1-FOA"
|
||||||
},
|
},
|
||||||
"ixml": {
|
{
|
||||||
"track_list": [
|
"channel_index": "3",
|
||||||
{
|
"interleave_index": "3",
|
||||||
"channel_index": "1",
|
"name": "",
|
||||||
"interleave_index": "1",
|
"function": "ACN2-FOA"
|
||||||
"name": "MKH516 A",
|
|
||||||
"function": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"channel_index": "2",
|
|
||||||
"interleave_index": "2",
|
|
||||||
"name": "Boom",
|
|
||||||
"function": ""
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"project": "BMH",
|
|
||||||
"scene": "A101",
|
|
||||||
"take": "1",
|
|
||||||
"tape": "18Y12M31",
|
|
||||||
"family_uid": "USSDVGR1112089007124001008206300",
|
|
||||||
"family_name": null
|
|
||||||
},
|
},
|
||||||
"bext": {
|
{
|
||||||
"description": "sSPEED=023.976-ND\r\nsTAKE=1\r\nsUBITS=$12311801\r\nsSWVER=2.67\r\nsPROJECT=BMH\r\nsSCENE=A101\r\nsFILENAME=A101_1.WAV\r\nsTAPE=18Y12M31\r\nsTRK1=MKH516 A\r\nsTRK2=Boom\r\nsNOTE=\r\n",
|
"channel_index": "4",
|
||||||
"originator": "Sound Dev: 702T S#GR1112089007",
|
"interleave_index": "4",
|
||||||
"originator_ref": "USSDVGR1112089007124001008206301",
|
"name": "",
|
||||||
"originator_date": "2018-12-31",
|
"function": "ACN3-FOA"
|
||||||
"originator_time": "12:40:00",
|
|
||||||
"time_reference": 2190940753,
|
|
||||||
"version": 1,
|
|
||||||
"umid": "0000000000000000000000000000000000000000000000000000000000000000",
|
|
||||||
"coding_history": "A=PCM,F=48000,W=24,M=stereo,R=48000,T=2 Ch\r\n",
|
|
||||||
"loudness_value": null,
|
|
||||||
"loudness_range": null,
|
|
||||||
"max_true_peak": null,
|
|
||||||
"max_momentary_loudness": null,
|
|
||||||
"max_shortterm_loudness": null
|
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"project": "wavinfo Test Project",
|
||||||
|
"scene": null,
|
||||||
|
"take": null,
|
||||||
|
"tape": null,
|
||||||
|
"family_uid": "E5DDE719B9484A758162FF7B652383A3",
|
||||||
|
"family_name": null
|
||||||
|
},
|
||||||
|
"bext": {
|
||||||
|
"description": "wavinfo Test Project Nuendo output",
|
||||||
|
"originator": "Nuendo",
|
||||||
|
"originator_ref": "USJPHNNNNNNNNN202829RRRRRRRRR",
|
||||||
|
"originator_date": "2022-12-02",
|
||||||
|
"originator_time": "10:21:06",
|
||||||
|
"time_reference": 172800000,
|
||||||
|
"version": 2,
|
||||||
|
"umid": "base64:k/zr4qE4RiaXyd/fO7GuCwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||||
|
"coding_history": "A=PCM,F=48000,W=24,T=Nuendo\r\n",
|
||||||
|
"loudness_value": 327.67,
|
||||||
|
"loudness_range": 327.67,
|
||||||
|
"max_true_peak": 327.67,
|
||||||
|
"max_momentary_loudness": 327.67,
|
||||||
|
"max_shortterm_loudness": 327.67
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ import json
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
import importlib.metadata
|
import importlib.metadata
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
|
from cmd import Cmd
|
||||||
|
from shlex import split
|
||||||
|
from typing import List, Dict, Union
|
||||||
|
|
||||||
|
|
||||||
class MyJSONEncoder(json.JSONEncoder):
|
class MyJSONEncoder(json.JSONEncoder):
|
||||||
@@ -24,6 +27,85 @@ class MissingDataError(RuntimeError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MetaBrowser(Cmd):
|
||||||
|
prompt = "(wavinfo) "
|
||||||
|
|
||||||
|
metadata: Union[List, Dict]
|
||||||
|
path: List[str] = []
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cwd(self):
|
||||||
|
root: List | Dict = self.metadata
|
||||||
|
for key in self.path:
|
||||||
|
if isinstance(root, list):
|
||||||
|
root = root[int(key)]
|
||||||
|
else:
|
||||||
|
root = root[key]
|
||||||
|
|
||||||
|
return root
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def print_value(collection, key):
|
||||||
|
val = collection[key]
|
||||||
|
if isinstance(val, int):
|
||||||
|
print(f" - {key}: {val}")
|
||||||
|
elif isinstance(val, str):
|
||||||
|
print(f" - {key}: \"{val}\"")
|
||||||
|
elif isinstance(val, dict):
|
||||||
|
print(f" - {key}: Dict ({len(val)} keys)")
|
||||||
|
elif isinstance(val, list):
|
||||||
|
print(f" - {key}: List ({len(val)} keys)")
|
||||||
|
elif isinstance(val, bytes):
|
||||||
|
print(f" - {key}: ({len(val)} bytes)")
|
||||||
|
elif val is None:
|
||||||
|
print(f" - {key}: (NO VALUE)")
|
||||||
|
else:
|
||||||
|
print(f" - {key}: Unknown")
|
||||||
|
|
||||||
|
def do_ls(self, _):
|
||||||
|
'List items at the current node: LS'
|
||||||
|
root = self.cwd
|
||||||
|
|
||||||
|
if isinstance(root, list):
|
||||||
|
print("List:")
|
||||||
|
for i in range(len(root)):
|
||||||
|
self.print_value(root, i)
|
||||||
|
|
||||||
|
elif isinstance(root, dict):
|
||||||
|
print("Dictionary:")
|
||||||
|
for key in root:
|
||||||
|
self.print_value(root, key)
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("Cannot print node, is not a list or dictionary.")
|
||||||
|
|
||||||
|
def do_cd(self, args):
|
||||||
|
'Switch to a different node: CD node-name | ".."'
|
||||||
|
argv = split(args)
|
||||||
|
if argv[0] == "..":
|
||||||
|
self.path = self.path[0:-1]
|
||||||
|
else:
|
||||||
|
if isinstance(self.cwd, list):
|
||||||
|
if int(argv[0]) < len(self.cwd):
|
||||||
|
self.path = self.path + [argv[0]]
|
||||||
|
else:
|
||||||
|
print(f"Index {argv[0]} does not exist")
|
||||||
|
elif isinstance(self.cwd, dict):
|
||||||
|
if argv[0] in self.cwd.keys():
|
||||||
|
self.path = self.path + [argv[0]]
|
||||||
|
else:
|
||||||
|
print(f"Key \"{argv[0]}\" does not exist")
|
||||||
|
|
||||||
|
if len(self.path) > 0:
|
||||||
|
self.prompt = "(" + "/".join(self.path) + ") "
|
||||||
|
else:
|
||||||
|
self.prompt = "(wavinfo) "
|
||||||
|
|
||||||
|
def do_bye(self, _):
|
||||||
|
'Exit the interactive browser: BYE'
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
version = importlib.metadata.version('wavinfo')
|
version = importlib.metadata.version('wavinfo')
|
||||||
manpath = os.path.dirname(__file__) + "/man"
|
manpath = os.path.dirname(__file__) + "/man"
|
||||||
@@ -51,8 +133,15 @@ def main():
|
|||||||
default=False,
|
default=False,
|
||||||
action='store_true')
|
action='store_true')
|
||||||
|
|
||||||
|
parser.add_option('-i',
|
||||||
|
help='Read metadata with an interactive prompt',
|
||||||
|
default=False,
|
||||||
|
action='store_true')
|
||||||
|
|
||||||
(options, args) = parser.parse_args(sys.argv)
|
(options, args) = parser.parse_args(sys.argv)
|
||||||
|
|
||||||
|
interactive_dict = []
|
||||||
|
|
||||||
# if options.install_manpages:
|
# if options.install_manpages:
|
||||||
# print("Installing manpages...")
|
# print("Installing manpages...")
|
||||||
# print(f"Docfiles at {__file__}")
|
# print(f"Docfiles at {__file__}")
|
||||||
@@ -98,7 +187,12 @@ def main():
|
|||||||
|
|
||||||
ret_dict['scopes'][scope][name] = value
|
ret_dict['scopes'][scope][name] = value
|
||||||
|
|
||||||
json.dump(ret_dict, cls=MyJSONEncoder, fp=sys.stdout, indent=2)
|
if options.i:
|
||||||
|
interactive_dict.append(ret_dict)
|
||||||
|
else:
|
||||||
|
json.dump(ret_dict, cls=MyJSONEncoder, fp=sys.stdout,
|
||||||
|
indent=2)
|
||||||
|
|
||||||
except MissingDataError as e:
|
except MissingDataError as e:
|
||||||
print("MissingDataError: Missing metadata (%s) in file %s" %
|
print("MissingDataError: Missing metadata (%s) in file %s" %
|
||||||
(e, arg), file=sys.stderr)
|
(e, arg), file=sys.stderr)
|
||||||
@@ -106,6 +200,11 @@ def main():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
if len(interactive_dict) > 0:
|
||||||
|
cli = MetaBrowser()
|
||||||
|
cli.metadata = interactive_dict
|
||||||
|
cli.cmdloop()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
wavinfo \- probe wave files for metadata
|
wavinfo \- probe wave files for metadata
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
.SY wavinfo
|
.SY wavinfo
|
||||||
|
.I "[\-i]"
|
||||||
.I "[\-\-adm]"
|
.I "[\-\-adm]"
|
||||||
.I "[\-\-ixml]"
|
.I "[\-\-ixml]"
|
||||||
.I FILE ...
|
.I FILE ...
|
||||||
@@ -24,6 +25,10 @@ Output any iXML metdata in
|
|||||||
.BR FILE .
|
.BR FILE .
|
||||||
.IP "\-h, \-\-help"
|
.IP "\-h, \-\-help"
|
||||||
Print brief help.
|
Print brief help.
|
||||||
|
.IP "\-i"
|
||||||
|
Enter
|
||||||
|
.I "interactive mode"
|
||||||
|
and browse metadata in FILE with an interactive command prompt.
|
||||||
.SH DETAILED DESCRIPTION
|
.SH DETAILED DESCRIPTION
|
||||||
.B wavinfo
|
.B wavinfo
|
||||||
collects metadata according to different
|
collects metadata according to different
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ class WavBextReader:
|
|||||||
# umid_str = umid_parsed.basic_umid_to_str()
|
# umid_str = umid_parsed.basic_umid_to_str()
|
||||||
# else:
|
# else:
|
||||||
|
|
||||||
umid_str = None
|
# umid_str = None
|
||||||
|
|
||||||
return {'description': self.description,
|
return {'description': self.description,
|
||||||
'originator': self.originator,
|
'originator': self.originator,
|
||||||
@@ -98,7 +98,7 @@ class WavBextReader:
|
|||||||
'originator_time': self.originator_time,
|
'originator_time': self.originator_time,
|
||||||
'time_reference': self.time_reference,
|
'time_reference': self.time_reference,
|
||||||
'version': self.version,
|
'version': self.version,
|
||||||
'umid': umid_str,
|
'umid': self.umid,
|
||||||
'coding_history': self.coding_history,
|
'coding_history': self.coding_history,
|
||||||
'loudness_value': self.loudness_value,
|
'loudness_value': self.loudness_value,
|
||||||
'loudness_range': self.loudness_range,
|
'loudness_range': self.loudness_range,
|
||||||
|
|||||||
Reference in New Issue
Block a user