Skip to content

Commit

Permalink
Merge branch 'master' into construct-2.10-patch
Browse files Browse the repository at this point in the history
  • Loading branch information
rlaphoenix authored Aug 7, 2023
2 parents d81c932 + 8e00455 commit 09a5b85
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 27 deletions.
26 changes: 17 additions & 9 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
name: cd
permissions:
contents: "write"
id-token: "write"
packages: "write"
pull-requests: "read"

on:
push:
Expand All @@ -14,13 +19,17 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Install build
run: pip install build
python-version: '3.8.x'
- 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@v2.2.4
uses: actions/upload-artifact@v3
with:
name: Python Wheel
path: "dist/*.whl"
Expand All @@ -32,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
23 changes: 19 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -26,4 +30,15 @@ Container:
iso5
avc1

```
```

## Contributors

<a href="https://github.com/beardypig"><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/16033421?v=4&h=25&w=25&fit=cover&mask=circle&maxage=7d" alt=""/></a>
<a href="https://github.com/truedread"><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/25360375?v=4&h=25&w=25&fit=cover&mask=circle&maxage=7d" alt=""/></a>
<a href="https://github.com/orca-eaa5a"><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/28733566?v=4&h=25&w=25&fit=cover&mask=circle&maxage=7d" alt=""/></a>
<a href="https://github.com/rlaphoenix"><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/17136956?v=4&h=25&w=25&fit=cover&mask=circle&maxage=7d" alt=""/></a>

## License

[Apache License, Version 2.0](LICENSE)
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 <git@beardypig.com>"]
license = "Apache-2.0"
Expand Down
85 changes: 72 additions & 13 deletions src/pymp4/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,8 @@
"mp4a": MP4ASampleEntryBox,
"enca": MP4ASampleEntryBox,
"avc1": AVC1SampleEntryBox,
"encv": AVC1SampleEntryBox
"encv": AVC1SampleEntryBox,
"wvtt": Struct("children" / LazyBound(lambda ctx: GreedyRange(Box)))
}, GreedyBytes)
), includelength=True)

Expand Down Expand Up @@ -549,18 +550,26 @@
)

TrackEncryptionBox = Struct(
"version" / Default(Int8ub, 0),
"version" / Default(OneOf(Int8ub, (0, 1)), 0),
"flags" / Default(Int24ub, 0),
"_reserved0" / Const(0, Int8ub),
"_reserved1" / Const(0, Int8ub),
"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(
Expand Down Expand Up @@ -608,7 +617,49 @@
}, GreedyBytes)
)

ContainerBoxLazy = LazyBound(lambda: ContainerBox)
# 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)


class TellMinusSizeOf(Subconstruct):
def __init__(self, subcon):
super(TellMinusSizeOf, self).__init__(subcon)
self.flagbuildnone = True

def _parse(self, stream, context, path):
return stream.tell() - self.subcon.sizeof(context)

def _build(self, obj, stream, context, path):
return b""

def sizeof(self, context=None, **kw):
return 0


Box = Prefixed(Int32ub, Struct(
Expand Down Expand Up @@ -667,7 +718,15 @@
# HDS boxes
"abst": HDSSegmentBox,
"asrt": HDSSegmentRunBox,
"afrt": HDSFragmentRunBox
"afrt": HDSFragmentRunBox,
# WebVTT
"vttC": WebVTTConfigurationBox,
"vlab": WebVTTSourceLabelBox,
"vttc": ContainerBoxLazy,
"vttx": ContainerBoxLazy,
"iden": CueIDBox,
"sttg": CueSettingsBox,
"payl": CuePayloadBox
}, default=RawBox),
"end" / TellPlusSizeOf(Int32ub)
), includelength=True)
Expand Down
2 changes: 2 additions & 0 deletions tests/test_dashboxes.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ def test_tenc_parse(self):
data=Container(
version=0,
flags=0,
_reserved=0,
default_byte_blocks=0,
is_encrypted=1,
iv_size=8,
key_ID=UUID('337b9643-21b6-4355-9e59-3eccb46c7ef7'),
Expand Down
90 changes: 90 additions & 0 deletions tests/test_webvtt_boxes.py
Original file line number Diff line number Diff line change
@@ -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')

0 comments on commit 09a5b85

Please sign in to comment.