Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@ The format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/
Version numbers follow [Semantic Versioning](https://semver.org/) and track
the CLI; the library target follows the same numbering.

## [3.1.2] — 2026-11-01

### Fixed

- MXF picture descriptors carrying SMPTE RP 2008 AVC Long GOP labels
(`04.01.02.02.01.31.xx.xx`, e.g. Sony XAVC Long and bmx/FFmpeg-written
H.264 MXF) now report codec `avc1`, and the profile from the label's
profile byte (e.g. `High 4:2:2`), instead of no codec.
- MXF MPEG-2 MP@HL and 422P@HL labels (`04.01.02.02.01.03.xx` /
`01.04.xx`, e.g. XDCAM HD and HD422) now report codec `mpeg2video`.

## [3.1.1] — 2026-10-31

### Fixed
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,11 @@ swift-exif --version
```

Or download the archive and checksum directly from the
[3.1.1 release](https://github.com/aagedal/SwiftMediaMetadata/releases/tag/3.1.1):
[3.1.2 release](https://github.com/aagedal/SwiftMediaMetadata/releases/tag/3.1.2):

```sh
curl -LO https://github.com/aagedal/SwiftMediaMetadata/releases/download/3.1.1/swift-exif-macos-arm64.tar.gz
curl -LO https://github.com/aagedal/SwiftMediaMetadata/releases/download/3.1.1/swift-exif-macos-arm64.tar.gz.sha256
curl -LO https://github.com/aagedal/SwiftMediaMetadata/releases/download/3.1.2/swift-exif-macos-arm64.tar.gz
curl -LO https://github.com/aagedal/SwiftMediaMetadata/releases/download/3.1.2/swift-exif-macos-arm64.tar.gz.sha256
shasum -a 256 -c swift-exif-macos-arm64.tar.gz.sha256
tar -xzf swift-exif-macos-arm64.tar.gz
./swift-exif-macos-arm64/swift-exif --version
Expand All @@ -83,7 +83,7 @@ Add the package to your `Package.swift` with:

```swift
dependencies: [
.package(url: "https://github.com/aagedal/SwiftMediaMetadata.git", from: "3.1.1"),
.package(url: "https://github.com/aagedal/SwiftMediaMetadata.git", from: "3.1.2"),
]
```

Expand Down
13 changes: 7 additions & 6 deletions RELEASE_CHECKLIST.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# SwiftMediaMetadata 3.1.1 release checklist
# SwiftMediaMetadata 3.1.2 release checklist

- [x] ProRes 4444 / 4444 XQ alpha follows the sample-entry depth (32 alpha,
24 opaque), falling back to the first frame header's alpha_channel_type.
- [x] Verified against ffmpeg-encoded `ap4h` / `ap4x` MOVs with and without
alpha, including copies with the depth zeroed: `pixelFormat` matches ffprobe.
- [x] Set CLI version, README and changelog to 3.1.1.
- [x] MXF AVC Long GOP picture coding labels (`04.01.02.02.01.31.xx.xx`,
SMPTE RP 2008) map to `avc1` with the profile from byte 14; MPEG-2 MP@HL /
422P@HL labels (`01.03.xx` / `01.04.xx`) map to `mpeg2video`.
- [x] Verified against Sony/Resolve XAVC Long 4:2:2 10-bit MXF (25p, 50i,
59.94p) and XDCAM HD422 MXF: codec and profile match ffprobe.
- [x] Set CLI version, README and changelog to 3.1.2.
- [x] Run complete release preflight on the final release tree.
- [ ] Merge the reviewed release changes into `main` before tagging. The release
workflow rejects tags whose commits are not in `main`'s history.
Expand Down
2 changes: 1 addition & 1 deletion Sources/CLI/SwiftMediaMetadata.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ struct SwiftMediaMetadataCLI: ParsableCommand {
static let configuration = CommandConfiguration(
commandName: "swift-exif",
abstract: "Read, write, and manipulate media metadata.",
version: "3.1.1",
version: "3.1.2",
subcommands: [
ReadCommand.self,
WriteCommand.self,
Expand Down
22 changes: 21 additions & 1 deletion Sources/SwiftMediaMetadata/Video/MXFReader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,10 @@ public struct MXFReader: Sendable {
// 0x2? — High 10 Intra (class 50/100/200, 4:2:0 10-bit)
// 0x3? — High 4:2:2 Intra (class 50/100/200, 10-bit)
// We fold all subclasses into the top-level ffprobe name.
// Long GOP AVC labels (variant 0x31) carry the profile in
// byte 14 per SMPTE RP 2008. 0x11 is left unmapped: bmx
// registers it as Constrained Baseline but FFmpeg writes it
// for any Baseline stream.
if value.count >= 16, stream.profile == nil {
let kind = value[value.startIndex + 11]
let variant = value[value.startIndex + 13]
Expand All @@ -893,6 +897,17 @@ public struct MXFReader: Sendable {
case 0x30: stream.profile = "High 4:2:2 Intra"
default: break
}
} else if kind == 0x02, variant == 0x31 {
switch byte14 {
case 0x10: stream.profile = "Baseline"
case 0x20: stream.profile = "Main"
case 0x30: stream.profile = "Extended"
case 0x40: stream.profile = "High"
case 0x50: stream.profile = "High 10"
case 0x60: stream.profile = "High 4:2:2"
case 0x70: stream.profile = "High 4:4:4 Predictive"
default: break
}
}
}
case 0x3301: // ComponentDepth (UInt32)
Expand Down Expand Up @@ -1161,8 +1176,13 @@ public struct MXFReader: Sendable {
if kind == 0x02 {
let byte14 = ul[s + 14]
switch variant {
case 0x32: // AVC in MXF (SMPTE ST 381-3 / RP 2008)
case 0x31: // AVC Long GOP profiles (SMPTE RP 2008), e.g. Sony XAVC Long
return ("avc1", "H.264 / AVC")
case 0x32: // AVC Intra profiles (SMPTE ST 381-3 / RP 2008)
return ("avc1", "H.264 / AVC")
case 0x03 where ul[s + 12] == 0x01, // MPEG-2 MP@HL (e.g. XDCAM HD Long GOP)
0x04 where ul[s + 12] == 0x01: // MPEG-2 422P@HL (e.g. XDCAM HD422)
return ("mpeg2video", "MPEG-2 Video")
case 0x33: // HEVC in MXF (SMPTE ST 2067-51)
return ("hvc1", "H.265 / HEVC")
case 0x0A: // JPEG 2000 in MXF (SMPTE ST 422)
Expand Down
55 changes: 55 additions & 0 deletions Tests/SwiftMediaMetadataTests/Video/MXFReaderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,61 @@ final class MXFReaderTests: XCTestCase {
XCTAssertEqual(stream.profile, "High 4:2:2 Intra")
}

func testParsePictureDescriptorDetectsXAVCLongGOPHigh422() {
// Sony XAVC Long 4:2:2 10-bit (camera/Resolve-written) — RP 2008
// AVC Long GOP label urn:smpte:ul:060e2b34.0401010d.04010202.01316001.
let ul = Data([
0x06, 0x0E, 0x2B, 0x34, 0x04, 0x01, 0x01, 0x0D,
0x04, 0x01, 0x02, 0x02, 0x01, 0x31, 0x60, 0x01,
])
let body = localTagLV(0x3201, value: ul)
var stream = VideoStream(index: 0)
var duration: TimeInterval? = nil
MXFReader.parsePictureDescriptor(body, into: &stream, duration: &duration)
XCTAssertEqual(stream.codec, "avc1")
XCTAssertEqual(stream.codecName, "H.264 / AVC")
XCTAssertEqual(stream.profile, "High 4:2:2")
}

func testParsePictureDescriptorDetectsBMXAVCLongGOPHigh() {
// bmx/FFmpeg-written registry version 0x0A, High profile.
let ul = Data([
0x06, 0x0E, 0x2B, 0x34, 0x04, 0x01, 0x01, 0x0A,
0x04, 0x01, 0x02, 0x02, 0x01, 0x31, 0x40, 0x01,
])
let body = localTagLV(0x3201, value: ul)
var stream = VideoStream(index: 0)
var duration: TimeInterval? = nil
MXFReader.parsePictureDescriptor(body, into: &stream, duration: &duration)
XCTAssertEqual(stream.codec, "avc1")
XCTAssertEqual(stream.profile, "High")
}

func testParsePictureDescriptorLeavesAmbiguousAVCBaselineProfileUnset() {
let ul = pictureCodingUL(kind: 0x02, variant: 0x31, byte14: 0x11)
let body = localTagLV(0x3201, value: ul)
var stream = VideoStream(index: 0)
var duration: TimeInterval? = nil
MXFReader.parsePictureDescriptor(body, into: &stream, duration: &duration)
XCTAssertEqual(stream.codec, "avc1")
XCTAssertNil(stream.profile)
}

func testParsePictureDescriptorMapsMPEG2HighLevelLongGOPULs() {
// RP 224 MPEG-2 MP@HL / 422P@HL Long GOP (XDCAM HD / HD422).
for variant: UInt8 in [0x03, 0x04] {
let ul = Data([
0x06, 0x0E, 0x2B, 0x34, 0x04, 0x01, 0x01, 0x03,
0x04, 0x01, 0x02, 0x02, 0x01, variant, 0x03, 0x00,
])
let body = localTagLV(0x3201, value: ul)
var stream = VideoStream(index: 0)
var duration: TimeInterval? = nil
MXFReader.parsePictureDescriptor(body, into: &stream, duration: &duration)
XCTAssertEqual(stream.codec, "mpeg2video", "variant \(variant)")
}
}

// MARK: - C. Picture descriptor — dimensions, frame rate, duration

func testParsePictureDescriptorReadsStoredAndDisplayDimensions() {
Expand Down
Loading