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