From 83bf656ad371c2afdac2d13dd8b9c2d8b99a9192 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Fri, 4 Jan 2019 19:58:03 -0800 Subject: [PATCH] Documentation --- README.md | 98 ----------------------------------- docs/index.rst | 14 +++-- docs/metadata_scopes/bext.rst | 65 +++++++++++++++++++++++ docs/metadata_scopes/info.rst | 32 ++++++++++++ docs/metadata_scopes/ixml.rst | 44 ++++++++++++++++ wavinfo/wave_bext_reader.py | 60 ++++++++++----------- wavinfo/wave_info_reader.py | 17 +++++- wavinfo/wave_ixml_reader.py | 25 ++++++++- 8 files changed, 223 insertions(+), 132 deletions(-) create mode 100644 docs/metadata_scopes/bext.rst create mode 100644 docs/metadata_scopes/info.rst create mode 100644 docs/metadata_scopes/ixml.rst diff --git a/README.md b/README.md index 5bbc424..eb7c009 100644 --- a/README.md +++ b/README.md @@ -53,104 +53,6 @@ The length of the file in frames (interleaved samples) and bytes is available, a >>> (48000, 2, 6, 24) ``` -### Broadcast WAV Extension - -A WAV file produced to Broadcast-WAV specifications will have the broadcast metadata extension, -which includes a 256-character free text descrption, creating entity identifier (usually the -recording application or equipment), the date and time of recording and a time reference for -timecode synchronization. - -The `coding_history` is designed to contain a record of every conversion performed on the audio -file. - -In this example (from a Sound Devices 702T) the bext metadata contains scene/take slating -information in the `description`. Here also the `originator_ref` is a serial number conforming -to EBU Rec 99. - -If the bext metadata conforms to EBU 3285 v1, it will contain the WAV's 32 or 64 byte SMPTE -330M UMID. The 32-byte version of the UMID is usually just a random number, while the 64-byte -UMID will also have information on the recording date and time, recording equipment and entity, -and geolocation data. - -If the bext metadata conforms to EBU 3285 v2, it will hold precomputed program loudness values -as described by EBU Rec 128. - -```python -print(info.bext.description) -print("----------") -print("Originator:", info.bext.originator) -print("Originator Ref:", info.bext.originator_ref) -print("Originator Date:", info.bext.originator_date) -print("Originator Time:", info.bext.originator_time) -print("Time Reference:", info.bext.time_reference) -print(info.bext.coding_history) -``` - - sSPEED=023.976-ND - sTAKE=1 - sUBITS=$12311801 - sSWVER=2.67 - sPROJECT=BMH - sSCENE=A101 - sFILENAME=A101_1.WAV - sTAPE=18Y12M31 - sTRK1=MKH516 A - sTRK2=Boom - sNOTE= - - ---------- - Originator: Sound Dev: 702T S#GR1112089007 - Originator Ref: USSDVGR1112089007124001008206301 - Originator Date: 2018-12-31 - Originator Time: 12:40:00 - Time Reference: 2190940753 - A=PCM,F=48000,W=24,M=stereo,R=48000,T=2 Ch -### iXML Production Recorder Metadata - -iXML allows an XML document to be embedded in a WAV file. - -The iXML website recommends a schema for recorder information but -there is no official DTD and vendors mostly do their own thing, apart from -hitting a few key xpaths. iXML is used by most location/production recorders -to save slating information, timecode and sync points in a reliable way. - -iXML is also used to link "families" of WAV files together, so WAV files -recorded simultaneously or contiguously can be related by a receiving client. - -```python -print("iXML Project:", info.ixml.project) -print("iXML Scene:", info.ixml.scene) -print("iXML Take:", info.ixml.take) -print("iXML Tape:", info.ixml.tape) -print("iXML File Family Name:", info.ixml.family_name) -print("iXML File Family UID:", info.ixml.family_uid) -``` - - iXML Project: BMH - iXML Scene: A101 - iXML Take: 1 - iXML Tape: 18Y12M31 - iXML File Family Name: None - iXML File Family UID: USSDVGR1112089007124001008206300 - - -### INFO Metadata - -INFO Metadata is a standard method for saving tagged text data in a WAV or AVI -file. INFO fields are often read by the file explorer and host OS, and used in -music library software. - -```python -bullet_path = '../tests/test_files/BULLET Impact Plastic LCD TV Screen Shatter Debris 2x.wav' - -bullet = WavInfoReader(bullet_path) -``` - - print("INFO Artist:", bullet.info.artist) - print("INFO Copyright:", bullet.info.copyright) - print("INFO Comment:", bullet.info.comment) - - diff --git a/docs/index.rst b/docs/index.rst index 2d79707..d9b86c7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -6,9 +6,6 @@ Welcome to wavinfo's documentation! =================================== -.. toctree:: - :maxdepth: 2 - :caption: Contents: .. module:: wavinfo @@ -24,6 +21,17 @@ Welcome to wavinfo's documentation! .. autoclass:: wavinfo.wave_reader.WavDataDescriptor :members: + +.. toctree:: + :maxdepth: 2 + :caption: Notes: + + metadata_scopes/bext.rst + metadata_scopes/ixml.rst + metadata_scopes/info.rst + + + Indices and tables ================== diff --git a/docs/metadata_scopes/bext.rst b/docs/metadata_scopes/bext.rst new file mode 100644 index 0000000..520f5e5 --- /dev/null +++ b/docs/metadata_scopes/bext.rst @@ -0,0 +1,65 @@ +Broadcast WAV Extension +======================= + +.. module:: wavinfo + +.. autoclass:: wavinfo.wave_bext_reader.WavBextReader + :members: + + +Notes +----- +A WAV file produced to Broadcast-WAV specifications will have the broadcast metadata extension, +which includes a 256-character free text descrption, creating entity identifier (usually the +recording application or equipment), the date and time of recording and a time reference for +timecode synchronization. + +The `coding_history` is designed to contain a record of every conversion performed on the audio +file. + +In this example (from a Sound Devices 702T) the bext metadata contains scene/take slating +information in the `description`. Here also the `originator_ref` is a serial number conforming +to EBU Rec 99. + +If the bext metadata conforms to EBU 3285 v1, it will contain the WAV's 32 or 64 byte SMPTE +330M UMID. The 32-byte version of the UMID is usually just a random number, while the 64-byte +UMID will also have information on the recording date and time, recording equipment and entity, +and geolocation data. + +If the bext metadata conforms to EBU 3285 v2, it will hold precomputed program loudness values +as described by EBU Rec 128. + +.. code:: python + + print(info.bext.description) + print("----------") + print("Originator:", info.bext.originator) + print("Originator Ref:", info.bext.originator_ref) + print("Originator Date:", info.bext.originator_date) + print("Originator Time:", info.bext.originator_time) + print("Time Reference:", info.bext.time_reference) + print(info.bext.coding_history) + +Result: + +:: + + sSPEED=023.976-ND + sTAKE=1 + sUBITS=$12311801 + sSWVER=2.67 + sPROJECT=BMH + sSCENE=A101 + sFILENAME=A101_1.WAV + sTAPE=18Y12M31 + sTRK1=MKH516 A + sTRK2=Boom + sNOTE= + + ---------- + Originator: Sound Dev: 702T S#GR1112089007 + Originator Ref: USSDVGR1112089007124001008206301 + Originator Date: 2018-12-31 + Originator Time: 12:40:00 + Time Reference: 2190940753 + A=PCM,F=48000,W=24,M=stereo,R=48000,T=2 Ch diff --git a/docs/metadata_scopes/info.rst b/docs/metadata_scopes/info.rst new file mode 100644 index 0000000..4c51548 --- /dev/null +++ b/docs/metadata_scopes/info.rst @@ -0,0 +1,32 @@ +INFO Metadata +============= + + +.. module:: wavinfo + +.. autoclass:: wavinfo.wave_info_reader.WavInfoChunkReader + :members: + + + +Notes +----- + +INFO Metadata is a standard method for saving tagged text data in a WAV or AVI +file. INFO fields are often read by the file explorer and host OS, and used in +music library software. + + +.. code:: python + + bullet_path = '../tests/test_files/BULLET Impact Plastic LCD TV Screen Shatter Debris 2x.wav' + + bullet = WavInfoReader(bullet_path) + + print("INFO Artist:", bullet.info.artist) + print("INFO Copyright:", bullet.info.copyright) + print("INFO Comment:", bullet.info.comment) + + + + diff --git a/docs/metadata_scopes/ixml.rst b/docs/metadata_scopes/ixml.rst new file mode 100644 index 0000000..15efcfc --- /dev/null +++ b/docs/metadata_scopes/ixml.rst @@ -0,0 +1,44 @@ +iXML Production Recorder Metadata +================================= + + +.. module:: wavinfo + +.. autoclass:: wavinfo.wave_ixml_reader.WavIXMLFormat + :members: + + +Notes +----- +iXML allows an XML document to be embedded in a WAV file. + +The iXML website recommends a schema for recorder information but +there is no official DTD and vendors mostly do their own thing, apart from +hitting a few key xpaths. iXML is used by most location/production recorders +to save slating information, timecode and sync points in a reliable way. + +iXML is also used to link "families" of WAV files together, so WAV files +recorded simultaneously or contiguously can be related by a receiving client. + +.. code:: python + + print("iXML Project:", info.ixml.project) + print("iXML Scene:", info.ixml.scene) + print("iXML Take:", info.ixml.take) + print("iXML Tape:", info.ixml.tape) + print("iXML File Family Name:", info.ixml.family_name) + print("iXML File Family UID:", info.ixml.family_uid) + +Result: + +:: + + iXML Project: BMH + iXML Scene: A101 + iXML Take: 1 + iXML Tape: 18Y12M31 + iXML File Family Name: None + iXML File Family UID: USSDVGR1112089007124001008206300 + + + diff --git a/wavinfo/wave_bext_reader.py b/wavinfo/wave_bext_reader.py index 9ff33ae..6292f15 100644 --- a/wavinfo/wave_bext_reader.py +++ b/wavinfo/wave_bext_reader.py @@ -2,28 +2,12 @@ import struct class WavBextReader: def __init__(self,bext_data,encoding): - # description[256] - - # originator[32] - # originatorref[32] - # originatordate[10] "YYYY:MM:DD" - # originatortime[8] "HH:MM:SS" - # lowtimeref U32 - # hightimeref U32 - # version U16 - # - # V1 field - # umid[64] - # - # V2 fields - # loudnessvalue S16 (in LUFS*100) - # loudnessrange S16 (in LUFS*100) - # maxtruepeak S16 (in dbTB*100) - # maxmomentaryloudness S16 (LUFS*100) - # maxshorttermloudness S16 (LUFS*100) - # - # reserved[180] - # codinghistory [] + """ + Read Broadcast-WAV extended metadata. + :param best_data: The bytes-like data. + "param encoding: The encoding to use when decoding the text fields of the + BEXT metadata scope. According to EBU Rec 3285 this shall be ASCII. + """ packstring = "<256s"+ "32s" + "32s" + "10s" + "8s" + "QH" + "64s" + "hhhhh" + "180s" rest_starts = struct.calcsize(packstring) @@ -39,20 +23,38 @@ class WavBextReader: decoded = trimmed.decode(encoding) return decoded + #: Description. A free-text field up to 256 characters long. self.description = sanatize_bytes(unpacked[0]) + #: Originator. Usually the name of the encoding application, sometimes + #: a artist name. self.originator = sanatize_bytes(unpacked[1]) + #: A unique identifer for the file, a serial number. self.originator_ref = sanatize_bytes(unpacked[2]) + #: Date of the recording, in the format YYY-MM-DD self.originator_date = sanatize_bytes(unpacked[3]) + #: Time of the recording, in the format HH:MM:SS. self.originator_time = sanatize_bytes(unpacked[4]) + #: The sample offset of the start of the file relative to an + #: epoch, usually midnight the day of the recording. self.time_reference = unpacked[5] - self.version = unpacked[6] - self.umid = None - self.loudness_value = None - self.loudness_range = None - self.max_true_peak = None - self.max_momentary_loudness = None - self.max_shortterm_loudness = None + #: A variable-length text field containing a list of processes and + #: and conversions performed on the file. self.coding_history = sanatize_bytes(bext_data[rest_starts:]) + #: BEXT version. + self.version = unpacked[6] + #: SMPTE 330M UMID of this audio file, 64 bytes are allocated though the UMID + #: may only be 32 bytes long. + self.umid = None + #: EBU R128 Integrated loudness, in LUFS. + self.loudness_value = None + #: EBU R128 Loudness rante, in LUFS. + self.loudness_range = None + #: True peak level, in dBFS TP + self.max_true_peak = None + #: EBU R128 Maximum momentary loudness, in LUFS + self.max_momentary_loudness = None + #: EBU R128 Maximum short-term loudness, in LUFS. + self.max_shortterm_loudness = None if self.version > 0: self.umid = unpacked[7] diff --git a/wavinfo/wave_info_reader.py b/wavinfo/wave_info_reader.py index abf2e47..fc9e0ae 100644 --- a/wavinfo/wave_info_reader.py +++ b/wavinfo/wave_info_reader.py @@ -14,18 +14,30 @@ class WavInfoChunkReader: self.info_chunk = next((chunk for chunk in list_chunks \ if chunk.signature == b'INFO'), None) - + + #: 'ICOP' Copyright self.copyright = self._get_field(f,b'ICOP') + #: 'IPRD' Product self.product = self._get_field(f,b'IPRD') + #: 'IGNR' Genre self.genre = self._get_field(f,b'IGNR') + #: 'IART' Artist, composer, author self.artist = self._get_field(f,b'IART') + #: 'ICMT' Comment self.comment = self._get_field(f,b'ICMT') + #: 'ISFT' Software, encoding application self.software = self._get_field(f,b'ISFT') + #: 'ICRD' Created date self.created_date = self._get_field(f,b'ICRD') + #: 'IENG' Engineer self.engineer = self._get_field(f,b'IENG') + #: 'IKEY' Keywords, keyword list self.keywords = self._get_field(f,b'IKEY') + #: 'INAM' Name, title self.title = self._get_field(f,b'INAM') + #: 'ISRC' Source self.source = self._get_field(f,b'ISRC') + #: 'TAPE' Tape self.tape = self._get_field(f,b'TAPE') @@ -43,6 +55,9 @@ class WavInfoChunkReader: def to_dict(self): + """ + A dictionary with all of the key/values read from the INFO scope. + """ return {'copyright': self.copyright, 'product': self.product, 'genre': self.genre, diff --git a/wavinfo/wave_ixml_reader.py b/wavinfo/wave_ixml_reader.py index bcfcd1a..575d941 100644 --- a/wavinfo/wave_ixml_reader.py +++ b/wavinfo/wave_ixml_reader.py @@ -3,9 +3,13 @@ import io class WavIXMLFormat: """ - iXML recorder metadata, as defined by iXML 2.0 + iXML recorder metadata. """ def __init__(self, xml): + """ + Parse iXML. + :param xml: A bytes-like object containing the iXML payload. + """ self.source = xml xmlBytes = io.BytesIO(xml) self.parsed = ET.parse(xmlBytes) @@ -17,26 +21,45 @@ class WavIXMLFormat: @property def project(self): + """ + The project/film name entered for the recording. + """ return self._get_text_value("PROJECT") @property def scene(self): + """ + Scene/slate. + """ return self._get_text_value("SCENE") @property def take(self): + """ + Take number. + """ return self._get_text_value("TAKE") @property def tape(self): + """ + Tape name. + """ return self._get_text_value("TAPE") @property def family_uid(self): + """ + The globally-unique ID for this file family. This may be in the format + of a GUID, or an EBU Rec 9 source identifier, or some other dumb number. + """ return self._get_text_value("FILE_SET/FAMILY_UID") @property def family_name(self): + """ + The name of this file's file family. + """ return self._get_text_value("FILE_SET/FAMILY_NAME")