initial check in
This commit is contained in:
commit
2e4a8fbcd6
20 changed files with 1304 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
.DS_Store
|
||||||
|
.idea
|
102
.gitlab-ci.yml
Normal file
102
.gitlab-ci.yml
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
image: golang:1.23
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- test
|
||||||
|
- sonarcloud
|
||||||
|
- build
|
||||||
|
|
||||||
|
checks:
|
||||||
|
stage: test
|
||||||
|
script:
|
||||||
|
- go fmt $(go list ./...)
|
||||||
|
- go vet $(go list ./...)
|
||||||
|
|
||||||
|
code coverage:
|
||||||
|
stage: test
|
||||||
|
script:
|
||||||
|
- go test -covermode=count -coverprofile coverage.cov $(go list ./...)
|
||||||
|
- go tool cover -func=coverage.cov
|
||||||
|
- go tool cover -html=coverage.cov -o coverage.html
|
||||||
|
coverage: '/\(statements\)\W+\d+\.\d+%/'
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- coverage.cov
|
||||||
|
- coverage.html
|
||||||
|
|
||||||
|
codecov.io:
|
||||||
|
stage: test
|
||||||
|
script:
|
||||||
|
- curl https://keybase.io/codecovsecurity/pgp_keys.asc | gpg --import
|
||||||
|
- curl -Os https://uploader.codecov.io/latest/linux/codecov
|
||||||
|
- curl -Os https://uploader.codecov.io/latest/linux/codecov.SHA256SUM
|
||||||
|
- curl -Os https://uploader.codecov.io/latest/linux/codecov.SHA256SUM.sig
|
||||||
|
- gpg --verify codecov.SHA256SUM.sig codecov.SHA256SUM
|
||||||
|
- shasum -a 256 -c codecov.SHA256SUM
|
||||||
|
- chmod +x codecov
|
||||||
|
- go test -race -coverprofile=coverage.out -covermode=atomic
|
||||||
|
- ./codecov -t ${CODECOV_TOKEN}
|
||||||
|
rules:
|
||||||
|
- if: $CODECOV_TOKEN
|
||||||
|
when: on_success
|
||||||
|
|
||||||
|
# stage "sonarcloud" is only needed because of this issue:
|
||||||
|
# https://gitlab.com/gitlab-org/gitlab/-/issues/30632
|
||||||
|
sonarcloud-check:
|
||||||
|
stage: sonarcloud
|
||||||
|
# result of coverage is needed
|
||||||
|
needs:
|
||||||
|
- code coverage
|
||||||
|
image:
|
||||||
|
name: sonarsource/sonar-scanner-cli:latest
|
||||||
|
entrypoint: [""]
|
||||||
|
variables:
|
||||||
|
SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar" # Defines the location of the analysis task cache
|
||||||
|
GIT_DEPTH: "0" # Tells git to fetch all the branches of the project, required by the analysis task
|
||||||
|
cache:
|
||||||
|
key: "${CI_JOB_NAME}"
|
||||||
|
paths:
|
||||||
|
- .sonar/cache
|
||||||
|
script:
|
||||||
|
- sonar-scanner
|
||||||
|
rules:
|
||||||
|
- if: $SONAR_HOST_URL
|
||||||
|
when: on_success
|
||||||
|
|
||||||
|
# build template
|
||||||
|
# execute the following command for all os/arch combinations: go tool dist list
|
||||||
|
.compile:
|
||||||
|
stage: build
|
||||||
|
# no dependencies -> no download of artifacts from previous jobs/stages
|
||||||
|
dependencies: []
|
||||||
|
script:
|
||||||
|
- go build .
|
||||||
|
|
||||||
|
darwin-amd64:
|
||||||
|
extends: .compile
|
||||||
|
variables:
|
||||||
|
GOOS: "darwin"
|
||||||
|
GOARCH: "amd64"
|
||||||
|
|
||||||
|
darwin-arm64:
|
||||||
|
extends: .compile
|
||||||
|
variables:
|
||||||
|
GOOS: "darwin"
|
||||||
|
GOARCH: "arm64"
|
||||||
|
|
||||||
|
linux-amd64:
|
||||||
|
extends: .compile
|
||||||
|
variables:
|
||||||
|
GOOS: "linux"
|
||||||
|
GOARCH: "amd64"
|
||||||
|
|
||||||
|
linux-arm64:
|
||||||
|
extends: .compile
|
||||||
|
variables:
|
||||||
|
GOOS: "linux"
|
||||||
|
GOARCH: "arm64"
|
||||||
|
|
||||||
|
windows-amd64:
|
||||||
|
extends: .compile
|
||||||
|
variables:
|
||||||
|
GOOS: "windows"
|
||||||
|
GOARCH: "amd64"
|
67
Box.go
Normal file
67
Box.go
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
// Copyright 2024 Martin Riedl
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package gomp4
|
||||||
|
|
||||||
|
import "encoding/binary"
|
||||||
|
|
||||||
|
const (
|
||||||
|
boxSizeLength uint32 = 4
|
||||||
|
boxTypeLength uint32 = 4
|
||||||
|
|
||||||
|
// BoxTypeFileType File Type Box
|
||||||
|
BoxTypeFileType = "ftyp"
|
||||||
|
// BoxTypeMediaData Media Data Box
|
||||||
|
BoxTypeMediaData = "mdat"
|
||||||
|
// BoxTypeMovie Movie Box
|
||||||
|
BoxTypeMovie = "moov"
|
||||||
|
// BoxTypeMovieFragment Movie Fragment Box
|
||||||
|
BoxTypeMovieFragment = "moof"
|
||||||
|
// BoxTypeMovieFragmentHeader Movie Fragment Header Box
|
||||||
|
BoxTypeMovieFragmentHeader = "mfhd"
|
||||||
|
// BoxTypeTrackFragment Track Fragment Box
|
||||||
|
BoxTypeTrackFragment = "traf"
|
||||||
|
// BoxTypeTrackFragmentHeader Track Fragment Header
|
||||||
|
BoxTypeTrackFragmentHeader = "tfhd"
|
||||||
|
// BoxTypeTrackFragmentRun Track Fragment Run Box
|
||||||
|
BoxTypeTrackFragmentRun = "trun"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Box is an abstract box struct
|
||||||
|
type Box struct {
|
||||||
|
FilePosition uint64
|
||||||
|
HeaderSize uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// FullBox is an abstract box type extending base box
|
||||||
|
type FullBox struct {
|
||||||
|
*Box
|
||||||
|
Version uint8
|
||||||
|
Flag []byte
|
||||||
|
flagUInt32 uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFullBox parses based on 4 bytes data the version number (first byte) and the flags (bytes 2 - 4)
|
||||||
|
func newFullBox(box *Box, data []byte) *FullBox {
|
||||||
|
fullBox := &FullBox{Box: box, Version: data[0], Flag: data[1:4]}
|
||||||
|
|
||||||
|
// build uint32 for flags (used for easier matching)
|
||||||
|
flag4Bytes := append(make([]byte, 1), fullBox.Flag...)
|
||||||
|
fullBox.flagUInt32 = binary.BigEndian.Uint32(flag4Bytes)
|
||||||
|
|
||||||
|
// extend header size
|
||||||
|
fullBox.HeaderSize = fullBox.HeaderSize + 4
|
||||||
|
|
||||||
|
return fullBox
|
||||||
|
}
|
87
FileTypeBox.go
Normal file
87
FileTypeBox.go
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
// Copyright 2024 Martin Riedl
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package gomp4
|
||||||
|
|
||||||
|
import "encoding/binary"
|
||||||
|
|
||||||
|
// FileTypeBox file type box struct
|
||||||
|
//
|
||||||
|
// 4.3 File Type Box
|
||||||
|
// Box Type: `ftyp’
|
||||||
|
// Container: File
|
||||||
|
// Mandatory: Yes
|
||||||
|
// Quantity: Exactly one (but see below)
|
||||||
|
//
|
||||||
|
// Files written to this version of this specification must contain a file‐type box. For compatibility with an
|
||||||
|
// earlier version of this specification, files may be conformant to this specification and not contain a file‐
|
||||||
|
// type box. Files with no file‐type box should be read as if they contained an FTYP box with
|
||||||
|
// Major_brand='mp41', minor_version=0, and the single compatible brand 'mp41'.
|
||||||
|
//
|
||||||
|
// A media‐file structured to this part of this specification may be compatible with more than one detailed
|
||||||
|
// specification, and it is therefore not always possible to speak of a single ‘type’ or ‘brand’ for the file. This
|
||||||
|
// means that the utility of the file name extension and Multipurpose Internet Mail Extension (MIME) type
|
||||||
|
// are somewhat reduced.
|
||||||
|
//
|
||||||
|
// This box must be placed as early as possible in the file (e.g. after any obligatory signature, but before
|
||||||
|
// any significant variable‐size boxes such as a Movie Box, Media Data Box, or Free Space). It identifies
|
||||||
|
// which specification is the ‘best use’ of the file, and a minor version of that specification; and also a set of
|
||||||
|
// other specifications to which the file complies. Readers implementing this format should attempt to
|
||||||
|
// read files that are marked as compatible with any of the specifications that the reader implements. Any
|
||||||
|
// incompatible change in a specification should therefore register a new ‘brand’ identifier to identify files
|
||||||
|
// conformant to the new specification.
|
||||||
|
//
|
||||||
|
// The minor version is informative only. It does not appear for compatible‐brands, and must not be used
|
||||||
|
// to determine the conformance of a file to a standard. It may allow more precise identification of the
|
||||||
|
// major specification, for inspection, debugging, or improved decoding.
|
||||||
|
//
|
||||||
|
// Files would normally be externally identified (e.g. with a file extension or mime type) that identifies the
|
||||||
|
// ‘best use’ (major brand), or the brand that the author believes will provide the greatest compatibility.
|
||||||
|
//
|
||||||
|
// This section of this specification does not define any brands. However, see subclause 6.3 below for brands for
|
||||||
|
// files conformant to the whole specification and not just this section. All file format brands
|
||||||
|
// defined in this specification are included in Annex E with a summary of which features they require.
|
||||||
|
type FileTypeBox struct {
|
||||||
|
*Box
|
||||||
|
// is a brand identifier
|
||||||
|
MajorBrand string
|
||||||
|
// is an informative integer for the minor version of the major brand
|
||||||
|
MinorBrand uint32
|
||||||
|
// is a list, to the end of the box, of brands
|
||||||
|
CompatibleBrands []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseFileTypeBox creates new file type box based on bytes
|
||||||
|
func ParseFileTypeBox(filePosition uint64, headerSize uint32, content []byte) *FileTypeBox {
|
||||||
|
// create new box
|
||||||
|
box := &FileTypeBox{
|
||||||
|
Box: &Box{filePosition, headerSize},
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse major brand
|
||||||
|
majorBrandBytes := content[0:4]
|
||||||
|
box.MajorBrand = string(majorBrandBytes)
|
||||||
|
|
||||||
|
// parse minor brand
|
||||||
|
box.MinorBrand = binary.BigEndian.Uint32(content[4:8])
|
||||||
|
|
||||||
|
// parse brands
|
||||||
|
for i := 8; i < len(content); i = i + 4 {
|
||||||
|
data := content[i : i+4]
|
||||||
|
brand := string(data)
|
||||||
|
box.CompatibleBrands = append(box.CompatibleBrands, brand)
|
||||||
|
}
|
||||||
|
|
||||||
|
return box
|
||||||
|
}
|
176
LICENSE.txt
Normal file
176
LICENSE.txt
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
26
Log.go
Normal file
26
Log.go
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright 2024 Martin Riedl
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package gomp4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
logger = log.New(ioutil.Discard, "", 0)
|
||||||
|
verboseLogger = log.New(os.Stderr, "", log.Ldate|log.Ltime|log.Lshortfile)
|
||||||
|
)
|
52
MediaDataBox.go
Normal file
52
MediaDataBox.go
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
// Copyright 2024 Martin Riedl
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package gomp4
|
||||||
|
|
||||||
|
// MediaDataBox Media Data Box
|
||||||
|
//
|
||||||
|
// 8.1.1 Media Data Box
|
||||||
|
// Box Type: ‘mdat’
|
||||||
|
// Container: File
|
||||||
|
// Mandatory: No
|
||||||
|
// Quantity: Zero or more
|
||||||
|
//
|
||||||
|
// This box contains the media data. In video tracks, this box would contain video frames. A presentation
|
||||||
|
// may contain zero or more Media Data Boxes. The actual media data follows the type field; its structure
|
||||||
|
// is described by the metadata (see particularly the sample table, subclause 8.5, and the item location box,
|
||||||
|
// subclause 8.11.3).
|
||||||
|
//
|
||||||
|
// In large presentations, it may be desirable to have more data in this box than a 32‐bit size would permit.
|
||||||
|
// In this case, the large variant of the size field, above in subclause 4.2, is used.
|
||||||
|
//
|
||||||
|
// There may be any number of these boxes in the file (including zero, if all the media data is in other files).
|
||||||
|
// The metadata refers to media data by its absolute offset within the file (see subclause 8.7.5, the Chunk
|
||||||
|
// Offset Box); so Media Data Box headers and free space may easily be skipped, and files without any box
|
||||||
|
// structure may also be referenced and used.
|
||||||
|
type MediaDataBox struct {
|
||||||
|
*Box
|
||||||
|
ContentStartPosition uint64
|
||||||
|
ContentEndPosition uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseMediaDataBox creates a new media data box struct
|
||||||
|
func ParseMediaDataBox(filePosition uint64, headerSize uint32, content []byte) *MediaDataBox {
|
||||||
|
box := &MediaDataBox{Box: &Box{filePosition, headerSize}}
|
||||||
|
|
||||||
|
// parse positions of content
|
||||||
|
box.ContentStartPosition = filePosition + uint64(headerSize)
|
||||||
|
box.ContentEndPosition = box.ContentStartPosition + uint64(len(content))
|
||||||
|
|
||||||
|
return box
|
||||||
|
}
|
40
MovieBox.go
Normal file
40
MovieBox.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
// Copyright 2024 Martin Riedl
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package gomp4
|
||||||
|
|
||||||
|
// MovieBox movie box struct
|
||||||
|
//
|
||||||
|
// 8.2.1 Movie Box
|
||||||
|
// Box Type: ‘moov’
|
||||||
|
// Container: File
|
||||||
|
// Mandatory: Yes
|
||||||
|
// Quantity: Exactly one
|
||||||
|
//
|
||||||
|
// The metadata for a presentation is stored in the single Movie Box which occurs at the top‐level of a file.
|
||||||
|
// Normally this box is close to the beginning or end of the file, though this is not required.
|
||||||
|
type MovieBox struct {
|
||||||
|
*Box
|
||||||
|
ChildBoxes []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseMovieBox creates a new movie box struct based on bytes
|
||||||
|
func ParseMovieBox(filePosition uint64, headerSize uint32, content []byte) (*MovieBox, error) {
|
||||||
|
box := &MovieBox{Box: &Box{filePosition, headerSize}}
|
||||||
|
|
||||||
|
// parse content boxes
|
||||||
|
var err error
|
||||||
|
box.ChildBoxes, err = box.parseChildBoxes(filePosition, content)
|
||||||
|
return box, err
|
||||||
|
}
|
51
MovieFragmentBox.go
Normal file
51
MovieFragmentBox.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
// Copyright 2024 Martin Riedl
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package gomp4
|
||||||
|
|
||||||
|
// MovieFragmentBox Movie Fragment Box struct
|
||||||
|
//
|
||||||
|
// 8.8.4 Movie Fragment Box
|
||||||
|
// Box Type: ‘moof’
|
||||||
|
// Container: File
|
||||||
|
// Mandatory: No
|
||||||
|
// Quantity: Zero or more
|
||||||
|
//
|
||||||
|
// The movie fragments extend the presentation in time. They provide the information that would
|
||||||
|
// previously have been in the Movie Box. The actual samples are in Media Data Boxes, as usual, if they are
|
||||||
|
// in the same file. The data reference index is in the sample description, so it is possible to build
|
||||||
|
// incremental presentations where the media data is in files other than the file containing the Movie Box.
|
||||||
|
//
|
||||||
|
// The Movie Fragment Box is a top‐level box, (i.e. a peer to the Movie Box and Media Data boxes). It
|
||||||
|
// contains a Movie Fragment Header Box, and then one or more Track Fragment Boxes.
|
||||||
|
//
|
||||||
|
// NOTE There is no requirement that any particular movie fragment extend all tracks present in the movie
|
||||||
|
// header, and there is no restriction on the location of the media data referred to by the movie fragments.
|
||||||
|
// However, derived specifications may make such restrictions.
|
||||||
|
type MovieFragmentBox struct {
|
||||||
|
*Box
|
||||||
|
ChildBoxes []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseMovieFragmentBox creates a new movie fragment box struct based on bytes
|
||||||
|
func ParseMovieFragmentBox(filePosition uint64, headerSize uint32, content []byte) (*MovieFragmentBox, error) {
|
||||||
|
box := &MovieFragmentBox{
|
||||||
|
Box: &Box{filePosition, headerSize},
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse child boxes
|
||||||
|
var err error
|
||||||
|
box.ChildBoxes, err = box.parseChildBoxes(filePosition, content)
|
||||||
|
return box, err
|
||||||
|
}
|
49
MovieFragmentHeaderBox.go
Normal file
49
MovieFragmentHeaderBox.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
// Copyright 2024 Martin Riedl
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package gomp4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MovieFragmentHeaderBox Movie Fragment Header Box struct
|
||||||
|
//
|
||||||
|
// 8.8.5 Movie Fragment Header Box
|
||||||
|
// Box Type: ‘mfhd’
|
||||||
|
// Container: Movie Fragment Box ('moof')
|
||||||
|
// Mandatory: Yes
|
||||||
|
// Quantity: Exactly one
|
||||||
|
//
|
||||||
|
// The movie fragment header contains a sequence number, as a safety check. The sequence number
|
||||||
|
// usually starts at 1 and increases for each movie fragment in the file, in the order in which they occur.
|
||||||
|
// This allows readers to verify integrity of the sequence in environments where undesired re‐ordering
|
||||||
|
// might occur.
|
||||||
|
type MovieFragmentHeaderBox struct {
|
||||||
|
*FullBox
|
||||||
|
// a number associated with this fragment
|
||||||
|
SequenceNumber uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseMovieFragmentHeaderBox creates a new Movie Fragment Header Box struct
|
||||||
|
func ParseMovieFragmentHeaderBox(filePosition uint64, headerSize uint32, content []byte) *MovieFragmentHeaderBox {
|
||||||
|
box := &MovieFragmentHeaderBox{
|
||||||
|
FullBox: newFullBox(&Box{filePosition, headerSize}, content[0:4]),
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse sequence number
|
||||||
|
box.SequenceNumber = binary.BigEndian.Uint32(content[4:8])
|
||||||
|
|
||||||
|
return box
|
||||||
|
}
|
160
Parser.go
Normal file
160
Parser.go
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
// Copyright 2024 Martin Riedl
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package gomp4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parser struct for mp4 container parsing
|
||||||
|
type Parser struct {
|
||||||
|
reader io.Reader
|
||||||
|
Content []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates a new parser with byte data
|
||||||
|
func NewParser(reader io.Reader) *Parser {
|
||||||
|
return &Parser{reader: reader}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse starts the file parsing
|
||||||
|
func (parser *Parser) Parse() error {
|
||||||
|
var filePosition uint64 = 0
|
||||||
|
for {
|
||||||
|
box, endPosition, endOfFile, err := parseNextBox(parser.reader, filePosition)
|
||||||
|
if box != nil {
|
||||||
|
parser.Content = append(parser.Content, box)
|
||||||
|
}
|
||||||
|
|
||||||
|
if endOfFile {
|
||||||
|
return nil
|
||||||
|
} else if err != nil {
|
||||||
|
logger.Println("error at", endPosition)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
filePosition = endPosition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseNextBox(reader io.Reader, filePosition uint64) (box interface{}, endPosition uint64, endOfFile bool, err error) {
|
||||||
|
// first 4 bytes are the size of the box
|
||||||
|
sizeBytes := make([]byte, boxSizeLength)
|
||||||
|
sizeBytesCount, err := reader.Read(sizeBytes)
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return nil, filePosition, true, nil
|
||||||
|
}
|
||||||
|
return nil, filePosition, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check read bytes count
|
||||||
|
if sizeBytesCount != len(sizeBytes) {
|
||||||
|
return nil, filePosition, false, fmt.Errorf("unable to parse next box size")
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse box size
|
||||||
|
boxSize := binary.BigEndian.Uint32(sizeBytes)
|
||||||
|
logger.Println("new box size", boxSize, "at", filePosition)
|
||||||
|
|
||||||
|
// TODO: check large size box type
|
||||||
|
|
||||||
|
// parse box type
|
||||||
|
boxTypeBytes := make([]byte, boxTypeLength)
|
||||||
|
boxTypeBytesCount, err := reader.Read(boxTypeBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, filePosition, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check read bytes count
|
||||||
|
if boxTypeBytesCount != len(boxTypeBytes) {
|
||||||
|
return nil, filePosition, false, fmt.Errorf("unable to parse box type at %d", filePosition)
|
||||||
|
}
|
||||||
|
boxType := string(boxTypeBytes)
|
||||||
|
logger.Println("new box type", boxType, "at", filePosition)
|
||||||
|
|
||||||
|
// parse box data
|
||||||
|
boxHeaderSize := boxSizeLength + boxTypeLength
|
||||||
|
boxContentSize := boxSize - boxHeaderSize
|
||||||
|
boxContentBytes := make([]byte, boxContentSize)
|
||||||
|
boxContentBytesCount, err := reader.Read(boxContentBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, filePosition, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check read bytes count
|
||||||
|
if boxContentBytesCount != len(boxContentBytes) {
|
||||||
|
return nil, filePosition, false, fmt.Errorf("unable to parse box content at %d", filePosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse struct of box type
|
||||||
|
switch boxType {
|
||||||
|
case BoxTypeFileType:
|
||||||
|
box = ParseFileTypeBox(filePosition, boxHeaderSize, boxContentBytes)
|
||||||
|
case BoxTypeMediaData:
|
||||||
|
box = ParseMediaDataBox(filePosition, boxHeaderSize, boxContentBytes)
|
||||||
|
case BoxTypeMovie:
|
||||||
|
box, err = ParseMovieBox(filePosition, boxHeaderSize, boxContentBytes)
|
||||||
|
case BoxTypeMovieFragment:
|
||||||
|
box, err = ParseMovieFragmentBox(filePosition, boxHeaderSize, boxContentBytes)
|
||||||
|
case BoxTypeMovieFragmentHeader:
|
||||||
|
box = ParseMovieFragmentHeaderBox(filePosition, boxHeaderSize, boxContentBytes)
|
||||||
|
case BoxTypeTrackFragment:
|
||||||
|
box, err = ParseTrackFragmentBox(filePosition, boxHeaderSize, boxContentBytes)
|
||||||
|
case BoxTypeTrackFragmentHeader:
|
||||||
|
box = ParseTrackFragmentHeaderBox(filePosition, boxHeaderSize, boxContentBytes)
|
||||||
|
case BoxTypeTrackFragmentRun:
|
||||||
|
box, err = ParseTrackFragmentRunBox(filePosition, boxHeaderSize, boxContentBytes)
|
||||||
|
default:
|
||||||
|
logger.Println("unknown box type", boxType, "at", filePosition)
|
||||||
|
err = errors.New("unknown box type " + boxType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for box errors
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate end position
|
||||||
|
endPosition = filePosition + uint64(boxSize)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (box *Box) parseChildBoxes(filePosition uint64, content []byte) (subBoxes []interface{}, e error) {
|
||||||
|
byteReader := bytes.NewReader(content)
|
||||||
|
endPosition := filePosition + uint64(box.HeaderSize) + uint64(len(content))
|
||||||
|
var currentFilePosition uint64 = filePosition + uint64(box.HeaderSize)
|
||||||
|
for {
|
||||||
|
// parse next packet
|
||||||
|
currentBox, currentEndPosition, _, err := parseNextBox(byteReader, currentFilePosition)
|
||||||
|
if err != nil {
|
||||||
|
e = err
|
||||||
|
logger.Println("error at", currentEndPosition, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
currentFilePosition = currentEndPosition
|
||||||
|
|
||||||
|
// store packet
|
||||||
|
subBoxes = append(subBoxes, currentBox)
|
||||||
|
|
||||||
|
// check for end of content
|
||||||
|
if endPosition == currentEndPosition {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
83
Parser_test.go
Normal file
83
Parser_test.go
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
// Copyright 2024 Martin Riedl
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package gomp4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
// enable debug log
|
||||||
|
logger = verboseLogger
|
||||||
|
|
||||||
|
// run test
|
||||||
|
code := m.Run()
|
||||||
|
os.Exit(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParser(t *testing.T) {
|
||||||
|
parser, err := testFile(t, "test/fmp4.mp4")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check box count
|
||||||
|
if len(parser.Content) != 10 {
|
||||||
|
t.Error("invalid amount of boxes", len(parser.Content))
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate file type data
|
||||||
|
validateFileType(t, parser)
|
||||||
|
|
||||||
|
// TODO: validate movie fragment header data
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateFileType(t *testing.T, parser *Parser) {
|
||||||
|
// get first file type box
|
||||||
|
fileTypeBox := parser.Content[0].(*FileTypeBox)
|
||||||
|
|
||||||
|
// check major brand
|
||||||
|
if fileTypeBox.MajorBrand != "mp42" {
|
||||||
|
t.Error("invalid major brand", fileTypeBox.MajorBrand)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check minor brand
|
||||||
|
if fileTypeBox.MinorBrand != 0x00000001 {
|
||||||
|
t.Error("invalid minor brand", fileTypeBox.MinorBrand)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: check
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParserInvalidSampleCount(t *testing.T) {
|
||||||
|
if _, err := testFile(t, "test/fmp4-incorrect-sample-count.mp4"); err == nil {
|
||||||
|
t.Error("missing error of invalid sample count of track fragment run box")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testFile(t *testing.T, filePath string) (*Parser, error) {
|
||||||
|
// open test file
|
||||||
|
file, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// create new parser
|
||||||
|
parser := NewParser(file)
|
||||||
|
err = parser.Parse()
|
||||||
|
return parser, err
|
||||||
|
}
|
111
README.md
Normal file
111
README.md
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
# goMP4
|
||||||
|
[](https://gitlab.com/martinr92/gomp4/commits/main)
|
||||||
|
[](https://gitlab.com/martinr92/gomp4/commits/main)
|
||||||
|
|
||||||
|
mp4 implementation in golang based on spec ISO ICE 14496-12:2015
|
||||||
|
|
||||||
|
## Parser
|
||||||
|
```go
|
||||||
|
parser := gomp4.NewParser(file)
|
||||||
|
if err := parser.Parse(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Progress
|
||||||
|
Implementation progress
|
||||||
|
|
||||||
|
| Chapter | Box Types | Parser |
|
||||||
|
|----------------------------------------------------|----------------|-------:|
|
||||||
|
| 4.3 File Type Box | ftyp | 100% |
|
||||||
|
| 8.1.1 Media Data Box | mdat | 100% |
|
||||||
|
| 8.1.2 Free Space Box | free, skip | - |
|
||||||
|
| 8.1.3 Progressive Download Information Box | pdin | - |
|
||||||
|
| 8.2.1 Movie Box | moov | 100% |
|
||||||
|
| 8.2.2 Movie Header Box | mvhd | - |
|
||||||
|
| 8.3.1 Track Box | trak | - |
|
||||||
|
| 8.3.2 Track Header Box | tkhd | - |
|
||||||
|
| 8.3.3 Track Reference Box | tref | - |
|
||||||
|
| 8.3.4 Track Group Box | trgr | - |
|
||||||
|
| 8.4.1 Media Box | mdia | - |
|
||||||
|
| 8.4.2 Media Header Box | mdhd | - |
|
||||||
|
| 8.4.3 Handler Reference Box | hdlr | - |
|
||||||
|
| 8.4.4 Media Information Box | minf | - |
|
||||||
|
| 8.4.5.2 Null Media Header Box | nmhd | - |
|
||||||
|
| 8.4.6 Extended language tag | elng | - |
|
||||||
|
| 8.5.1 Sample Table Box | stbl | - |
|
||||||
|
| 8.5.2 Sample Description Box | stsd | - |
|
||||||
|
| 8.5.3 Degradation Priority Box | stdp | - |
|
||||||
|
| 8.6.1.2 Decoding Time to Sample Box | stts | - |
|
||||||
|
| 8.6.1.3 Composition Time to Sample Box | ctts | - |
|
||||||
|
| 8.6.1.4 Composition to Decode Box | cslg | - |
|
||||||
|
| 8.6.2 Sync Sample Box | stss | - |
|
||||||
|
| 8.6.3 Shadow Sync Sample Box | stsh | - |
|
||||||
|
| 8.6.4 Independent and Disposable Samples Box | sdtp | - |
|
||||||
|
| 8.6.5 Edit Box | edts | - |
|
||||||
|
| 8.6.6 Edit List Box | elst | - |
|
||||||
|
| 8.7.1 Data Information Box | dinf | - |
|
||||||
|
| 8.7.2 Data Reference Box | dref, url, urn | - |
|
||||||
|
| 8.7.3 Sample Size Boxes | stsz, stz2 | - |
|
||||||
|
| 8.7.4 Sample To Chunk Box | stsc | - |
|
||||||
|
| 8.7.5 Chunk Offset Box | stco, co64 | - |
|
||||||
|
| 8.7.6 Padding Bits Box | padb | - |
|
||||||
|
| 8.7.7 Sub-Sample Information Box | subs | - |
|
||||||
|
| 8.7.8 Sample Auxiliary Information Sizes Box | saiz | - |
|
||||||
|
| 8.7.9 Sample Auxiliary Information Offsets Box | saio | - |
|
||||||
|
| 8.8.1 Movie Extends Box | mvex | - |
|
||||||
|
| 8.8.2 Movie Extends Header Box | mehd | - |
|
||||||
|
| 8.8.3 Track Extends Box | trex | - |
|
||||||
|
| 8.8.4 Movie Fragment Box | moof | 100% |
|
||||||
|
| 8.8.5 Movie Fragment Header Box | mfhd | 100% |
|
||||||
|
| 8.8.6 Track Fragment Box | traf | 100% |
|
||||||
|
| 8.8.7 Track Fragment Header Box | tfhd | 20% |
|
||||||
|
| 8.8.8 Track Fragment Run Box | traf | 100% |
|
||||||
|
| 8.8.9 Movie Fragment Random Access Box | mfra | - |
|
||||||
|
| 8.8.10 Track Fragment Random Access Box | tfra | - |
|
||||||
|
| 8.8.11 Movie Fragment Random Access Offset Box | mfro | - |
|
||||||
|
| 8.8.12 Track fragment decode time | tfdt | - |
|
||||||
|
| 8.8.13 Level Assignment Box | leva | - |
|
||||||
|
| 8.8.15 Track Extension Properties Box | trep | - |
|
||||||
|
| 8.8.16 Alternative Startup Sequence Properties Box | assp | - |
|
||||||
|
| 8.9.2 Sample to Group Box | sbgp | - |
|
||||||
|
| 8.9.3 Sample Group Description Box | sgpd | - |
|
||||||
|
| 8.10.1 User Data Box | udta | - |
|
||||||
|
| 8.10.2 Copyright Box | cprt | - |
|
||||||
|
| 8.10.3 Track Selection Box | tsel | - |
|
||||||
|
| 8.10.4 Track kind | kind | - |
|
||||||
|
| 8.11.1 The Meta box | meta | - |
|
||||||
|
| 8.11.2 XML Boxes | xml, bxml | - |
|
||||||
|
| 8.11.3 The Item Location Box | iloc | - |
|
||||||
|
| 8.11.4 Primary Item Box | pitm | - |
|
||||||
|
| 8.11.5 Item Protection Box | ipro | - |
|
||||||
|
| 8.11.6 Item Information Box | iinf | - |
|
||||||
|
| 8.11.7 Additional Metadata Container Box | meco | - |
|
||||||
|
| 8.11.8 Metabox Relation Box | mere | - |
|
||||||
|
| 8.11.11 Item Data Box | idat | - |
|
||||||
|
| 8.11.12 Item Reference Box | iref | - |
|
||||||
|
| 8.12.1 Protection Scheme Information Box | sinf | - |
|
||||||
|
| 8.12.2 Original Format Box | frma | - |
|
||||||
|
| 8.12.5 Scheme Type Box | schm | - |
|
||||||
|
| 8.12.6 Scheme Information Box | schi | - |
|
||||||
|
| 8.13.2 FD Item Information Box | fiin | - |
|
||||||
|
| 8.13.3 File Partition Box | fpar | - |
|
||||||
|
| 8.13.4 FEC Reservoir Box | fecr | - |
|
||||||
|
| 8.13.5 FD Session Group Box | segr | - |
|
||||||
|
| 8.13.6 Group ID to Name Box | gitn | - |
|
||||||
|
| 8.13.7 File Reservoir Box | fire | - |
|
||||||
|
| 8.14.3 Sub Track box | strk | - |
|
||||||
|
| 8.14.4 Sub Track Information box | stri | - |
|
||||||
|
| 8.14.5 Sub Track Definition box | strd | - |
|
||||||
|
| 8.14.6 Sub Track Sample Group box | stsg | - |
|
||||||
|
| 8.15.3 Restricted Scheme Information box | rinf | - |
|
||||||
|
| 8.15.4.2 Stereo video box | stvi | - |
|
||||||
|
| 8.16.2 Segment Type Box | styp | - |
|
||||||
|
| 8.16.3 Segment Index Box | sidx | - |
|
||||||
|
| 8.16.4 Subsegment Index Box | ssix | - |
|
||||||
|
| 8.16.5 Producer Reference Time Box | prft | - |
|
||||||
|
| 8.17.3 Complete Track Information Box | cinf | - |
|
||||||
|
| 9.1.2.1 SRTP Process box | srpp | - |
|
||||||
|
|
||||||
|
## Helper Tools
|
||||||
|
http://mp4parser.com/
|
44
TrackFragmentBox.go
Normal file
44
TrackFragmentBox.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// Copyright 2024 Martin Riedl
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package gomp4
|
||||||
|
|
||||||
|
// TrackFragmentBox Track Fragment Box struct
|
||||||
|
//
|
||||||
|
// 8.8.6 Track Fragment Box
|
||||||
|
// Box Type: ‘traf’
|
||||||
|
// Container: Movie Fragment Box ('moof')
|
||||||
|
// Mandatory: No
|
||||||
|
// Quantity: Zero or more
|
||||||
|
//
|
||||||
|
// Within the movie fragment there is a set of track fragments, zero or more per track. The track fragments
|
||||||
|
// in turn contain zero or more track runs, each of which document a contiguous run of samples for that
|
||||||
|
// track. Within these structures, many fields are optional and can be defaulted.
|
||||||
|
//
|
||||||
|
// It is possible to add 'empty time' to a track using these structures, as well as adding samples. Empty
|
||||||
|
// inserts can be used in audio tracks doing silence suppression, for example.
|
||||||
|
type TrackFragmentBox struct {
|
||||||
|
*Box
|
||||||
|
ChildBoxes []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseTrackFragmentBox creates a new Track Fragment Box struct
|
||||||
|
func ParseTrackFragmentBox(filePosition uint64, headerSize uint32, content []byte) (*TrackFragmentBox, error) {
|
||||||
|
box := &TrackFragmentBox{Box: &Box{filePosition, headerSize}}
|
||||||
|
|
||||||
|
// parse child boxes
|
||||||
|
var err error
|
||||||
|
box.ChildBoxes, err = box.parseChildBoxes(filePosition, content)
|
||||||
|
return box, err
|
||||||
|
}
|
88
TrackFragmentHeaderBox.go
Normal file
88
TrackFragmentHeaderBox.go
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
// Copyright 2024 Martin Riedl
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package gomp4
|
||||||
|
|
||||||
|
import "encoding/binary"
|
||||||
|
|
||||||
|
// TrackFragmentHeaderBox Track Fragment Header
|
||||||
|
//
|
||||||
|
// 8.8.7 Track Fragment Header Box
|
||||||
|
// Box Type: ‘tfhd’
|
||||||
|
// Container: Track Fragment Box ('traf')
|
||||||
|
// Mandatory: Yes
|
||||||
|
// Quantity: Exactly one
|
||||||
|
//
|
||||||
|
// Each movie fragment can add zero or more fragments to each track; and a track fragment can add zero
|
||||||
|
// or more contiguous runs of samples. The track fragment header sets up information and defaults used
|
||||||
|
// for those runs of samples.
|
||||||
|
//
|
||||||
|
// The base‐data‐offset, if explicitly provided, is a data offset that is identical to a chunk offset in the Chunk
|
||||||
|
// Offset Box, i.e. applying to the complete file (e.g. starting with a file‐type box and movie box). In
|
||||||
|
// circumstances when the complete file does not exist or its size is unknown, it may be impossible to use
|
||||||
|
// an explicit base‐data‐offset; then, offsets need to be established relative to the movie fragment.
|
||||||
|
type TrackFragmentHeaderBox struct {
|
||||||
|
*FullBox
|
||||||
|
TrackID uint32
|
||||||
|
// the base offset to use when calculating data offsets
|
||||||
|
BaseDataOffset uint64
|
||||||
|
SampleDescriptionIndex uint32
|
||||||
|
DefaultSampleDuration uint32
|
||||||
|
DefaultSampleSize uint32
|
||||||
|
DefaultSampleFlags uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// TrackFragmentHeaderBoxFlagBaseDataOffsetPresent Base Offset Present Flag
|
||||||
|
//
|
||||||
|
// indicates the presence of the base‐data‐offset field. This
|
||||||
|
// provides an explicit anchor for the data offsets in each track run (see below). If not provided and
|
||||||
|
// if the default‐base‐is‐moof flag is not set, the base‐data‐offset for the first track in the movie
|
||||||
|
// fragment is the position of the first byte of the enclosing Movie Fragment Box, and for second
|
||||||
|
// and subsequent track fragments, the default is the end of the data defined by the preceding
|
||||||
|
// track fragment. Fragments 'inheriting' their offset in this way must all use the same data‐
|
||||||
|
// reference (i.e., the data for these tracks must be in the same file)
|
||||||
|
TrackFragmentHeaderBoxFlagBaseDataOffsetPresent uint32 = 0x00000001
|
||||||
|
|
||||||
|
// TrackFragmentHeaderBoxFlagSampleDescriptionIndexPresent Sample Description Index Present Flag
|
||||||
|
//
|
||||||
|
// indicates the presence of this field, which over‐rides,
|
||||||
|
// in this fragment, the default set up in the Track Extends Box.
|
||||||
|
TrackFragmentHeaderBoxFlagSampleDescriptionIndexPresent uint32 = 0x00000002
|
||||||
|
|
||||||
|
// TrackFragmentHeaderBoxFlagDefaultSampleDurationPresent Default Sample Duration Present Flag
|
||||||
|
TrackFragmentHeaderBoxFlagDefaultSampleDurationPresent uint32 = 0x00000008
|
||||||
|
|
||||||
|
// TrackFragmentHeaderBoxFlagDefaultSampleSize Default Sample Size Present Flag
|
||||||
|
TrackFragmentHeaderBoxFlagDefaultSampleSize uint32 = 0x00000010
|
||||||
|
|
||||||
|
// TrackFragmentHeaderBoxFlagDefaultSampleFlags Default Sample Flags Present Flag
|
||||||
|
TrackFragmentHeaderBoxFlagDefaultSampleFlags uint32 = 0x00000020
|
||||||
|
|
||||||
|
// TODO: define other flags
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseTrackFragmentHeaderBox creates a new track fragment header box struct
|
||||||
|
func ParseTrackFragmentHeaderBox(filePosition uint64, headerSize uint32, content []byte) *TrackFragmentHeaderBox {
|
||||||
|
box := &TrackFragmentHeaderBox{FullBox: newFullBox(&Box{filePosition, headerSize}, content[0:4])}
|
||||||
|
position := 4
|
||||||
|
|
||||||
|
// parse track ID
|
||||||
|
box.TrackID = binary.BigEndian.Uint32(content[position : position+4])
|
||||||
|
position += 4
|
||||||
|
|
||||||
|
// TODO: check other flags
|
||||||
|
|
||||||
|
return box
|
||||||
|
}
|
163
TrackFragmentRunBox.go
Normal file
163
TrackFragmentRunBox.go
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
// Copyright 2024 Martin Riedl
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package gomp4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TrackFragmentRunBox Track Fragment Run Box struct
|
||||||
|
//
|
||||||
|
// 8.8.8 Track Fragment Run Box
|
||||||
|
// Box Type: ‘trun’
|
||||||
|
// Container: Track Fragment Box ('traf')
|
||||||
|
// Mandatory: No
|
||||||
|
// Quantity: Zero or more
|
||||||
|
//
|
||||||
|
// Within the Track Fragment Box, there are zero or more Track Run Boxes. If the duration‐is‐empty flag is
|
||||||
|
// set in the tf_flags, there are no track runs. A track run documents a contiguous set of samples for a track.
|
||||||
|
//
|
||||||
|
// The number of optional fields is determined from the number of bits set in the lower byte of the flags,
|
||||||
|
// and the size of a record from the bits set in the second byte of the flags. This procedure shall be
|
||||||
|
// followed, to allow for new fields to be defined.
|
||||||
|
//
|
||||||
|
// If the data‐offset is not present, then the data for this run starts immediately after the data of the
|
||||||
|
// previous run, or at the base‐data‐offset defined by the track fragment header if this is the first run in a
|
||||||
|
// track fragment, If the data‐offset is present, it is relative to the base‐data‐offset established in the track
|
||||||
|
// fragment header.
|
||||||
|
//
|
||||||
|
// The composition offset values in the composition time‐to‐sample box and in the track run box may be
|
||||||
|
// signed or unsigned. The recommendations given in the composition time‐to‐sample box concerning the
|
||||||
|
// use of signed composition offsets also apply here.
|
||||||
|
type TrackFragmentRunBox struct {
|
||||||
|
*FullBox
|
||||||
|
// the number of samples being added in this run; also the number of rows in the
|
||||||
|
// following table (the rows can be empty)
|
||||||
|
SampleCount uint32
|
||||||
|
// is added to the implicit or explicit data_offset established in the track fragment header.
|
||||||
|
DataOffset int32
|
||||||
|
// provides a set of flags for the first sample only of this run.
|
||||||
|
FirstSampleFlags uint32
|
||||||
|
Samples []TrackFragmentRunBoxSample
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// TrackFragmentRunBoxFlagDataOffsetPresent Data Offset Flag
|
||||||
|
TrackFragmentRunBoxFlagDataOffsetPresent uint32 = 0x00000001
|
||||||
|
|
||||||
|
// TrackFragmentRunBoxFlagFirstSampleFlagsPresent First Sample Flags
|
||||||
|
// this over‐rides the default flags for the first sample only. This
|
||||||
|
// makes it possible to record a group of frames where the first is a key and the rest are difference
|
||||||
|
// frames, without supplying explicit flags for every sample. If this flag and field are used, sample‐
|
||||||
|
// flags shall not be present.
|
||||||
|
TrackFragmentRunBoxFlagFirstSampleFlagsPresent uint32 = 0x00000004
|
||||||
|
|
||||||
|
// TrackFragmentRunBoxFlagSampleDurationPresent Sample Duration Flag
|
||||||
|
// indicates that each sample has its own duration, otherwise the default is used.
|
||||||
|
TrackFragmentRunBoxFlagSampleDurationPresent uint32 = 0x00000100
|
||||||
|
|
||||||
|
// TrackFragmentRunBoxFlagSampleSizePresent Sample Size Flag
|
||||||
|
// each sample has its own size, otherwise the default is used.
|
||||||
|
TrackFragmentRunBoxFlagSampleSizePresent uint32 = 0x00000200
|
||||||
|
|
||||||
|
// TrackFragmentRunBoxFlagSampleFlagsPresent Sample Flags Present Flag
|
||||||
|
// each sample has its own flags, otherwise the default is used.
|
||||||
|
TrackFragmentRunBoxFlagSampleFlagsPresent uint32 = 0x00000400
|
||||||
|
|
||||||
|
// TrackFragmentRunBoxFlagSampleCompositionTimeOffsetPresent Sample Composition Time Offset
|
||||||
|
// each sample has a composition time offset
|
||||||
|
// (e.g. as used for I/P/B video in MPEG).
|
||||||
|
TrackFragmentRunBoxFlagSampleCompositionTimeOffsetPresent uint32 = 0x00000800
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseTrackFragmentRunBox creates a new Track Fragment Run Box struct
|
||||||
|
func ParseTrackFragmentRunBox(filePosition uint64, headerSize uint32, content []byte) (*TrackFragmentRunBox, error) {
|
||||||
|
// build full box with additional 4 bytes header
|
||||||
|
fullBox := newFullBox(&Box{filePosition, headerSize}, content[0:4])
|
||||||
|
position := 4
|
||||||
|
|
||||||
|
// create track fragment run box
|
||||||
|
box := &TrackFragmentRunBox{FullBox: fullBox}
|
||||||
|
|
||||||
|
// parse sample counter
|
||||||
|
box.SampleCount = binary.BigEndian.Uint32(content[position : position+4])
|
||||||
|
position += 4
|
||||||
|
|
||||||
|
// parse data offset
|
||||||
|
if fullBox.flagUInt32&TrackFragmentRunBoxFlagDataOffsetPresent == TrackFragmentRunBoxFlagDataOffsetPresent {
|
||||||
|
box.DataOffset = int32(binary.BigEndian.Uint32(content[position : position+4]))
|
||||||
|
position += 4
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse first sample flags
|
||||||
|
if fullBox.flagUInt32&TrackFragmentRunBoxFlagFirstSampleFlagsPresent == TrackFragmentRunBoxFlagFirstSampleFlagsPresent {
|
||||||
|
box.FirstSampleFlags = binary.BigEndian.Uint32(content[position : position+4])
|
||||||
|
position += 4
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse samples
|
||||||
|
for i := uint32(0); i < box.SampleCount; i++ {
|
||||||
|
sample := TrackFragmentRunBoxSample{}
|
||||||
|
|
||||||
|
// parse duration
|
||||||
|
if fullBox.flagUInt32&TrackFragmentRunBoxFlagSampleDurationPresent == TrackFragmentRunBoxFlagSampleDurationPresent {
|
||||||
|
sample.Duration = binary.BigEndian.Uint32(content[position : position+4])
|
||||||
|
position += 4
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse size
|
||||||
|
if fullBox.flagUInt32&TrackFragmentRunBoxFlagSampleSizePresent == TrackFragmentRunBoxFlagSampleSizePresent {
|
||||||
|
sample.Size = binary.BigEndian.Uint32(content[position : position+4])
|
||||||
|
position += 4
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse flags
|
||||||
|
if fullBox.flagUInt32&TrackFragmentRunBoxFlagSampleFlagsPresent == TrackFragmentRunBoxFlagSampleFlagsPresent {
|
||||||
|
sample.Flags = binary.BigEndian.Uint32(content[position : position+4])
|
||||||
|
position += 4
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse composition time offset
|
||||||
|
if fullBox.flagUInt32&TrackFragmentRunBoxFlagSampleCompositionTimeOffsetPresent == TrackFragmentRunBoxFlagSampleCompositionTimeOffsetPresent {
|
||||||
|
if fullBox.Version == 0 {
|
||||||
|
sample.CompositionTimeOffsetV0 = binary.BigEndian.Uint32(content[position : position+4])
|
||||||
|
position += 4
|
||||||
|
} else {
|
||||||
|
sample.CompositionTimeOffset = int32(binary.BigEndian.Uint32(content[position : position+4]))
|
||||||
|
position += 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// store new sample
|
||||||
|
box.Samples = append(box.Samples, sample)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check, if everything has been parsed
|
||||||
|
if position != len(content) {
|
||||||
|
return nil, fmt.Errorf("invalid byte position %d; expected position %d in file box position %d", position, len(content), filePosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
return box, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrackFragmentRunBoxSample contains sample information
|
||||||
|
type TrackFragmentRunBoxSample struct {
|
||||||
|
Duration uint32
|
||||||
|
Size uint32
|
||||||
|
Flags uint32
|
||||||
|
CompositionTimeOffsetV0 uint32
|
||||||
|
CompositionTimeOffset int32
|
||||||
|
}
|
3
go.mod
Normal file
3
go.mod
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module gitlab.com/martinr92/gomp4
|
||||||
|
|
||||||
|
go 1.15
|
BIN
specification/ISO_IEC_14496-12_2015.pdf
Normal file
BIN
specification/ISO_IEC_14496-12_2015.pdf
Normal file
Binary file not shown.
BIN
test/fmp4-incorrect-sample-count.mp4
Normal file
BIN
test/fmp4-incorrect-sample-count.mp4
Normal file
Binary file not shown.
BIN
test/fmp4.mp4
Normal file
BIN
test/fmp4.mp4
Normal file
Binary file not shown.
Loading…
Add table
Reference in a new issue