From cf5934dfbdf550bff35a187a1db35ad6b0518f0a Mon Sep 17 00:00:00 2001 From: rlaphoenix Date: Wed, 19 Apr 2023 15:07:45 +0100 Subject: [PATCH 1/8] Add badges to README, state license on README --- README.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 435afe8..43a155d 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,13 @@ # pymp4 -Python MP4 box parser and toolkit -# Usage +[![Build status](https://github.com/beardypig/pymp4/actions/workflows/ci.yml/badge.svg)](https://github.com/beardypig/pymp4/actions/workflows/ci.yml) +[![License](https://img.shields.io/pypi/l/pymp4)](LICENSE) +[![Python versions](https://img.shields.io/pypi/pyversions/pymp4)](https://pypi.org/project/pymp4) +[![Coverage](https://codecov.io/gh/beardypig/pymp4/branch/master/graph/badge.svg)](https://app.codecov.io/github/beardypig/pymp4) -`pymp4` is based on the excellent parsing library [construct](https://github.com/construct/construct). +Python MP4 box parser and toolkit based on the [construct](https://github.com/construct/construct) library. + +## Usage ```python >>> from pymp4.parser import Box @@ -26,4 +30,8 @@ Container: iso5 avc1 -``` \ No newline at end of file +``` + +## License + +[Apache License, Version 2.0](LICENSE) From 47628e8abeb3a7ed0f0c2e45a407fdd205d93d24 Mon Sep 17 00:00:00 2001 From: rlaphoenix Date: Wed, 19 Apr 2023 15:11:07 +0100 Subject: [PATCH 2/8] List contributor images with hyperlinks in README Note: This is not automated and I recommend continuing to update it by adding to this within the pull request of a contributor's first contribution. --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 43a155d..a1aabf8 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,13 @@ Container: ``` +## Contributors + + + + + + ## License [Apache License, Version 2.0](LICENSE) From 5aa2bf8a9518586292906dd01c10aa3f63989452 Mon Sep 17 00:00:00 2001 From: rlaphoenix Date: Sun, 23 Apr 2023 22:04:01 +0100 Subject: [PATCH 3/8] Correctly define _reserved1 in tenc box and improve it's Const checks (#30) --- src/pymp4/parser.py | 28 ++++++++++++++++++---------- tests/test_dashboxes.py | 2 ++ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/pymp4/parser.py b/src/pymp4/parser.py index 1c2311c..bfe2b50 100644 --- a/src/pymp4/parser.py +++ b/src/pymp4/parser.py @@ -694,18 +694,26 @@ def _encode(self, obj, context): TrackEncryptionBox = Struct( "type" / If(this._.type != b"uuid", Const(b"tenc")), - "version" / Default(Int8ub, 0), + "version" / Default(OneOf(Int8ub, (0, 1)), 0), "flags" / Default(Int24ub, 0), - "_reserved0" / Const(Int8ub, 0), - "_reserved1" / Const(Int8ub, 0), - "is_encrypted" / Int8ub, - "iv_size" / Int8ub, + "_reserved" / Const(Int8ub, 0), + "default_byte_blocks" / Default(IfThenElse( + this.version > 0, + BitStruct( + # count of encrypted blocks in the protection pattern, where each block is 16-bytes + "crypt" / Nibble, + # count of unencrypted blocks in the protection pattern + "skip" / Nibble + ), + Const(Int8ub, 0) + ), 0), + "is_encrypted" / OneOf(Int8ub, (0, 1)), + "iv_size" / OneOf(Int8ub, (0, 8, 16)), "key_ID" / UUIDBytes(Bytes(16)), - "constant_iv" / Default(If(this.is_encrypted and this.iv_size == 0, - PrefixedArray(Int8ub, Byte), - ), - None) - + "constant_iv" / Default(If( + this.is_encrypted and this.iv_size == 0, + PrefixedArray(Int8ub, Byte) + ), None) ) SampleEncryptionBox = Struct( diff --git a/tests/test_dashboxes.py b/tests/test_dashboxes.py index e1b014b..3500619 100644 --- a/tests/test_dashboxes.py +++ b/tests/test_dashboxes.py @@ -32,6 +32,8 @@ def test_tenc_parse(self): (type=b"tenc") (version=0) (flags=0) + (_reserved=0) + (default_byte_blocks=0) (is_encrypted=1) (iv_size=8) (key_ID=UUID('337b9643-21b6-4355-9e59-3eccb46c7ef7')) From 427f232beb326c07a1035d7bd8ff6e05049b76e9 Mon Sep 17 00:00:00 2001 From: rlaphoenix Date: Sun, 23 Apr 2023 22:04:57 +0100 Subject: [PATCH 4/8] Define WebVTT boxes (#31) Co-authored-by: truedread --- src/pymp4/parser.py | 40 ++++++++++++++++- tests/test_webvtt_boxes.py | 90 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 tests/test_webvtt_boxes.py diff --git a/src/pymp4/parser.py b/src/pymp4/parser.py index bfe2b50..e3ab8b2 100644 --- a/src/pymp4/parser.py +++ b/src/pymp4/parser.py @@ -411,7 +411,8 @@ def _encode(self, obj, context): b"mp4a": MP4ASampleEntryBox, b"enca": MP4ASampleEntryBox, b"avc1": AVC1SampleEntryBox, - b"encv": AVC1SampleEntryBox + b"encv": AVC1SampleEntryBox, + b"wvtt": Struct("children" / LazyBound(lambda ctx: GreedyRange(Box))) }, Struct("data" / GreedyBytes))) )) @@ -766,6 +767,33 @@ def _encode(self, obj, context): }, GreedyBytes) ) +# WebVTT boxes + +CueIDBox = Struct( + "type" / Const(b"iden"), + "cue_id" / GreedyString("utf8") +) + +CueSettingsBox = Struct( + "type" / Const(b"sttg"), + "settings" / GreedyString("utf8") +) + +CuePayloadBox = Struct( + "type" / Const(b"payl"), + "cue_text" / GreedyString("utf8") +) + +WebVTTConfigurationBox = Struct( + "type" / Const(b"vttC"), + "config" / GreedyString("utf8") +) + +WebVTTSourceLabelBox = Struct( + "type" / Const(b"vlab"), + "label" / GreedyString("utf8") +) + ContainerBoxLazy = LazyBound(lambda ctx: ContainerBox) @@ -840,7 +868,15 @@ def sizeof(self, context=None, **kw): # HDS boxes b'abst': HDSSegmentBox, b'asrt': HDSSegmentRunBox, - b'afrt': HDSFragmentRunBox + b'afrt': HDSFragmentRunBox, + # WebVTT + b"vttC": WebVTTConfigurationBox, + b"vlab": WebVTTSourceLabelBox, + b"vttc": ContainerBoxLazy, + b"vttx": ContainerBoxLazy, + b"iden": CueIDBox, + b"sttg": CueSettingsBox, + b"payl": CuePayloadBox }, default=RawBox)), "end" / Tell )) diff --git a/tests/test_webvtt_boxes.py b/tests/test_webvtt_boxes.py new file mode 100644 index 0000000..84f7db3 --- /dev/null +++ b/tests/test_webvtt_boxes.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python +import logging +import unittest + +from construct import Container +from pymp4.parser import Box + +log = logging.getLogger(__name__) + + +class BoxTests(unittest.TestCase): + def test_iden_parse(self): + self.assertEqual( + Box.parse(b'\x00\x00\x00\x27iden2 - this is the second subtitle'), + Container(offset=0) + (type=b"iden") + (cue_id="2 - this is the second subtitle") + (end=39) + ) + + def test_iden_build(self): + self.assertEqual( + Box.build(dict( + type=b"iden", + cue_id="1 - first subtitle")), + b'\x00\x00\x00\x1aiden1 - first subtitle') + + def test_sttg_parse(self): + self.assertEqual( + Box.parse(b'\x00\x00\x003sttgline:10% position:50% size:48% align:center'), + Container(offset=0) + (type=b"sttg") + (settings="line:10% position:50% size:48% align:center") + (end=51) + ) + + def test_sttg_build(self): + self.assertEqual( + Box.build(dict( + type=b"sttg", + settings="line:75% position:20% size:2em align:right")), + b'\x00\x00\x002sttgline:75% position:20% size:2em align:right') + + def test_payl_parse(self): + self.assertEqual( + Box.parse(b'\x00\x00\x00\x13payl[chuckling]'), + Container(offset=0) + (type=b"payl") + (cue_text="[chuckling]") + (end=19) + ) + + def test_payl_build(self): + self.assertEqual( + Box.build(dict( + type=b"payl", + cue_text="I have a bad feeling about- [boom]")), + b'\x00\x00\x00*paylI have a bad feeling about- [boom]') + + def test_vttC_parse(self): + self.assertEqual( + Box.parse(b'\x00\x00\x00\x0evttCWEBVTT'), + Container(offset=0) + (type=b"vttC") + (config="WEBVTT") + (end=14) + ) + + def test_vttC_build(self): + self.assertEqual( + Box.build(dict( + type=b"vttC", + config="WEBVTT with a text header\n\nSTYLE\n::cue {\ncolor: red;\n}")), + b'\x00\x00\x00>vttCWEBVTT with a text header\n\nSTYLE\n::cue {\ncolor: red;\n}') + + def test_vlab_parse(self): + self.assertEqual( + Box.parse(b'\x00\x00\x00\x14vlabsource_label'), + Container(offset=0) + (type=b"vlab") + (label="source_label") + (end=20) + ) + + def test_vlab_build(self): + self.assertEqual( + Box.build(dict( + type=b"vlab", + label="1234 \n test_label \n\n")), + b'\x00\x00\x00\x1cvlab1234 \n test_label \n\n') From 9cbe010e2e8d5639227ca06f165205ac136e1e30 Mon Sep 17 00:00:00 2001 From: rlaphoenix Date: Sun, 7 May 2023 14:51:34 +0100 Subject: [PATCH 5/8] Update upload-artifact action to v3 in CD workflow The permissions are now required due to changes on GitHub's side behind the scenes. It might not be necessary if the user account settings or repo settings defaulted to allowing these, but for the sake of things lets explicitly specify the required permissions. --- .github/workflows/cd.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 440fd14..1c2193f 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -1,4 +1,9 @@ name: cd +permissions: + contents: "write" + id-token: "write" + packages: "write" + pull-requests: "read" on: push: @@ -20,7 +25,7 @@ jobs: - name: Build project run: python -m build - name: Upload wheel - uses: actions/upload-artifact@v2.2.4 + uses: actions/upload-artifact@v3 with: name: Python Wheel path: "dist/*.whl" From ac4095a74c3581eb21c157a13e59bbf49e4cc56c Mon Sep 17 00:00:00 2001 From: rlaphoenix Date: Sun, 7 May 2023 14:52:56 +0100 Subject: [PATCH 6/8] Use specifically Python 3.8 for the CD workflow For safety, let's use a version that the project definitely currently supports. --- .github/workflows/cd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 1c2193f..9394b1b 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -19,7 +19,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.x' + python-version: '3.8.x' - name: Install build run: pip install build - name: Build project From 74cf82c1c41169714af3b260ac81fdff1edf951a Mon Sep 17 00:00:00 2001 From: rlaphoenix Date: Sun, 7 May 2023 14:53:44 +0100 Subject: [PATCH 7/8] Use Poetry to install, build, and publish in the CD workflow --- .github/workflows/cd.yml | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 9394b1b..dce53a7 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -20,10 +20,14 @@ jobs: uses: actions/setup-python@v4 with: python-version: '3.8.x' - - name: Install build - run: pip install build + - name: Install Poetry + uses: abatilo/actions-poetry@v2.3.0 + with: + poetry-version: '1.4.1' + - name: Install dependencies + run: poetry install --no-dev - name: Build project - run: python -m build + run: poetry build - name: Upload wheel uses: actions/upload-artifact@v3 with: @@ -37,7 +41,6 @@ jobs: files: | dist/*.whl - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 - with: - user: __token__ - password: ${{ secrets.PYPI_API_TOKEN }} + env: + POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_API_TOKEN }} + run: poetry publish From 8e0045511adc212ea0427435a58a6887948bbd8d Mon Sep 17 00:00:00 2001 From: rlaphoenix Date: Sun, 7 May 2023 16:00:49 +0100 Subject: [PATCH 8/8] Bump to v1.4.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b0b6ef0..e6b8594 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "pymp4" -version = "1.3.2" +version = "1.4.0" description = "Python parser for MP4 boxes" authors = ["beardypig "] license = "Apache-2.0"