diff --git a/.cursorrules b/.cursorrules index aea2d0c..c62960a 100644 --- a/.cursorrules +++ b/.cursorrules @@ -4,6 +4,8 @@ OpenFCPXMLKit is a modern, fully modular Swift 6 framework for Final Cut Pro FCP Keep this file in sync with AGENT.md. Both should describe the same overview, architecture, test structure, and conventions. When you update one, update the other. +**Hard constraints:** [GUARDRAILS.md](GUARDRAILS.md) — must / must-not for layers, naming, FCPXML compatibility, reporting honesty, and fixtures. Prefer GUARDRAILS for “what not to do”; [ARCHITECTURE.md](ARCHITECTURE.md) for structure and diagrams. + **Naming:** Use OpenFCPXMLKit naming exclusively in all code, documentation, comments, and agent files (`ServiceLogger`, `createService()`, `OFKXML*` types). Do not use legacy project names or identifiers from prior forks. Never use the terms "PBF" or "Production's Best Friend" in source code, code comments, symbol names, or CLI/log output; describe the reporting feature neutrally (e.g. "Excel report", "PDF report", "role inventory report", "workbook export"). Those terms may appear only in prose documentation (README, CHANGELOG, Manual, and these agent guides) — never in the codebase itself. --- @@ -40,7 +42,7 @@ OpenFCPXMLKit targets macOS 26+, iOS 26+, Xcode 26+, and Swift 6.3 with full con **Backward compatibility:** The entire codebase must remain backward compatible with FCPXML 1.5. Optional attributes and elements introduced in later versions (e.g. 1.11, 1.13) must be omitted or ignored when reading/writing or converting to 1.5; mark such features in code comments with the minimum FCPXML version (e.g. `FCPXML 1.13+`). -Current status: **1076** tests listed in `swift test --list-tests` (**1072** in `OpenFCPXMLKitTests`: 1069 XCTest + 3 Swift Testing `@Test`; plus **4** optional `ExcelReportTest` integration tests); FCPXML versions 1.5–1.14 supported (DTDs included, full parsing, typed element-type coverage for all DTD elements via FCPXMLElementType); Final Cut Pro frame rates (23.976, 24, 25, 29.97, 30, 50, 59.94, 60); thread-safe and concurrency-compliant with comprehensive async/await support; no known security vulnerabilities. Version conversion automatically drops elements not in the target version’s DTD (e.g. adjust-colorConform, adjust-stereo-3D); DTD validation runs per version (validateDocumentAgainstDTD, validateDocumentAgainstDeclaredVersion) and after CLI convert. FCPXMLVersion.supportsBundleFormat is true for 1.10+ (.fcpxmld bundle); 1.5–1.9 support only single-file .fcpxml. FCPXML creation: create FCPXML documents from scratch with events, projects, resources, and clips via XMLDocumentManager, XMLDocument initializers, or FCPXMLService. Timeline manipulation: ripple insert (shifts subsequent clips), auto lane assignment, clip queries (by lane, time range, asset ID), lane range computation, secondary storylines. Timeline metadata: markers, chapter markers, keywords, ratings, custom metadata, timestamps (createdAt, modifiedAt). FCPXMLTimecode: custom timecode type (arithmetic, frame alignment, CMTime conversion, FCPXML string parsing). MIME type detection, asset validation, silence detection, asset duration measurement, parallel file I/O, still image asset support. TimelineFormat enhancements: presets (hd720p, dci4K, hd1080i, hd720i), computed properties (aspectRatio, isHD, isUHD, interlaced). Typed adjustment models: Crop, Transform, Blend, Stabilization, Volume, Loudness, NoiseReduction, HumReduction, Equalization, MatchEqualization, Transform360, ColorConform, Stereo3D, VoiceIsolation with full clip integration. Typed effect/filter models: VideoFilter, AudioFilter, VideoFilterMask with FilterParameter support and keyframe animation (auxValue support FCPXML 1.11+). Typed caption/title models: Caption and Title with TextStyle and TextStyleDefinition for full text formatting. SmartCollection models: SmartCollection with match-clip, match-media, match-ratings, match-text, match-usage (1.9+), match-representation (1.10+), match-markers (1.10+), match-analysis-type (1.14). Keyframe animation: KeyframeAnimation, Keyframe with interpolation types, FadeIn/FadeOut with fade types, integrated with FilterParameter. CMTime Codable extension: Direct CMTime encoding/decoding as FCPXML time strings. Collection organization: CollectionFolder and KeywordCollection models for organizing clips and media. Live Drawing (FCPXML 1.11+): LiveDrawing model for live-drawing story elements. HiddenClipMarker (FCPXML 1.13+): HiddenClipMarker model for hidden clip markers. Format/Asset 1.13+: Format heroEye, Asset heroEyeOverride, Asset mediaReps (multiple media-rep). Cross-platform XML abstraction: protocol layer (OFKXMLNode, OFKXMLElement, OFKXMLDocument, OFKXMLFactory); Foundation backend on macOS; AEXML backend on iOS; OFKXMLDefaultFactory() for platform dispatch; FCPXMLStructuralValidator for cross-platform structural validation; FCPXMLDTDValidator platform-conditional (full DTD on macOS, structural fallback on iOS). Comprehensive test coverage: **1076** tests across 59 FCPXML sample files including 360 video, auditions, conform-rate, still images, multicam, secondary storylines, audio keyframes (FCPXMLAudioKeyframeTests: adjust-volume param keyframeAnimation parsing, decibel/time validation, fadeIn/fadeOut integration, secondary storyline detection), keyword collections/folders, Photoshop integration, smart collections, and reporting column layout/exclusion/disabled-clip/workbook/PDF formatting tests. Excel and PDF reporting: multi-sheet `.xlsx` workbooks via `FinalCutPro.FCPXML.buildReport(options:)` (ReportBuilder, ReportOptions presets, ReportExcelExport on XLKit) and optional `.pdf` via `ReportPDFExport` (CoreGraphics; cover page with black “About This PDF Export” + `info.circle`, TOC with accent colour chips + content-tint washes, per-sheet tints, column-width expansion after exclusions, section pagination; same Report configuration as Excel); sheets for Role Inventory (**Selected Roles Inventory** + per-role sheets with expanded column layout and dynamic metadata keys), Markers, Keywords, Titles & Generators, Transitions, Video & Audio Effects, Speed Change Effects, **Summary** (project title in **B1**, narrow Row column A, black role-duration data), and **Media Summary** (Row + red missing-media paths); 1-based **Row** on all tabular Excel/PDF sheets by default (`ensuringRowColumn` / `allowsInjectedRowColumn`); inventory and section-sheet cell formatting, role exclusions, global column exclusion (`ReportColumn` / `excludedColumns`, including `ReportColumn.row`), disabled-clip filtering (`excludeDisabledClips`), project-name / compound-clip-name filtering (`allReportTimelineSources()`; standalone compound-clip exports without ``), `ReportTimecodeFormat` / `--timecode-format`, inventory-first `ReportBuildPhase` progress callbacks; optional `copyrightLabel` / CLI `--label-copyright` (Excel cover **A2**; PDF cover + footer centre). Extraction presets: Captions, Effects, FrameData, Markers, Roles, Titles. Experimental CLI (OpenFCPXMLKit-CLI): single binary with embedded DTDs; --check-version, --convert-version (stripping + DTD validation), --extension-type (fcpxmld | fcpxml; default fcpxmld; 1.5–1.9 always .fcpxml), --validate, --media-copy, --create-project (new empty FCPXML project: --width, --height, --rate, --project-version, output-dir; DTD validation before write; FCP-style output with DOCTYPE, colorSpace, default smart collections), --report (Excel report: role inventory by default; --report-full, per-section flags including --report-markers, --report-keywords, --report-titles-generators, --report-transitions, --report-effects, --report-speed-change-effects, --report-summary, --report-media-summary, --media-resolution, --media-summary-distinguish-proxy, --exclude-role, --exclude-column, --exclude-disabled-clips, --timecode-format, --report-project, --label-copyright, --create-pdf); --log writes user-visible output for all commands to the log file; see Sources/OpenFCPXMLKitCLI/README.md. +Current status: **1084** tests listed in `swift test --list-tests` (**1078** in `OpenFCPXMLKitTests`: 1075 XCTest + 3 Swift Testing `@Test`; plus **6** optional `ExcelReportTest` integration tests); FCPXML versions 1.5–1.14 supported (DTDs included, full parsing, typed element-type coverage for all DTD elements via FCPXMLElementType); Final Cut Pro frame rates (23.976, 24, 25, 29.97, 30, 50, 59.94, 60); thread-safe and concurrency-compliant with comprehensive async/await support; no known security vulnerabilities. Version conversion automatically drops elements not in the target version’s DTD (e.g. adjust-colorConform, adjust-stereo-3D); DTD validation runs per version (validateDocumentAgainstDTD, validateDocumentAgainstDeclaredVersion) and after CLI convert. FCPXMLVersion.supportsBundleFormat is true for 1.10+ (.fcpxmld bundle); 1.5–1.9 support only single-file .fcpxml. FCPXML creation: create FCPXML documents from scratch with events, projects, resources, and clips via XMLDocumentManager, XMLDocument initializers, or FCPXMLService. Timeline manipulation: ripple insert (shifts subsequent clips), auto lane assignment, clip queries (by lane, time range, asset ID), lane range computation, secondary storylines. Timeline metadata: markers, chapter markers, keywords, ratings, custom metadata, timestamps (createdAt, modifiedAt). FCPXMLTimecode: custom timecode type (arithmetic, frame alignment, CMTime conversion, FCPXML string parsing). MIME type detection, asset validation, silence detection, asset duration measurement, parallel file I/O, still image asset support. TimelineFormat enhancements: presets (hd720p, dci4K, hd1080i, hd720i), computed properties (aspectRatio, isHD, isUHD, interlaced). Typed adjustment models: Crop, Transform, Blend, Stabilization, Volume, Loudness, NoiseReduction, HumReduction, Equalization, MatchEqualization, Transform360, ColorConform, Stereo3D, VoiceIsolation with full clip integration. Typed effect/filter models: VideoFilter, AudioFilter, VideoFilterMask with FilterParameter support and keyframe animation (auxValue support FCPXML 1.11+). Typed caption/title models: Caption and Title with TextStyle and TextStyleDefinition for full text formatting. SmartCollection models: SmartCollection with match-clip, match-media, match-ratings, match-text, match-usage (1.9+), match-representation (1.10+), match-markers (1.10+), match-analysis-type (1.14). Keyframe animation: KeyframeAnimation, Keyframe with interpolation types, FadeIn/FadeOut with fade types, integrated with FilterParameter. CMTime Codable extension: Direct CMTime encoding/decoding as FCPXML time strings. Collection organization: CollectionFolder and KeywordCollection models for organizing clips and media. Live Drawing (FCPXML 1.11+): LiveDrawing model for live-drawing story elements. HiddenClipMarker (FCPXML 1.13+): HiddenClipMarker model for hidden clip markers. Format/Asset 1.13+: Format heroEye, Asset heroEyeOverride, Asset mediaReps (multiple media-rep). Cross-platform XML abstraction: protocol layer (OFKXMLNode, OFKXMLElement, OFKXMLDocument, OFKXMLFactory); Foundation backend on macOS; AEXML backend on iOS; OFKXMLDefaultFactory() for platform dispatch; FCPXMLStructuralValidator for cross-platform structural validation; FCPXMLDTDValidator platform-conditional (full DTD on macOS, structural fallback on iOS). Comprehensive test coverage: **1078** tests across 60 FCPXML sample files including 360 video, auditions, conform-rate, still images, multicam, secondary storylines, audio keyframes (FCPXMLAudioKeyframeTests: adjust-volume param keyframeAnimation parsing, decibel/time validation, fadeIn/fadeOut integration, secondary storyline detection), keyword collections/folders, Photoshop integration, smart collections, and reporting column layout/exclusion/disabled-clip/workbook/PDF formatting tests. Excel and PDF reporting: multi-sheet `.xlsx` workbooks via `FinalCutPro.FCPXML.buildReport(options:)` (ReportBuilder, ReportOptions presets, ReportExcelExport on XLKit) and optional `.pdf` via `ReportPDFExport` (CoreGraphics; cover page with black “About This PDF Export” + `info.circle`, TOC with accent colour chips + content-tint washes, per-sheet tints, column-width expansion after exclusions, section pagination; same Report configuration as Excel); sheets for Role Inventory (**Selected Roles Inventory** + per-role sheets with expanded column layout and dynamic metadata keys), Markers, Keywords, Titles & Generators, Transitions, Video & Audio Effects, Speed Change Effects, **Summary** (project title in **B1**, narrow Row column A, black role-duration data), and **Media Summary** (Row + red missing-media paths); 1-based **Row** on all tabular Excel/PDF sheets by default (`ensuringRowColumn` / `allowsInjectedRowColumn`); inventory and section-sheet cell formatting, role exclusions, global column exclusion (`ReportColumn` / `excludedColumns`, including `ReportColumn.row`), disabled-clip filtering (`excludeDisabledClips`), project-name / compound-clip-name filtering (`allReportTimelineSources()`; standalone compound-clip exports without ``), `ReportTimecodeFormat` / `--timecode-format`, inventory-first `ReportBuildPhase` progress callbacks; optional `copyrightLabel` / CLI `--label-copyright` (Excel cover **A2**; PDF cover + footer centre). Extraction presets: Captions, Effects, FrameData, Markers, Roles, Titles. Experimental CLI (OpenFCPXMLKit-CLI): single binary with embedded DTDs; --check-version, --convert-version (stripping + DTD validation), --extension-type (fcpxmld | fcpxml; default fcpxmld; 1.5–1.9 always .fcpxml), --validate, --media-copy, --create-project (new empty FCPXML project: --width, --height, --rate, --project-version, output-dir; DTD validation before write; FCP-style output with DOCTYPE, colorSpace, default smart collections), --report (Excel report: role inventory by default; --report-full, per-section flags including --report-markers, --report-keywords, --report-titles-generators, --report-transitions, --report-effects, --report-speed-change-effects, --report-summary, --report-media-summary, --media-resolution, --media-summary-distinguish-proxy, --exclude-role, --exclude-column, --exclude-disabled-clips, --include-markers-outside-clip-boundaries, --protect-sheets, --timecode-format, --report-project, --label-copyright, --create-pdf); --log writes user-visible output for all commands to the log file; see Sources/OpenFCPXMLKitCLI/README.md. Xcode 26 dynamic linking compatibility: `swift-log` (`Logging`) is an explicit direct dependency in `Package.swift` to satisfy stricter transitive dylib linking rules when building OpenFCPXMLKit as a dynamic framework. @@ -139,15 +141,15 @@ Tests live under Tests/. Full description is in Tests/README.md. Summary: - Tests/README.md: test categories, how to run (Swift PM, Xcode, Linux), sample files, frame rates, FCPXML versions. Keep it current when adding or changing tests. -- Tests/ExcelReportTest/: optional integration target (4 tests; writes `Output/OFK-Default.xlsx`, `Output/OFK-Full.xlsx`, `Output/OFK-Default.pdf`, `Output/OFK-ExcludedColumns.pdf`, `Output/OFK-Copyright.xlsx`, and `Output/OFK-Copyright.pdf` from a local fixture; XCTSkip when fixture missing). See Tests/ExcelReportTest/README.md. +- Tests/ExcelReportTest/: optional integration target (6 tests; writes `Output/OFK-Default.xlsx`, `Output/OFK-Full.xlsx`, `Output/OFK-Default.pdf`, `Output/OFK-ExcludedColumns.pdf`, `Output/OFK-Copyright.xlsx`, `Output/OFK-Copyright.pdf`, `Output/OFK-OutsideClipBoundaries.xlsx` / `.pdf`, and `Output/OFK-ProtectedSheets.xlsx` from a local fixture; XCTSkip when fixture missing). See Tests/ExcelReportTest/README.md. - Tests/Submitted FCPXML/: private local inbox for user-supplied FCPXML (`Inbox/`, `Notes/`). Contents are **gitignored** (never commit or push to GitHub); only `README.md` and `.gitkeep` are tracked. Workflow: anonymise → reproduce → fix → promote minimal public fixture. Smoke: `FCPXMLSubmittedFCPXMLSmokeTests` (XCTSkip when empty). See Tests/Submitted FCPXML/README.md. -- Tests/FCPXML Samples/FCPXML/: sample .fcpxml files (59 samples). File tests and logic tests load these via shared utilities; tests that require a missing sample use XCTSkip. +- Tests/FCPXML Samples/FCPXML/: sample .fcpxml files (60 samples). File tests and logic tests load these via shared utilities; tests that require a missing sample use XCTSkip. -- Tests/OpenFCPXMLKitTests/: FCPXMLTestResources.swift (packageRoot, fcpxmlSamplesDirectory, urlForFCPXMLSample, FCPXMLSampleName); FCPXMLTestUtilities.swift (loadFCPXMLSampleData, loadFCPXMLSample, fcpxmlFrameRateSampleNames, allFCPXMLSampleNames; XCTSkip when sample missing); FCPXMLReportingReportFixture.swift and FCPXMLReportingReportTestSupport.swift (optional reporting integration fixtures/assertions). OpenFCPXMLKitTests.swift: main test class with injected dependencies; MARK sections group tests. FileTests/: one test class per sample or category (e.g. FCPXMLFileTest_24, FCPXMLFileTest_AllSamples, FCPXMLFileTest_FrameRates, FCPXMLFileTest_GeneralDemo, FCPXMLFileTest_360Video, FCPXMLFileTest_AuditionSample, FCPXMLFileTest_ImageSample, FCPXMLFileTest_Multicam, FCPXMLFileTest_Photoshop, FCPXMLFileTest_SmartCollection). LogicAndParsing/: FCPXMLRootVersionTests, FCPXMLStructureTests, FCPXMLFormatAssetTests. Every test-case class is FCPXML-prefixed except the module-named umbrella OpenFCPXMLKitTests. FCPXMLSubmittedFCPXMLSmokeTests: optional Inbox parse smoke. FCPXMLTimelineManipulationTests: ripple insert, auto lane assignment, clip queries, lane range, timestamps, metadata, secondary storylines, audio keyframes; injectable "now" for timestamp tests via lock-based NowBox (no DispatchSemaphore); do-catch for insertClipAutoLane/insertingClipAutoLane to verify success. FCPXMLTimecodeTests: initialization, arithmetic, comparison, CMTime conversion, frame alignment, hashing, codable. FCPXMLMIMETypeDetectionTests: sync and async detection for various file types. FCPXMLAssetValidationTests: asset existence, lane compatibility, TimelineClip integration. FCPXMLSilenceDetectionTests: silence detection at start/end of audio files. FCPXMLAssetDurationMeasurementTests: duration measurement for audio/video/images. FCPXMLParallelFileIOTests: concurrent read/write operations. FCPXMLAudioEnhancementTests: NoiseReduction, HumReduction, Equalization, MatchEqualization, Clip integration. FCPXMLTransform360Tests: coordinate types, spherical/cartesian, clip integration. FCPXMLCaptionTitleTests: TextStyle, TextStyleDefinition, Caption/Title integration, CaptionSample file test. FCPXMLKeyframeAnimationTests: FadeIn, FadeOut, Keyframe, KeyframeAnimation, FilterParameter integration. FCPXMLAudioKeyframeTests: audio keyframes in adjust-volume (param name="amount" with keyframeAnimation); parsing from FCPXML samples; decibel values (-3dB, -37dB); time values (FCPXML fractional format); fadeIn/fadeOut integration; multiple keyframes in sequence; secondary storyline and nested clip detection; TimelineWithSecondaryStorylineWithAudioKeyframes, TimelineSample file tests. FCPXMLCMTimeCodableTests: CMTime encoding/decoding as FCPXML time strings. FCPXMLCollectionTests: CollectionFolder, KeywordCollection, nested structures. FCPXMLSmartCollectionTests: SmartCollection models, match rules (MatchUsage, MatchRepresentation, MatchMarkers, MatchAnalysisType), round-trip, version stripping. FCPXMLAdjustmentTests: typed adjustment models and clip integration. FCPXMLFilterTests: VideoFilter, AudioFilter, VideoFilterMask, FilterParameter. FCPXMLImportOptionsTests: import options and library location parsing. FCPXMLCodableTests: Codable round-trip for model types. FCPXMLMediaExtractionTests: media reference extraction and copy (CLI --media-copy flow). FCPXMLDTDValidatorTests: per-version DTD validation. FCPXMLStructuralValidatorTests: cross-platform structural validation. FCPXMLAEXMLSerializationParityTests: AEXML vs Foundation serialization parity. FCPXMLTimelineExportValidationTests: timeline, exporters (empty timeline creation and project-creation export at different sizes and frame rates; includeDefaultSmartCollections and DTD validation), validators, file loader. FCPXMLAPIAndEdgeCaseTests: async load API, ServiceLogger injection, edge cases, Live Drawing (1.11+), HiddenClipMarker (1.13+). FCPXMLCutDetectionTests: edit points, transitions, gaps, CutSample file test. FCPXMLPerformanceTests: parameterised and basic performance tests. Reporting/extraction tests are all FCPXML-prefixed (FCPXMLCompoundClipReportTests, FCPXMLRoleInventoryReportTests, FCPXMLRoleInventoryColumnLayoutTests, FCPXMLMarkersReportTests, FCPXMLKeywordsReportTests, FCPXMLTitlesReportTests, FCPXMLTransitionsReportTests, FCPXMLEffectsReportTests, FCPXMLSpeedChangeEffectsReportTests, FCPXMLSummaryReportTests, FCPXMLReportExcelExportTests, FCPXMLReportPDFExportTests, FCPXMLReportPDFSheetPlanTests, FCPXMLReportPDFTableLayoutTests, FCPXMLReportFormattingTests, FCPXMLReportRoleExclusionTests, FCPXMLReportTimecodeFormatTests, FCPXMLReportBuildPhaseTests, FCPXMLReportColumnExclusionTests, FCPXMLReportExcludeDisabledClipsTests, FCPXMLRoleDisplayPreferenceTests, FCPXMLRoleInventoryClipCollectorTests, FCPXMLRoleInventoryRoleSheetOrderingTests, FCPXMLSummaryRoleDurationAggregatorTests, FCPXMLEffectsReportPolicyTests, FCPXMLSpeedChangeFormattingTests, FCPXMLDisplayClipNameTests, FCPXMLTitleDisplayTests, FCPXMLExtractionScopeTests, FCPXMLExtractedElementTests, FCPXMLEffectsCollectorTests, FCPXMLRolesExtractionPresetTests, FCPXMLEffectAppleSuppliedTests, FCPXMLClipParsingCarriesAudioTests, FCPXMLTransformAdjustmentParsingTests, FCPXMLTransitionSpinePlacementTests). +- Tests/OpenFCPXMLKitTests/: FCPXMLTestResources.swift (packageRoot, fcpxmlSamplesDirectory, urlForFCPXMLSample, FCPXMLSampleName); FCPXMLTestUtilities.swift (loadFCPXMLSampleData, loadFCPXMLSample, fcpxmlFrameRateSampleNames, allFCPXMLSampleNames; XCTSkip when sample missing); FCPXMLReportingReportFixture.swift and FCPXMLReportingReportTestSupport.swift (optional reporting integration fixtures/assertions). OpenFCPXMLKitTests.swift: main test class with injected dependencies; MARK sections group tests. FileTests/: one test class per sample or category (e.g. FCPXMLFileTest_24, FCPXMLFileTest_AllSamples, FCPXMLFileTest_FrameRates, FCPXMLFileTest_GeneralDemo, FCPXMLFileTest_HiddenMarkers, FCPXMLFileTest_360Video, FCPXMLFileTest_AuditionSample, FCPXMLFileTest_ImageSample, FCPXMLFileTest_Multicam, FCPXMLFileTest_Photoshop, FCPXMLFileTest_SmartCollection). LogicAndParsing/: FCPXMLRootVersionTests, FCPXMLStructureTests, FCPXMLFormatAssetTests. Every test-case class is FCPXML-prefixed except the module-named umbrella OpenFCPXMLKitTests. FCPXMLSubmittedFCPXMLSmokeTests: optional Inbox parse smoke. FCPXMLTimelineManipulationTests: ripple insert, auto lane assignment, clip queries, lane range, timestamps, metadata, secondary storylines, audio keyframes; injectable "now" for timestamp tests via lock-based NowBox (no DispatchSemaphore); do-catch for insertClipAutoLane/insertingClipAutoLane to verify success. FCPXMLTimecodeTests: initialization, arithmetic, comparison, CMTime conversion, frame alignment, hashing, codable. FCPXMLMIMETypeDetectionTests: sync and async detection for various file types. FCPXMLAssetValidationTests: asset existence, lane compatibility, TimelineClip integration. FCPXMLSilenceDetectionTests: silence detection at start/end of audio files. FCPXMLAssetDurationMeasurementTests: duration measurement for audio/video/images. FCPXMLParallelFileIOTests: concurrent read/write operations. FCPXMLAudioEnhancementTests: NoiseReduction, HumReduction, Equalization, MatchEqualization, Clip integration. FCPXMLTransform360Tests: coordinate types, spherical/cartesian, clip integration. FCPXMLCaptionTitleTests: TextStyle, TextStyleDefinition, Caption/Title integration, CaptionSample file test. FCPXMLKeyframeAnimationTests: FadeIn, FadeOut, Keyframe, KeyframeAnimation, FilterParameter integration. FCPXMLAudioKeyframeTests: audio keyframes in adjust-volume (param name="amount" with keyframeAnimation); parsing from FCPXML samples; decibel values (-3dB, -37dB); time values (FCPXML fractional format); fadeIn/fadeOut integration; multiple keyframes in sequence; secondary storyline and nested clip detection; TimelineWithSecondaryStorylineWithAudioKeyframes, TimelineSample file tests. FCPXMLCMTimeCodableTests: CMTime encoding/decoding as FCPXML time strings. FCPXMLCollectionTests: CollectionFolder, KeywordCollection, nested structures. FCPXMLSmartCollectionTests: SmartCollection models, match rules (MatchUsage, MatchRepresentation, MatchMarkers, MatchAnalysisType), round-trip, version stripping. FCPXMLAdjustmentTests: typed adjustment models and clip integration. FCPXMLFilterTests: VideoFilter, AudioFilter, VideoFilterMask, FilterParameter. FCPXMLImportOptionsTests: import options and library location parsing. FCPXMLCodableTests: Codable round-trip for model types. FCPXMLMediaExtractionTests: media reference extraction and copy (CLI --media-copy flow). FCPXMLDTDValidatorTests: per-version DTD validation. FCPXMLStructuralValidatorTests: cross-platform structural validation. FCPXMLAEXMLSerializationParityTests: AEXML vs Foundation serialization parity. FCPXMLTimelineExportValidationTests: timeline, exporters (empty timeline creation and project-creation export at different sizes and frame rates; includeDefaultSmartCollections and DTD validation), validators, file loader. FCPXMLAPIAndEdgeCaseTests: async load API, ServiceLogger injection, edge cases, Live Drawing (1.11+), HiddenClipMarker (1.13+). FCPXMLCutDetectionTests: edit points, transitions, gaps, CutSample file test. FCPXMLPerformanceTests: parameterised and basic performance tests. Reporting/extraction tests are all FCPXML-prefixed (FCPXMLCompoundClipReportTests, FCPXMLRoleInventoryReportTests, FCPXMLRoleInventoryColumnLayoutTests, FCPXMLMarkersReportTests, FCPXMLKeywordsReportTests, FCPXMLTitlesReportTests, FCPXMLTransitionsReportTests, FCPXMLEffectsReportTests, FCPXMLSpeedChangeEffectsReportTests, FCPXMLSummaryReportTests, FCPXMLReportExcelExportTests, FCPXMLReportPDFExportTests, FCPXMLReportPDFSheetPlanTests, FCPXMLReportPDFTableLayoutTests, FCPXMLReportFormattingTests, FCPXMLReportRoleExclusionTests, FCPXMLReportTimecodeFormatTests, FCPXMLReportBuildPhaseTests, FCPXMLReportColumnExclusionTests, FCPXMLReportExcludeDisabledClipsTests, FCPXMLRoleDisplayPreferenceTests, FCPXMLRoleInventoryClipCollectorTests, FCPXMLRoleInventoryRoleSheetOrderingTests, FCPXMLSummaryRoleDurationAggregatorTests, FCPXMLEffectsReportPolicyTests, FCPXMLSpeedChangeFormattingTests, FCPXMLDisplayClipNameTests, FCPXMLTitleDisplayTests, FCPXMLExtractionScopeTests, FCPXMLExtractedElementTests, FCPXMLEffectsCollectorTests, FCPXMLRolesExtractionPresetTests, FCPXMLEffectAppleSuppliedTests, FCPXMLClipParsingCarriesAudioTests, FCPXMLTransformAdjustmentParsingTests, FCPXMLTransitionSpinePlacementTests). -Use descriptive test method names; group related tests logically; include setup and teardown; use meaningful assertions. Current total: **1076** tests listed in `swift test --list-tests` (**1072** in `OpenFCPXMLKitTests` + **4** optional `ExcelReportTest`) covering all functionality including async/await, timeline manipulation, metadata, timestamps, FCPXMLTimecode, MIME type detection, asset validation, silence detection, asset duration measurement, parallel file I/O, version conversion stripping, per-version DTD validation, extract-then-copy (CLI --media-copy flow), synchronized clip matching, secondary storyline traversal, clip identification, URL resolution, version conversion edge cases, typed adjustment models (including Transform360, ColorConform, Stereo3D, VoiceIsolation), typed effect/filter models, typed caption/title models, smart collections (match-clip, match-media, match-ratings, match-text, match-usage, match-representation, match-markers, match-analysis-type), keyframe animation, audio keyframes (FCPXMLAudioKeyframeTests: adjust-volume param keyframeAnimation parsing, decibel/time validation, fadeIn/fadeOut integration), CMTime Codable extension, collection organization, Live Drawing (1.11+), HiddenClipMarker (1.13+), Format/Asset 1.13+ (heroEye, heroEyeOverride, mediaReps), FCPXMLExporter clip-level metadata export and XML declaration standalone="no", FCPXMLTimelineManipulationTests refactor (lock-based NowBox, do-catch for throwing APIs), Excel reporting (Selected Roles Inventory column layout, Summary/Media Summary split, ReportTimecodeFormat / format-aware headers, inventory-first ReportBuildPhase progress, global column exclusion, disabled-clip filtering, workbook cell formatting via FCPXMLReportWorkbookExporter), 360 video features, auditions, conform-rate, still images, multicam, secondary storylines, audio keyframes, keyword collections/folders, and Photoshop integration. +Use descriptive test method names; group related tests logically; include setup and teardown; use meaningful assertions. Current total: **1084** tests listed in `swift test --list-tests` (**1078** in `OpenFCPXMLKitTests` + **6** optional `ExcelReportTest`) covering all functionality including async/await, timeline manipulation, metadata, timestamps, FCPXMLTimecode, MIME type detection, asset validation, silence detection, asset duration measurement, parallel file I/O, version conversion stripping, per-version DTD validation, extract-then-copy (CLI --media-copy flow), synchronized clip matching, secondary storyline traversal, clip identification, URL resolution, version conversion edge cases, typed adjustment models (including Transform360, ColorConform, Stereo3D, VoiceIsolation), typed effect/filter models, typed caption/title models, smart collections (match-clip, match-media, match-ratings, match-text, match-usage, match-representation, match-markers, match-analysis-type), keyframe animation, audio keyframes (FCPXMLAudioKeyframeTests: adjust-volume param keyframeAnimation parsing, decibel/time validation, fadeIn/fadeOut integration), CMTime Codable extension, collection organization, Live Drawing (1.11+), HiddenClipMarker (1.13+), Format/Asset 1.13+ (heroEye, heroEyeOverride, mediaReps), FCPXMLExporter clip-level metadata export and XML declaration standalone="no", FCPXMLTimelineManipulationTests refactor (lock-based NowBox, do-catch for throwing APIs), Excel reporting (Selected Roles Inventory column layout, Summary/Media Summary split, ReportTimecodeFormat / format-aware headers, inventory-first ReportBuildPhase progress, global column exclusion, disabled-clip filtering, workbook cell formatting via FCPXMLReportWorkbookExporter), 360 video features, auditions, conform-rate, still images, multicam, secondary storylines, audio keyframes, keyword collections/folders, and Photoshop integration. --- @@ -163,7 +165,7 @@ SwiftTimecode integration: use SwiftTimecode for all timecode operations; suppor ## Testing Requirements -Test coverage: unit tests for all public APIs; integration tests for complex workflows; performance tests for time-critical operations; concurrency tests for async operations; test all supported frame rates (Final Cut Pro compatible). Current: **1076** tests listed in `swift test --list-tests` (including AEXML parity, FCPXMLDTDValidatorTests, FCPXMLStructuralValidatorTests, FCPXMLReportTimecodeFormatTests, FCPXMLReportBuildPhaseTests, FCPXMLReportColumnExclusionTests, FCPXMLReportExcludeDisabledClipsTests, FCPXMLRoleInventoryColumnLayoutTests, FCPXMLReportExcelExportTests workbook cell formatting, FCPXMLReportPDFExportTests PDF export, FCPXMLReportPDFSheetPlanTests TOC colour-index parity, FCPXMLReportPDFTableLayoutTests column-width expansion, optional ExcelReportTest integration) covering all functionality including async/await, timeline manipulation, metadata, timestamps, FCPXMLTimecode, MIME type detection, asset validation, silence detection, asset duration measurement, parallel file I/O, version conversion, DTD validation, extract-then-copy flow, synchronized clip matching, secondary storyline traversal, clip identification, URL resolution, version conversion edge cases, typed adjustment models (including Transform360, ColorConform, Stereo3D, VoiceIsolation), typed effect/filter models, typed caption/title models, smart collections (match-clip, match-media, match-ratings, match-text, match-usage, match-representation, match-markers, match-analysis-type), keyframe animation, audio keyframes (FCPXMLAudioKeyframeTests: adjust-volume param keyframeAnimation parsing, decibel/time validation, fadeIn/fadeOut integration), CMTime Codable extension, collection organization, Live Drawing (1.11+), HiddenClipMarker (1.13+), Format/Asset 1.13+ (heroEye, heroEyeOverride, mediaReps), FCPXMLExporter clip-level metadata export and XML declaration standalone="no", FCPXMLTimelineManipulationTests (lock-based NowBox, do-catch), Excel reporting enhancements, PDF report export (TOC colour chips, column expansion after exclusions), 360 video features, auditions, conform-rate, still images, multicam, secondary storylines, audio keyframes, keyword collections/folders, and Photoshop integration. +Test coverage: unit tests for all public APIs; integration tests for complex workflows; performance tests for time-critical operations; concurrency tests for async operations; test all supported frame rates (Final Cut Pro compatible). Current: **1084** tests listed in `swift test --list-tests` (including AEXML parity, FCPXMLDTDValidatorTests, FCPXMLStructuralValidatorTests, FCPXMLReportTimecodeFormatTests, FCPXMLReportBuildPhaseTests, FCPXMLReportColumnExclusionTests, FCPXMLReportExcludeDisabledClipsTests, FCPXMLRoleInventoryColumnLayoutTests, FCPXMLReportExcelExportTests workbook cell formatting, FCPXMLReportPDFExportTests PDF export, FCPXMLReportPDFSheetPlanTests TOC colour-index parity, FCPXMLReportPDFTableLayoutTests column-width expansion, optional ExcelReportTest integration) covering all functionality including async/await, timeline manipulation, metadata, timestamps, FCPXMLTimecode, MIME type detection, asset validation, silence detection, asset duration measurement, parallel file I/O, version conversion, DTD validation, extract-then-copy flow, synchronized clip matching, secondary storyline traversal, clip identification, URL resolution, version conversion edge cases, typed adjustment models (including Transform360, ColorConform, Stereo3D, VoiceIsolation), typed effect/filter models, typed caption/title models, smart collections (match-clip, match-media, match-ratings, match-text, match-usage, match-representation, match-markers, match-analysis-type), keyframe animation, audio keyframes (FCPXMLAudioKeyframeTests: adjust-volume param keyframeAnimation parsing, decibel/time validation, fadeIn/fadeOut integration), CMTime Codable extension, collection organization, Live Drawing (1.11+), HiddenClipMarker (1.13+), Format/Asset 1.13+ (heroEye, heroEyeOverride, mediaReps), FCPXMLExporter clip-level metadata export and XML declaration standalone="no", FCPXMLTimelineManipulationTests (lock-based NowBox, do-catch), Excel reporting enhancements, PDF report export (TOC colour chips, column expansion after exclusions), 360 video features, auditions, conform-rate, still images, multicam, secondary storylines, audio keyframes, keyword collections/folders, and Photoshop integration. Test data: use realistic FCPXML samples; include edge cases and error conditions; test all supported frame rates; validate against actual Final Cut Pro output where applicable. @@ -222,7 +224,7 @@ Build requirements: Swift build successful; all tests passing; no warnings or er ## Project-Specific Rules -FCPXML handling: support FCPXML versions 1.5 through 1.14 (DTDs included; full parsing; typed element-type coverage via FCPXMLElementType for all DTD elements); validate against DTD schemas (FCPXMLDTDValidator, FCPXMLService.validateDocumentAgainstDTD/validateDocumentAgainstDeclaredVersion); version conversion (FCPXMLVersionConverter) sets root version and automatically strips elements not in the target version’s DTD (e.g. adjust-colorConform, adjust-stereo-3D); FCPXMLVersion.supportsBundleFormat is true for 1.10+ (save as .fcpxmld bundle); 1.5–1.9 support only .fcpxml; handle all supported frame rates (Final Cut Pro compatible); implement proper XML formatting. Media extraction: extract media references (asset media-rep src, locator url) and copy referenced file URLs to a destination directory (MediaExtraction protocol, MediaExtractor). Timeline manipulation: ripple insert (shifts subsequent clips), auto lane assignment (finds available lanes), clip queries (by lane, time range, asset ID), lane range computation, secondary storylines. Timeline metadata: markers, chapter markers, keywords, ratings, custom metadata on timeline and clips; timestamps (createdAt, modifiedAt) updated on all mutating operations. TimelineFormat: presets (hd720p, dci4K, hd1080i, hd720i), computed properties (aspectRatio, isHD, isUHD, interlaced). FCPXMLTimecode: custom timecode type wrapping Fraction (arithmetic, frame alignment, CMTime conversion, FCPXML string parsing). MIME type detection: MIMETypeDetection protocol and MIMETypeDetector implementation (UTType, AVFoundation, file extension fallback). Asset validation: AssetValidation protocol and AssetValidator implementation (existence check, MIME type compatibility, lane rules: negative = audio only, non-negative = video/image/audio); still image asset support (duration=0s). Silence detection: SilenceDetection protocol and SilenceDetector implementation (configurable threshold and minimum duration). Asset duration measurement: AssetDurationMeasurement protocol and AssetDurationMeasurer implementation (AVFoundation-based for audio/video/images). Parallel file I/O: ParallelFileIO protocol and ParallelFileIOExecutor implementation (concurrent read/write operations). SmartCollection: SmartCollection model with match-clip, match-media, match-ratings, match-text, match-usage (1.9+), match-representation (1.10+), match-markers (1.10+), match-analysis-type (1.14); library and event integration. Live Drawing (1.11+): LiveDrawing model for live-drawing story elements. HiddenClipMarker (1.13+): HiddenClipMarker model for hidden clip markers. Format/Asset 1.13+: Format heroEye (left|right), Asset heroEyeOverride, Asset mediaReps (multiple media-rep). Excel and PDF reporting: multi-sheet `.xlsx` workbooks (FinalCutPro.FCPXML.buildReport(options:), ReportBuilder, ReportOptions presets, ReportExcelExport on XLKit) with Role Inventory (**Selected Roles Inventory** + per-role sheets), Markers, Keywords, Titles & Generators, Transitions, Video & Audio Effects, Speed Change Effects, Summary (project title in **B1**, role durations), and Media Summary (missing media paths); universal **Row** via `ensuringRowColumn` / `allowsInjectedRowColumn`; role exclusions, global column exclusion (`excludedColumns` / `ReportColumn`), disabled-clip filtering (`excludeDisabledClips`), `ReportTimecodeFormat` / `--timecode-format` (SMPTE frames with DF/NDF, Frames, Feet+Frames, HH:MM:SS; format-aware headers; numeric sort for Frames/Feet+Frames), inventory-first `ReportBuildPhase.enabledPhases(for:)` progress, project-name filtering, and optional `copyrightLabel` / `--label-copyright` (Excel cover **A2**; PDF cover + footer centre); builders consume Extraction/Model and own presentation only (see ARCHITECTURE.md §2.7). Experimental CLI: OpenFCPXMLKit-CLI single binary (embedded DTDs) with --check-version, --convert-version (stripping + DTD validation), --extension-type fcpxml|fcpxmld (default fcpxmld; 1.5–1.9 always .fcpxml), --validate, --media-copy, --create-project (width, height, rate, project-version, output-dir; mandatory DTD validation; FCP-style DOCTYPE, format colorSpace, default smart collections), --report (Excel report: role inventory by default; --report-full, per-section flags including --report-summary and --report-media-summary, --media-resolution, --media-summary-distinguish-proxy, --exclude-role, --exclude-column, --exclude-disabled-clips, --timecode-format, --report-project, --label-copyright, --create-pdf), and LOG options (--log, --log-level, --quiet; --log records user-visible output for all commands); see Sources/OpenFCPXMLKitCLI/README.md. +FCPXML handling: support FCPXML versions 1.5 through 1.14 (DTDs included; full parsing; typed element-type coverage via FCPXMLElementType for all DTD elements); validate against DTD schemas (FCPXMLDTDValidator, FCPXMLService.validateDocumentAgainstDTD/validateDocumentAgainstDeclaredVersion); version conversion (FCPXMLVersionConverter) sets root version and automatically strips elements not in the target version’s DTD (e.g. adjust-colorConform, adjust-stereo-3D); FCPXMLVersion.supportsBundleFormat is true for 1.10+ (save as .fcpxmld bundle); 1.5–1.9 support only .fcpxml; handle all supported frame rates (Final Cut Pro compatible); implement proper XML formatting. Media extraction: extract media references (asset media-rep src, locator url) and copy referenced file URLs to a destination directory (MediaExtraction protocol, MediaExtractor). Timeline manipulation: ripple insert (shifts subsequent clips), auto lane assignment (finds available lanes), clip queries (by lane, time range, asset ID), lane range computation, secondary storylines. Timeline metadata: markers, chapter markers, keywords, ratings, custom metadata on timeline and clips; timestamps (createdAt, modifiedAt) updated on all mutating operations. TimelineFormat: presets (hd720p, dci4K, hd1080i, hd720i), computed properties (aspectRatio, isHD, isUHD, interlaced). FCPXMLTimecode: custom timecode type wrapping Fraction (arithmetic, frame alignment, CMTime conversion, FCPXML string parsing). MIME type detection: MIMETypeDetection protocol and MIMETypeDetector implementation (UTType, AVFoundation, file extension fallback). Asset validation: AssetValidation protocol and AssetValidator implementation (existence check, MIME type compatibility, lane rules: negative = audio only, non-negative = video/image/audio); still image asset support (duration=0s). Silence detection: SilenceDetection protocol and SilenceDetector implementation (configurable threshold and minimum duration). Asset duration measurement: AssetDurationMeasurement protocol and AssetDurationMeasurer implementation (AVFoundation-based for audio/video/images). Parallel file I/O: ParallelFileIO protocol and ParallelFileIOExecutor implementation (concurrent read/write operations). SmartCollection: SmartCollection model with match-clip, match-media, match-ratings, match-text, match-usage (1.9+), match-representation (1.10+), match-markers (1.10+), match-analysis-type (1.14); library and event integration. Live Drawing (1.11+): LiveDrawing model for live-drawing story elements. HiddenClipMarker (1.13+): HiddenClipMarker model for hidden clip markers. Format/Asset 1.13+: Format heroEye (left|right), Asset heroEyeOverride, Asset mediaReps (multiple media-rep). Excel and PDF reporting: multi-sheet `.xlsx` workbooks (FinalCutPro.FCPXML.buildReport(options:), ReportBuilder, ReportOptions presets, ReportExcelExport on XLKit) with Role Inventory (**Selected Roles Inventory** + per-role sheets), Markers, Keywords, Titles & Generators, Transitions, Video & Audio Effects, Speed Change Effects, Summary (project title in **B1**, role durations), and Media Summary (missing media paths); universal **Row** via `ensuringRowColumn` / `allowsInjectedRowColumn`; role exclusions, global column exclusion (`excludedColumns` / `ReportColumn`), disabled-clip filtering (`excludeDisabledClips`), `ReportTimecodeFormat` / `--timecode-format` (SMPTE frames with DF/NDF, Frames, Feet+Frames, HH:MM:SS; format-aware headers; numeric sort for Frames/Feet+Frames), inventory-first `ReportBuildPhase.enabledPhases(for:)` progress, project-name filtering, and optional `copyrightLabel` / `--label-copyright` (Excel cover **A2**; PDF cover + footer centre); builders consume Extraction/Model and own presentation only (see ARCHITECTURE.md §2.7). Experimental CLI: OpenFCPXMLKit-CLI single binary (embedded DTDs) with --check-version, --convert-version (stripping + DTD validation), --extension-type fcpxml|fcpxmld (default fcpxmld; 1.5–1.9 always .fcpxml), --validate, --media-copy, --create-project (width, height, rate, project-version, output-dir; mandatory DTD validation; FCP-style DOCTYPE, format colorSpace, default smart collections), --report (Excel report: role inventory by default; --report-full, per-section flags including --report-summary and --report-media-summary, --media-resolution, --media-summary-distinguish-proxy, --exclude-role, --exclude-column, --exclude-disabled-clips, --include-markers-outside-clip-boundaries, --protect-sheets, --timecode-format, --report-project, --label-copyright, --create-pdf), and LOG options (--log, --log-level, --quiet; --log records user-visible output for all commands); see Sources/OpenFCPXMLKitCLI/README.md. Timecode operations: use SwiftTimecode for all timecode operations; support all FCPXML frame rates (Final Cut Pro compatible); implement proper frame rate conversions; handle drop frame and non-drop frame timecode. FCPXMLTimecode provides a custom timecode type for FCPXML-specific operations (arithmetic, frame alignment, CMTime conversion, FCPXML string parsing). @@ -256,7 +258,7 @@ Code review process: review for Swift 6.3 compliance; check concurrency implemen ## Documentation Sync -Keep this file in sync with AGENT.md. Both must reflect: changelog styling (CHANGELOG.md: Keep a Changelog format, version links to release tags, ✨ New Features / 🔧 Improvements / 🐛 Bug Fixes); project overview and codebase rewrite/refactor; architecture and single injection point (FCPXMLUtility.defaultForExtensions); source layout (Analysis, Classes, Delegates, Errors, Extensions including +Modular and +Codable, Implementations, Protocols, Services, Utilities, Annotations, Export, Timeline, Timing, Validation, FileIO, Logging, Format, Model including Adjustments, Animations, Filters, Clips with +Adjustments and +Typed, CommonElements with TextStyle/TextStyleDefinition, Structure with CollectionFolder/KeywordCollection/SmartCollection, Parsing, Extraction, Projection (TimelineProjector / MulticamProjection / RefClipProjection / ChannelKindFilter), Reporting including Excel/ and PDF/, XML with Protocols/Foundation/AEXML/OFKXMLDefaultFactory, FCPXML DTDs; reporting vs core layers in ARCHITECTURE.md §2.7); test structure (Tests/ layout, FCPXMLTestResources, FCPXMLTestUtilities, FileTests/ including FCPXMLFileTest_GeneralDemo, FCPXMLFileTest_360Video, FCPXMLFileTest_AuditionSample, FCPXMLFileTest_ImageSample, FCPXMLFileTest_Multicam, FCPXMLFileTest_Photoshop, FCPXMLFileTest_SmartCollection, LogicAndParsing/ including FCPXMLFormatAssetTests, FCPXMLCutDetectionTests, FCPXMLTimelineProjectionTests, FCPXMLVersionConversionTests, FCPXMLMediaExtractionTests, FCPXMLTimelineManipulationTests, FCPXMLTimecodeTests, FCPXMLMIMETypeDetectionTests, FCPXMLAssetValidationTests, FCPXMLSilenceDetectionTests, FCPXMLAssetDurationMeasurementTests, FCPXMLParallelFileIOTests, FCPXMLAudioEnhancementTests, FCPXMLTransform360Tests, FCPXMLCaptionTitleTests, FCPXMLKeyframeAnimationTests, FCPXMLAudioKeyframeTests, FCPXMLCMTimeCodableTests, FCPXMLCollectionTests, FCPXMLSmartCollectionTests, FCPXMLAdjustmentTests, FCPXMLFilterTests, FCPXMLImportOptionsTests, FCPXMLCodableTests, FCPXMLAEXMLSerializationParityTests, FCPXMLDTDValidatorTests, FCPXMLStructuralValidatorTests, OpenFCPXMLKitTests.swift, FCPXMLTimelineExportValidationTests, FCPXMLAPIAndEdgeCaseTests, FCPXMLPerformanceTests, FCPXMLTimelineProjectionTests, FCPXMLProjectionCoverageTests, FCPXMLParsingCoverageTests, FCPXMLEngineHygieneTests, FCPXMLReportObligationCorpusTests, FCPXMLExtractionNestFidelityTests, FCPXMLRoleInheritanceMatrixTests, FCPXMLExtractionProjectionPolicyTests, FCPXMLMarkersKeywordsProjectionTests, FCPXMLTitlesProjectionTests, FCPXMLTransitionsProjectionTests, FCPXMLEffectsProjectionTests, FCPXMLSubmittedFCPXMLSmokeTests, and FCPXML-prefixed reporting/extraction tests (FCPXMLRoleInventoryReportTests, FCPXMLMarkersReportTests, FCPXMLKeywordsReportTests, FCPXMLTitlesReportTests, FCPXMLTransitionsReportTests, FCPXMLEffectsReportTests, FCPXMLSpeedChangeEffectsReportTests, FCPXMLSummaryReportTests, FCPXMLReportExcelExportTests, FCPXMLReportPDFExportTests, FCPXMLReportPDFSheetPlanTests, FCPXMLReportPDFTableLayoutTests, FCPXMLReportFormattingTests, FCPXMLReportRoleExclusionTests, FCPXMLReportTimecodeFormatTests, FCPXMLReportBuildPhaseTests, FCPXMLRoleDisplayPreferenceTests, FCPXMLRoleInventoryClipCollectorTests, FCPXMLRoleInventoryRoleSheetOrderingTests, FCPXMLSummaryRoleDurationAggregatorTests, FCPXMLEffectsReportPolicyTests, FCPXMLSpeedChangeFormattingTests, FCPXMLDisplayClipNameTests, FCPXMLTitleDisplayTests, FCPXMLExtractionScopeTests, FCPXMLExtractedElementTests, FCPXMLEffectsCollectorTests, FCPXMLRolesExtractionPresetTests, FCPXMLEffectAppleSuppliedTests, FCPXMLClipParsingCarriesAudioTests, FCPXMLTransformAdjustmentParsingTests, FCPXMLTransitionSpinePlacementTests); every test-case class is FCPXML-prefixed except the module-named umbrella OpenFCPXMLKitTests; empty timeline creation and project-creation export at different sizes and frame rates in FCPXMLTimelineExportValidationTests (clip-level metadata export, XML declaration standalone="no")). Cross-platform XML (OFKXML*, FCPXMLStructuralValidator, iOS). Private `Tests/Submitted FCPXML/` inbox (gitignored contents; never commit private FCPXML to GitHub). FCPXML 1.5–1.14 and FCPXMLElementType; FCPXMLVersion.supportsBundleFormat (1.10+); version conversion with element stripping and per-version DTD validation; FCPXML creation from scratch; timeline manipulation (ripple insert, auto lane assignment, clip queries, lane range, secondary storylines); timeline metadata (markers, chapter markers, keywords, ratings, timestamps); FCPXMLTimecode custom type; MIME type detection; asset validation (including still images); silence detection; asset duration measurement; parallel file I/O; TimelineFormat enhancements; typed adjustment models (Crop, Transform, Blend, Stabilization, Volume, Loudness, NoiseReduction, HumReduction, Equalization, MatchEqualization, Transform360, ColorConform, Stereo3D, VoiceIsolation); typed effect/filter models (VideoFilter, AudioFilter, VideoFilterMask, FilterParameter with keyframe animation and auxValue 1.11+); typed caption/title models (Caption, Title with TextStyle, TextStyleDefinition); smart collections (SmartCollection with match-clip, match-media, match-ratings, match-text, match-usage, match-representation, match-markers, match-analysis-type); keyframe animation (KeyframeAnimation, Keyframe, FadeIn, FadeOut); audio keyframes (FCPXMLAudioKeyframeTests: adjust-volume param keyframeAnimation parsing, decibel/time validation, fadeIn/fadeOut integration); CMTime Codable extension; collection organization (CollectionFolder, KeywordCollection); Live Drawing (1.11+); HiddenClipMarker (1.13+); Format/Asset 1.13+ (heroEye, heroEyeOverride, mediaReps); experimental CLI (OpenFCPXMLKit-CLI, single binary, embedded DTDs, --check-version, --convert-version, --extension-type fcpxml|fcpxmld, --validate, --media-copy, --create-project with DTD validation and FCP-style output, --report Excel/PDF report with --report-full/per-section flags/--report-summary/--report-media-summary/--media-resolution/--media-summary-distinguish-proxy/--exclude-role/--exclude-column/--exclude-disabled-clips/--timecode-format/--report-project/--label-copyright/--create-pdf, --log/--log-level/--quiet with log file capturing all command output); Excel and PDF reporting subsystem (`allReportTimelineSources` / compound-clip timelines) (Reporting/ builders, Sections/Rows, Support including RoleInventoryColumnLayout, ReportColumnExclusion, ReportFormatting, ReportTimecodeFormat, ReportBuildProgress; Excel/ via XLKit; buildReport/ReportExcelExport/ReportPDFExport; Summary and Media Summary sheets; excludeDisabledClips/excludedColumns/timecodeFormat/copyrightLabel; `--label-copyright`; inventory-first enabledPhases; PDF cover notes + TOC colour chips / SheetPlan colorIndex; TableLayout column expansion to contentWidth and Row inject); extraction presets (Captions, Effects, FrameData, Markers, Roles, Titles); Tests/ExcelReportTest optional integration; Tests/Submitted FCPXML private inbox; Final Cut Pro frame rates; Swift 6 concurrency (Sendable, async/await, CI strict-concurrency job); Xcode 26 dynamic framework linking compatibility via explicit `swift-log` (`Logging`) dependency in `Package.swift`; SwiftExtensions 3.0.0+ and SwiftSemanticVersion 1.0.0+ (`SemanticVersion` for `FinalCutPro.FCPXML.Version`). When updating either file, update both and keep terminology and examples consistent. +Keep this file in sync with AGENT.md. Hard must / must-not constraints live in **GUARDRAILS.md** (update Signs when a design lock or regression is learned; keep ARCHITECTURE.md for structure). Both AGENT.md and this file must reflect: changelog styling (CHANGELOG.md: Keep a Changelog format, version links to release tags, ✨ New Features / 🔧 Improvements / 🐛 Bug Fixes); project overview and codebase rewrite/refactor; architecture and single injection point (FCPXMLUtility.defaultForExtensions); source layout (Analysis, Classes, Delegates, Errors, Extensions including +Modular and +Codable, Implementations, Protocols, Services, Utilities, Annotations, Export, Timeline, Timing, Validation, FileIO, Logging, Format, Model including Adjustments, Animations, Filters, Clips with +Adjustments and +Typed, CommonElements with TextStyle/TextStyleDefinition, Structure with CollectionFolder/KeywordCollection/SmartCollection, Parsing, Extraction, Projection (TimelineProjector / MulticamProjection / RefClipProjection / ChannelKindFilter), Reporting including Excel/ and PDF/, XML with Protocols/Foundation/AEXML/OFKXMLDefaultFactory, FCPXML DTDs; reporting vs core layers in ARCHITECTURE.md §2.7); test structure (Tests/ layout, FCPXMLTestResources, FCPXMLTestUtilities, FileTests/ including FCPXMLFileTest_GeneralDemo, FCPXMLFileTest_HiddenMarkers, FCPXMLFileTest_360Video, FCPXMLFileTest_AuditionSample, FCPXMLFileTest_ImageSample, FCPXMLFileTest_Multicam, FCPXMLFileTest_Photoshop, FCPXMLFileTest_SmartCollection, LogicAndParsing/ including FCPXMLFormatAssetTests, FCPXMLCutDetectionTests, FCPXMLTimelineProjectionTests, FCPXMLVersionConversionTests, FCPXMLMediaExtractionTests, FCPXMLTimelineManipulationTests, FCPXMLTimecodeTests, FCPXMLMIMETypeDetectionTests, FCPXMLAssetValidationTests, FCPXMLSilenceDetectionTests, FCPXMLAssetDurationMeasurementTests, FCPXMLParallelFileIOTests, FCPXMLAudioEnhancementTests, FCPXMLTransform360Tests, FCPXMLCaptionTitleTests, FCPXMLKeyframeAnimationTests, FCPXMLAudioKeyframeTests, FCPXMLCMTimeCodableTests, FCPXMLCollectionTests, FCPXMLSmartCollectionTests, FCPXMLAdjustmentTests, FCPXMLFilterTests, FCPXMLImportOptionsTests, FCPXMLCodableTests, FCPXMLAEXMLSerializationParityTests, FCPXMLDTDValidatorTests, FCPXMLStructuralValidatorTests, OpenFCPXMLKitTests.swift, FCPXMLTimelineExportValidationTests, FCPXMLAPIAndEdgeCaseTests, FCPXMLPerformanceTests, FCPXMLTimelineProjectionTests, FCPXMLProjectionCoverageTests, FCPXMLParsingCoverageTests, FCPXMLEngineHygieneTests, FCPXMLReportObligationCorpusTests, FCPXMLExtractionNestFidelityTests, FCPXMLRoleInheritanceMatrixTests, FCPXMLExtractionProjectionPolicyTests, FCPXMLMarkersKeywordsProjectionTests, FCPXMLTitlesProjectionTests, FCPXMLTransitionsProjectionTests, FCPXMLEffectsProjectionTests, FCPXMLSubmittedFCPXMLSmokeTests, and FCPXML-prefixed reporting/extraction tests (FCPXMLRoleInventoryReportTests, FCPXMLMarkersReportTests, FCPXMLKeywordsReportTests, FCPXMLTitlesReportTests, FCPXMLTransitionsReportTests, FCPXMLEffectsReportTests, FCPXMLSpeedChangeEffectsReportTests, FCPXMLSummaryReportTests, FCPXMLReportExcelExportTests, FCPXMLReportPDFExportTests, FCPXMLReportPDFSheetPlanTests, FCPXMLReportPDFTableLayoutTests, FCPXMLReportFormattingTests, FCPXMLReportRoleExclusionTests, FCPXMLReportTimecodeFormatTests, FCPXMLReportBuildPhaseTests, FCPXMLRoleDisplayPreferenceTests, FCPXMLRoleInventoryClipCollectorTests, FCPXMLRoleInventoryRoleSheetOrderingTests, FCPXMLSummaryRoleDurationAggregatorTests, FCPXMLEffectsReportPolicyTests, FCPXMLSpeedChangeFormattingTests, FCPXMLDisplayClipNameTests, FCPXMLTitleDisplayTests, FCPXMLExtractionScopeTests, FCPXMLExtractedElementTests, FCPXMLEffectsCollectorTests, FCPXMLRolesExtractionPresetTests, FCPXMLEffectAppleSuppliedTests, FCPXMLClipParsingCarriesAudioTests, FCPXMLTransformAdjustmentParsingTests, FCPXMLTransitionSpinePlacementTests); every test-case class is FCPXML-prefixed except the module-named umbrella OpenFCPXMLKitTests; empty timeline creation and project-creation export at different sizes and frame rates in FCPXMLTimelineExportValidationTests (clip-level metadata export, XML declaration standalone="no")). Cross-platform XML (OFKXML*, FCPXMLStructuralValidator, iOS). Private `Tests/Submitted FCPXML/` inbox (gitignored contents; never commit private FCPXML to GitHub). FCPXML 1.5–1.14 and FCPXMLElementType; FCPXMLVersion.supportsBundleFormat (1.10+); version conversion with element stripping and per-version DTD validation; FCPXML creation from scratch; timeline manipulation (ripple insert, auto lane assignment, clip queries, lane range, secondary storylines); timeline metadata (markers, chapter markers, keywords, ratings, timestamps); FCPXMLTimecode custom type; MIME type detection; asset validation (including still images); silence detection; asset duration measurement; parallel file I/O; TimelineFormat enhancements; typed adjustment models (Crop, Transform, Blend, Stabilization, Volume, Loudness, NoiseReduction, HumReduction, Equalization, MatchEqualization, Transform360, ColorConform, Stereo3D, VoiceIsolation); typed effect/filter models (VideoFilter, AudioFilter, VideoFilterMask, FilterParameter with keyframe animation and auxValue 1.11+); typed caption/title models (Caption, Title with TextStyle, TextStyleDefinition); smart collections (SmartCollection with match-clip, match-media, match-ratings, match-text, match-usage, match-representation, match-markers, match-analysis-type); keyframe animation (KeyframeAnimation, Keyframe, FadeIn, FadeOut); audio keyframes (FCPXMLAudioKeyframeTests: adjust-volume param keyframeAnimation parsing, decibel/time validation, fadeIn/fadeOut integration); CMTime Codable extension; collection organization (CollectionFolder, KeywordCollection); Live Drawing (1.11+); HiddenClipMarker (1.13+); Format/Asset 1.13+ (heroEye, heroEyeOverride, mediaReps); experimental CLI (OpenFCPXMLKit-CLI, single binary, embedded DTDs, --check-version, --convert-version, --extension-type fcpxml|fcpxmld, --validate, --media-copy, --create-project with DTD validation and FCP-style output, --report Excel/PDF report with --report-full/per-section flags/--report-summary/--report-media-summary/--media-resolution/--media-summary-distinguish-proxy/--exclude-role/--exclude-column/--exclude-disabled-clips/--include-markers-outside-clip-boundaries/--protect-sheets/--timecode-format/--report-project/--label-copyright/--create-pdf, --log/--log-level/--quiet with log file capturing all command output); Excel and PDF reporting subsystem (`allReportTimelineSources` / compound-clip timelines) (Reporting/ builders, Sections/Rows, Support including RoleInventoryColumnLayout, ReportColumnExclusion, ReportFormatting, ReportTimecodeFormat, ReportBuildProgress; Excel/ via XLKit; buildReport/ReportExcelExport/ReportPDFExport; Summary and Media Summary sheets; excludeDisabledClips/excludedColumns/timecodeFormat/copyrightLabel/includeMarkersOutsideClipBoundaries/protectSheets; `--label-copyright`; inventory-first enabledPhases; PDF cover notes + TOC colour chips / SheetPlan colorIndex; TableLayout column expansion to contentWidth and Row inject); extraction presets (Captions, Effects, FrameData, Markers, Roles, Titles); Tests/ExcelReportTest optional integration; Tests/Submitted FCPXML private inbox; Final Cut Pro frame rates; Swift 6 concurrency (Sendable, async/await, CI strict-concurrency job); Xcode 26 dynamic framework linking compatibility via explicit `swift-log` (`Logging`) dependency in `Package.swift`; SwiftExtensions 3.0.0+ and SwiftSemanticVersion 1.0.0+ (`SemanticVersion` for `FinalCutPro.FCPXML.Version`). When updating either file, update both and keep terminology and examples consistent. --- diff --git a/.gitignore b/.gitignore index 6fcdf52..0c3581e 100644 --- a/.gitignore +++ b/.gitignore @@ -22,11 +22,14 @@ Package.resolved # Xcode xcuserdata/ -# Local Excel report integration fixture and generated workbooks (not committed). -Tests/ExcelReportTest/Sample.fcpxmld/ -Tests/ExcelReportTest/Sample.fcpxml +# Local Excel report integration fixtures and generated workbooks (not committed). +Tests/ExcelReportTest/*.fcpxmld/ +Tests/ExcelReportTest/*.fcpxml +Tests/ExcelReportTest/Output/*.fcpxmld/ +Tests/ExcelReportTest/Output/*.fcpxml Tests/ExcelReportTest/Output/*.xlsx Tests/ExcelReportTest/Output/*.pdf +Tests/ExcelReportTest/Output/~$* # Private user-submitted FCPXML inbox (never commit contents — only README / .gitkeep). Tests/Submitted FCPXML/Inbox/** diff --git a/AGENT.md b/AGENT.md index 15dabd0..9b3768c 100644 --- a/AGENT.md +++ b/AGENT.md @@ -4,6 +4,8 @@ OpenFCPXMLKit is a modern, fully modular Swift 6 framework for Final Cut Pro FCP Keep this file in sync with `.cursorrules`. Both should describe the same overview, architecture, test structure, and conventions. When you update one, update the other. +**Hard constraints:** [GUARDRAILS.md](GUARDRAILS.md) — must / must-not for layers, naming, FCPXML compatibility, reporting honesty, and fixtures. Prefer GUARDRAILS for “what not to do”; [ARCHITECTURE.md](ARCHITECTURE.md) for structure and diagrams. + **Naming:** Use OpenFCPXMLKit naming exclusively in all code, documentation, comments, and agent files (`ServiceLogger`, `createService()`, `OFKXML*` types). Do not use legacy project names or identifiers from prior forks. Never use the terms "PBF" or "Production's Best Friend" in source code, code comments, symbol names, or CLI/log output; describe the reporting feature neutrally (e.g. "Excel report", "PDF report", "role inventory report", "workbook export"). Those terms may appear only in prose documentation (README, CHANGELOG, Manual, and these agent guides) — never in the codebase itself. --- @@ -41,7 +43,7 @@ OpenFCPXMLKit targets macOS 26+, iOS 26+, Xcode 26+, and Swift 6.3 with full con **Backward compatibility:** The entire codebase must remain backward compatible with FCPXML 1.5. Optional attributes and elements introduced in later versions (e.g. 1.11, 1.13) must be omitted or ignored when reading/writing or converting to 1.5; mark such features in code comments with the minimum FCPXML version (e.g. `FCPXML 1.13+`). -Current status: **1076** tests listed in `swift test --list-tests` (**1072** in `OpenFCPXMLKitTests`: 1069 XCTest + 3 Swift Testing `@Test`; plus **4** optional `ExcelReportTest` integration tests); FCPXML versions 1.5–1.14 supported (DTDs included, full parsing, typed element-type coverage for all DTD elements via FCPXMLElementType); Final Cut Pro frame rates (23.976, 24, 25, 29.97, 30, 50, 59.94, 60); thread-safe and concurrency-compliant with comprehensive async/await support; no known security vulnerabilities. Version conversion automatically drops elements not in the target version’s DTD (e.g. adjust-colorConform, adjust-stereo-3D); DTD validation runs per version (validateDocumentAgainstDTD, validateDocumentAgainstDeclaredVersion) and after CLI convert. FCPXMLVersion.supportsBundleFormat is true for 1.10+ (.fcpxmld bundle); 1.5–1.9 support only single-file .fcpxml. FCPXML creation: create FCPXML documents from scratch with events, projects, resources, and clips via XMLDocumentManager, XMLDocument initializers, or FCPXMLService. Timeline manipulation: ripple insert (shifts subsequent clips), auto lane assignment, clip queries (by lane, time range, asset ID), lane range computation, secondary storylines. Timeline metadata: markers, chapter markers, keywords, ratings, custom metadata, timestamps (createdAt, modifiedAt). FCPXMLTimecode: custom timecode type (arithmetic, frame alignment, CMTime conversion, FCPXML string parsing). MIME type detection, asset validation, silence detection, asset duration measurement, parallel file I/O, still image asset support. TimelineFormat enhancements: presets (hd720p, dci4K, hd1080i, hd720i), computed properties (aspectRatio, isHD, isUHD, interlaced). Typed adjustment models: Crop, Transform, Blend, Stabilization, Volume, Loudness, NoiseReduction, HumReduction, Equalization, MatchEqualization, Transform360, ColorConform, Stereo3D, VoiceIsolation with full clip integration. Typed effect/filter models: VideoFilter, AudioFilter, VideoFilterMask with FilterParameter support and keyframe animation (auxValue support FCPXML 1.11+). Typed caption/title models: Caption and Title with TextStyle and TextStyleDefinition for full text formatting. SmartCollection models: SmartCollection with match-clip, match-media, match-ratings, match-text, match-usage (1.9+), match-representation (1.10+), match-markers (1.10+), match-analysis-type (1.14). Keyframe animation: KeyframeAnimation, Keyframe with interpolation types, FadeIn/FadeOut with fade types, integrated with FilterParameter. CMTime Codable extension: Direct CMTime encoding/decoding as FCPXML time strings. Collection organization: CollectionFolder and KeywordCollection models for organizing clips and media. Live Drawing (FCPXML 1.11+): LiveDrawing model for live-drawing story elements. HiddenClipMarker (FCPXML 1.13+): HiddenClipMarker model for hidden clip markers. Format/Asset 1.13+: Format heroEye, Asset heroEyeOverride, Asset mediaReps (multiple media-rep). Cross-platform XML abstraction: protocol layer (OFKXMLNode, OFKXMLElement, OFKXMLDocument, OFKXMLFactory); Foundation backend on macOS (unchanged behaviour); AEXML backend on iOS; OFKXMLDefaultFactory() for platform dispatch; FCPXMLStructuralValidator for cross-platform structural validation; FCPXMLDTDValidator is platform-conditional (full DTD on macOS, structural fallback on iOS). Comprehensive test coverage: **1076** tests across 59 FCPXML sample files including 360 video, auditions, conform-rate, still images, multicam, secondary storylines, audio keyframes (FCPXMLAudioKeyframeTests: adjust-volume param keyframeAnimation parsing, decibel/time validation, fadeIn/fadeOut integration, secondary storyline detection), keyword collections/folders, Photoshop integration, smart collections, and reporting column layout/exclusion/disabled-clip/workbook/PDF formatting tests. Excel and PDF reporting: multi-sheet `.xlsx` workbooks via `FinalCutPro.FCPXML.buildReport(options:)` (ReportBuilder, ReportOptions presets, ReportExcelExport on XLKit) and optional `.pdf` via `ReportPDFExport` (CoreGraphics; cover page with black “About This PDF Export” + `info.circle`, TOC with accent colour chips + content-tint washes, per-sheet tints, column-width expansion after exclusions, section pagination; same Report configuration as Excel); sheets for Role Inventory (**Selected Roles Inventory** + per-role sheets with expanded column layout and dynamic metadata keys), Markers, Keywords, Titles & Generators, Transitions, Video & Audio Effects, Speed Change Effects, **Summary** (project title in **B1**, narrow Row column A, black role-duration data), and **Media Summary** (Row + red missing-media paths); 1-based **Row** on all tabular Excel/PDF sheets by default (`ensuringRowColumn` / `allowsInjectedRowColumn`); inventory and section-sheet cell formatting, role exclusions, global column exclusion (`ReportColumn` / `excludedColumns`, including `ReportColumn.row`), disabled-clip filtering (`excludeDisabledClips`), project-name / compound-clip-name filtering (`allReportTimelineSources()`; standalone compound-clip exports without ``), `ReportTimecodeFormat` / `--timecode-format`, inventory-first `ReportBuildPhase` progress callbacks; optional `copyrightLabel` / CLI `--label-copyright` (Excel cover **A2**; PDF cover + footer centre). Extraction presets: Captions, Effects, FrameData, Markers, Roles, Titles. Experimental CLI (OpenFCPXMLKit-CLI): single binary with embedded DTDs; --check-version, --convert-version (stripping + DTD validation), --extension-type (fcpxmld | fcpxml; default fcpxmld; 1.5–1.9 always .fcpxml), --validate, --media-copy, --create-project (new empty FCPXML project: --width, --height, --rate, --project-version, output-dir; DTD validation before write; FCP-style output with DOCTYPE, colorSpace, default smart collections), --report (Excel report: role inventory by default; --report-full, per-section flags including --report-markers, --report-keywords, --report-titles-generators, --report-transitions, --report-effects, --report-speed-change-effects, --report-summary, --report-media-summary, --media-resolution, --media-summary-distinguish-proxy, --exclude-role, --exclude-column, --exclude-disabled-clips, --timecode-format, --report-project, --label-copyright, --create-pdf); --log writes user-visible output for all commands to the log file; see Sources/OpenFCPXMLKitCLI/README.md. +Current status: **1084** tests listed in `swift test --list-tests` (**1078** in `OpenFCPXMLKitTests`: 1075 XCTest + 3 Swift Testing `@Test`; plus **6** optional `ExcelReportTest` integration tests); FCPXML versions 1.5–1.14 supported (DTDs included, full parsing, typed element-type coverage for all DTD elements via FCPXMLElementType); Final Cut Pro frame rates (23.976, 24, 25, 29.97, 30, 50, 59.94, 60); thread-safe and concurrency-compliant with comprehensive async/await support; no known security vulnerabilities. Version conversion automatically drops elements not in the target version’s DTD (e.g. adjust-colorConform, adjust-stereo-3D); DTD validation runs per version (validateDocumentAgainstDTD, validateDocumentAgainstDeclaredVersion) and after CLI convert. FCPXMLVersion.supportsBundleFormat is true for 1.10+ (.fcpxmld bundle); 1.5–1.9 support only single-file .fcpxml. FCPXML creation: create FCPXML documents from scratch with events, projects, resources, and clips via XMLDocumentManager, XMLDocument initializers, or FCPXMLService. Timeline manipulation: ripple insert (shifts subsequent clips), auto lane assignment, clip queries (by lane, time range, asset ID), lane range computation, secondary storylines. Timeline metadata: markers, chapter markers, keywords, ratings, custom metadata, timestamps (createdAt, modifiedAt). FCPXMLTimecode: custom timecode type (arithmetic, frame alignment, CMTime conversion, FCPXML string parsing). MIME type detection, asset validation, silence detection, asset duration measurement, parallel file I/O, still image asset support. TimelineFormat enhancements: presets (hd720p, dci4K, hd1080i, hd720i), computed properties (aspectRatio, isHD, isUHD, interlaced). Typed adjustment models: Crop, Transform, Blend, Stabilization, Volume, Loudness, NoiseReduction, HumReduction, Equalization, MatchEqualization, Transform360, ColorConform, Stereo3D, VoiceIsolation with full clip integration. Typed effect/filter models: VideoFilter, AudioFilter, VideoFilterMask with FilterParameter support and keyframe animation (auxValue support FCPXML 1.11+). Typed caption/title models: Caption and Title with TextStyle and TextStyleDefinition for full text formatting. SmartCollection models: SmartCollection with match-clip, match-media, match-ratings, match-text, match-usage (1.9+), match-representation (1.10+), match-markers (1.10+), match-analysis-type (1.14). Keyframe animation: KeyframeAnimation, Keyframe with interpolation types, FadeIn/FadeOut with fade types, integrated with FilterParameter. CMTime Codable extension: Direct CMTime encoding/decoding as FCPXML time strings. Collection organization: CollectionFolder and KeywordCollection models for organizing clips and media. Live Drawing (FCPXML 1.11+): LiveDrawing model for live-drawing story elements. HiddenClipMarker (FCPXML 1.13+): HiddenClipMarker model for hidden clip markers. Format/Asset 1.13+: Format heroEye, Asset heroEyeOverride, Asset mediaReps (multiple media-rep). Cross-platform XML abstraction: protocol layer (OFKXMLNode, OFKXMLElement, OFKXMLDocument, OFKXMLFactory); Foundation backend on macOS (unchanged behaviour); AEXML backend on iOS; OFKXMLDefaultFactory() for platform dispatch; FCPXMLStructuralValidator for cross-platform structural validation; FCPXMLDTDValidator is platform-conditional (full DTD on macOS, structural fallback on iOS). Comprehensive test coverage: **1078** tests across 60 FCPXML sample files including 360 video, auditions, conform-rate, still images, multicam, secondary storylines, audio keyframes (FCPXMLAudioKeyframeTests: adjust-volume param keyframeAnimation parsing, decibel/time validation, fadeIn/fadeOut integration, secondary storyline detection), keyword collections/folders, Photoshop integration, smart collections, and reporting column layout/exclusion/disabled-clip/workbook/PDF formatting tests. Excel and PDF reporting: multi-sheet `.xlsx` workbooks via `FinalCutPro.FCPXML.buildReport(options:)` (ReportBuilder, ReportOptions presets, ReportExcelExport on XLKit) and optional `.pdf` via `ReportPDFExport` (CoreGraphics; cover page with black “About This PDF Export” + `info.circle`, TOC with accent colour chips + content-tint washes, per-sheet tints, column-width expansion after exclusions, section pagination; same Report configuration as Excel); sheets for Role Inventory (**Selected Roles Inventory** + per-role sheets with expanded column layout and dynamic metadata keys), Markers, Keywords, Titles & Generators, Transitions, Video & Audio Effects, Speed Change Effects, **Summary** (project title in **B1**, narrow Row column A, black role-duration data), and **Media Summary** (Row + red missing-media paths); 1-based **Row** on all tabular Excel/PDF sheets by default (`ensuringRowColumn` / `allowsInjectedRowColumn`); inventory and section-sheet cell formatting, role exclusions, global column exclusion (`ReportColumn` / `excludedColumns`, including `ReportColumn.row`), disabled-clip filtering (`excludeDisabledClips`), project-name / compound-clip-name filtering (`allReportTimelineSources()`; standalone compound-clip exports without ``), `ReportTimecodeFormat` / `--timecode-format`, inventory-first `ReportBuildPhase` progress callbacks; optional `copyrightLabel` / CLI `--label-copyright` (Excel cover **A2**; PDF cover + footer centre). Extraction presets: Captions, Effects, FrameData, Markers, Roles, Titles. Experimental CLI (OpenFCPXMLKit-CLI): single binary with embedded DTDs; --check-version, --convert-version (stripping + DTD validation), --extension-type (fcpxmld | fcpxml; default fcpxmld; 1.5–1.9 always .fcpxml), --validate, --media-copy, --create-project (new empty FCPXML project: --width, --height, --rate, --project-version, output-dir; DTD validation before write; FCP-style output with DOCTYPE, colorSpace, default smart collections), --report (Excel report: role inventory by default; --report-full, per-section flags including --report-markers, --report-keywords, --report-titles-generators, --report-transitions, --report-effects, --report-speed-change-effects, --report-summary, --report-media-summary, --media-resolution, --media-summary-distinguish-proxy, --exclude-role, --exclude-column, --exclude-disabled-clips, --include-markers-outside-clip-boundaries, --protect-sheets, --timecode-format, --report-project, --label-copyright, --create-pdf); --log writes user-visible output for all commands to the log file; see Sources/OpenFCPXMLKitCLI/README.md. Xcode 26 dynamic linking compatibility: `swift-log` (`Logging`) is an explicit direct dependency in `Package.swift` to satisfy stricter transitive dylib linking rules when building OpenFCPXMLKit as a dynamic framework. @@ -163,9 +165,9 @@ Tests live under Tests/. The suite is organised as follows. - Tests/README.md: Full description of test categories, how to run tests (Swift PM, Xcode, Linux), sample files, frame rates, FCPXML versions, and contributing. Keep it current when adding or changing tests. -- Tests/FCPXML Samples/FCPXML/: Sample .fcpxml files (59 samples, e.g. 24.fcpxml, GeneralDemo.fcpxml, Structure.fcpxml, frame-rate samples). File tests and logic tests load these via shared utilities; tests that require a missing sample use XCTSkip. +- Tests/FCPXML Samples/FCPXML/: Sample .fcpxml files (60 samples, e.g. 24.fcpxml, GeneralDemo.fcpxml, Structure.fcpxml, frame-rate samples). File tests and logic tests load these via shared utilities; tests that require a missing sample use XCTSkip. -- Tests/ExcelReportTest/: Optional integration target (4 tests). Writes `Output/OFK-Default.xlsx`, `Output/OFK-Full.xlsx`, `Output/OFK-Default.pdf`, `Output/OFK-ExcludedColumns.pdf`, `Output/OFK-Copyright.xlsx`, and `Output/OFK-Copyright.pdf` from a local `.fcpxml`/`.fcpxmld` fixture (`Sample.fcpxmld`, `OFK_REPORTING_FCPXML_BUNDLE`, or auto-discovery including under `Output/`). Skips when no fixture is present. See Tests/ExcelReportTest/README.md. +- Tests/ExcelReportTest/: Optional integration target (6 tests). Writes `Output/OFK-Default.xlsx`, `Output/OFK-Full.xlsx`, `Output/OFK-Default.pdf`, `Output/OFK-ExcludedColumns.pdf`, `Output/OFK-Copyright.xlsx`, `Output/OFK-Copyright.pdf`, `Output/OFK-OutsideClipBoundaries.xlsx` / `.pdf`, and `Output/OFK-ProtectedSheets.xlsx` from a local `.fcpxml`/`.fcpxmld` fixture (`Sample.fcpxmld`, `OFK_REPORTING_FCPXML_BUNDLE`, or auto-discovery including under `Output/`). Skips when no fixture is present. See Tests/ExcelReportTest/README.md. - Tests/Submitted FCPXML/: Private local inbox for user-supplied FCPXML (`Inbox/`, `Notes/`). Contents are **gitignored** (never commit or push to GitHub); only `README.md` and `.gitkeep` placeholders are tracked. Workflow: drop private export → anonymise → reproduce → fix Parsing/Projection/Reporting → promote a **minimal anonymised** fixture into `FCPXML Samples/` when useful. Optional smoke: `FCPXMLSubmittedFCPXMLSmokeTests` (XCTSkip when Inbox empty). See Tests/Submitted FCPXML/README.md and Tests/README.md §12a. @@ -201,7 +203,7 @@ Tests live under Tests/. The suite is organised as follows. - FCPXMLAEXMLSerializationParityTests.swift: AEXML vs Foundation XML serialization parity. - Report and extraction tests (FCPXML-prefixed): FCPXMLCompoundClipReportTests, FCPXMLRoleInventoryReportTests, FCPXMLRoleInventoryColumnLayoutTests, FCPXMLMarkersReportTests, FCPXMLKeywordsReportTests, FCPXMLTitlesReportTests, FCPXMLTransitionsReportTests, FCPXMLEffectsReportTests, FCPXMLSpeedChangeEffectsReportTests, FCPXMLSummaryReportTests, FCPXMLReportExcelExportTests, FCPXMLReportPDFExportTests, FCPXMLReportPDFSheetPlanTests, FCPXMLReportPDFTableLayoutTests, FCPXMLReportFormattingTests, FCPXMLReportRoleExclusionTests, FCPXMLReportTimecodeFormatTests, FCPXMLReportBuildPhaseTests, FCPXMLReportColumnExclusionTests, FCPXMLReportExcludeDisabledClipsTests, FCPXMLRoleDisplayPreferenceTests, FCPXMLRoleInventoryClipCollectorTests, FCPXMLRoleInventoryRoleSheetOrderingTests, FCPXMLSummaryRoleDurationAggregatorTests, FCPXMLEffectsReportPolicyTests, FCPXMLSpeedChangeFormattingTests, FCPXMLDisplayClipNameTests, FCPXMLTitleDisplayTests, FCPXMLExtractionScopeTests, FCPXMLExtractedElementTests, FCPXMLEffectsCollectorTests, FCPXMLRolesExtractionPresetTests, FCPXMLEffectAppleSuppliedTests, FCPXMLClipParsingCarriesAudioTests, FCPXMLTransformAdjustmentParsingTests, FCPXMLTransitionSpinePlacementTests. - OpenFCPXMLKitTests.swift: Main test class; setUpWithError injects parser, timecodeConverter, documentManager, errorHandler, FCPXMLUtility, FCPXMLService. MARK sections group tests (FCPXMLUtility, FCPXMLService, modular components, async/concurrency, performance, frame rates, time values, FCPXML time strings, time conforming, error handling, document management, element filtering, modular extensions, edge cases, FCPXMLElementType, FCPXMLError, ModularUtilities API, XMLDocument extension, XMLElement extension, parser filter). - - FileTests/: One test class per sample or category (e.g. FCPXMLFileTest_24, FCPXMLFileTest_AllSamples, FCPXMLFileTest_FrameRates, FCPXMLFileTest_GeneralDemo, FCPXMLFileTest_360Video, FCPXMLFileTest_AuditionSample, FCPXMLFileTest_ImageSample, FCPXMLFileTest_Multicam, FCPXMLFileTest_Photoshop, FCPXMLFileTest_SmartCollection). Each loads one or more samples and asserts parse success, root, version, events, projects, or resources as appropriate. + - FileTests/: One test class per sample or category (e.g. FCPXMLFileTest_24, FCPXMLFileTest_AllSamples, FCPXMLFileTest_FrameRates, FCPXMLFileTest_GeneralDemo, FCPXMLFileTest_HiddenMarkers, FCPXMLFileTest_360Video, FCPXMLFileTest_AuditionSample, FCPXMLFileTest_ImageSample, FCPXMLFileTest_Multicam, FCPXMLFileTest_Photoshop, FCPXMLFileTest_SmartCollection). Each loads one or more samples and asserts parse success, root, version, events, projects, or resources as appropriate. - FCPXMLSubmittedFCPXMLSmokeTests: Optional parse smoke for private files in `Tests/Submitted FCPXML/Inbox/` (XCTSkip when empty; CI-safe). - FCPXMLAudioKeyframeTests: Audio keyframes in adjust-volume (param name="amount" with keyframeAnimation); parsing from FCPXML samples; decibel values (-3dB, -37dB); time values (FCPXML fractional format); fadeIn/fadeOut integration; multiple keyframes in sequence; secondary storyline and nested clip detection; TimelineWithSecondaryStorylineWithAudioKeyframes, TimelineSample file tests. - LogicAndParsing/: FCPXMLRootVersionTests (Version init, rawValue, Equatable, Comparable, invalid strings), FCPXMLStructureTests (Structure sample, allEvents/allProjects, root structure), FCPXMLFormatAssetTests (Format heroEye, Asset heroEyeOverride, Asset mediaReps). @@ -210,7 +212,7 @@ Tests live under Tests/. The suite is organised as follows. - FCPXMLAPIAndEdgeCaseTests: FCPXMLFileLoader async load(from:), ServiceLogger injection (NoOp, Print), edge cases (empty/invalid/malformed XML, invalid paths), validation types, Live Drawing (1.11+), HiddenClipMarker (1.13+). - FCPXMLPerformanceTests: Parameterised and basic performance tests (timecode conversion, document creation, element filtering). -Test organisation: use descriptive test method names; group related tests logically; include setup and teardown; use meaningful assertions. Test all supported frame rates (Final Cut Pro compatible). Use realistic FCPXML samples and edge cases; validate against actual FCP behaviour where applicable. Current total: **1076** tests listed in `swift test --list-tests` (**1072** in `OpenFCPXMLKitTests` + **4** optional `ExcelReportTest`) covering all functionality including async/await, timeline manipulation, metadata, timestamps, FCPXMLTimecode, MIME type detection, asset validation, silence detection, asset duration measurement, parallel file I/O, cut detection, version conversion stripping, per-version DTD validation, extract-then-copy (CLI --media-copy flow), synchronized clip matching, secondary storyline traversal, clip identification, URL resolution, version conversion edge cases, typed adjustment models (including Transform360, ColorConform, Stereo3D, VoiceIsolation), typed effect/filter models, typed caption/title models, smart collections (match-clip, match-media, match-ratings, match-text, match-usage, match-representation, match-markers, match-analysis-type), keyframe animation, audio keyframes (FCPXMLAudioKeyframeTests: adjust-volume param keyframeAnimation parsing, decibel/time validation, fadeIn/fadeOut integration), CMTime Codable extension, collection organization, Live Drawing (1.11+), HiddenClipMarker (1.13+), Format/Asset 1.13+ (heroEye, heroEyeOverride, mediaReps), FCPXMLExporter clip-level metadata export and XML declaration standalone="no", FCPXMLTimelineManipulationTests refactor (lock-based NowBox, do-catch for throwing APIs), Excel reporting (Selected Roles Inventory column layout, Summary/Media Summary split, ReportTimecodeFormat / format-aware headers, inventory-first ReportBuildPhase progress, global column exclusion, disabled-clip filtering, workbook cell formatting via FCPXMLReportWorkbookExporter), 360 video features, auditions, conform-rate, still images, multicam, secondary storylines, audio keyframes, keyword collections/folders, and Photoshop integration. +Test organisation: use descriptive test method names; group related tests logically; include setup and teardown; use meaningful assertions. Test all supported frame rates (Final Cut Pro compatible). Use realistic FCPXML samples and edge cases; validate against actual FCP behaviour where applicable. Current total: **1084** tests listed in `swift test --list-tests` (**1078** in `OpenFCPXMLKitTests` + **6** optional `ExcelReportTest`) covering all functionality including async/await, timeline manipulation, metadata, timestamps, FCPXMLTimecode, MIME type detection, asset validation, silence detection, asset duration measurement, parallel file I/O, cut detection, version conversion stripping, per-version DTD validation, extract-then-copy (CLI --media-copy flow), synchronized clip matching, secondary storyline traversal, clip identification, URL resolution, version conversion edge cases, typed adjustment models (including Transform360, ColorConform, Stereo3D, VoiceIsolation), typed effect/filter models, typed caption/title models, smart collections (match-clip, match-media, match-ratings, match-text, match-usage, match-representation, match-markers, match-analysis-type), keyframe animation, audio keyframes (FCPXMLAudioKeyframeTests: adjust-volume param keyframeAnimation parsing, decibel/time validation, fadeIn/fadeOut integration), CMTime Codable extension, collection organization, Live Drawing (1.11+), HiddenClipMarker (1.13+), Format/Asset 1.13+ (heroEye, heroEyeOverride, mediaReps), FCPXMLExporter clip-level metadata export and XML declaration standalone="no", FCPXMLTimelineManipulationTests refactor (lock-based NowBox, do-catch for throwing APIs), Excel reporting (Selected Roles Inventory column layout, Summary/Media Summary split, ReportTimecodeFormat / format-aware headers, inventory-first ReportBuildPhase progress, global column exclusion, disabled-clip filtering, workbook cell formatting via FCPXMLReportWorkbookExporter), 360 video features, auditions, conform-rate, still images, multicam, secondary storylines, audio keyframes, keyword collections/folders, and Photoshop integration. --- @@ -231,7 +233,7 @@ SwiftTimecode usage: use `Timecode(.realTime(seconds: seconds), at: frameRate)` ## Testing Requirements -Test coverage: unit tests for all public APIs; integration tests for complex workflows; performance tests for time-critical operations; concurrency tests for async operations; test all supported frame rates (Final Cut Pro compatible). Current: **1076** tests listed in `swift test --list-tests` (including AEXML serialization parity, FCPXMLDTDValidatorTests, FCPXMLStructuralValidatorTests, FCPXMLReportTimecodeFormatTests, FCPXMLReportBuildPhaseTests, FCPXMLReportColumnExclusionTests, FCPXMLReportExcludeDisabledClipsTests, FCPXMLRoleInventoryColumnLayoutTests, FCPXMLReportExcelExportTests workbook cell formatting, FCPXMLReportPDFExportTests PDF export, FCPXMLReportPDFSheetPlanTests TOC colour-index parity, FCPXMLReportPDFTableLayoutTests column-width expansion, optional ExcelReportTest integration) covering all functionality including async/await, timeline manipulation, metadata, timestamps, FCPXMLTimecode, MIME type detection, asset validation, silence detection, asset duration measurement, parallel file I/O, version conversion, DTD validation, extract-then-copy flow, synchronized clip matching, secondary storyline traversal, clip identification, URL resolution, version conversion edge cases, typed adjustment models (including Transform360, ColorConform, Stereo3D, VoiceIsolation), typed effect/filter models, typed caption/title models, smart collections (match-clip, match-media, match-ratings, match-text, match-usage, match-representation, match-markers, match-analysis-type), keyframe animation, audio keyframes (FCPXMLAudioKeyframeTests: adjust-volume param keyframeAnimation parsing, decibel/time validation, fadeIn/fadeOut integration), CMTime Codable extension, collection organization, Live Drawing (1.11+), HiddenClipMarker (1.13+), Format/Asset 1.13+ (heroEye, heroEyeOverride, mediaReps), FCPXMLExporter clip-level metadata export and XML declaration standalone="no", FCPXMLTimelineManipulationTests (lock-based NowBox, do-catch), Excel reporting enhancements, PDF report export (TOC colour chips, column expansion after exclusions), 360 video features, auditions, conform-rate, still images, multicam, secondary storylines, audio keyframes, keyword collections/folders, and Photoshop integration. +Test coverage: unit tests for all public APIs; integration tests for complex workflows; performance tests for time-critical operations; concurrency tests for async operations; test all supported frame rates (Final Cut Pro compatible). Current: **1084** tests listed in `swift test --list-tests` (including AEXML serialization parity, FCPXMLDTDValidatorTests, FCPXMLStructuralValidatorTests, FCPXMLReportTimecodeFormatTests, FCPXMLReportBuildPhaseTests, FCPXMLReportColumnExclusionTests, FCPXMLReportExcludeDisabledClipsTests, FCPXMLRoleInventoryColumnLayoutTests, FCPXMLReportExcelExportTests workbook cell formatting, FCPXMLReportPDFExportTests PDF export, FCPXMLReportPDFSheetPlanTests TOC colour-index parity, FCPXMLReportPDFTableLayoutTests column-width expansion, optional ExcelReportTest integration) covering all functionality including async/await, timeline manipulation, metadata, timestamps, FCPXMLTimecode, MIME type detection, asset validation, silence detection, asset duration measurement, parallel file I/O, version conversion, DTD validation, extract-then-copy flow, synchronized clip matching, secondary storyline traversal, clip identification, URL resolution, version conversion edge cases, typed adjustment models (including Transform360, ColorConform, Stereo3D, VoiceIsolation), typed effect/filter models, typed caption/title models, smart collections (match-clip, match-media, match-ratings, match-text, match-usage, match-representation, match-markers, match-analysis-type), keyframe animation, audio keyframes (FCPXMLAudioKeyframeTests: adjust-volume param keyframeAnimation parsing, decibel/time validation, fadeIn/fadeOut integration), CMTime Codable extension, collection organization, Live Drawing (1.11+), HiddenClipMarker (1.13+), Format/Asset 1.13+ (heroEye, heroEyeOverride, mediaReps), FCPXMLExporter clip-level metadata export and XML declaration standalone="no", FCPXMLTimelineManipulationTests (lock-based NowBox, do-catch), Excel reporting enhancements, PDF report export (TOC colour chips, column expansion after exclusions), 360 video features, auditions, conform-rate, still images, multicam, secondary storylines, audio keyframes, keyword collections/folders, and Photoshop integration. Test data: use realistic FCPXML samples; include edge cases and error conditions; test all supported frame rates; validate against actual Final Cut Pro output where applicable. @@ -286,7 +288,7 @@ Build requirements: Swift build successful; all tests passing; no warnings or er ## Project-Specific Rules -FCPXML handling: support FCPXML versions 1.5 through 1.14 (DTDs included; full parsing; typed element-type coverage via FCPXMLElementType for all DTD elements); validate against DTD schemas (FCPXMLDTDValidator, FCPXMLService.validateDocumentAgainstDTD/validateDocumentAgainstDeclaredVersion); version conversion (FCPXMLVersionConverter) sets root version and automatically strips elements not in the target version’s DTD (e.g. adjust-colorConform, adjust-stereo-3D); FCPXMLVersion.supportsBundleFormat is true for 1.10+ (save as .fcpxmld bundle); 1.5–1.9 support only .fcpxml; handle all supported frame rates (Final Cut Pro compatible); implement proper XML formatting. Media extraction: extract media references (asset media-rep src, locator url) and copy referenced file URLs to a destination directory (MediaExtraction protocol, MediaExtractor). Timeline manipulation: ripple insert (shifts subsequent clips), auto lane assignment (finds available lanes), clip queries (by lane, time range, asset ID), lane range computation, secondary storylines. Timeline metadata: markers, chapter markers, keywords, ratings, custom metadata on timeline and clips; timestamps (createdAt, modifiedAt) updated on all mutating operations. TimelineFormat: presets (hd720p, dci4K, hd1080i, hd720i), computed properties (aspectRatio, isHD, isUHD, interlaced). FCPXMLTimecode: custom timecode type wrapping Fraction (arithmetic, frame alignment, CMTime conversion, FCPXML string parsing). MIME type detection: MIMETypeDetection protocol and MIMETypeDetector implementation (UTType, AVFoundation, file extension fallback). Asset validation: AssetValidation protocol and AssetValidator implementation (existence check, MIME type compatibility, lane rules: negative = audio only, non-negative = video/image/audio); still image asset support (duration=0s). Silence detection: SilenceDetection protocol and SilenceDetector implementation (configurable threshold and minimum duration). Asset duration measurement: AssetDurationMeasurement protocol and AssetDurationMeasurer implementation (AVFoundation-based for audio/video/images). Parallel file I/O: ParallelFileIO protocol and ParallelFileIOExecutor implementation (concurrent read/write operations). SmartCollection: SmartCollection model with match-clip, match-media, match-ratings, match-text, match-usage (1.9+), match-representation (1.10+), match-markers (1.10+), match-analysis-type (1.14); library and event integration. Live Drawing (1.11+): LiveDrawing model for live-drawing story elements. HiddenClipMarker (1.13+): HiddenClipMarker model for hidden clip markers. Format/Asset 1.13+: Format heroEye (left|right), Asset heroEyeOverride, Asset mediaReps (multiple media-rep). Excel and PDF reporting: multi-sheet `.xlsx` workbooks (`FinalCutPro.FCPXML.buildReport(options:)`, ReportBuilder, ReportOptions presets, ReportExcelExport on XLKit) with Role Inventory (**Selected Roles Inventory** + per-role sheets), Markers, Keywords, Titles & Generators, Transitions, Video & Audio Effects, Speed Change Effects, Summary (project title in **B1**, role durations), and Media Summary (missing media paths); universal **Row** via `ensuringRowColumn` / `allowsInjectedRowColumn`; role exclusions, global column exclusion (`excludedColumns` / `ReportColumn`), disabled-clip filtering (`excludeDisabledClips`), `ReportTimecodeFormat` / `--timecode-format` (SMPTE frames with DF/NDF, Frames, Feet+Frames, HH:MM:SS; format-aware headers; numeric sort for Frames/Feet+Frames), inventory-first `ReportBuildPhase.enabledPhases(for:)` progress, project-name filtering, and optional `copyrightLabel` / `--label-copyright` (Excel cover **A2**; PDF cover + footer centre); builders consume Extraction/Model and own presentation only (see ARCHITECTURE.md §2.7). Experimental CLI: OpenFCPXMLKit-CLI single binary (embedded DTDs) with --check-version, --convert-version (stripping + DTD validation), --extension-type fcpxml|fcpxmld (default fcpxmld; 1.5–1.9 always .fcpxml), --validate, --media-copy, --create-project (width, height, rate, project-version, output-dir; mandatory DTD validation; FCP-style DOCTYPE, format colorSpace, default smart collections), --report (Excel report: role inventory by default; --report-full, per-section flags including --report-summary and --report-media-summary, --media-resolution, --media-summary-distinguish-proxy, --exclude-role, --exclude-column, --exclude-disabled-clips, --timecode-format, --report-project, --label-copyright, --create-pdf), and LOG options (--log, --log-level, --quiet; --log records user-visible output for all commands); see Sources/OpenFCPXMLKitCLI/README.md. +FCPXML handling: support FCPXML versions 1.5 through 1.14 (DTDs included; full parsing; typed element-type coverage via FCPXMLElementType for all DTD elements); validate against DTD schemas (FCPXMLDTDValidator, FCPXMLService.validateDocumentAgainstDTD/validateDocumentAgainstDeclaredVersion); version conversion (FCPXMLVersionConverter) sets root version and automatically strips elements not in the target version’s DTD (e.g. adjust-colorConform, adjust-stereo-3D); FCPXMLVersion.supportsBundleFormat is true for 1.10+ (save as .fcpxmld bundle); 1.5–1.9 support only .fcpxml; handle all supported frame rates (Final Cut Pro compatible); implement proper XML formatting. Media extraction: extract media references (asset media-rep src, locator url) and copy referenced file URLs to a destination directory (MediaExtraction protocol, MediaExtractor). Timeline manipulation: ripple insert (shifts subsequent clips), auto lane assignment (finds available lanes), clip queries (by lane, time range, asset ID), lane range computation, secondary storylines. Timeline metadata: markers, chapter markers, keywords, ratings, custom metadata on timeline and clips; timestamps (createdAt, modifiedAt) updated on all mutating operations. TimelineFormat: presets (hd720p, dci4K, hd1080i, hd720i), computed properties (aspectRatio, isHD, isUHD, interlaced). FCPXMLTimecode: custom timecode type wrapping Fraction (arithmetic, frame alignment, CMTime conversion, FCPXML string parsing). MIME type detection: MIMETypeDetection protocol and MIMETypeDetector implementation (UTType, AVFoundation, file extension fallback). Asset validation: AssetValidation protocol and AssetValidator implementation (existence check, MIME type compatibility, lane rules: negative = audio only, non-negative = video/image/audio); still image asset support (duration=0s). Silence detection: SilenceDetection protocol and SilenceDetector implementation (configurable threshold and minimum duration). Asset duration measurement: AssetDurationMeasurement protocol and AssetDurationMeasurer implementation (AVFoundation-based for audio/video/images). Parallel file I/O: ParallelFileIO protocol and ParallelFileIOExecutor implementation (concurrent read/write operations). SmartCollection: SmartCollection model with match-clip, match-media, match-ratings, match-text, match-usage (1.9+), match-representation (1.10+), match-markers (1.10+), match-analysis-type (1.14); library and event integration. Live Drawing (1.11+): LiveDrawing model for live-drawing story elements. HiddenClipMarker (1.13+): HiddenClipMarker model for hidden clip markers. Format/Asset 1.13+: Format heroEye (left|right), Asset heroEyeOverride, Asset mediaReps (multiple media-rep). Excel and PDF reporting: multi-sheet `.xlsx` workbooks (`FinalCutPro.FCPXML.buildReport(options:)`, ReportBuilder, ReportOptions presets, ReportExcelExport on XLKit) with Role Inventory (**Selected Roles Inventory** + per-role sheets), Markers, Keywords, Titles & Generators, Transitions, Video & Audio Effects, Speed Change Effects, Summary (project title in **B1**, role durations), and Media Summary (missing media paths); universal **Row** via `ensuringRowColumn` / `allowsInjectedRowColumn`; role exclusions, global column exclusion (`excludedColumns` / `ReportColumn`), disabled-clip filtering (`excludeDisabledClips`), `ReportTimecodeFormat` / `--timecode-format` (SMPTE frames with DF/NDF, Frames, Feet+Frames, HH:MM:SS; format-aware headers; numeric sort for Frames/Feet+Frames), inventory-first `ReportBuildPhase.enabledPhases(for:)` progress, project-name filtering, and optional `copyrightLabel` / `--label-copyright` (Excel cover **A2**; PDF cover + footer centre); builders consume Extraction/Model and own presentation only (see ARCHITECTURE.md §2.7). Experimental CLI: OpenFCPXMLKit-CLI single binary (embedded DTDs) with --check-version, --convert-version (stripping + DTD validation), --extension-type fcpxml|fcpxmld (default fcpxmld; 1.5–1.9 always .fcpxml), --validate, --media-copy, --create-project (width, height, rate, project-version, output-dir; mandatory DTD validation; FCP-style DOCTYPE, format colorSpace, default smart collections), --report (Excel report: role inventory by default; --report-full, per-section flags including --report-summary and --report-media-summary, --media-resolution, --media-summary-distinguish-proxy, --exclude-role, --exclude-column, --exclude-disabled-clips, --include-markers-outside-clip-boundaries, --protect-sheets, --timecode-format, --report-project, --label-copyright, --create-pdf), and LOG options (--log, --log-level, --quiet; --log records user-visible output for all commands); see Sources/OpenFCPXMLKitCLI/README.md. Timecode operations: use SwiftTimecode for all timecode operations; support all FCPXML frame rates (Final Cut Pro compatible); implement proper frame rate conversions; handle drop frame and non-drop frame timecode. FCPXMLTimecode provides a custom timecode type for FCPXML-specific operations (arithmetic, frame alignment, CMTime conversion, FCPXML string parsing). @@ -314,13 +316,13 @@ Code review process: review for Swift 6.3 compliance; check concurrency implemen ## Documentation Sync -Keep AGENT.md and .cursorrules in sync. Both must reflect: changelog styling (CHANGELOG.md: Keep a Changelog format, version links to release tags, ✨ New Features / 🔧 Improvements / 🐛 Bug Fixes). +Keep AGENT.md and .cursorrules in sync. Both must reflect: changelog styling (CHANGELOG.md: Keep a Changelog format, version links to release tags, ✨ New Features / 🔧 Improvements / 🐛 Bug Fixes). Hard must / must-not constraints live in **GUARDRAILS.md** (update Signs when a design lock or regression is learned; keep ARCHITECTURE.md for structure). - Project overview and codebase rewrite/refactor. - Architecture (protocols, implementations, extensions, service, utilities) and single injection point (FCPXMLUtility.defaultForExtensions). - Source layout (Analysis, Classes, Delegates, Errors, Extensions including +Modular and +Codable, Implementations, Protocols, Services, Utilities, Annotations, Export, Timeline, Timing, Validation, FileIO, Logging, Format, Model including Adjustments, Animations, Filters, Clips with +Adjustments and +Typed, CommonElements with TextStyle/TextStyleDefinition, Structure with CollectionFolder/KeywordCollection/SmartCollection, Parsing, Extraction, Projection (TimelineProjector / MulticamProjection / RefClipProjection / ChannelKindFilter), Reporting including Excel/ and PDF/, XML with Protocols/Foundation/AEXML/OFKXMLDefaultFactory, FCPXML DTDs; reporting vs core layers in ARCHITECTURE.md §2.7). -- Test structure (Tests/ layout, FCPXMLTestResources, FCPXMLTestUtilities, FileTests/ including FCPXMLFileTest_GeneralDemo, FCPXMLFileTest_360Video, FCPXMLFileTest_AuditionSample, FCPXMLFileTest_ImageSample, FCPXMLFileTest_Multicam, FCPXMLFileTest_Photoshop, FCPXMLFileTest_SmartCollection, LogicAndParsing/ including FCPXMLFormatAssetTests, FCPXMLCutDetectionTests, FCPXMLVersionConversionTests, FCPXMLMediaExtractionTests, FCPXMLTimelineManipulationTests, FCPXMLTimecodeTests, FCPXMLMIMETypeDetectionTests, FCPXMLAssetValidationTests, FCPXMLSilenceDetectionTests, FCPXMLAssetDurationMeasurementTests, FCPXMLParallelFileIOTests, FCPXMLAudioEnhancementTests, FCPXMLTransform360Tests, FCPXMLCaptionTitleTests, FCPXMLKeyframeAnimationTests, FCPXMLAudioKeyframeTests, FCPXMLCMTimeCodableTests, FCPXMLCollectionTests, FCPXMLSmartCollectionTests, FCPXMLAdjustmentTests, FCPXMLFilterTests, FCPXMLImportOptionsTests, FCPXMLCodableTests, FCPXMLAEXMLSerializationParityTests, FCPXMLDTDValidatorTests, FCPXMLStructuralValidatorTests, OpenFCPXMLKitTests.swift, FCPXMLTimelineExportValidationTests, FCPXMLAPIAndEdgeCaseTests, FCPXMLPerformanceTests, FCPXMLTimelineProjectionTests, FCPXMLProjectionCoverageTests, FCPXMLParsingCoverageTests, FCPXMLEngineHygieneTests, FCPXMLReportObligationCorpusTests, FCPXMLExtractionNestFidelityTests, FCPXMLRoleInheritanceMatrixTests, FCPXMLExtractionProjectionPolicyTests, FCPXMLMarkersKeywordsProjectionTests, FCPXMLTitlesProjectionTests, FCPXMLTransitionsProjectionTests, FCPXMLEffectsProjectionTests, FCPXMLSubmittedFCPXMLSmokeTests, and report/extraction tests (FCPXMLRoleInventoryReportTests, FCPXMLMarkersReportTests, FCPXMLKeywordsReportTests, FCPXMLTitlesReportTests, FCPXMLTransitionsReportTests, FCPXMLEffectsReportTests, FCPXMLSpeedChangeEffectsReportTests, FCPXMLSummaryReportTests, FCPXMLReportExcelExportTests, FCPXMLReportPDFExportTests, FCPXMLReportPDFSheetPlanTests, FCPXMLReportPDFTableLayoutTests, FCPXMLReportFormattingTests, FCPXMLReportRoleExclusionTests, FCPXMLReportTimecodeFormatTests, FCPXMLReportBuildPhaseTests, FCPXMLRoleDisplayPreferenceTests, FCPXMLRoleInventoryClipCollectorTests, FCPXMLRoleInventoryRoleSheetOrderingTests, FCPXMLSummaryRoleDurationAggregatorTests, FCPXMLEffectsReportPolicyTests, FCPXMLSpeedChangeFormattingTests, FCPXMLDisplayClipNameTests, FCPXMLTitleDisplayTests, FCPXMLExtractionScopeTests, FCPXMLExtractedElementTests, FCPXMLEffectsCollectorTests, FCPXMLRolesExtractionPresetTests, FCPXMLEffectAppleSuppliedTests, FCPXMLClipParsingCarriesAudioTests, FCPXMLTransformAdjustmentParsingTests, FCPXMLTransitionSpinePlacementTests); every test-case class is FCPXML-prefixed except the module-named umbrella OpenFCPXMLKitTests; empty timeline creation and project-creation export at different sizes and frame rates in FCPXMLTimelineExportValidationTests (clip-level metadata export, XML declaration standalone="no")). Cross-platform XML (OFKXML*, Foundation vs AEXML, FCPXMLStructuralValidator, iOS). Private `Tests/Submitted FCPXML/` inbox (gitignored contents; never commit private FCPXML to GitHub). -- FCPXML 1.5–1.14 and FCPXMLElementType; FCPXMLVersion.supportsBundleFormat (1.10+); version conversion with element stripping and per-version DTD validation; FCPXML creation from scratch; timeline manipulation (ripple insert, auto lane assignment, clip queries, lane range, secondary storylines); timeline metadata (markers, chapter markers, keywords, ratings, timestamps); FCPXMLTimecode custom type; MIME type detection; asset validation (including still images); silence detection; asset duration measurement; parallel file I/O; TimelineFormat enhancements; typed adjustment models (Crop, Transform, Blend, Stabilization, Volume, Loudness, NoiseReduction, HumReduction, Equalization, MatchEqualization, Transform360, ColorConform, Stereo3D, VoiceIsolation); typed effect/filter models (VideoFilter, AudioFilter, VideoFilterMask, FilterParameter with keyframe animation and auxValue 1.11+); typed caption/title models (Caption, Title with TextStyle, TextStyleDefinition); smart collections (SmartCollection with match-clip, match-media, match-ratings, match-text, match-usage, match-representation, match-markers, match-analysis-type); keyframe animation (KeyframeAnimation, Keyframe, FadeIn, FadeOut); audio keyframes (FCPXMLAudioKeyframeTests: adjust-volume param keyframeAnimation parsing, decibel/time validation, fadeIn/fadeOut integration); CMTime Codable extension; collection organization (CollectionFolder, KeywordCollection); Live Drawing (1.11+); HiddenClipMarker (1.13+); Format/Asset 1.13+ (heroEye, heroEyeOverride, mediaReps); **Timeline Projection** (`TimelineProjecting` / `TimelineProjector` / `MediaUsageWindow` / `ReportProjectionContext`; project-once; Projection-first Markers/Keywords/Titles/Transitions/Effects); experimental CLI (OpenFCPXMLKit-CLI, single binary, embedded DTDs, --check-version, --convert-version, --extension-type fcpxml|fcpxmld, --validate, --media-copy, --create-project with DTD validation and FCP-style output, --report Excel/PDF report with --report-full/per-section flags/--report-summary/--report-media-summary/--media-resolution/--media-summary-distinguish-proxy/--exclude-role/--exclude-column/--exclude-disabled-clips/--timecode-format/--report-project/--label-copyright/--create-pdf, --log/--log-level/--quiet with log file capturing all command output); Excel and PDF reporting subsystem (`allReportTimelineSources` / compound-clip timelines) (Reporting/ builders, Sections/Rows, Support including ReportProjectionContext, RoleInventoryColumnLayout, ReportColumnExclusion, ReportFormatting, ReportTimecodeFormat, ReportBuildProgress, ReportMediaResolutionPolicy; Excel/ via XLKit; buildReport/ReportExcelExport/ReportPDFExport; Summary and Media Summary sheets; excludeDisabledClips/excludedColumns/timecodeFormat/copyrightLabel/mediaResolutionPolicy; `--label-copyright`; inventory-first enabledPhases with `.projecting`; PDF cover notes + TOC colour chips / SheetPlan colorIndex; TableLayout column expansion to contentWidth and Row inject); extraction presets (Captions, Effects, FrameData, Markers, Roles, Titles); Manual chapters including [20 — Timeline Projection](Documentation/Manual/20-Timeline-Projection.md); Tests/ExcelReportTest optional integration; Tests/Submitted FCPXML private inbox (gitignored; never commit private FCPXML); Final Cut Pro frame rates; Swift 6 concurrency (Sendable, async/await, CI strict-concurrency job); Xcode 26 dynamic framework linking compatibility via explicit `swift-log` (`Logging`) dependency in `Package.swift`; SwiftExtensions 3.0.0+ and SwiftSemanticVersion 1.0.0+ (`SemanticVersion` for `FinalCutPro.FCPXML.Version`). Current test count: **1076** (`1072` OpenFCPXMLKitTests + `4` ExcelReportTest). +- Test structure (Tests/ layout, FCPXMLTestResources, FCPXMLTestUtilities, FileTests/ including FCPXMLFileTest_GeneralDemo, FCPXMLFileTest_HiddenMarkers, FCPXMLFileTest_360Video, FCPXMLFileTest_AuditionSample, FCPXMLFileTest_ImageSample, FCPXMLFileTest_Multicam, FCPXMLFileTest_Photoshop, FCPXMLFileTest_SmartCollection, LogicAndParsing/ including FCPXMLFormatAssetTests, FCPXMLCutDetectionTests, FCPXMLVersionConversionTests, FCPXMLMediaExtractionTests, FCPXMLTimelineManipulationTests, FCPXMLTimecodeTests, FCPXMLMIMETypeDetectionTests, FCPXMLAssetValidationTests, FCPXMLSilenceDetectionTests, FCPXMLAssetDurationMeasurementTests, FCPXMLParallelFileIOTests, FCPXMLAudioEnhancementTests, FCPXMLTransform360Tests, FCPXMLCaptionTitleTests, FCPXMLKeyframeAnimationTests, FCPXMLAudioKeyframeTests, FCPXMLCMTimeCodableTests, FCPXMLCollectionTests, FCPXMLSmartCollectionTests, FCPXMLAdjustmentTests, FCPXMLFilterTests, FCPXMLImportOptionsTests, FCPXMLCodableTests, FCPXMLAEXMLSerializationParityTests, FCPXMLDTDValidatorTests, FCPXMLStructuralValidatorTests, OpenFCPXMLKitTests.swift, FCPXMLTimelineExportValidationTests, FCPXMLAPIAndEdgeCaseTests, FCPXMLPerformanceTests, FCPXMLTimelineProjectionTests, FCPXMLProjectionCoverageTests, FCPXMLParsingCoverageTests, FCPXMLEngineHygieneTests, FCPXMLReportObligationCorpusTests, FCPXMLExtractionNestFidelityTests, FCPXMLRoleInheritanceMatrixTests, FCPXMLExtractionProjectionPolicyTests, FCPXMLMarkersKeywordsProjectionTests, FCPXMLTitlesProjectionTests, FCPXMLTransitionsProjectionTests, FCPXMLEffectsProjectionTests, FCPXMLSubmittedFCPXMLSmokeTests, and report/extraction tests (FCPXMLRoleInventoryReportTests, FCPXMLMarkersReportTests, FCPXMLKeywordsReportTests, FCPXMLTitlesReportTests, FCPXMLTransitionsReportTests, FCPXMLEffectsReportTests, FCPXMLSpeedChangeEffectsReportTests, FCPXMLSummaryReportTests, FCPXMLReportExcelExportTests, FCPXMLReportPDFExportTests, FCPXMLReportPDFSheetPlanTests, FCPXMLReportPDFTableLayoutTests, FCPXMLReportFormattingTests, FCPXMLReportRoleExclusionTests, FCPXMLReportTimecodeFormatTests, FCPXMLReportBuildPhaseTests, FCPXMLRoleDisplayPreferenceTests, FCPXMLRoleInventoryClipCollectorTests, FCPXMLRoleInventoryRoleSheetOrderingTests, FCPXMLSummaryRoleDurationAggregatorTests, FCPXMLEffectsReportPolicyTests, FCPXMLSpeedChangeFormattingTests, FCPXMLDisplayClipNameTests, FCPXMLTitleDisplayTests, FCPXMLExtractionScopeTests, FCPXMLExtractedElementTests, FCPXMLEffectsCollectorTests, FCPXMLRolesExtractionPresetTests, FCPXMLEffectAppleSuppliedTests, FCPXMLClipParsingCarriesAudioTests, FCPXMLTransformAdjustmentParsingTests, FCPXMLTransitionSpinePlacementTests); every test-case class is FCPXML-prefixed except the module-named umbrella OpenFCPXMLKitTests; empty timeline creation and project-creation export at different sizes and frame rates in FCPXMLTimelineExportValidationTests (clip-level metadata export, XML declaration standalone="no")). Cross-platform XML (OFKXML*, Foundation vs AEXML, FCPXMLStructuralValidator, iOS). Private `Tests/Submitted FCPXML/` inbox (gitignored contents; never commit private FCPXML to GitHub). +- FCPXML 1.5–1.14 and FCPXMLElementType; FCPXMLVersion.supportsBundleFormat (1.10+); version conversion with element stripping and per-version DTD validation; FCPXML creation from scratch; timeline manipulation (ripple insert, auto lane assignment, clip queries, lane range, secondary storylines); timeline metadata (markers, chapter markers, keywords, ratings, timestamps); FCPXMLTimecode custom type; MIME type detection; asset validation (including still images); silence detection; asset duration measurement; parallel file I/O; TimelineFormat enhancements; typed adjustment models (Crop, Transform, Blend, Stabilization, Volume, Loudness, NoiseReduction, HumReduction, Equalization, MatchEqualization, Transform360, ColorConform, Stereo3D, VoiceIsolation); typed effect/filter models (VideoFilter, AudioFilter, VideoFilterMask, FilterParameter with keyframe animation and auxValue 1.11+); typed caption/title models (Caption, Title with TextStyle, TextStyleDefinition); smart collections (SmartCollection with match-clip, match-media, match-ratings, match-text, match-usage, match-representation, match-markers, match-analysis-type); keyframe animation (KeyframeAnimation, Keyframe, FadeIn, FadeOut); audio keyframes (FCPXMLAudioKeyframeTests: adjust-volume param keyframeAnimation parsing, decibel/time validation, fadeIn/fadeOut integration); CMTime Codable extension; collection organization (CollectionFolder, KeywordCollection); Live Drawing (1.11+); HiddenClipMarker (1.13+); Format/Asset 1.13+ (heroEye, heroEyeOverride, mediaReps); **Timeline Projection** (`TimelineProjecting` / `TimelineProjector` / `MediaUsageWindow` / `ReportProjectionContext`; project-once; Projection-first Markers/Keywords/Titles/Transitions/Effects); experimental CLI (OpenFCPXMLKit-CLI, single binary, embedded DTDs, --check-version, --convert-version, --extension-type fcpxml|fcpxmld, --validate, --media-copy, --create-project with DTD validation and FCP-style output, --report Excel/PDF report with --report-full/per-section flags/--report-summary/--report-media-summary/--media-resolution/--media-summary-distinguish-proxy/--exclude-role/--exclude-column/--exclude-disabled-clips/--include-markers-outside-clip-boundaries/--protect-sheets/--timecode-format/--report-project/--label-copyright/--create-pdf, --log/--log-level/--quiet with log file capturing all command output); Excel and PDF reporting subsystem (`allReportTimelineSources` / compound-clip timelines) (Reporting/ builders, Sections/Rows, Support including ReportProjectionContext, RoleInventoryColumnLayout, ReportColumnExclusion, ReportFormatting, ReportTimecodeFormat, ReportBuildProgress, ReportMediaResolutionPolicy; Excel/ via XLKit; buildReport/ReportExcelExport/ReportPDFExport; Summary and Media Summary sheets; excludeDisabledClips/excludedColumns/timecodeFormat/copyrightLabel/includeMarkersOutsideClipBoundaries/protectSheets/mediaResolutionPolicy; `--label-copyright`; inventory-first enabledPhases with `.projecting`; PDF cover notes + TOC colour chips / SheetPlan colorIndex; TableLayout column expansion to contentWidth and Row inject); extraction presets (Captions, Effects, FrameData, Markers, Roles, Titles); Manual chapters including [20 — Timeline Projection](Documentation/Manual/20-Timeline-Projection.md); Tests/ExcelReportTest optional integration; Tests/Submitted FCPXML private inbox (gitignored; never commit private FCPXML); Final Cut Pro frame rates; Swift 6 concurrency (Sendable, async/await, CI strict-concurrency job); Xcode 26 dynamic framework linking compatibility via explicit `swift-log` (`Logging`) dependency in `Package.swift`; SwiftExtensions 3.0.0+ and SwiftSemanticVersion 1.0.0+ (`SemanticVersion` for `FinalCutPro.FCPXML.Version`). Current test count: **1084** (`1078` OpenFCPXMLKitTests + `6` ExcelReportTest). When updating either file, apply the same information to both and keep terminology and examples consistent. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 2e3ee22..786cf5a 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -2,7 +2,7 @@ A guide for contributors: project structure, architecture, naming, styling, and design decisions. -**See also:** [.cursorrules](.cursorrules), [AGENT.md](AGENT.md), [Tests/README.md](Tests/README.md). +**See also:** [GUARDRAILS.md](GUARDRAILS.md) (must / must-not), [.cursorrules](.cursorrules), [AGENT.md](AGENT.md), [Tests/README.md](Tests/README.md). --- @@ -16,7 +16,7 @@ OpenFCPXMLKit is a **Swift 6** framework for Final Cut Pro FCPXML: parsing, crea - **Repository:** https://github.com/TheAcharya/OpenFCPXMLKit - **Dependencies:** SwiftTimecode 3.1.2+, SwiftExtensions 3.0.0+, SwiftSemanticVersion 1.0.0+, swift-log 1.14.0+, AEXML 4.7.0+, swift-argument-parser 1.8.2+ (CLI only), Foundation, CoreMedia. - **FCPXML:** Versions 1.5–1.14 (DTDs included); Final Cut Pro frame rates (23.976, 24, 25, 29.97, 30, 50, 59.94, 60). -- **Tests:** **1076** tests listed in `swift test --list-tests` — **1072** in `OpenFCPXMLKitTests` (1069 XCTest `func test` + 3 Swift Testing `@Test`) and **4** optional `ExcelReportTest` integration tests; **59** sample `.fcpxml` files under `Tests/FCPXML Samples/FCPXML/`; private local inbox under `Tests/Submitted FCPXML/` (gitignored — never commit private FCPXML). +- **Tests:** **1084** tests listed in `swift test --list-tests` — **1078** in `OpenFCPXMLKitTests` (1075 XCTest `func test` + 3 Swift Testing `@Test`) and **6** optional `ExcelReportTest` integration tests; **60** sample `.fcpxml` files under `Tests/FCPXML Samples/FCPXML/`; private local inbox under `Tests/Submitted FCPXML/` (gitignored — never commit private FCPXML). --- @@ -116,6 +116,8 @@ Reporting/ Row models, builders, sheet-specific presentation rules - `ReportOptions.mediaSummaryDistinguishProxyAndOriginal` (CLI `--media-summary-distinguish-proxy`) splits Missing Original / Missing Proxy columns when Projection windows carry both URL kinds. - `ReportOptions.timecodeFormat` is stored on `Report.timecodeFormat` and drives cell strings plus Excel/PDF header suffixes (e.g. `Timeline In (frames)`). - `ReportOptions.copyrightLabel` is stored on `Report.copyrightLabel` (whitespace-normalized) and applied by Excel cover export and PDF cover/footer rendering. +- `ReportOptions.includeMarkersOutsideClipBoundaries` (CLI `--include-markers-outside-clip-boundaries`) controls whether markers whose `start` is outside the host clip media range are included; when `true`, the Markers sheet gains a trailing **Hidden** column (✓/✗). Default omits those markers (FCP Tags/timeline behaviour). Distinct from FCPXML 1.13+ `hidden-clip-marker`. Boundary helper: `FCPXMLMarkerClipBoundary` / Projection `WindowMarkerAnnotation.isOutsideClipBoundaries`. +- `ReportOptions.protectSheets` (CLI `--protect-sheets`) is stored on `Report.protectSheets` and applied by Excel export only: XLKit `SheetProtection` on every worksheet (edit lock, no password). **Not** file-open encryption; PDF ignores this flag. - Build / progress order is **`ReportBuildPhase.enabledPhases(for:)`** (product / workbook order: Selected Roles Inventory first, then Markers … Media Summary; includes `.projecting` when sections consume Projection). `ReportBuilder` and CLI/GUI progress share this list. - **Timeline resolution** for `buildReport` / `ReportBuilder` uses **`FinalCutPro.FCPXML.allReportTimelineSources()`** (defined on `FCPXMLProperties`): every `` sequence, plus event-level compound clips (`ref-clip` → `media`/`sequence`) when FCP exported a compound clip with no ``. Prefer a real project when both exist. `ReportOptions.projectName` / CLI `--report-project` match project or compound-clip display names. Discovery belongs on `FCPXML` (Classes); Reporting only consumes `ReportTimelineSource`. - **Summary Excel layout:** project title in **B1** (narrow **Row** column A; generous title width on B). **PDF cover:** black header band with white `info.circle` + “About This PDF Export”; body notes reference default/excludable Row; optional `copyrightLabel` under Created-by (same subtitle font) and centred in the running footer (same footer font). @@ -133,6 +135,8 @@ Reporting/ Row models, builders, sheet-specific presentation rules | Column labels, timecode strings, enabled checkmarks | Reporting | | Timecode display mode (SMPTE / Frames / Feet+Frames / HH:MM:SS) and header suffixes | `ReportOptions.timecodeFormat` → `Report.timecodeFormat` → Formatting + Excel/PDF export | | Optional copyright / attribution line (Excel cover A2; PDF cover + footer centre) | `ReportOptions.copyrightLabel` → `Report.copyrightLabel` → Excel / PDF export (CLI `--label-copyright`) | +| Markers outside host clip media range + Markers **Hidden** column | `ReportOptions.includeMarkersOutsideClipBoundaries` → Markers builder / Projection annotations (CLI `--include-markers-outside-clip-boundaries`) | +| Excel worksheet edit lock on every sheet (not encryption) | `ReportOptions.protectSheets` → `Report.protectSheets` → `FCPXMLReportWorkbookExporter` (CLI `--protect-sheets`; PDF ignores) | | Build / progress / GUI section order | `ReportBuildPhase.enabledPhases(for:)` | | Omit `enabled="0"` clips from all timeline sections | `ReportOptions.excludeDisabledClips` → Extraction scope + `TimelineProjectionOptions.forReport` | | Projection failure abort vs continue | `ReportOptions.mediaResolutionPolicy` (`.failSoft` / `.failLoud`; CLI `--media-resolution`) | @@ -150,7 +154,7 @@ Reporting/ Row models, builders, sheet-specific presentation rules #### Excel export -Lives under **`Reporting/Excel/`** and serialises `Report` to XLKit workbooks via `ReportExcelExport` and `FCPXMLReportWorkbookExporter`; it applies column exclusion (including format-suffixed timecode headers and universal **Row** via `ensuringRowColumn`), `Report.timecodeFormat` header suffixes, tabular header styling (black fill, white bold text), Summary project title in **B1** with narrow Row column A, cover-sheet branding (`workbookCoverSheet` / `exportBrandingText` in **A1**) plus optional `copyrightLabel` in **A2**, and sheet-specific row text colours but should not introduce new FCPXML interpretation. +Lives under **`Reporting/Excel/`** and serialises `Report` to XLKit workbooks via `ReportExcelExport` and `FCPXMLReportWorkbookExporter`; it applies column exclusion (including format-suffixed timecode headers and universal **Row** via `ensuringRowColumn`), `Report.timecodeFormat` header suffixes, tabular header styling (black fill, white bold text), Summary project title in **B1** with narrow Row column A, cover-sheet branding (`workbookCoverSheet` / `exportBrandingText` in **A1**) plus optional `copyrightLabel` in **A2**, sheet-specific row text colours, and optional **`Report.protectSheets`** (XLKit `SheetProtection` on every sheet — edit lock only) but should not introduce new FCPXML interpretation. #### PDF export @@ -172,15 +176,15 @@ flowchart TB Pkg --> SRC["Sources/"] Pkg --> TST["Tests/"] - Pkg --> DOC["Documentation/"] + Pkg --> DOC["Documentation/ + ARCHITECTURE + GUARDRAILS"] SRC --> LIB["OpenFCPXMLKit library"] SRC --> CLI["OpenFCPXMLKitCLI → OpenFCPXMLKit-CLI"] SRC --> GEN["GenerateEmbeddedDTDs"] - TST --> OKT["OpenFCPXMLKitTests — 1072 tests"] - TST --> ERT["ExcelReportTest — 4 optional integration tests"] - TST --> SMP["FCPXML Samples/ — 59 .fcpxml files"] + TST --> OKT["OpenFCPXMLKitTests — 1078 tests"] + TST --> ERT["ExcelReportTest — 6 optional integration tests"] + TST --> SMP["FCPXML Samples/ — 60 .fcpxml files"] TST --> SUB["Submitted FCPXML/ — private inbox (gitignored)"] ``` @@ -191,9 +195,9 @@ flowchart TB DTD["FCPXML DTDs 1.5–1.14"] XML["XML/ — OFKXML protocols · Foundation · AEXML"] PRS["Parsing/ — attributes, clips, roles, time"] - MDL["Model/ — typed elements, adjustments, filters, roles"] + MDL["Model/ — typed elements, adjustments, filters, roles · MarkerClipBoundary"] EXT["Extraction/ — fcpExtract · Context · Presets"] - PRJ["Projection/ — TimelineProjector · MediaUsageWindow · OccupancyIndex"] + PRJ["Projection/ — TimelineProjector · MediaUsageWindow · OccupancyIndex · WindowAnnotations"] REP["Reporting/ — ReportBuilder · Sections · Excel · PDF"] DTD --> XML --> PRS --> MDL --> EXT --> PRJ --> REP @@ -218,12 +222,12 @@ flowchart TB subgraph REP_DETAIL["Reporting/"] direction TB - R_TOP["Report · ReportOptions · ReportBuilder · ReportTimecodeFormat · ReportBuildProgress · mediaResolutionPolicy · copyrightLabel"] + R_TOP["Report · ReportOptions · ReportBuilder · ReportTimecodeFormat · ReportBuildProgress · mediaResolutionPolicy · copyrightLabel · protectSheets · includeMarkersOutsideClipBoundaries"] R_CTX["Support/ReportProjectionContext — windows + clipAnnotations + occupancy"] R_BLD["Builders/ — RoleInventory · Markers · Keywords · Titles · Transitions · Effects · SpeedChange · Summary · MediaSummary"] - R_SEC["Sections/ + Rows/ — typed sheet models · format-aware columnHeaders"] + R_SEC["Sections/ + Rows/ — typed sheet models · format-aware columnHeaders · Markers Hidden opt-in"] R_SUP["Support/ — RoleInventoryClipCollector · ColumnLayout · ReportColumnExclusion · ReportFormatting · FCPXMLReportRowColorPolicy"] - R_XLS["Excel/ — ReportExcelExport · WorkbookExporter · ColumnAutoFit"] + R_XLS["Excel/ — ReportExcelExport · WorkbookExporter · ColumnAutoFit · optional SheetProtection"] R_PDF["PDF/ — ReportPDFExport · Exporter · Canvas · SheetPlan · TableLayout · CoverNotes"] R_TOP --> R_CTX --> R_BLD --> R_SEC R_BLD --> R_SUP @@ -236,7 +240,7 @@ flowchart TB subgraph CLI_DETAIL["OpenFCPXMLKitCLI/"] direction TB C_ROOT["OpenFCPXMLKitCLI.swift"] - C_OPT["Options/ — General · Timeline · Report (--media-resolution · --create-pdf · --label-copyright · …) · Log"] + C_OPT["Options/ — General · Timeline · Report (--protect-sheets · --include-markers-outside-clip-boundaries · --media-resolution · --create-pdf · --label-copyright · …) · Log"] C_CMD["Commands/ — CheckVersion · ConvertVersion · Validate · ExtractMedia · CreateProject · ExportReport"] C_GEN["Generated/EmbeddedDTDs.swift"] C_ROOT --> C_OPT --> C_CMD @@ -301,11 +305,11 @@ Source layout under **`Sources/OpenFCPXMLKit/`**: | **Logging** | ServiceLogger, ServiceLogLevel, NoOp/Print/FileServiceLogger. | | **Media** | MediaReference, MediaExtractionResult, MediaCopyResult. | | **Format** | ColorSpace. | -| **Model** | FCPXML element models: Adjustments, Animations, Attributes, Clips, CommonElements, ElementTypes, Filters, Occlusion, Protocols, Resources, Roles, Structure (CollectionFolder, KeywordCollection, etc.). | +| **Model** | FCPXML element models: Adjustments, Animations, Attributes, Clips, CommonElements, ElementTypes, Filters, Occlusion, Protocols, Resources, Roles, Structure (CollectionFolder, KeywordCollection, etc.); `FCPXMLMarkerClipBoundary` (marker start vs host media range). | | **Parsing** | XML parsing extensions (Attributes, Clip, Elements, Metadata, Resources, Roles, Root, Time and Frame Rate). | | **Extraction** | `fcpExtract`, ExtractedElement, ExtractionScope, ExtractableChildren. **Context/** (DisplayClipName, ElementContext, ElementContextItems/Tools, FrameRateSource), **Effects/** (EffectsCollector, ExtractedEffect), **Presets/** (Captions, Effects, FrameData, Markers, Roles, Titles, plus the base ExtractionPreset). | -| **Projection** | Timeline analysis mid-layer. `TimelineProjecting`, `TimelineProjector`, `TimelineProjectionOptions`, `MediaChannel`, `MediaUsageWindow`, `LanePath`, `RetimingSegment`, `TimelineOccupancyIndex`; **Retiming/** + **Walk/** including `MulticamProjection`, `RefClipProjection`, `ChannelKindFilter`. Multicam/ref/audition unfold + nested lanes + J/L cuts. Reporting consume via `ReportProjectionContext` + `TimelineOccupancyIndex` (Role Inventory, Markers, Keywords, Titles, Transitions, Effects, Speed Change, Media Summary, Summary project-once; annotation sections Projection-first with Extraction fallback). | -| **Reporting** | Excel and PDF report export. Top-level: `Report`, `ReportOptions`, `ReportBuilder`, `ReportTimecodeFormat` (`.smpteFrames` / `.frames` / `.feetAndFrames` / `.smpteNoFrames`), `ReportBuildProgress` (`ReportBuildPhase.enabledPhases(for:)` — inventory-first product order shared by builder, CLI, and GUI progress). **Builders/** — per-sheet builders including `MediaSummaryReportBuilder` (missing media paths) and `SummaryReportBuilder` (project metrics + role durations). **Sections/** and **Rows/** — typed section/row models with `columnHeaders(timecodeFormat:)`. **Support/** — `ReportProjectionContext` / `TimelineOccupancyIndex` (project-once for inventory, markers, keywords, titles, transitions, effects, speed-change, media summary, summary), `EffectsReportTiming`, `RoleInventoryClipCollector`, `RoleInventoryRowBuilder`, `RoleInventoryColumnLayout`, `RoleInventoryRoleSheetOrdering`, `RoleInventoryTimelineBounds`, `ReportFormatting`, `ReportRoleExclusion`, `ReportColumnExclusion` (`ensuringRowColumn` / `allowsInjectedRowColumn` — **Row** on all tabular Excel/PDF sheets), `ReportClipCategory`, `FCPXMLReportRowColorPolicy` (shared Excel/PDF row colours), `EffectsReportPolicy`, `SpeedChangeFormatting`, `SummaryRoleDurationAggregator`. **Excel/** — `ReportExcelExport`, `FCPXMLReportWorkbookExporter` (Summary title in **B1**), `ReportWorkbookColumnAutoFit` (narrow Row; generous Summary title width). **PDF/** — `ReportPDFExport`, `FCPXMLReportPDFExporter`, `FCPXMLReportPDFCanvas` (cover black header + `info.circle`; TOC accent chips + content-tint washes), `FCPXMLReportPDFSheetPlan` (ordered sheet titles + sequential `colorIndex`), `FCPXMLReportPDFTableLayout` (pack + expand columns to fill `contentWidth` after exclusions; pinned/injected Row with `allowInjectedRowColumn`), `FCPXMLReportPDFStyle`, `FCPXMLReportPDFCoverNotes`. `ReportOptions`: `excludeDisabledClips`, `excludedColumns`, `timecodeFormat`, `includeMediaSummary`, `mediaBaseURL`, `projectName`. `Report.exportBrandingText` for Excel cover and PDF cover/footer. Timeline pick via `allReportTimelineSources()` (see §2.7). Consumes Extraction and Projection (`ReportProjectionContext`); owns presentation only. | +| **Projection** | Timeline analysis mid-layer. `TimelineProjecting`, `TimelineProjector`, `TimelineProjectionOptions`, `MediaChannel`, `MediaUsageWindow`, `LanePath`, `RetimingSegment`, `TimelineOccupancyIndex`; **Retiming/** + **Walk/** including `MulticamProjection`, `RefClipProjection`, `ChannelKindFilter`; **WindowAnnotations** / `WindowAnnotationBuilder` (markers include `isOutsideClipBoundaries`). Multicam/ref/audition unfold + nested lanes + J/L cuts. Reporting consume via `ReportProjectionContext` + `TimelineOccupancyIndex` (Role Inventory, Markers, Keywords, Titles, Transitions, Effects, Speed Change, Media Summary, Summary project-once; annotation sections Projection-first with Extraction fallback). | +| **Reporting** | Excel and PDF report export. Top-level: `Report`, `ReportOptions` (including `copyrightLabel`, `includeMarkersOutsideClipBoundaries`, `protectSheets`), `ReportBuilder`, `ReportTimecodeFormat` (`.smpteFrames` / `.frames` / `.feetAndFrames` / `.smpteNoFrames`), `ReportBuildProgress` (`ReportBuildPhase.enabledPhases(for:)` — inventory-first product order shared by builder, CLI, and GUI progress). **Builders/** — per-sheet builders including Markers (optional **Hidden** column), `MediaSummaryReportBuilder`, and `SummaryReportBuilder`. **Sections/** and **Rows/** — typed section/row models with `columnHeaders(timecodeFormat:)`. **Support/** — `ReportProjectionContext` / `TimelineOccupancyIndex`, collectors/layout/exclusion/formatting/row-colour helpers (`ensuringRowColumn` / `allowsInjectedRowColumn` — **Row** on all tabular Excel/PDF sheets). **Excel/** — `ReportExcelExport`, `FCPXMLReportWorkbookExporter` (Summary title in **B1**; optional worksheet protection when `protectSheets`), `ReportWorkbookColumnAutoFit`. **PDF/** — `ReportPDFExport` and layout helpers (ignores `protectSheets`). Timeline pick via `allReportTimelineSources()` (see §2.7). Consumes Extraction and Projection; owns presentation only. | | **XML** | Platform-agnostic XML layer: Protocols (OFKXMLNode, OFKXMLElement, OFKXMLDocument, OFKXMLDTDProtocol, OFKXMLFactory), Foundation/ (Foundation backends), AEXML/ (AEXML backends), OFKXMLDefaultFactory. | | **FCPXML DTDs** | Version 1.5–1.14 DTDs. | @@ -391,16 +395,16 @@ Source layout under **`Sources/OpenFCPXMLKit/`**: Binary name: **`OpenFCPXMLKit-CLI`**. Mutually exclusive modes: `--check-version`, `--convert-version`, `--extension-type` (fcpxmld | fcpxml), `--validate`, `--media-copy`, `--report`, `--create-project` (requires `--width`, `--height`, `--rate`, `--project-version`, output-dir). -**`--report`** builds an Excel workbook from a normal project **or** a standalone compound-clip export (role inventory by default — **Selected Roles Inventory** + per-role sheets). `--report-full` adds every optional sheet. Per-section flags: `--report-markers`, `--report-keywords`, `--report-titles-generators`, `--report-transitions`, `--report-effects`, `--report-speed-change-effects`, `--report-summary`, `--report-media-summary`. **`--create-pdf`** also writes a `.pdf` from the same built `Report` (sections, column exclusions, timecode format). Filtering: `--exclude-role` (repeatable), `--exclude-column` (repeatable; global column omission), `--exclude-disabled-clips` (omit `enabled="0"` clips), `--report-project` (project or compound-clip name). Timecode cells: `--timecode-format` (`HH:MM:SS:FF` default, `Frames`, `Feet+Frames`, `HH:MM:SS`). Progress labels follow `ReportBuildPhase.enabledPhases(for:)` (inventory first), then Saving Workbook, then Saving PDF when `--create-pdf` is set. Log options: `--log`, `--log-level`, `--quiet`. See `Sources/OpenFCPXMLKitCLI/README.md` and `Documentation/Manual/16-CLI.md`. +**`--report`** builds an Excel workbook from a normal project **or** a standalone compound-clip export (role inventory by default — **Selected Roles Inventory** + per-role sheets). `--report-full` adds every optional sheet. Per-section flags: `--report-markers`, `--report-keywords`, `--report-titles-generators`, `--report-transitions`, `--report-effects`, `--report-speed-change-effects`, `--report-summary`, `--report-media-summary`. **`--create-pdf`** also writes a `.pdf` from the same built `Report` (sections, column exclusions, timecode format). Filtering: `--exclude-role` (repeatable), `--exclude-column` (repeatable; global column omission), `--exclude-disabled-clips` (omit `enabled="0"` clips), `--include-markers-outside-clip-boundaries` (out-of-bounds markers + Markers **Hidden** column), `--protect-sheets` (Excel worksheet edit lock on every sheet — not encryption; PDF unaffected), `--report-project` (project or compound-clip name), `--label-copyright`. Timecode cells: `--timecode-format` (`HH:MM:SS:FF` default, `Frames`, `Feet+Frames`, `HH:MM:SS`). Progress labels follow `ReportBuildPhase.enabledPhases(for:)` (inventory first), then Saving Workbook, then Saving PDF when `--create-pdf` is set. Log options: `--log`, `--log-level`, `--quiet`. See `Sources/OpenFCPXMLKitCLI/README.md` and `Documentation/Manual/16-CLI.md`. --- ## 8. Tests -- **Count:** **1076** listed in `swift test --list-tests` — **1072** in `OpenFCPXMLKitTests` (1069 XCTest + 3 Swift Testing `@Test` in `FCPXMLReportRoleExclusionTests`) and **4** in optional `ExcelReportTest` (skips without a local `.fcpxml`/`.fcpxmld` fixture). -- **Location:** `Tests/OpenFCPXMLKitTests/`; public samples in `Tests/FCPXML Samples/FCPXML/` (59 files); optional integration under `Tests/ExcelReportTest/`; private investigation inbox under `Tests/Submitted FCPXML/` (gitignored `Inbox/` / `Notes/` — never commit private FCPXML to GitHub; see `Tests/Submitted FCPXML/README.md`). +- **Count:** **1084** listed in `swift test --list-tests` — **1078** in `OpenFCPXMLKitTests` (1075 XCTest + 3 Swift Testing `@Test` in `FCPXMLReportRoleExclusionTests`) and **6** in optional `ExcelReportTest` (skips without a local `.fcpxml`/`.fcpxmld` fixture). +- **Location:** `Tests/OpenFCPXMLKitTests/`; public samples in `Tests/FCPXML Samples/FCPXML/` (60 files, including `HiddenMarkers.fcpxml`); optional integration under `Tests/ExcelReportTest/`; private investigation inbox under `Tests/Submitted FCPXML/` (gitignored `Inbox/` / `Notes/` — never commit private FCPXML to GitHub; see `Tests/Submitted FCPXML/README.md`). - **Utilities:** `FCPXMLTestResources.swift`, `FCPXMLTestUtilities.swift` (path resolution, sample loading; `XCTSkip` when a sample is missing); `FCPXMLReportingReportFixture.swift` / `FCPXMLReportingReportTestSupport.swift` for optional reporting fixtures; `FCPXMLSubmittedFCPXMLSmokeTests` for optional Inbox parse smoke. -- **Reporting tests:** `FCPXMLCompoundClipReportTests` (standalone compound-clip FCPXML / `allReportTimelineSources()`), `FCPXMLReportTimecodeFormatTests` (DF/NDF, all four formats, format-aware headers, full-report shape), `FCPXMLReportBuildPhaseTests` (inventory-first `enabledPhases` / `onPhaseStarted` order), `FCPXMLRoleInventoryColumnLayoutTests`, `FCPXMLReportColumnExclusionTests` (including `ensuringRowColumn` / `allowsInjectedRowColumn`, suffixed Timeline In headers, Row on all sheets), `FCPXMLReportExcludeDisabledClipsTests`, `FCPXMLReportExcelExportTests` (workbook cell formatting; Summary **B1**; section-sheet Row columns), `FCPXMLReportPDFExportTests` (cover notes / black header + `info.circle`, TOC, section parity, pagination, branding), `FCPXMLReportPDFSheetPlanTests` (TOC accent chips share sequential `colorIndex` with content-page tints), `FCPXMLReportPDFTableLayoutTests` (remaining columns expand to fill page width after exclusions; pinned Row; `allowInjectedRowColumn`; horizontal chunks still fill `contentWidth`), `FCPXMLReportFormattingTests` (SMPTE / Frames / Feet+Frames / HH:MM:SS formatting and numeric sort guardrails), plus role inventory, section, and related support tests. See **Tests/README.md** §1 for the full file tree. +- **Reporting tests:** `FCPXMLCompoundClipReportTests` (standalone compound-clip FCPXML / `allReportTimelineSources()`), `FCPXMLMarkersReportTests` / `FCPXMLFileTest_HiddenMarkers` (out-of-bounds markers + **Hidden** column), `FCPXMLReportTimecodeFormatTests` (DF/NDF, all four formats, format-aware headers, full-report shape), `FCPXMLReportBuildPhaseTests` (inventory-first `enabledPhases` / `onPhaseStarted` order), `FCPXMLRoleInventoryColumnLayoutTests`, `FCPXMLReportColumnExclusionTests` (including `ensuringRowColumn` / `allowsInjectedRowColumn`, suffixed Timeline In headers, Row on all sheets), `FCPXMLReportExcludeDisabledClipsTests`, `FCPXMLReportExcelExportTests` (workbook cell formatting; Summary **B1**; section-sheet Row columns; **`protectSheets`** sheet protection), `FCPXMLReportPDFExportTests` (cover notes / black header + `info.circle`, TOC, section parity, pagination, branding), `FCPXMLReportPDFSheetPlanTests` (TOC accent chips share sequential `colorIndex` with content-page tints), `FCPXMLReportPDFTableLayoutTests` (remaining columns expand to fill page width after exclusions; pinned Row; `allowInjectedRowColumn`; horizontal chunks still fill `contentWidth`), `FCPXMLReportFormattingTests` (SMPTE / Frames / Feet+Frames / HH:MM:SS formatting and numeric sort guardrails), plus role inventory, section, and related support tests. Optional `ExcelReportTest` writes `OFK-OutsideClipBoundaries` and `OFK-ProtectedSheets` among other outputs. See **Tests/README.md** §1 for the full file tree. - **Coverage:** Unit, integration, and performance tests; sync and async; all supported frame rates and FCPXML versions. See **Tests/README.md** for categories and how to run tests. --- @@ -415,5 +419,5 @@ Binary name: **`OpenFCPXMLKit-CLI`**. Mutually exclusive modes: `--check-version ## 10. References -- **Internal:** [.cursorrules](.cursorrules), [AGENT.md](AGENT.md), [Documentation/Manual.md](Documentation/Manual.md), [Tests/README.md](Tests/README.md). +- **Internal:** [GUARDRAILS.md](GUARDRAILS.md), [.cursorrules](.cursorrules), [AGENT.md](AGENT.md), [Documentation/Manual.md](Documentation/Manual.md), [Tests/README.md](Tests/README.md). - **External:** [Final Cut Pro XML](https://fcp.cafe/developers/fcpxml/), [SwiftTimecode](https://github.com/orchetect/swift-timecode), [Swift API Design Guidelines](https://swift.org/documentation/api-design-guidelines/), [Swift Concurrency](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/). diff --git a/CHANGELOG.md b/CHANGELOG.md index f5da912..469dd55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,27 @@ OpenFCPXMLKit uses **New Features**, **Improvements**, and **Bug Fixes** for eac --- +## [3.1.1](https://github.com/TheAcharya/OpenFCPXMLKit/releases/tag/3.1.1) - 2026-07-18 + +### ✨ New Features + +- **Markers outside clip boundaries:** By default the Markers report omits markers whose `start` is outside the host clip’s media range (hidden in Final Cut Pro’s timeline/Tags). Opt in with `ReportOptions.includeMarkersOutsideClipBoundaries` / CLI `--include-markers-outside-clip-boundaries` to include them and add a **Hidden** column (✓ outside / ✗ inside). The Hidden column is not part of `--exclude-column`. Sample: `HiddenMarkers.fcpxml`. Tests: `FCPXMLMarkersReportTests`, `FCPXMLFileTest_HiddenMarkers`; ExcelReportTest writes `OFK-OutsideClipBoundaries.xlsx` / `.pdf`. +- **Excel sheet protection:** `ReportOptions.protectSheets` / CLI `--protect-sheets` applies worksheet protection to every sheet in the Excel workbook (cover + content). Edit lock only — not file-open encryption; PDF export is unchanged (use Preview → Encrypt for PDF passwords). Tests: `FCPXMLReportExcelExportTests`; ExcelReportTest writes `OFK-ProtectedSheets.xlsx`. + +### 🔧 Improvements + +- **Documentation:** Added [GUARDRAILS.md](GUARDRAILS.md) as a companion to [ARCHITECTURE.md](ARCHITECTURE.md) — hard must / must-not constraints for contributors and agents (layer boundaries, naming, FCPXML 1.5 floor, reporting honesty, fixtures, Signs for learned locks). Cross-linked from README, Manual index, CONTRIBUTING, AGENT, and `.cursorrules`. +- **Documentation sync:** Test/sample counts and API surface refreshed across README, Manual, Tests READMEs, ARCHITECTURE (incl. Mermaid), AGENT, `.cursorrules`, and GUARDRAILS — **1084** listed tests (**1078** + **6**), **60** public FCPXML samples; documenting `includeMarkersOutsideClipBoundaries` / `--protect-sheets`. Manual chapters **19** / **16** / **17** / **12** / **01** / **20** aligned for `protectSheets`, Markers **Hidden** vs `hidden-clip-marker`, and Excel-only sheet protection. + +### 🐛 Bug Fixes + +- None in this release. + +--- + ## [3.1.0](https://github.com/TheAcharya/OpenFCPXMLKit/releases/tag/3.1.0) - 2026-07-16 + ### ✨ New Features - **Effects → Projection:** Report builder prefers ``WindowReportEffectAnnotation`` collected during timeline Projection via shared ``EffectsCollector`` (filters, volume/implicit volume, transform rows, compositing, spatial conform; video-filter occlusion policy preserved). Extraction remains fallback when Projection has no effect annotations. Tests: `FCPXMLEffectsProjectionTests`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 735420b..0975415 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,6 +2,8 @@ Please note that this project is not under active or regular development, so response times to pull requests may be slow. The codebase is also highly complex, so please take time to understand its structure and logic before proposing changes. +Before opening a PR, read **[ARCHITECTURE.md](ARCHITECTURE.md)** (how the system is shaped) and **[GUARDRAILS.md](GUARDRAILS.md)** (must / must-not constraints — layer boundaries, naming, tests, FCPXML compatibility). + We appreciate your input! If you'd like to contribute, whether by: - Submitting a fix diff --git a/Documentation/Manual/00-Index.md b/Documentation/Manual/00-Index.md index 7d61281..f8598b4 100644 --- a/Documentation/Manual/00-Index.md +++ b/Documentation/Manual/00-Index.md @@ -27,13 +27,15 @@ Complete manual and usage guide for **OpenFCPXMLKit**, a Swift 6 framework for F | [16 — CLI](16-CLI.md) | Experimental command-line interface (OpenFCPXMLKit-CLI) | | [17 — Examples](17-Examples.md) | End-to-end workflows and code examples | | [18 — Cross-Platform & iOS](18-Cross-Platform-iOS.md) | XML abstraction layer, OFKXML protocols, Foundation vs AEXML backends, iOS support | -| [19 — Reporting, Excel & PDF Export](19-Reporting.md) | Report builder, ReportOptions (`mediaResolutionPolicy`, `copyrightLabel`, …), ReportTimecodeFormat, ReportBuildPhase (incl. projecting), report sections, universal **Row**, Projection-first Markers/Keywords/Titles/Transitions/Effects, XLKit Excel + CoreGraphics PDF | +| [19 — Reporting, Excel & PDF Export](19-Reporting.md) | Report builder, ReportOptions (`mediaResolutionPolicy`, `copyrightLabel`, `includeMarkersOutsideClipBoundaries`, `protectSheets`, …), ReportTimecodeFormat, ReportBuildPhase (incl. projecting), report sections, universal **Row**, Projection-first Markers/Keywords/Titles/Transitions/Effects, XLKit Excel + CoreGraphics PDF | --- ## Quick links - **Project README:** [../README.md](../README.md) (repository root) +- **Architecture:** [../ARCHITECTURE.md](../ARCHITECTURE.md) — layers and codebase map +- **Guardrails:** [../GUARDRAILS.md](../GUARDRAILS.md) — must / must-not for contributors and agents - **CLI reference:** [../Sources/OpenFCPXMLKitCLI/README.md](../Sources/OpenFCPXMLKitCLI/README.md) - **Tests:** [../Tests/README.md](../Tests/README.md) — suite layout; [Submitted FCPXML](../Tests/Submitted%20FCPXML/README.md) for private local investigation (never commit private FCPXML) - **FCPXML reference:** [fcp.cafe/developers/fcpxml](https://fcp.cafe/developers/fcpxml) diff --git a/Documentation/Manual/01-Overview.md b/Documentation/Manual/01-Overview.md index b6259d7..00e5295 100644 --- a/Documentation/Manual/01-Overview.md +++ b/Documentation/Manual/01-Overview.md @@ -50,7 +50,7 @@ All core behaviour is defined by **protocols** with both sync and async APIs. De Semantic validators (`FCPXMLValidator`, `FCPXMLDTDValidator`, `FCPXMLStructuralValidator`) are injectable concrete types, not protocol-backed. -**Layer stack (bottom → top):** XML → Parsing → Model → Extraction → **Projection** → Reporting. Extend lower layers before adding report-only XML walks. See [20 — Timeline Projection](20-Timeline-Projection.md) and [ARCHITECTURE.md](../../ARCHITECTURE.md) §2.7. +**Layer stack (bottom → top):** XML → Parsing → Model → Extraction → **Projection** → Reporting. Extend lower layers before adding report-only XML walks. See [20 — Timeline Projection](20-Timeline-Projection.md), [ARCHITECTURE.md](../../ARCHITECTURE.md) §2.7, and [GUARDRAILS.md](../../GUARDRAILS.md) for must / must-not constraints. ## Logging diff --git a/Documentation/Manual/12-Typed-Models.md b/Documentation/Manual/12-Typed-Models.md index 35937cf..de7f4ae 100644 --- a/Documentation/Manual/12-Typed-Models.md +++ b/Documentation/Manual/12-Typed-Models.md @@ -152,6 +152,8 @@ let marker = FinalCutPro.FCPXML.HiddenClipMarker() // Add to clip via fcpxAnnotations / addToClip(annotationElements:) ``` +Do **not** confuse this DTD element with the Markers **report** concept of “Hidden”: report **Hidden** means a normal `marker` / `chapter-marker` whose `start` is outside the host clip’s media range (omitted by default; opt in with `includeMarkersOutsideClipBoundaries` / `--include-markers-outside-clip-boundaries`). See [19 — Reporting](19-Reporting.md#markers). + --- ## Format and Asset (1.13+) diff --git a/Documentation/Manual/16-CLI.md b/Documentation/Manual/16-CLI.md index c671232..063fb1e 100644 --- a/Documentation/Manual/16-CLI.md +++ b/Documentation/Manual/16-CLI.md @@ -15,7 +15,7 @@ The package includes an experimental command-line tool **OpenFCPXMLKit-CLI**. It ## Commands and options -Use **one** of: `--check-version`, `--convert-version`, `--validate`, `--media-copy`, `--report`, or `--create-project`. For `--convert-version`, `--media-copy`, `--report` (and default process), `` is required and is **created if missing**. For `--create-project`, the single positional argument is `` (also created if missing). `--extension-type` requires `--convert-version`. REPORT modifiers (`--report-full`, section flags, `--timecode-format`, `--media-resolution`, etc.) require `--report`. +Use **one** of: `--check-version`, `--convert-version`, `--validate`, `--media-copy`, `--report`, or `--create-project`. For `--convert-version`, `--media-copy`, `--report` (and default process), `` is required and is **created if missing**. For `--create-project`, the single positional argument is `` (also created if missing). `--extension-type` requires `--convert-version`. REPORT modifiers (`--report-full`, section flags, `--include-markers-outside-clip-boundaries`, `--protect-sheets`, `--timecode-format`, `--media-resolution`, `--label-copyright`, `--create-pdf`, etc.) require `--report`. ### GENERAL @@ -50,6 +50,8 @@ Build an Excel (`.xlsx`) report workbook from FCPXML/FCPXMLD, with optional PDF | **--label-copyright <text>** | Optional copyright / attribution line (with `--report`). Excel cover sheet **A2** below Created-by; PDF cover below Created-by (same subtitle font/size); PDF running footer centre (same footer font/size). | | **--exclude-role <role>** | Exclude a role or subrole from the role inventory (repeatable). Excluding a main role also excludes its subroles. | | **--exclude-disabled-clips** | Omit disabled clips (`enabled="0"`) from all timeline-based report sections (with `--report`). | +| **--include-markers-outside-clip-boundaries** | Include markers outside the host clip’s media range (hidden in FCP Tags/timeline) and add a **Hidden** column (✓/✗) on the Markers sheet (with `--report`). Default omits those markers. Not available via `--exclude-column`. | +| **--protect-sheets** | Protect every sheet in the Excel workbook against casual edits (with `--report`). Cover + all content sheets. **Edit lock only** — not file-open encryption; Excel still opens freely and protection can be turned off. PDF export is unaffected (use Preview → Encrypt for a PDF open password). | | **--exclude-column <column>** | Exclude a workbook column from every applicable report sheet (repeatable; with `--report`). | | **--timecode-format <format>** | Timeline time display format for report cells in Excel and PDF (with `--report`). Values: `HH:MM:SS:FF` (default; SMPTE with frames, `;` before frames for drop-frame), `Frames`, `Feet+Frames`, `HH:MM:SS`. | @@ -75,6 +77,23 @@ All REPORT flags except `--report` itself require `--report`. OpenFCPXMLKit-CLI --report --exclude-role Music --exclude-role Dialogue --exclude-role "SRT ▸ de-DE" /path/to/project.fcpxmld /path/to/output-dir ``` +#### Markers outside clip boundaries + +`--include-markers-outside-clip-boundaries` is a **boolean flag** (no value). By default the Markers sheet matches Final Cut Pro’s Tags list: markers whose `start` is outside the host clip’s media range are omitted and no **Hidden** column is shown. Pass the flag to include those markers and append **Hidden** (✓ = outside bounds, ✗ = inside). The Hidden column cannot be removed with `--exclude-column`. + +```bash +OpenFCPXMLKit-CLI --report --report-markers --include-markers-outside-clip-boundaries \ + /path/to/project.fcpxmld /path/to/output-dir +``` + +#### Excel sheet protection + +`--protect-sheets` is a **boolean flag** (no value). When set, every worksheet in the exported `.xlsx` (cover and content) gets XLKit sheet protection so casual cell edits are blocked. This is **not** workbook encryption: anyone can open the file, and Excel can remove protection without a password. PDF export ignores this flag — use macOS Preview’s **Encrypt** command if you need a PDF open password. + +```bash +OpenFCPXMLKit-CLI --report --protect-sheets /path/to/project.fcpxmld /path/to/output-dir +``` + #### Disabled clip exclusion `--exclude-disabled-clips` is a **boolean flag** (no value). Add it to any `--report` command to omit clips with `enabled="0"` from role inventory, markers, keywords, titles, transitions, effects, speed-change effects, and summary role durations. diff --git a/Documentation/Manual/17-Examples.md b/Documentation/Manual/17-Examples.md index 8879df7..a3f5c7d 100644 --- a/Documentation/Manual/17-Examples.md +++ b/Documentation/Manual/17-Examples.md @@ -287,6 +287,23 @@ let report = try await fcpxml.buildReport(options: options) // Excel cover A2 + PDF cover/footer centre ``` +Markers outside clip boundaries + Excel sheet protection: + +```swift +var options = FinalCutPro.FCPXML.ReportOptions.markersOnly +options.includeMarkersOutsideClipBoundaries = true // Hidden column (✓/✗) +options.protectSheets = true // Excel edit lock (not encryption) +let report = try await fcpxml.buildReport(options: options) +try await FinalCutPro.FCPXML.ReportExcelExport.export(report, to: xlsxURL) +``` + +```bash +OpenFCPXMLKit-CLI --report --report-markers \ + --include-markers-outside-clip-boundaries \ + --protect-sheets \ + /path/to/project.fcpxmld /path/to/output-dir +``` + --- ## Project a timeline (MediaUsageWindow) diff --git a/Documentation/Manual/19-Reporting.md b/Documentation/Manual/19-Reporting.md index b1e2b0b..df7a0a9 100644 --- a/Documentation/Manual/19-Reporting.md +++ b/Documentation/Manual/19-Reporting.md @@ -12,19 +12,19 @@ Everything lives under **`FinalCutPro.FCPXML`**: - **buildReport(options:scope:onPhaseStarted:)** — convenience entry point on a parsed document. - **ReportBuilder** — assembles a **Report** from a document or a single **Project**. -- **ReportOptions** — selects which sections to include, plus project filter, media base URL, role display preference, cover sheet, role exclusions, disabled-clip filtering, column exclusions, **timecodeFormat**, **mediaResolutionPolicy**, **mediaSummaryDistinguishProxyAndOriginal**, and optional **copyrightLabel**. +- **ReportOptions** — selects which sections to include, plus project filter, media base URL, role display preference, cover sheet, role exclusions, disabled-clip filtering, column exclusions, **timecodeFormat**, **mediaResolutionPolicy**, **mediaSummaryDistinguishProxyAndOriginal**, optional **copyrightLabel**, **includeMarkersOutsideClipBoundaries**, and **protectSheets** (Excel edit lock). - **ReportTimecodeFormat** — how timeline time values appear in workbook/PDF cells (`HH:MM:SS:FF`, Frames, Feet+Frames, `HH:MM:SS`). -- **Report** — the assembled value type (one optional property per section, plus resolved column exclusions, `timecodeFormat`, and `copyrightLabel`). +- **Report** — the assembled value type (one optional property per section, plus resolved column exclusions, `timecodeFormat`, `copyrightLabel`, and `protectSheets`). - **ReportBuildPhase** — content phases in product / workbook order; use `enabledPhases(for:)` for GUI progress bars. - **ReportColumn** — logical columns that can be omitted globally at export (Excel and PDF). -- **ReportExcelExport** — turns a `Report` into an XLKit `Workbook` or writes it to disk. -- **ReportPDFExport** — turns a `Report` into PDF `Data` or writes a multi-page `.pdf` file. +- **ReportExcelExport** — turns a `Report` into an XLKit `Workbook` or writes it to disk (honours `protectSheets`). +- **ReportPDFExport** — turns a `Report` into PDF `Data` or writes a multi-page `.pdf` file (ignores `protectSheets`). All **build** APIs are **async**. PDF export is **synchronous** once a `Report` exists. **Project-once Projection:** When Role Inventory, Markers, Keywords, Titles & Generators, Transitions, Effects, Speed Change Effects, Media Summary, or Summary is enabled, `ReportBuilder` projects the timeline **once** (progress phase `.projecting`) and shares `ReportProjectionContext` across those sections. Markers / Keywords / Titles / Transitions / Effects are Projection-first with Extraction fallback. See [20 — Timeline Projection](20-Timeline-Projection.md). -**Configuration parity:** Build the report **once** with `ReportOptions`, then export to Excel, PDF, or both. Section flags, `excludedColumns`, `excludedRoles`, `excludeDisabledClips`, `timecodeFormat`, `copyrightLabel`, and `projectName` all apply to both exporters. PDF adds presentation-only features (cover page, TOC with sheet colour chips + tint washes, per-sheet content tints, pagination, remaining-column width expansion after exclusions, truncation) on top of the same `Report` data. +**Configuration parity:** Build the report **once** with `ReportOptions`, then export to Excel, PDF, or both. Section flags, `excludedColumns`, `excludedRoles`, `excludeDisabledClips`, `timecodeFormat`, `copyrightLabel`, `includeMarkersOutsideClipBoundaries`, and `projectName` all apply to both exporters (they shape the shared `Report`). **`protectSheets` is Excel-only** (worksheet edit lock — not encryption). PDF adds presentation-only features (cover page, TOC with sheet colour chips + tint washes, per-sheet content tints, pagination, remaining-column width expansion after exclusions, truncation) on top of the same `Report` data. --- @@ -69,6 +69,7 @@ try FinalCutPro.FCPXML.ReportPDFExport.export(report, to: pdfURL) | `includeMediaSummary` | `false` | Media Summary (missing media file paths) | | `includeRoleInventory` | `false` | Selected Roles Inventory + per-role sheets | | `includeChapterMarkersInMarkersReport` | `false` | Add chapter markers to the Markers sheet | +| `includeMarkersOutsideClipBoundaries` | `false` | Include markers outside the host clip’s media range (hidden in FCP Tags/timeline) and show a **Hidden** column (✓/✗). Not part of `excludedColumns` / `--exclude-column`. | ### Other configuration @@ -87,6 +88,7 @@ try FinalCutPro.FCPXML.ReportPDFExport.export(report, to: pdfURL) | `mediaSummaryDistinguishProxyAndOriginal` | `false` | When `true`, Media Summary uses separate Missing Original / Missing Proxy columns. | | `summaryOverlapAwareDurations` | `false` | When `true`, Summary role durations use occupied-union via Projection occupancy. | | `emitPerSourceInventoryRows` | `false` | When `true`, Role Inventory may emit distinct rows per media `src` index. | +| `protectSheets` | `false` | When `true`, Excel export applies XLKit worksheet protection to **every** sheet (cover + content). Edit lock only — **not** file-open encryption; PDF ignores this flag. CLI `--protect-sheets`. | ### Presets @@ -168,6 +170,7 @@ let report = try await fcpxml.buildReport(options: options) - `copyrightLabel: String?` — optional copyright / attribution line (Excel cover **A2**; PDF cover below branding; PDF footer centre) - `excludedColumns: Set` — resolved from `ReportOptions.excludedColumns` at build time - `timecodeFormat: ReportTimecodeFormat` — copied from options; drives Excel and PDF headers and cell formatting +- `protectSheets: Bool` — copied from options; Excel export applies worksheet protection when `true` (PDF ignores) - **`exportBrandingText`** — resolved branding label from `workbookCoverSheet` (or the OpenFCPXMLKit default) for Excel cover cell A1 and PDF cover/footer A section property is `nil` when that section was not requested. Every section conforms to **ReportSection** and exposes a `defaultSheetName`. Row models expose `columnHeaders` / `columnHeaders(timecodeFormat:)` and `columnValues` in matching order, so sections can be rendered by either export backend. @@ -179,7 +182,7 @@ These contracts define what a “near-zero miss” report must not omit when the | Sheet | Obligation (FCPXML-derived) | |-------|-----------------------------| | Selected Roles Inventory / per-role | One row per visible host clip × role (Projection windows when inventory is enabled); fixed columns after **Row** as listed below; dynamic metadata keys discovered on those clips | -| Markers | Every non-filtered marker on the report timeline (standard / to-do / chapter when enabled); host clip name and timeline position | +| Markers | Every non-filtered marker on the report timeline (standard / to-do / chapter when enabled); host clip name and timeline position. Default omits markers whose `start` is outside the host media range unless `includeMarkersOutsideClipBoundaries` is set | | Keywords | Every keyword range attached to timeline hosts in scope | | Titles & Generators | Every title / generator clip in scope with clip name and timeline bounds | | Transitions | Every transition element on the report spine(s) in scope | @@ -248,7 +251,11 @@ Use **RoleInventoryColumnLayout** (internal layout helper) or `RoleClipReportRow #### Markers -**MarkersReportSection** of **MarkerReportRow**: **Row**, Marker Name, Type, Notes, Position, Clip Name, Role ▸ Subrole, Reel, Scene, Source Position. (**Row** is added at export unless excluded.) +**MarkersReportSection** of **MarkerReportRow**: **Row**, Marker Name, Type, Notes, Position, Clip Name, Role ▸ Subrole, Reel, Scene, Source Position — and, when `includeMarkersOutsideClipBoundaries` is `true`, a trailing **Hidden** column (✓/✗). (**Row** is added at export unless excluded.) + +By default, markers whose `start` lies outside the host clip’s media range (`[start, start + duration)`) are **omitted** — Final Cut Pro hides them from the timeline and Tags list. Set `includeMarkersOutsideClipBoundaries` (CLI `--include-markers-outside-clip-boundaries`) to include them; the sheet then gains **Hidden** (✓ = outside bounds, ✗ = inside). **Hidden** is not a `ReportColumn` / `--exclude-column` target. + +This is **not** the FCPXML 1.13+ empty `hidden-clip-marker` element (see [12 — Typed Models](12-Typed-Models.md#hidden-clip-marker-fcpxml-113)). Boundary helper: `FCPXMLMarkerClipBoundary`; Projection annotations expose `isOutsideClipBoundaries`. **MarkerReportType**: `.standard`, `.incompleteToDo`, `.completedToDo`, `.chapter`. @@ -508,6 +515,17 @@ options.workbookCoverSheet = FinalCutPro.FCPXML.ReportWorkbookCoverSheet( options.copyrightLabel = "© 2026 My Studio" ``` +### Sheet protection (Excel only) + +Optional **`ReportOptions.protectSheets`** / **`Report.protectSheets`** (CLI `--protect-sheets`) applies XLKit worksheet protection to **every** sheet in the workbook after content is written (cover + inventory + section sheets). Defaults to `false`. + +This is an **edit lock** to discourage accidental changes — **not** file-open encryption. Excel still opens the workbook without a password, and anyone can turn protection off in Excel unless you add a password later in Excel itself. **`ReportPDFExport` ignores this flag**; password-protect PDFs with Preview or another PDF tool after export. + +```swift +var options = FinalCutPro.FCPXML.ReportOptions.full +options.protectSheets = true +``` + --- ## PDF export @@ -554,8 +572,10 @@ Per-section presentation: | `projectName` | Applied at build time (timeline source and `report.projectName`) | | `workbookCoverSheet` | `exportBrandingText` on cover and footer (Excel cover tab is separate) | | `copyrightLabel` | Cover line below branding; centred running footer (Excel cover **A2**) | +| `includeMarkersOutsideClipBoundaries` | Applied at build time (Markers rows + optional **Hidden** column) | +| `protectSheets` | **Ignored** — Excel-only worksheet edit lock; use Preview → Encrypt for PDF open passwords | -Headers such as **Marker Name** or **Type** on the Markers sheet are **not** `ReportColumn` cases; `--exclude-column` cannot remove them in Excel or PDF. +Headers such as **Marker Name**, **Type**, or the opt-in **Hidden** column on the Markers sheet are **not** `ReportColumn` cases; `--exclude-column` cannot remove them in Excel or PDF. --- @@ -577,6 +597,8 @@ The same reports are available through **OpenFCPXMLKit-CLI**: | `--report-project ` | Timeline name filter (project or standalone compound-clip name) | | `--exclude-role ` | Omit roles from role inventory (repeatable) | | `--exclude-disabled-clips` | Omit `enabled="0"` clips from all timeline sections | +| `--include-markers-outside-clip-boundaries` | Include out-of-bounds markers + Markers **Hidden** column | +| `--protect-sheets` | Excel worksheet edit lock on every sheet (not encryption; PDF unaffected) | | `--exclude-column ` | Omit a column from every applicable sheet (repeatable) | | `--timecode-format ` | Timeline cell format: `HH:MM:SS:FF` (default), `Frames`, `Feet+Frames`, `HH:MM:SS` | diff --git a/Documentation/Manual/20-Timeline-Projection.md b/Documentation/Manual/20-Timeline-Projection.md index d0c154f..d87e64e 100644 --- a/Documentation/Manual/20-Timeline-Projection.md +++ b/Documentation/Manual/20-Timeline-Projection.md @@ -105,7 +105,7 @@ try await projector.project(from: source, fcpxml: fcpxml, options: options) { wi - Nested spines / anchored children and J/L cuts (`audioStart` / `audioDuration`) - `mc-clip` angles (active or all; split video/audio), `ref-clip` media sequences, auditions - `video` / `audio` leaves with `ChannelKindFilter` / `srcEnable` -- Optional annotations when `includeAnnotations` is on (roles, volume/effects breadcrumbs, markers/keywords/titles/transitions/effects for reporting) +- Optional annotations when `includeAnnotations` is on (roles, volume/effects breadcrumbs, markers/keywords/titles/transitions/effects for reporting). Marker annotations include **`isOutsideClipBoundaries`** (start outside host media range) for Markers report filtering / the opt-in **Hidden** column — see [19 — Reporting](19-Reporting.md#markers). Timing composition uses **`ProjectionTiming`** (Double intermediates → `Fraction` at 12 decimal places). Do not use SwiftTimecode `Fraction.+` / `.-` for absolute timeline placement when mixing conform-scaled values with literal FCPXML rationals — see [03 — Timecode & Timing](03-Timecode-Timing.md). diff --git a/Documentation/README.md b/Documentation/README.md index 16b303d..7fb9e83 100644 --- a/Documentation/README.md +++ b/Documentation/README.md @@ -31,11 +31,11 @@ The manual is split into **chapters** for easier navigation and maintenance: | [15 — Errors & Utilities](Manual/15-Errors-Utilities.md) | Error types, ErrorHandling, ProgressBar, FCPXMLUID | | [16 — CLI](Manual/16-CLI.md) | Experimental command-line interface (OpenFCPXMLKit-CLI) | | [17 — Examples](Manual/17-Examples.md) | End-to-end workflows and code examples | -| [19 — Reporting, Excel & PDF Export](Manual/19-Reporting.md) | Report builder, ReportOptions, Projection-first sections, Excel/PDF export | +| [19 — Reporting, Excel & PDF Export](Manual/19-Reporting.md) | Report builder, ReportOptions (`copyrightLabel`, `includeMarkersOutsideClipBoundaries`, `protectSheets`, …), Projection-first sections, Excel/PDF export | -The manual covers the **entire public API** with examples: core operations, async/await, file I/O, validation, timeline creation and manipulation, metadata, media processing, typed models, version conversion, **Timeline Projection**, reporting and Excel/PDF export, CLI, and utilities. **Chapter 18** describes the cross-platform XML abstraction. **Chapter 19** covers reporting. **Chapter 20** covers Projection (`MediaUsageWindow`, project-once for reports). Architecture philosophy: [ARCHITECTURE.md](../ARCHITECTURE.md) §2.7. +The manual covers the **entire public API** with examples: core operations, async/await, file I/O, validation, timeline creation and manipulation, metadata, media processing, typed models, version conversion, **Timeline Projection**, reporting and Excel/PDF export, CLI, and utilities. **Chapter 18** describes the cross-platform XML abstraction. **Chapter 19** covers reporting. **Chapter 20** covers Projection (`MediaUsageWindow`, project-once for reports). Architecture philosophy: [ARCHITECTURE.md](../ARCHITECTURE.md) §2.7. Hard constraints: [GUARDRAILS.md](../GUARDRAILS.md). -**Test count (keep in sync):** **1076** listed in `swift test --list-tests` — **1072** in `OpenFCPXMLKitTests` (1069 XCTest + 3 `@Test`) + **4** optional `ExcelReportTest`; **59** sample `.fcpxml` files. Private user exports for local investigation: [Tests/Submitted FCPXML/](../Tests/Submitted%20FCPXML/README.md) (gitignored; never commit to GitHub). +**Test count (keep in sync):** **1084** listed in `swift test --list-tests` — **1078** in `OpenFCPXMLKitTests` (1075 XCTest + 3 `@Test`) + **6** optional `ExcelReportTest`; **60** sample `.fcpxml` files. Private user exports for local investigation: [Tests/Submitted FCPXML/](../Tests/Submitted%20FCPXML/README.md) (gitignored; never commit to GitHub). --- @@ -46,3 +46,4 @@ The manual covers the **entire public API** with examples: core operations, asyn - **[Tests/README.md](../Tests/README.md)** — Test suite layout and categories (including §12a Submitted FCPXML). - **[Submitted FCPXML](../Tests/Submitted%20FCPXML/README.md)** — Private inbox workflow for parsing / reporting edge cases (local only). - **[ARCHITECTURE.md](../ARCHITECTURE.md)** — Layer stack, Projection, reporting. +- **[GUARDRAILS.md](../GUARDRAILS.md)** — Must / must-not constraints (layers, naming, tests, reporting honesty). diff --git a/GUARDRAILS.md b/GUARDRAILS.md new file mode 100644 index 0000000..f367b6f --- /dev/null +++ b/GUARDRAILS.md @@ -0,0 +1,194 @@ +# OpenFCPXMLKit — Guardrails + +Hard constraints for contributors and AI agents. Prefer this file when deciding **what not to do**; prefer [ARCHITECTURE.md](ARCHITECTURE.md) for **how the system is shaped**. + +**See also:** [ARCHITECTURE.md](ARCHITECTURE.md), [.cursorrules](.cursorrules), [AGENT.md](AGENT.md), [Tests/README.md](Tests/README.md), [CONTRIBUTING.md](CONTRIBUTING.md). + +**Current suite (keep in sync):** **1084** tests listed in `swift test --list-tests` — **1078** in `OpenFCPXMLKitTests` (1075 XCTest + 3 Swift Testing `@Test`) + **6** optional `ExcelReportTest`; **60** public sample `.fcpxml` files. + +--- + +## How to use this document + +| Audience | Expectation | +|----------|-------------| +| **Human contributors** | Treat §1–§8 as merge blockers unless an ADR / maintainer explicitly waives a rule. | +| **AI agents** | Read this file before structural or reporting changes. Do not rationalize around a guardrail; ask or stop. | +| **Both** | When a rule is learned from a regression, add a new entry under §9 (Signs) with Trigger / Instruction / Reason / Provenance. | + +**Relationship to other docs** + +- **ARCHITECTURE.md** — layers, folders, design decisions, diagrams. +- **AGENT.md / .cursorrules** — living agent briefing (must stay in sync with each other). +- **GUARDRAILS.md** — short, enforceable “never / always” list. Keep it scannable; link out for depth. + +--- + +## 1. Naming & product identity + +| Rule | Detail | +|------|--------| +| **OpenFCPXMLKit only** | Use OpenFCPXMLKit naming in code, comments, symbols, CLI, and logs (`ServiceLogger`, `OFKXML*`, `createService()`, …). No legacy fork identifiers. | +| **No marketing names in code** | Never use “PBF” or “Production’s Best Friend” in source, comments, symbol names, or CLI/log output. Describe reporting neutrally (“Excel report”, “PDF report”, “role inventory”, “workbook export”). Those marketing terms may appear **only** in prose docs (README, CHANGELOG, Manual, agent guides). | +| **Tests are FCPXML-prefixed** | Every XCTest case class is `FCPXML…` except the module umbrella `OpenFCPXMLKitTests`. | + +--- + +## 2. Layer boundaries (non-negotiable) + +Extend the engine **bottom-up**. Do not invent FCPXML meaning inside Reporting. + +```text +XML → Parsing → Model → Extraction → Projection → Reporting +``` + +| Always | Never | +|--------|-------| +| Put new XML facts in **Model / Parsing** first | Parse or reinterpret FCPXML only inside `Reporting/` builders | +| Put occupancy / retiming / channel visibility in **Projection** | Duplicate timeline math, role resolution, or story walks in Excel/PDF exporters | +| Keep **Reporting** presentation-thin (rows, columns, colours, sheet layout) | Add report-only ad hoc XML walks when Extraction/Projection can supply the fact | +| Prefer **Projection-first** for Markers / Keywords / Titles / Transitions / Effects (Extraction fallback) | Bypass `ReportProjectionContext` / project-once when those sections are enabled | + +See ARCHITECTURE.md §2.7 for the full “where to put a change” table. + +--- + +## 3. FCPXML compatibility & versions + +| Rule | Detail | +|------|--------| +| **1.5 floor** | Remain backward compatible with FCPXML **1.5**. Optional attributes/elements from later versions (e.g. 1.11, 1.13) must be omitted or ignored when reading/writing/converting to 1.5. Mark newer features in comments with the minimum version (`FCPXML 1.13+`). | +| **Supported range** | DTDs and parsing cover **1.5–1.14**. Do not claim support for versions outside that without DTD + tests. | +| **Conversion strips** | Version conversion must set the root version **and** strip elements not in the target DTD. Validate after convert when the CLI/API path requires it. | +| **Bundle format** | `.fcpxmld` only for versions that support it (`FCPXMLVersion.supportsBundleFormat` → 1.10+). 1.5–1.9 always `.fcpxml`. | +| **Frame rates** | Only Final Cut Pro rates used in tests and public APIs: 23.976, 24, 25, 29.97, 30, 50, 59.94, 60. | + +--- + +## 4. Architecture & concurrency + +| Rule | Detail | +|------|--------| +| **Protocol + DI** | Core operations live behind protocols with sync and async APIs. Inject via `FCPXMLService` / `FCPXMLUtility`; do not hard-wire concrete types in public extension APIs. | +| **`defaultForExtensions`** | Extension APIs that cannot take parameters use **`FCPXMLUtility.defaultForExtensions`** only. Custom behaviour → modular API with `using:`. No hidden concrete types. | +| **One load path** | URL loading goes through **`FCPXMLFileLoader`** (`.fcpxml` / `.fcpxmld`). Do not add a second URL→document path. | +| **OFKXML on all platforms** | Cross-platform code uses `OFKXML*` + `OFKXMLDefaultFactory()`. Do not assume Foundation `XMLDocument` on iOS. Full DTD validation is macOS-only; iOS uses structural validation. | +| **Sendable honesty** | Foundation XML, OFKXML wrappers, and SwiftTimecode types are **not** Sendable. Provide async/await, but **do not** introduce Task-based concurrency over those types. | +| **Strict concurrency** | Code must build under Swift 6 `-strict-concurrency=complete` (CI enforces this). Prefer removing `@unchecked Sendable` over spreading it. | +| **SwiftTimecode API** | Use `Timecode(.realTime(seconds:), at:)` and `.fps23_976`, `.fps24`, … — not legacy `._24` / `realTime: at:` initialisers. | + +--- + +## 5. Reporting & CLI honesty + +| Rule | Detail | +|------|--------| +| **Build once, export many** | Build a single `Report`; export Excel and/or PDF from that model. Do not diverge section logic between exporters. | +| **Presentation vs security** | `protectSheets` / `--protect-sheets` is an **Excel edit lock**, not file-open encryption. Document that clearly in help and Manual. Do **not** imply PDF password protection from this flag. | +| **Markers “Hidden”** | Out-of-bounds markers (`start` outside host media range) are **not** FCPXML `hidden-clip-marker` (1.13+). Default omits out-of-bounds markers; `--include-markers-outside-clip-boundaries` adds them + a **Hidden** column. **Hidden** is not a `--exclude-column` / `ReportColumn` target. | +| **Universal Row** | Tabular Excel/PDF sheets get a 1-based **Row** column by default (`ensuringRowColumn` / `allowsInjectedRowColumn`) unless explicitly excluded. | +| **CLI modifiers need `--report`** | Report-only flags (`--report-full`, section flags, `--protect-sheets`, `--create-pdf`, exclusions, …) must require `--report`. | +| **No help-submenu refactor by default** | Keep flat ArgumentParser flags + `@OptionGroup` unless a maintainer explicitly requests a subcommand redesign. | + +--- + +## 6. Tests & fixtures + +| Rule | Detail | +|------|--------| +| **Tests with behaviour** | Public API and report behaviour changes need tests. Prefer core (parse / extract / project) tests **plus** report shape tests when fixing a report gap. | +| **Hybrid XCTest** | Do **not** wholesale migrate XCTest → Swift Testing. Stay hybrid; use Swift Testing selectively for new/parameterized tests when it helps. | +| **Missing samples skip** | Tests that need a sample use `XCTSkip` when the file is absent — do not fail CI on optional local fixtures. | +| **Never commit private FCPXML** | `Tests/Submitted FCPXML/` inbox contents and private ExcelReportTest fixtures (`.fcpxml` / `.fcpxmld` under those trees) are **gitignored**. Never commit or push private project XML to GitHub. Anonymise → reproduce → fix → promote a **minimal public** sample when appropriate. | +| **ExcelReportTest is optional** | Integration target may skip without a local fixture; do not make CI depend on private Sample bundles. | +| **Update counts when adding tests** | Keep listed counts aligned in `Tests/README.md`, `AGENT.md`, and `.cursorrules` when you add or remove tests. | + +--- + +## 7. Documentation & changelog + +| Rule | Detail | +|------|--------| +| **AGENT ↔ .cursorrules** | When you update one, update the other. Same overview, architecture, test structure, and conventions. | +| **Feature docs** | User-visible behaviour → Manual (esp. 16 CLI / 19 Reporting / 20 Projection) and CLI README as needed. Structural boundaries → ARCHITECTURE.md. Hard constraints → this file. | +| **CHANGELOG** | Keep a Changelog format. Version heading links to the GitHub release tag. Sections: **✨ New Features**, **🔧 Improvements**, **🐛 Bug Fixes** (empty → “None in this release.”). | +| **File headers** | New Swift files use the project header (see ARCHITECTURE.md §5.2): OpenFCPXMLKit URL line, MIT, tabbed purpose block — no `Created by` / extra copyright lines. | + +--- + +## 8. Safety & scope + +| Rule | Detail | +|------|--------| +| **No unsafe / C escape hatches** | Do not introduce unsafe pointers, dynamic code execution, or C APIs for convenience. | +| **No exploit / malware work** | Do not write exploits, exploit PoCs, or attack tooling against any system. | +| **Secrets stay out of git** | Do not commit `.env`, credentials, or private media paths that identify a customer library. Anonymise sample paths. | +| **Destructive git only if asked** | No force-push to main, hard reset, or hook-skipping unless the user explicitly requests it. | +| **Commit only when asked** | Agents create commits only when the user explicitly requests a commit. | + +--- + +## 9. Signs (learned constraints) + +Append new signs when a failure repeats or a design decision must not drift. Keep each entry short. + +### Template + +```markdown +### Sign: short-title +- **Trigger:** When … +- **Instruction:** Always / Never … +- **Reason:** … +- **Provenance:** YYYY-MM-DD — brief note (PR / incident / design lock) +``` + +### Active signs + +### Sign: reporting-stays-thin +- **Trigger:** A report sheet is missing a clip, marker, role, effect, or duration fact. +- **Instruction:** Check Model → Extraction → Projection before adding XML walks in `Reporting/`. +- **Reason:** Duplicate walks diverge from CLI presets and timeline tools; ARCHITECTURE §2.7. +- **Provenance:** 2026-07 — Timeline Projection / reporting layer lock. + +### Sign: markers-hidden-vs-hidden-clip-marker +- **Trigger:** Implementing or documenting “hidden markers”. +- **Instruction:** Treat timeline/Tags-hidden markers as **out-of-bounds `start`**; do not conflate with empty `hidden-clip-marker` (1.13+). Default filter omits out-of-bounds; opt-in flag adds **Hidden** column. +- **Reason:** Matches FCP Tags behaviour; MarkersExtractor #34-inspired semantics. +- **Provenance:** 2026-07 — design lock for `--include-markers-outside-clip-boundaries`. + +### Sign: protect-sheets-is-edit-lock +- **Trigger:** Password / protect / encrypt options for reports. +- **Instruction:** `--protect-sheets` / `protectSheets` applies XLKit worksheet protection only. Do not advertise workbook open-password or PDF encryption via this flag. +- **Reason:** XLKit supports sheet protection, not file encryption; PDF passwords belong in Preview (or a future dedicated API). +- **Provenance:** 2026-07 — design lock for `--protect-sheets`. + +### Sign: no-wholesale-swift-testing-migration +- **Trigger:** Urge to “modernise” the test suite to Swift Testing. +- **Instruction:** Stay hybrid; migrate selectively for new/parameterized tests only. +- **Reason:** Large XCTest surface; migration risk outweighs benefit without a dedicated project. +- **Provenance:** 2026-07 — explicit project decision. + +### Sign: never-commit-submitted-fcpxml +- **Trigger:** Debugging with a user-supplied `.fcpxml` / `.fcpxmld`. +- **Instruction:** Keep it under `Tests/Submitted FCPXML/` (gitignored). Promote only anonymised minimal public fixtures. +- **Reason:** Private library paths and project names must not land on GitHub. +- **Provenance:** Standing project policy — see `Tests/Submitted FCPXML/README.md`. + +--- + +## 10. Quick checklist before merge + +- [ ] Change sits in the correct layer (ARCHITECTURE §2.7 / Guardrails §2). +- [ ] Public behaviour has tests; optional fixtures use `XCTSkip`. +- [ ] No PBF / legacy naming in code or CLI output. +- [ ] FCPXML 1.5 compatibility preserved; newer features version-marked. +- [ ] Concurrency: no Task over non-Sendable XML/timecode types. +- [ ] Docs: AGENT.md ↔ .cursorrules if agent briefing changed; Manual/CLI/CHANGELOG as needed. +- [ ] Private FCPXML / secrets not staged. + +--- + +## 11. References + +- **Internal:** [ARCHITECTURE.md](ARCHITECTURE.md), [AGENT.md](AGENT.md), [.cursorrules](.cursorrules), [Tests/README.md](Tests/README.md), [Tests/Submitted FCPXML/README.md](Tests/Submitted%20FCPXML/README.md), [Documentation/Manual/00-Index.md](Documentation/Manual/00-Index.md). +- **External:** [Final Cut Pro XML](https://fcp.cafe/developers/fcpxml/), [Swift API Design Guidelines](https://swift.org/documentation/api-design-guidelines/), [Swift Concurrency](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/). diff --git a/README.md b/README.md index 34dff34..ab98f58 100755 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ A modern Swift 6 framework for working with Final Cut Pro's FCPXML with full con OpenFCPXMLKit provides a type-safe API for parsing, creating, and manipulating FCPXML with async/await, SwiftTimecode, and Excel/PDF reporting. Targets **macOS 26+** and **iOS 26+** (Foundation XML on macOS; AEXML on iOS). -**Tests:** **1076** listed in `swift test --list-tests` — **1072** in `OpenFCPXMLKitTests` + **4** optional `ExcelReportTest` — across **59** sample `.fcpxml` files. Private local investigation inbox: [`Tests/Submitted FCPXML/`](Tests/Submitted%20FCPXML/README.md) (gitignored; never commit private FCPXML). +**Tests:** **1084** listed in `swift test --list-tests` — **1078** in `OpenFCPXMLKitTests` + **6** optional `ExcelReportTest` — across **60** sample `.fcpxml` files. Private local investigation inbox: [`Tests/Submitted FCPXML/`](Tests/Submitted%20FCPXML/README.md) (gitignored; never commit private FCPXML). OpenFCPXMLKit is currently in an experimental stage. It covers most core FCPXML attributes and parameters and provides a solid foundation for parsing, creation, and manipulation, with room for future expansion and additional feature coverage. @@ -95,7 +95,9 @@ This codebase is developed using AI agents. - Build once with `buildReport(options:)`, then export `.xlsx` (XLKit) and/or `.pdf` (CoreGraphics) - Sheets: Role Inventory, Markers, Keywords, Titles, Transitions, Effects, Speed Change, Summary, Media Summary - Filters: roles, columns (incl. **Row**), disabled clips, project name, timecode format, copyright label -- CLI: `--report`, `--report-full`, `--create-pdf`, `--media-resolution`, `--timecode-format`, … +- Markers: default omits out-of-bounds starts; `--include-markers-outside-clip-boundaries` adds them + **Hidden** column +- Excel: `--protect-sheets` / `protectSheets` applies worksheet edit locks (not encryption; PDF unaffected) +- CLI: `--report`, `--report-full`, `--create-pdf`, `--media-resolution`, `--timecode-format`, `--protect-sheets`, … - See [Manual 19 — Reporting](Documentation/Manual/19-Reporting.md) ### CLI @@ -106,6 +108,7 @@ This codebase is developed using AI agents. - Protocol-oriented + dependency injection; sync and async APIs - Layer stack: `XML → Parsing → Model → Extraction → Projection → Reporting` - Swift 6 strict concurrency; cross-platform OFKXML (Foundation / AEXML) +- See [ARCHITECTURE.md](ARCHITECTURE.md) and [GUARDRAILS.md](GUARDRAILS.md) ## Requirements @@ -138,7 +141,7 @@ let package = Package( .iOS(.v26) ], dependencies: [ - .package(url: "https://github.com/TheAcharya/OpenFCPXMLKit", from: "3.1.0") + .package(url: "https://github.com/TheAcharya/OpenFCPXMLKit", from: "3.1.1") ], targets: [ .target( @@ -201,7 +204,7 @@ sudo rm /usr/local/bin/OpenFCPXMLKit-CLI ### Compiled From Source ```shell -VERSION=3.1.0 # replace this with the git tag of the version you need +VERSION=3.1.1 # replace this with the git tag of the version you need git clone https://github.com/TheAcharya/OpenFCPXMLKit.git cd OpenFCPXMLKit git checkout "tags/$VERSION" @@ -278,6 +281,14 @@ REPORT: excludes its subroles. --exclude-disabled-clips Omit disabled clips (enabled="0") from all report sections (with --report). + --include-markers-outside-clip-boundaries + Include markers whose start is outside the host clip’s media range (hidden in FCP + timeline/Tags) and add a Hidden column (✓/✗) on the Markers sheet (with --report / + --report-markers). Default omits those markers and does not show Hidden. + --protect-sheets Protect every sheet in the Excel workbook against casual edits (with --report). Applies to + the cover sheet and all content sheets. This is an edit lock, not file-open encryption — + Excel can still open the file, and anyone can turn protection off. PDF export is unaffected + (use Preview’s Encrypt to password-protect a PDF). --exclude-column Exclude a report column from every applicable Excel/PDF sheet (repeatable; with --report). Case-insensitive names include Row / Row Numbers (all tabular sheets + PDF Row injection), @@ -316,6 +327,7 @@ Complete manual, usage guide, and examples are in the [Documentation](Documentat - [19 — Reporting, Excel & PDF Export](Documentation/Manual/19-Reporting.md) — `buildReport`, Excel/PDF export, filters, progress - [CLI](Sources/OpenFCPXMLKitCLI/README.md) — Flags, examples, building and extending - [ARCHITECTURE.md](ARCHITECTURE.md) — Layer stack, codebase map, Mermaid diagrams +- [GUARDRAILS.md](GUARDRAILS.md) — Must / must-not constraints for contributors and agents ## FCPXML Version Support @@ -335,9 +347,9 @@ OpenFCPXMLKit supports FCPXML versions 1.5 through 1.14. All DTDs for these vers - Protocols define parsing, timecode conversion, document operations, error handling, MIME type detection, asset validation, silence detection, asset duration measurement, and parallel file I/O; each has a default implementation you can swap. FCPXMLService (and FCPXMLUtility) composes these and exposes sync and async APIs. ModularUtilities provides createService, processFCPXML, validateDocument, convertTimecodes, and similar helpers. - FCPXMLFileLoader handles .fcpxml and .fcpxmld (including bundle Info.fcpxml). FCPXMLValidator and FCPXMLDTDValidator handle structural and schema validation (full DTD on macOS; FCPXMLStructuralValidator on iOS when DTD is unavailable); DTDs for 1.5–1.14 are bundled. - A cross-platform XML layer (`Sources/OpenFCPXMLKit/XML/`) provides protocol types (OFKXMLNode, OFKXMLElement, OFKXMLDocument, OFKXMLFactory) with Foundation and AEXML backends. Extensions on CMTime and the XML protocol types offer convenience APIs; use modular overloads with an explicit dependency to inject your own. Error types are explicit (FCPXMLError, FCPXMLLoadError, export and validation errors); you can inject a custom error handler. -- The engine is layered bottom-up — `XML → Parsing → Model → Extraction → Projection → Reporting` — so the CLI, extraction presets, timeline tools, and reports share one foundation. **Projection** emits playable `MediaUsageWindow`s; **Reporting** (Excel via XLKit, PDF via CoreGraphics) maps Projection + Extraction facts into sheets and owns presentation only. See [ARCHITECTURE.md](ARCHITECTURE.md) for the full codebase map and layer boundaries. +- The engine is layered bottom-up — `XML → Parsing → Model → Extraction → Projection → Reporting` — so the CLI, extraction presets, timeline tools, and reports share one foundation. **Projection** emits playable `MediaUsageWindow`s; **Reporting** (Excel via XLKit, PDF via CoreGraphics) maps Projection + Extraction facts into sheets and owns presentation only. See [ARCHITECTURE.md](ARCHITECTURE.md) for the full codebase map and layer boundaries, and [GUARDRAILS.md](GUARDRAILS.md) for hard must / must-not constraints on those layers. -See AGENT.md for a detailed breakdown for AI agents and contributors. +See [AGENT.md](AGENT.md) for a detailed breakdown for AI agents and contributors, and [GUARDRAILS.md](GUARDRAILS.md) for hard must / must-not constraints. ## Utilised By diff --git a/Sources/OpenFCPXMLKit/Model/FCPXMLMarkerClipBoundary.swift b/Sources/OpenFCPXMLKit/Model/FCPXMLMarkerClipBoundary.swift new file mode 100644 index 0000000..eddb0d2 --- /dev/null +++ b/Sources/OpenFCPXMLKit/Model/FCPXMLMarkerClipBoundary.swift @@ -0,0 +1,35 @@ +// +// FCPXMLMarkerClipBoundary.swift +// OpenFCPXMLKit • https://github.com/TheAcharya/OpenFCPXMLKit +// © 2026 • Licensed under MIT License +// + +// +// Host-media-range checks for markers outside clip duration (FCP-hidden markers). +// + +import Foundation +import SwiftTimecode + +extension FinalCutPro.FCPXML { + /// Utilities for detecting markers whose `start` lies outside the host clip’s media range. + /// + /// Final Cut Pro hides such markers from the timeline and Tags list while still emitting them + /// in FCPXML. The inclusive media window is `[hostStart, hostStart + hostDuration)`. + public enum MarkerClipBoundary { + /// Returns `true` when `markerStart` is strictly before the host start or at/after the host end. + /// + /// When `hostDuration` is missing, returns `false` (cannot determine bounds). + public static func isOutsideHostMediaRange( + markerStart: Fraction, + hostStart: Fraction?, + hostDuration: Fraction? + ) -> Bool { + guard let hostDuration else { return false } + let start = hostStart ?? .zero + let end = ProjectionTiming.adding(start, hostDuration) + let marker = markerStart.doubleValue + return marker < start.doubleValue || marker >= end.doubleValue + } + } +} diff --git a/Sources/OpenFCPXMLKit/Projection/WindowAnnotationBuilder.swift b/Sources/OpenFCPXMLKit/Projection/WindowAnnotationBuilder.swift index 1491417..281563e 100644 --- a/Sources/OpenFCPXMLKit/Projection/WindowAnnotationBuilder.swift +++ b/Sources/OpenFCPXMLKit/Projection/WindowAnnotationBuilder.swift @@ -300,6 +300,7 @@ extension FinalCutPro.FCPXML { absoluteStart: Fraction ) -> [WindowMarkerAnnotation] { let hostStart = element.fcpStart ?? .zero + let hostDuration = element.fcpDuration let reel = element._fcpMetadataChildStringValue(forKey: .reel) ?? "" let scene = element._fcpMetadataChildStringValue(forKey: .scene) ?? "" @@ -312,6 +313,11 @@ extension FinalCutPro.FCPXML { let sourcePosition = marker.start let relative = ProjectionTiming.subtracting(sourcePosition, hostStart) let timelinePosition = ProjectionTiming.adding(absoluteStart, relative) + let isOutside = MarkerClipBoundary.isOutsideHostMediaRange( + markerStart: sourcePosition, + hostStart: element.fcpStart, + hostDuration: hostDuration + ) result.append( WindowMarkerAnnotation( @@ -321,7 +327,8 @@ extension FinalCutPro.FCPXML { timelinePosition: timelinePosition, sourcePosition: sourcePosition, reel: reel, - scene: scene + scene: scene, + isOutsideClipBoundaries: isOutside ) ) } diff --git a/Sources/OpenFCPXMLKit/Projection/WindowAnnotations.swift b/Sources/OpenFCPXMLKit/Projection/WindowAnnotations.swift index 8dab669..acc7899 100644 --- a/Sources/OpenFCPXMLKit/Projection/WindowAnnotations.swift +++ b/Sources/OpenFCPXMLKit/Projection/WindowAnnotations.swift @@ -81,6 +81,8 @@ extension FinalCutPro.FCPXML { public var sourcePosition: Fraction public var reel: String public var scene: String + /// `true` when `sourcePosition` is outside the host’s media range (hidden in FCP Tags/timeline). + public var isOutsideClipBoundaries: Bool public init( name: String, @@ -89,7 +91,8 @@ extension FinalCutPro.FCPXML { timelinePosition: Fraction, sourcePosition: Fraction, reel: String = "", - scene: String = "" + scene: String = "", + isOutsideClipBoundaries: Bool = false ) { self.name = name self.kind = kind @@ -98,6 +101,7 @@ extension FinalCutPro.FCPXML { self.sourcePosition = sourcePosition self.reel = reel self.scene = scene + self.isOutsideClipBoundaries = isOutsideClipBoundaries } } diff --git a/Sources/OpenFCPXMLKit/Reporting/Builders/FCPXMLMarkersReportBuilder.swift b/Sources/OpenFCPXMLKit/Reporting/Builders/FCPXMLMarkersReportBuilder.swift index 5877550..5f086f7 100644 --- a/Sources/OpenFCPXMLKit/Reporting/Builders/FCPXMLMarkersReportBuilder.swift +++ b/Sources/OpenFCPXMLKit/Reporting/Builders/FCPXMLMarkersReportBuilder.swift @@ -18,6 +18,7 @@ extension FinalCutPro.FCPXML { from timeline: any OFKXMLElement, scope: ExtractionScope, includeChapterMarkers: Bool, + includeMarkersOutsideClipBoundaries: Bool = false, roleDisplayPreference: RoleDisplayPreference = .builtIn, timecodeFormat: ReportTimecodeFormat = .smpteFrames, projection: ReportProjectionContext? = nil, @@ -31,16 +32,21 @@ extension FinalCutPro.FCPXML { timeline: timeline, resources: resources, includeChapterMarkers: includeChapterMarkers, + includeMarkersOutsideClipBoundaries: includeMarkersOutsideClipBoundaries, roleDisplayPreference: roleDisplayPreference, timecodeFormat: timecodeFormat ) - return MarkersReportSection(rows: rows) + return MarkersReportSection( + rows: rows, + showsHiddenColumn: includeMarkersOutsideClipBoundaries + ) } return await buildFromExtraction( from: timeline, scope: scope, includeChapterMarkers: includeChapterMarkers, + includeMarkersOutsideClipBoundaries: includeMarkersOutsideClipBoundaries, roleDisplayPreference: roleDisplayPreference, timecodeFormat: timecodeFormat ) @@ -50,6 +56,7 @@ extension FinalCutPro.FCPXML { from timeline: any OFKXMLElement, scope: ExtractionScope, includeChapterMarkers: Bool, + includeMarkersOutsideClipBoundaries: Bool, roleDisplayPreference: RoleDisplayPreference, timecodeFormat: ReportTimecodeFormat ) async -> MarkersReportSection { @@ -57,7 +64,11 @@ extension FinalCutPro.FCPXML { let filtered = extracted.filter { marker in if case .chapter = marker.configuration { - return includeChapterMarkers + if !includeChapterMarkers { return false } + } + let isOutside = isOutsideClipBoundaries(extracted: marker) + if isOutside, !includeMarkersOutsideClipBoundaries { + return false } return true } @@ -68,11 +79,15 @@ extension FinalCutPro.FCPXML { markerRows( from: $0, roleDisplayPreference: roleDisplayPreference, - timecodeFormat: timecodeFormat + timecodeFormat: timecodeFormat, + includeHiddenColumn: includeMarkersOutsideClipBoundaries ) } - return MarkersReportSection(rows: rows) + return MarkersReportSection( + rows: rows, + showsHiddenColumn: includeMarkersOutsideClipBoundaries + ) } private static func rowsFromProjection( @@ -80,6 +95,7 @@ extension FinalCutPro.FCPXML { timeline: any OFKXMLElement, resources: (any OFKXMLElement)?, includeChapterMarkers: Bool, + includeMarkersOutsideClipBoundaries: Bool, roleDisplayPreference: RoleDisplayPreference, timecodeFormat: ReportTimecodeFormat ) -> [MarkerReportRow] { @@ -93,6 +109,9 @@ extension FinalCutPro.FCPXML { for marker in host.markers { if marker.kind == .chapter, !includeChapterMarkers { continue } + if marker.isOutsideClipBoundaries, !includeMarkersOutsideClipBoundaries { + continue + } let position = formatFraction( marker.timelinePosition, @@ -118,7 +137,8 @@ extension FinalCutPro.FCPXML { roleSubrole: roleDisplay, reel: marker.reel, scene: marker.scene, - sourcePosition: sourcePosition + sourcePosition: sourcePosition, + isHidden: marker.isOutsideClipBoundaries ) ) } @@ -191,6 +211,15 @@ extension FinalCutPro.FCPXML { return ReportFormatting.timecodeString(timecode, format: timecodeFormat) } + private static func isOutsideClipBoundaries(extracted: ExtractedMarker) -> Bool { + let host = extracted.ancestorClipElement() + return MarkerClipBoundary.isOutsideHostMediaRange( + markerStart: extracted.model.start, + hostStart: host?.fcpStart, + hostDuration: host?.fcpDuration + ) + } + /// Builds one report row per host component role. /// /// A marker on a clip carrying both video and audio yields two rows (for example @@ -198,7 +227,8 @@ extension FinalCutPro.FCPXML { private static func markerRows( from extracted: ExtractedMarker, roleDisplayPreference: RoleDisplayPreference, - timecodeFormat: ReportTimecodeFormat + timecodeFormat: ReportTimecodeFormat, + includeHiddenColumn: Bool ) -> [MarkerReportRow] { guard let positionTimecode = extracted.value( forContext: .absoluteStartAsTimecode(frameRateSource: .mainTimeline) @@ -220,6 +250,7 @@ extension FinalCutPro.FCPXML { } let metadata = extracted.ancestorClipContext()?.value(forContext: .metadata) ?? [] + let isHidden = isOutsideClipBoundaries(extracted: extracted) let roleDisplays = ReportFormatting.markerRoleDisplays( for: extracted, @@ -240,7 +271,8 @@ extension FinalCutPro.FCPXML { roleSubrole: roleDisplay, reel: ReportFormatting.metadataString(from: metadata, key: .reel), scene: ReportFormatting.metadataString(from: metadata, key: .scene), - sourcePosition: sourcePosition + sourcePosition: sourcePosition, + isHidden: includeHiddenColumn ? isHidden : false ) } } diff --git a/Sources/OpenFCPXMLKit/Reporting/Excel/FCPXMLReportExcelExport.swift b/Sources/OpenFCPXMLKit/Reporting/Excel/FCPXMLReportExcelExport.swift index e0faadd..236dfd7 100644 --- a/Sources/OpenFCPXMLKit/Reporting/Excel/FCPXMLReportExcelExport.swift +++ b/Sources/OpenFCPXMLKit/Reporting/Excel/FCPXMLReportExcelExport.swift @@ -26,6 +26,9 @@ extension FinalCutPro.FCPXML { } /// Writes a report to an `.xlsx` file at the given URL. + /// + /// When ``Report/protectSheets`` is `true`, every worksheet is edit-locked + /// (XLKit sheet protection without a password). This is not file encryption. @MainActor public static func export(_ report: Report, to url: URL) async throws { let workbook = makeWorkbook(from: report) diff --git a/Sources/OpenFCPXMLKit/Reporting/Excel/FCPXMLReportWorkbookExporter.swift b/Sources/OpenFCPXMLKit/Reporting/Excel/FCPXMLReportWorkbookExporter.swift index be5bdcb..be89857 100644 --- a/Sources/OpenFCPXMLKit/Reporting/Excel/FCPXMLReportWorkbookExporter.swift +++ b/Sources/OpenFCPXMLKit/Reporting/Excel/FCPXMLReportWorkbookExporter.swift @@ -168,9 +168,22 @@ enum FCPXMLReportWorkbookExporter { appendMediaSummary(mediaSummary, excludedColumns: excludedColumns, to: workbook) } + if report.protectSheets { + applySheetProtection(to: workbook) + } + return workbook } + /// Applies XLKit worksheet protection to every sheet (edit lock; no password). + /// + /// This discourages accidental edits. It is **not** workbook open-password encryption. + private static func applySheetProtection(to workbook: Workbook) { + for sheet in workbook.getSheets() { + sheet.protection = SheetProtection() + } + } + /// Minimum width for the cover sheet's header column, wider than the auto-fit estimate so the /// branding cell reads as a banner rather than a tight-fitting label. private static let coverSheetColumnWidth = 48.0 @@ -203,10 +216,8 @@ enum FCPXMLReportWorkbookExporter { to workbook: Workbook ) { let filtered = filteredTabularSection( - headers: FinalCutPro.FCPXML.MarkerReportRow.columnHeaders( - timecodeFormat: timecodeFormat - ), - rows: markers.rows.map(\.columnValues), + headers: markers.columnHeaders(timecodeFormat: timecodeFormat), + rows: markers.rows.map { markers.columnValues(for: $0) }, excludedColumns: excludedColumns ) let headers = filtered.headers diff --git a/Sources/OpenFCPXMLKit/Reporting/FCPXMLReport.swift b/Sources/OpenFCPXMLKit/Reporting/FCPXMLReport.swift index e2a5d6b..12f3735 100644 --- a/Sources/OpenFCPXMLKit/Reporting/FCPXMLReport.swift +++ b/Sources/OpenFCPXMLKit/Reporting/FCPXMLReport.swift @@ -86,6 +86,10 @@ extension FinalCutPro.FCPXML { /// Timecode display format used for workbook timecode columns. public var timecodeFormat: ReportTimecodeFormat + /// When `true`, ``ReportExcelExport`` protects every worksheet (edit lock, not encryption). + /// PDF export ignores this. See ``ReportOptions/protectSheets``. + public var protectSheets: Bool + public init( projectName: String, eventName: String? = nil, @@ -101,7 +105,8 @@ extension FinalCutPro.FCPXML { workbookCoverSheet: ReportWorkbookCoverSheet? = nil, copyrightLabel: String? = nil, excludedColumns: Set = [], - timecodeFormat: ReportTimecodeFormat = .smpteFrames + timecodeFormat: ReportTimecodeFormat = .smpteFrames, + protectSheets: Bool = false ) { self.projectName = projectName self.eventName = eventName @@ -118,6 +123,7 @@ extension FinalCutPro.FCPXML { self.copyrightLabel = ReportOptions.normalizedCopyrightLabel(copyrightLabel) self.excludedColumns = excludedColumns self.timecodeFormat = timecodeFormat + self.protectSheets = protectSheets } /// Resolved export branding for Excel cover and PDF cover/footer. diff --git a/Sources/OpenFCPXMLKit/Reporting/FCPXMLReportBuilder.swift b/Sources/OpenFCPXMLKit/Reporting/FCPXMLReportBuilder.swift index 0be29d9..66bbbce 100644 --- a/Sources/OpenFCPXMLKit/Reporting/FCPXMLReportBuilder.swift +++ b/Sources/OpenFCPXMLKit/Reporting/FCPXMLReportBuilder.swift @@ -100,7 +100,8 @@ extension FinalCutPro.FCPXML { workbookCoverSheet: options.workbookCoverSheet, copyrightLabel: options.copyrightLabel, excludedColumns: ReportColumnExclusion.resolve(options.excludedColumns), - timecodeFormat: options.timecodeFormat + timecodeFormat: options.timecodeFormat, + protectSheets: options.protectSheets ) for phase in ReportBuildPhase.enabledPhases(for: options) { @@ -197,6 +198,7 @@ extension FinalCutPro.FCPXML { from: timelineElement, scope: extractionScope, includeChapterMarkers: options.includeChapterMarkersInMarkersReport, + includeMarkersOutsideClipBoundaries: options.includeMarkersOutsideClipBoundaries, roleDisplayPreference: options.roleDisplayPreference, timecodeFormat: options.timecodeFormat, projection: projection, diff --git a/Sources/OpenFCPXMLKit/Reporting/FCPXMLReportOptions.swift b/Sources/OpenFCPXMLKit/Reporting/FCPXMLReportOptions.swift index cf59a5e..39fe7d2 100644 --- a/Sources/OpenFCPXMLKit/Reporting/FCPXMLReportOptions.swift +++ b/Sources/OpenFCPXMLKit/Reporting/FCPXMLReportOptions.swift @@ -47,6 +47,15 @@ extension FinalCutPro.FCPXML { /// Chapter markers are omitted from the Markers sheet by default. public var includeChapterMarkersInMarkersReport: Bool + /// When building markers, include markers whose `start` is outside the host clip’s + /// media range (`[start, start + duration)`). Final Cut Pro hides those markers from + /// the timeline and Tags list. + /// + /// Default is `false` (FCP-visible markers only). When `true`, those markers are included + /// and the Markers sheet gains a **Hidden** column (✓ outside bounds / ✗ inside). + /// The Hidden column is not part of ``ReportColumn`` / `--exclude-column`. + public var includeMarkersOutsideClipBoundaries: Bool + /// Optional timeline name filter. When `nil`, the first project is preferred; if the /// document has no ``, the first event-level compound clip (`ref-clip` → /// `media`/`sequence`) is used. Matching uses the project name or compound clip name. @@ -110,6 +119,12 @@ extension FinalCutPro.FCPXML { /// Default `false` keeps a single Missing Media column (combined paths). public var mediaSummaryDistinguishProxyAndOriginal: Bool + /// When `true`, Excel export applies worksheet protection to every sheet in the workbook + /// (cover and content). This is an edit lock to discourage casual changes — **not** + /// file-open encryption. Anyone can turn protection off in Excel unless a password is + /// set later. PDF export ignores this flag. Default is `false`. + public var protectSheets: Bool + public init( includeMarkers: Bool = true, includeKeywords: Bool = false, @@ -121,6 +136,7 @@ extension FinalCutPro.FCPXML { includeMediaSummary: Bool = false, includeRoleInventory: Bool = false, includeChapterMarkersInMarkersReport: Bool = false, + includeMarkersOutsideClipBoundaries: Bool = false, projectName: String? = nil, mediaBaseURL: URL? = nil, roleDisplayPreference: RoleDisplayPreference = .builtIn, @@ -133,7 +149,8 @@ extension FinalCutPro.FCPXML { summaryOverlapAwareDurations: Bool = false, emitPerSourceInventoryRows: Bool = false, mediaResolutionPolicy: ReportMediaResolutionPolicy = .failSoft, - mediaSummaryDistinguishProxyAndOriginal: Bool = false + mediaSummaryDistinguishProxyAndOriginal: Bool = false, + protectSheets: Bool = false ) { self.includeMarkers = includeMarkers self.includeKeywords = includeKeywords @@ -145,6 +162,7 @@ extension FinalCutPro.FCPXML { self.includeMediaSummary = includeMediaSummary self.includeRoleInventory = includeRoleInventory self.includeChapterMarkersInMarkersReport = includeChapterMarkersInMarkersReport + self.includeMarkersOutsideClipBoundaries = includeMarkersOutsideClipBoundaries self.projectName = projectName self.mediaBaseURL = mediaBaseURL self.roleDisplayPreference = roleDisplayPreference @@ -158,6 +176,7 @@ extension FinalCutPro.FCPXML { self.emitPerSourceInventoryRows = emitPerSourceInventoryRows self.mediaResolutionPolicy = mediaResolutionPolicy self.mediaSummaryDistinguishProxyAndOriginal = mediaSummaryDistinguishProxyAndOriginal + self.protectSheets = protectSheets } /// Trims and drops empty copyright labels so exporters can treat “unset” uniformly. diff --git a/Sources/OpenFCPXMLKit/Reporting/PDF/FCPXMLReportPDFExporter.swift b/Sources/OpenFCPXMLKit/Reporting/PDF/FCPXMLReportPDFExporter.swift index 2cf61f6..9f7a0a3 100644 --- a/Sources/OpenFCPXMLKit/Reporting/PDF/FCPXMLReportPDFExporter.swift +++ b/Sources/OpenFCPXMLKit/Reporting/PDF/FCPXMLReportPDFExporter.swift @@ -307,10 +307,8 @@ enum FCPXMLReportPDFExporter { to canvas: FCPXMLReportPDFCanvas.Builder ) { let filtered = filteredTabularSection( - headers: FinalCutPro.FCPXML.MarkerReportRow.columnHeaders( - timecodeFormat: timecodeFormat - ), - rows: markers.rows.map(\.columnValues), + headers: markers.columnHeaders(timecodeFormat: timecodeFormat), + rows: markers.rows.map { markers.columnValues(for: $0) }, excludedColumns: excludedColumns ) diff --git a/Sources/OpenFCPXMLKit/Reporting/Rows/FCPXMLMarkerReportRow.swift b/Sources/OpenFCPXMLKit/Reporting/Rows/FCPXMLMarkerReportRow.swift index 8f57f11..fff02f3 100644 --- a/Sources/OpenFCPXMLKit/Reporting/Rows/FCPXMLMarkerReportRow.swift +++ b/Sources/OpenFCPXMLKit/Reporting/Rows/FCPXMLMarkerReportRow.swift @@ -13,8 +13,12 @@ import Foundation extension FinalCutPro.FCPXML { /// One row in the Markers report sheet. /// - /// Column order for the Markers sheet: + /// Default column order: /// Marker Name, Type, Notes, Position, Clip Name, Role ▸ Subrole, Reel, Scene, Source Position. + /// + /// When ``MarkersReportSection/showsHiddenColumn`` is `true` (opt-in via + /// ``ReportOptions/includeMarkersOutsideClipBoundaries``), a trailing **Hidden** column is + /// appended (✓ = outside host media range; ✗ = inside). public struct MarkerReportRow: Sendable, Equatable { public var markerName: String public var type: MarkerReportType @@ -25,6 +29,8 @@ extension FinalCutPro.FCPXML { public var reel: String public var scene: String public var sourcePosition: String + /// Outside host media range (hidden in FCP Tags/timeline). Used when the Hidden column is shown. + public var isHidden: Bool public init( markerName: String, @@ -35,7 +41,8 @@ extension FinalCutPro.FCPXML { roleSubrole: String, reel: String = "", scene: String = "", - sourcePosition: String + sourcePosition: String, + isHidden: Bool = false ) { self.markerName = markerName self.type = type @@ -46,15 +53,17 @@ extension FinalCutPro.FCPXML { self.reel = reel self.scene = scene self.sourcePosition = sourcePosition + self.isHidden = isHidden } - /// Column headers for the Markers sheet. + /// Column headers for the Markers sheet (no Hidden column). public static let columnHeaders: [String] = columnHeaders(timecodeFormat: .smpteFrames) public static func columnHeaders( - timecodeFormat: ReportTimecodeFormat = .smpteFrames + timecodeFormat: ReportTimecodeFormat = .smpteFrames, + includeHiddenColumn: Bool = false ) -> [String] { - [ + var headers = [ "Marker Name", "Type", "Notes", @@ -65,11 +74,20 @@ extension FinalCutPro.FCPXML { "Scene", timecodeFormat.formattedColumnHeader("Source Position") ] + if includeHiddenColumn { + headers.append("Hidden") + } + return headers } - /// Values in ``columnHeaders`` order. + /// Values matching ``columnHeaders(timecodeFormat:includeHiddenColumn:)`` with Hidden omitted. public var columnValues: [String] { - [ + columnValues(includeHiddenColumn: false) + } + + /// Values in header order, optionally including the Hidden checkmark column. + public func columnValues(includeHiddenColumn: Bool) -> [String] { + var values = [ markerName, type.displayName, notes, @@ -80,6 +98,10 @@ extension FinalCutPro.FCPXML { scene, sourcePosition ] + if includeHiddenColumn { + values.append(ReportFormatting.enabledCheckmark(forEnabled: isHidden)) + } + return values } } diff --git a/Sources/OpenFCPXMLKit/Reporting/Sections/FCPXMLMarkersReportSection.swift b/Sources/OpenFCPXMLKit/Reporting/Sections/FCPXMLMarkersReportSection.swift index 15c5b3d..8092bb4 100644 --- a/Sources/OpenFCPXMLKit/Reporting/Sections/FCPXMLMarkersReportSection.swift +++ b/Sources/OpenFCPXMLKit/Reporting/Sections/FCPXMLMarkersReportSection.swift @@ -17,8 +17,28 @@ extension FinalCutPro.FCPXML { public var rows: [MarkerReportRow] - public init(rows: [MarkerReportRow]) { + /// When `true`, Excel/PDF append a **Hidden** column (✓/✗) for out-of-bounds markers. + /// + /// Set only when ``ReportOptions/includeMarkersOutsideClipBoundaries`` is `true`. + /// Not controllable via ``ReportColumn`` / `--exclude-column`. + public var showsHiddenColumn: Bool + + public init(rows: [MarkerReportRow], showsHiddenColumn: Bool = false) { self.rows = rows + self.showsHiddenColumn = showsHiddenColumn + } + + public func columnHeaders( + timecodeFormat: ReportTimecodeFormat = .smpteFrames + ) -> [String] { + MarkerReportRow.columnHeaders( + timecodeFormat: timecodeFormat, + includeHiddenColumn: showsHiddenColumn + ) + } + + public func columnValues(for row: MarkerReportRow) -> [String] { + row.columnValues(includeHiddenColumn: showsHiddenColumn) } } } diff --git a/Sources/OpenFCPXMLKitCLI/Options/ReportCLIOptions.swift b/Sources/OpenFCPXMLKitCLI/Options/ReportCLIOptions.swift index ab6ae6f..4fb5e32 100644 --- a/Sources/OpenFCPXMLKitCLI/Options/ReportCLIOptions.swift +++ b/Sources/OpenFCPXMLKitCLI/Options/ReportCLIOptions.swift @@ -108,6 +108,27 @@ struct ReportCLIOptions: ParsableArguments { ) var excludeDisabledClips: Bool = false + @Flag( + name: .customLong("include-markers-outside-clip-boundaries"), + help: """ + Include markers whose start is outside the host clip’s media range (hidden in FCP \ + timeline/Tags) and add a Hidden column (✓/✗) on the Markers sheet (with --report / \ + --report-markers). Default omits those markers and does not show Hidden. + """ + ) + var includeMarkersOutsideClipBoundaries: Bool = false + + @Flag( + name: .customLong("protect-sheets"), + help: """ + Protect every sheet in the Excel workbook against casual edits (with --report). \ + Applies to the cover sheet and all content sheets. This is an edit lock, not \ + file-open encryption — Excel can still open the file, and anyone can turn protection \ + off. PDF export is unaffected (use Preview’s Encrypt to password-protect a PDF). + """ + ) + var protectSheets: Bool = false + @Option( name: .customLong("exclude-column"), help: """ @@ -169,6 +190,8 @@ struct ReportCLIOptions: ParsableArguments { || labelCopyright != nil || !excludeRole.isEmpty || excludeDisabledClips + || includeMarkersOutsideClipBoundaries + || protectSheets || !excludeColumn.isEmpty || timecodeFormat != nil || mediaResolution != nil @@ -201,6 +224,8 @@ struct ReportCLIOptions: ParsableArguments { options.copyrightLabel = labelCopyright options.excludedRoles = excludeRole options.excludeDisabledClips = excludeDisabledClips + options.includeMarkersOutsideClipBoundaries = includeMarkersOutsideClipBoundaries + options.protectSheets = protectSheets options.excludedColumns = excludeColumn options.timecodeFormat = try resolvedTimecodeFormat() options.mediaResolutionPolicy = try resolvedMediaResolutionPolicy() diff --git a/Sources/OpenFCPXMLKitCLI/README.md b/Sources/OpenFCPXMLKitCLI/README.md index e92780b..5ffb362 100644 --- a/Sources/OpenFCPXMLKitCLI/README.md +++ b/Sources/OpenFCPXMLKitCLI/README.md @@ -102,7 +102,7 @@ OpenFCPXMLKit-CLI --log-level debug --convert-version 1.10 /path/to/project.fcpx OpenFCPXMLKit-CLI --quiet --media-copy /path/to/project.fcpxml /path/to/media ``` -**Validation:** Use only one of `--check-version`, `--convert-version`, `--validate`, `--media-copy`, `--report`, or `--create-project`. When using `--convert-version`, `--media-copy`, or `--report`, or when running the default process, you must provide `` (created automatically if missing). When using `--create-project`, you must provide `--width`, `--height`, `--rate`, and the output directory as the single positional argument (also created if missing). `--report-full`, REPORT section flags, `--exclude-role`, `--exclude-disabled-clips`, `--exclude-column`, `--timecode-format`, `--media-resolution`, `--media-summary-distinguish-proxy`, `--label-copyright`, and `--create-pdf` require `--report`. `--extension-type` requires `--convert-version`. If `--log` is set and the file exists, it must be writable. Invalid `--log-level`, `--project-version` (for create-project), `--timecode-format`, or `--media-resolution` values produce an error. +**Validation:** Use only one of `--check-version`, `--convert-version`, `--validate`, `--media-copy`, `--report`, or `--create-project`. When using `--convert-version`, `--media-copy`, or `--report`, or when running the default process, you must provide `` (created automatically if missing). When using `--create-project`, you must provide `--width`, `--height`, `--rate`, and the output directory as the single positional argument (also created if missing). `--report-full`, REPORT section flags, `--exclude-role`, `--exclude-disabled-clips`, `--include-markers-outside-clip-boundaries`, `--protect-sheets`, `--exclude-column`, `--timecode-format`, `--media-resolution`, `--media-summary-distinguish-proxy`, `--label-copyright`, and `--create-pdf` require `--report`. `--extension-type` requires `--convert-version`. If `--log` is set and the file exists, it must be writable. Invalid `--log-level`, `--project-version` (for create-project), `--timecode-format`, or `--media-resolution` values produce an error. --- @@ -135,7 +135,9 @@ OpenFCPXMLKit-CLI --quiet --media-copy /path/to/project.fcpxml /path/to/media | `--label-copyright ` | Optional copyright / attribution line (requires `--report`). Excel cover **A2** below Created-by (same banner style); PDF cover below Created-by (same subtitle font/size); PDF running footer centre (same footer font/size). | | `--exclude-role ` | Exclude a role or subrole from role inventory (repeatable). Excluding a main role also excludes its subroles. Case-insensitive. | | `--exclude-disabled-clips` | Omit disabled clips (`enabled="0"`) from all timeline-based report sections (requires `--report`). | -| `--exclude-column ` | Exclude a report column from every applicable Excel/PDF sheet (repeatable; requires `--report`). Case-insensitive; includes **`Row`** / `Row Numbers` (omits the 1-based Row index on all tabular sheets and suppresses PDF multi-page Row injection). See [19 — Reporting](../../Documentation/Manual/19-Reporting.md#column-exclusion) for accepted names. | +| `--include-markers-outside-clip-boundaries` | Include markers whose start is outside the host clip’s media range (hidden in FCP timeline/Tags) and add a **Hidden** column (✓/✗) on the Markers sheet (requires `--report`). Default omits those markers and does not show Hidden. Not available via `--exclude-column`. | +| `--protect-sheets` | Protect every sheet in the Excel workbook against casual edits (requires `--report`). Cover + all content sheets. **Edit lock only** — not file-open encryption; Excel still opens freely and protection can be turned off. PDF export is unaffected (use Preview → Encrypt for a PDF open password). | +| `--exclude-column ` | Exclude a report column from every applicable Excel/PDF sheet (repeatable; requires `--report`). Case-insensitive; includes **`Row`** / `Row Numbers` (omits the 1-based Row index on all tabular sheets and suppresses PDF multi-page Row injection). See [19 — Reporting](../../Documentation/Manual/19-Reporting.md#column-exclusion) for accepted names. **Hidden** is not an exclude-column target. | | `--timecode-format ` | Timeline time display format for Excel and PDF report cells (requires `--report`). Values: `HH:MM:SS:FF` (default; SMPTE with frames; `;` before frames for drop-frame), `Frames`, `Feet+Frames`, `HH:MM:SS`. Non-default formats append a suffix to timecode column headers (e.g. `Timeline In (frames)`). See [19 — Reporting](../../Documentation/Manual/19-Reporting.md#timecode-display-format). | When `--report` is used without `--report-full` or section flags, the CLI exports role inventory only. Use `--report-full` for every optional sheet, or set individual `--report-*` section flags for a partial export (role inventory is always included). `--report-full` takes precedence when combined with section flags. diff --git a/Tests/ExcelReportTest/ExcelReportExportTests.swift b/Tests/ExcelReportTest/ExcelReportExportTests.swift index d6da9dc..eada08e 100644 --- a/Tests/ExcelReportTest/ExcelReportExportTests.swift +++ b/Tests/ExcelReportTest/ExcelReportExportTests.swift @@ -175,6 +175,107 @@ final class ExcelReportExportTests: XCTestCase, @unchecked Sendable { #endif } + /// Writes `Output/OFK-OutsideClipBoundaries.xlsx` / `.pdf` with + /// `--include-markers-outside-clip-boundaries` parity (Hidden column on Markers). + @MainActor + func testExportMarkersIncludingOutsideClipBoundaries() async throws { + let fixtureURL = try ExcelReportFixture.requireFixtureURL() + let outputDir = ExcelReportFixture.outputDirectoryURL() + try FileManager.default.createDirectory(at: outputDir, withIntermediateDirectories: true) + + var defaultOptions = FinalCutPro.FCPXML.ReportOptions.markersOnly + defaultOptions.includeChapterMarkersInMarkersReport = true + let defaultMarkersReport = try await loadReport( + options: defaultOptions, + fixtureURL: fixtureURL + ) + let defaultMarkers = try XCTUnwrap(defaultMarkersReport.markers) + XCTAssertFalse( + defaultMarkers.showsHiddenColumn, + "Default Markers sheet must omit the Hidden column" + ) + XCTAssertFalse(defaultMarkers.columnHeaders().contains("Hidden")) + + var includeOptions = FinalCutPro.FCPXML.ReportOptions.markersOnly + includeOptions.includeChapterMarkersInMarkersReport = true + includeOptions.includeMarkersOutsideClipBoundaries = true + // Keep role inventory off for a focused Markers workbook, but still write a cover sheet. + let includeReport = try await loadReport(options: includeOptions, fixtureURL: fixtureURL) + let includeMarkers = try XCTUnwrap(includeReport.markers) + + XCTAssertTrue(includeMarkers.showsHiddenColumn) + XCTAssertEqual(includeMarkers.columnHeaders().last, "Hidden") + XCTAssertGreaterThanOrEqual( + includeMarkers.rows.count, + defaultMarkers.rows.count, + "Opt-in must not drop in-bounds markers and may add out-of-bounds rows" + ) + + let hiddenValues = Set( + includeMarkers.rows.map { includeMarkers.columnValues(for: $0).last ?? "" } + ) + XCTAssertTrue( + hiddenValues.isSubset(of: ["✓", "✗"]), + "Hidden column cells must be ✓ or ✗" + ) + if includeMarkers.rows.count > defaultMarkers.rows.count { + XCTAssertTrue( + includeMarkers.rows.contains(where: \.isHidden), + "Extra rows from opt-in should be marked Hidden" + ) + } + + let xlsxURL = try await writeWorkbook( + includeReport, + named: ExcelReportFixture.outsideClipBoundariesOutputXLSXFileName, + to: outputDir + ) + try assertWorkbookExists(at: xlsxURL) + + let pdfURL = outputDir.appendingPathComponent( + ExcelReportFixture.outsideClipBoundariesOutputPDFFileName + ) + if FileManager.default.fileExists(atPath: pdfURL.path) { + try FileManager.default.removeItem(at: pdfURL) + } + try FinalCutPro.FCPXML.ReportPDFExport.export(includeReport, to: pdfURL) + + let pdfData = try Data(contentsOf: pdfURL) + XCTAssertEqual(String(data: pdfData.prefix(4), encoding: .ascii), "%PDF") + XCTAssertGreaterThan(pdfData.count, 5_000) + } + + /// Writes `Output/OFK-ProtectedSheets.xlsx` with worksheet protection on every sheet + /// (CLI `--protect-sheets` parity). Excel only — PDF is unaffected. + @MainActor + func testExportProtectedSheetsWorkbook() async throws { + let fixtureURL = try ExcelReportFixture.requireFixtureURL() + let outputDir = ExcelReportFixture.outputDirectoryURL() + try FileManager.default.createDirectory(at: outputDir, withIntermediateDirectories: true) + + var options = FinalCutPro.FCPXML.ReportOptions.roleInventoryOnly + options.protectSheets = true + let report = try await loadReport(options: options, fixtureURL: fixtureURL) + XCTAssertTrue(report.protectSheets) + + let workbook = FinalCutPro.FCPXML.ReportExcelExport.makeWorkbook(from: report) + let sheets = workbook.getSheets() + XCTAssertFalse(sheets.isEmpty) + for sheet in sheets { + XCTAssertNotNil( + sheet.protection, + "Sheet '\(sheet.name)' should be protected" + ) + } + + let xlsxURL = try await writeWorkbook( + report, + named: ExcelReportFixture.protectedSheetsOutputXLSXFileName, + to: outputDir + ) + try assertWorkbookExists(at: xlsxURL) + } + @MainActor private func writeWorkbook( _ report: FinalCutPro.FCPXML.Report, diff --git a/Tests/ExcelReportTest/ExcelReportFixture.swift b/Tests/ExcelReportTest/ExcelReportFixture.swift index 291f0b3..b7a9192 100644 --- a/Tests/ExcelReportTest/ExcelReportFixture.swift +++ b/Tests/ExcelReportTest/ExcelReportFixture.swift @@ -21,6 +21,9 @@ enum ExcelReportFixture { static let fullOutputFileName = "OFK-Full.xlsx" static let copyrightOutputXLSXFileName = "OFK-Copyright.xlsx" static let copyrightOutputPDFFileName = "OFK-Copyright.pdf" + static let outsideClipBoundariesOutputXLSXFileName = "OFK-OutsideClipBoundaries.xlsx" + static let outsideClipBoundariesOutputPDFFileName = "OFK-OutsideClipBoundaries.pdf" + static let protectedSheetsOutputXLSXFileName = "OFK-ProtectedSheets.xlsx" /// URL to a `.fcpxml` file or `.fcpxmld` bundle directory. static func fixtureURL() -> URL? { diff --git a/Tests/ExcelReportTest/Output/README.md b/Tests/ExcelReportTest/Output/README.md index 9261033..2155957 100644 --- a/Tests/ExcelReportTest/Output/README.md +++ b/Tests/ExcelReportTest/Output/README.md @@ -1,6 +1,6 @@ # Excel and PDF report test output -This folder holds **generated** `.xlsx` workbooks and `.pdf` reports from the `ExcelReportTest` target. It is gitignored; files here are produced on your machine when you run the export tests. +This folder holds **generated** `.xlsx` workbooks and `.pdf` reports from the `ExcelReportTest` target (**6** optional integration tests). It is gitignored; files here are produced on your machine when you run the export tests. --- @@ -13,6 +13,8 @@ This folder holds **generated** `.xlsx` workbooks and `.pdf` reports from the `E | **`OFK-Default.pdf`** | `ReportOptions.roleInventoryOnly` + `ReportPDFExport` | Role-inventory PDF with cover (black “About This PDF Export” + `info.circle`), TOC colour chips / content-tint washes, and tinted section pages (same as CLI `--report --create-pdf` without `--report-full`) | | **`OFK-ExcludedColumns.pdf`** | role inventory + many `excludedColumns` | Same inventory with leftover page width redistributed across remaining columns | | **`OFK-Copyright.xlsx`** / **`OFK-Copyright.pdf`** | role inventory + `copyrightLabel` | Same as default, with Excel cover **A2** and PDF cover/footer centre copyright line (`--label-copyright` parity) | +| **`OFK-OutsideClipBoundaries.xlsx`** / **`OFK-OutsideClipBoundaries.pdf`** | markers + `includeMarkersOutsideClipBoundaries` | Markers sheet with **Hidden** column (✓ outside host media range / ✗ inside); CLI `--include-markers-outside-clip-boundaries` | +| **`OFK-ProtectedSheets.xlsx`** | role inventory + `protectSheets` | Every worksheet protected (edit lock, no password); CLI `--protect-sheets`; Excel only | Each test run **overwrites** these files if they already exist. @@ -25,7 +27,9 @@ Reports are built from whatever fixture `ExcelReportFixture` resolves: - A **`.fcpxmld`** bundle (directory with `Info.fcpxml`), or - A **`.fcpxml`** single file -Fixture lookup: `OFK_REPORTING_FCPXML_BUNDLE` → `Sample.fcpxmld` / `Sample.fcpxml` in the parent folder → any other valid `.fcpxml` / `.fcpxmld` there. +Fixture lookup: `OFK_REPORTING_FCPXML_BUNDLE` → `Sample.fcpxmld` / `Sample.fcpxml` in the parent folder → **`Output/Sample.fcpxmld`** / **`Output/Sample.fcpxml`** → any other valid `.fcpxml` / `.fcpxmld` there. + +`Output/Sample.fcpxmld` (and `Output/Sample.fcpxml`) are **gitignored** — keep private local fixtures there; do not commit them. See [../README.md](../README.md) for full setup. @@ -39,7 +43,7 @@ From the repository root: swift test --filter ExcelReportExportTests ``` -Then open `OFK-Default.xlsx`, `OFK-Full.xlsx`, `OFK-Default.pdf`, `OFK-ExcludedColumns.pdf`, or `OFK-Copyright.xlsx` / `OFK-Copyright.pdf` in Excel, Preview, or your diff tool and compare against a reference export. +Then open `OFK-Default.xlsx`, `OFK-Full.xlsx`, `OFK-Default.pdf`, `OFK-ExcludedColumns.pdf`, `OFK-Copyright.xlsx` / `OFK-Copyright.pdf`, `OFK-OutsideClipBoundaries.xlsx` / `OFK-OutsideClipBoundaries.pdf`, or `OFK-ProtectedSheets.xlsx` in Excel, Preview, or your diff tool and compare against a reference export. For a full PDF on a real fixture (named after the project), use the CLI: @@ -53,7 +57,7 @@ OpenFCPXMLKit-CLI --report --report-full --create-pdf \ ## Notes -- Output file names are fixed (`OFK-Default.xlsx`, `OFK-Full.xlsx`, `OFK-Default.pdf`, `OFK-ExcludedColumns.pdf`, `OFK-Copyright.xlsx`, `OFK-Copyright.pdf`) so paths stay stable for scripts and future parity tests. +- Output file names are fixed (`OFK-Default.xlsx`, `OFK-Full.xlsx`, `OFK-Default.pdf`, `OFK-ExcludedColumns.pdf`, `OFK-Copyright.xlsx`, `OFK-Copyright.pdf`, `OFK-OutsideClipBoundaries.xlsx`, `OFK-OutsideClipBoundaries.pdf`, `OFK-ProtectedSheets.xlsx`) so paths stay stable for scripts and future parity tests. - The CLI names files after the **project or compound-clip name** inside the FCPXML; test output uses these constant names instead. - Fixture bundles used for local investigation (e.g. `Sample.fcpxmld`) may also live here; discovery prefers root `Sample.*`, then falls back to `Output/`. - Do not commit large generated workbooks or PDFs unless you intentionally add golden files for regression testing. diff --git a/Tests/ExcelReportTest/README.md b/Tests/ExcelReportTest/README.md index dfded96..0eb704a 100644 --- a/Tests/ExcelReportTest/README.md +++ b/Tests/ExcelReportTest/README.md @@ -4,9 +4,9 @@ Optional integration tests that build real `.xlsx` workbooks and `.pdf` reports **Target:** `ExcelReportTest` **Depends on:** `OpenFCPXMLKit`, `XLKit` -**Tests:** 4 (`ExcelReportExportTests`) +**Tests:** 6 (`ExcelReportExportTests`) -Unit-level reporting behaviour (universal **Row** on all tabular sheets, Summary title in **B1**, column layout, column exclusion including `ReportColumn.row`, disabled-clip filtering, timecode formats / DF·NDF, format-aware headers, build-phase order including `.projecting`, workbook cell formatting, optional `copyrightLabel` cover/footer branding, `ReportMediaResolutionPolicy` / Media Summary proxy-original distinction, PDF cover notes / black header + `info.circle`, TOC colour chips, column-width expansion after exclusions, pagination, shared row colours, **standalone compound-clip timeline resolution**, **Projection-first** Markers/Keywords/Titles/Transitions/Effects) lives in **`OpenFCPXMLKitTests`** — see [Tests/README.md](../README.md#reporting--excelpdf-export) (`FCPXMLCompoundClipReportTests`, `FCPXMLTimelineProjectionTests`, `FCPXMLReportObligationCorpusTests`, `FCPXMLReportPDFExportTests`, `FCPXMLReportPDFSheetPlanTests`, `FCPXMLReportPDFTableLayoutTests`, `FCPXMLReportColumnExclusionTests`, and related files). +Unit-level reporting behaviour (universal **Row** on all tabular sheets, Summary title in **B1**, column layout, column exclusion including `ReportColumn.row`, disabled-clip filtering, timecode formats / DF·NDF, format-aware headers, build-phase order including `.projecting`, workbook cell formatting, optional `copyrightLabel` cover/footer branding, `includeMarkersOutsideClipBoundaries` / Markers **Hidden** column, `protectSheets` worksheet protection, `ReportMediaResolutionPolicy` / Media Summary proxy-original distinction, PDF cover notes / black header + `info.circle`, TOC colour chips, column-width expansion after exclusions, pagination, shared row colours, **standalone compound-clip timeline resolution**, **Projection-first** Markers/Keywords/Titles/Transitions/Effects) lives in **`OpenFCPXMLKitTests`** — see [Tests/README.md](../README.md#reporting--excelpdf-export) (`FCPXMLCompoundClipReportTests`, `FCPXMLTimelineProjectionTests`, `FCPXMLReportObligationCorpusTests`, `FCPXMLMarkersReportTests`, `FCPXMLReportPDFExportTests`, `FCPXMLReportPDFSheetPlanTests`, `FCPXMLReportPDFTableLayoutTests`, `FCPXMLReportColumnExclusionTests`, `FCPXMLReportExcelExportTests`, and related files). --- @@ -64,8 +64,10 @@ Running the export tests writes workbooks and a sample PDF to **`Output/`** (als | `Output/OFK-Default.pdf` | `ReportOptions.roleInventoryOnly` | `OpenFCPXMLKit-CLI --report --create-pdf ` | Role-inventory PDF with cover page (black “About This PDF Export” + `info.circle`), TOC (accent colour chips + content-tint washes per sheet `colorIndex`), and per-sheet tinted content pages | | `Output/OFK-ExcludedColumns.pdf` | role inventory + many `excludedColumns` | `--report --create-pdf --exclude-column …` | Same sheets with remaining columns expanded to fill A4 landscape width | | `Output/OFK-Copyright.xlsx` / `Output/OFK-Copyright.pdf` | role inventory + `copyrightLabel` | `--report --create-pdf --label-copyright "…"` | Cover/footer copyright line for manual review of `--label-copyright` | +| `Output/OFK-OutsideClipBoundaries.xlsx` / `Output/OFK-OutsideClipBoundaries.pdf` | markers + `includeMarkersOutsideClipBoundaries` | `--report --report-markers --include-markers-outside-clip-boundaries --create-pdf` | Markers sheet with **Hidden** column (✓/✗) for out-of-bounds markers | +| `Output/OFK-ProtectedSheets.xlsx` | role inventory + `protectSheets` | `--report --protect-sheets` | Every worksheet protected (edit lock; not encryption; Excel only) | -Cell colours, header styling, section-sheet colour rules, TOC colour chips, and PDF column-width expansion are covered by **`FCPXMLReportExcelExportTests`**, **`FCPXMLReportPDFExportTests`**, **`FCPXMLReportPDFSheetPlanTests`**, and **`FCPXMLReportPDFTableLayoutTests`** in `OpenFCPXMLKitTests`. This integration target checks that a real fixture produces complete workbooks and readable PDFs; open `OFK-Full.xlsx`, `OFK-Default.pdf`, `OFK-ExcludedColumns.pdf`, or `OFK-Copyright.xlsx` / `OFK-Copyright.pdf` locally to compare layout and copyright branding against a reference export if you maintain one. +Cell colours, header styling, section-sheet colour rules, TOC colour chips, and PDF column-width expansion are covered by **`FCPXMLReportExcelExportTests`**, **`FCPXMLReportPDFExportTests`**, **`FCPXMLReportPDFSheetPlanTests`**, and **`FCPXMLReportPDFTableLayoutTests`** in `OpenFCPXMLKitTests`. This integration target checks that a real fixture produces complete workbooks and readable PDFs; open `OFK-Full.xlsx`, `OFK-Default.pdf`, `OFK-ExcludedColumns.pdf`, `OFK-Copyright.xlsx` / `.pdf`, `OFK-OutsideClipBoundaries.xlsx` / `.pdf`, or `OFK-ProtectedSheets.xlsx` locally to compare layout, copyright branding, Markers **Hidden**, and sheet protection against a reference export if you maintain one. See [Output/README.md](Output/README.md) for details on that folder. @@ -77,6 +79,10 @@ See [Output/README.md](Output/README.md) for details on that folder. `testExportRoleInventoryWithCopyrightLabel` writes `OFK-Copyright.xlsx` / `OFK-Copyright.pdf` and asserts Excel cover **A2** plus PDF cover/footer text for `--label-copyright` parity. +`testExportMarkersIncludingOutsideClipBoundaries` writes `OFK-OutsideClipBoundaries.xlsx` / `OFK-OutsideClipBoundaries.pdf` with `includeMarkersOutsideClipBoundaries` (CLI `--include-markers-outside-clip-boundaries`), asserts the Markers **Hidden** column, and compares row counts against the default Markers filter. + +`testExportProtectedSheetsWorkbook` writes `OFK-ProtectedSheets.xlsx` with `protectSheets` (CLI `--protect-sheets`) and asserts every worksheet has XLKit sheet protection. + --- ## CLI parity @@ -136,7 +142,7 @@ First run on a large fixture can take ~1–2 minutes (report build + XLKit save; | File | Purpose | |------|---------| | `ExcelReportFixture.swift` | Resolves fixture URL (including under `Output/`), `mediaBaseURL`, and `Output/` path; defines output file names | -| `ExcelReportExportTests.swift` | Builds and writes `OFK-Default.xlsx`, `OFK-Full.xlsx`, `OFK-Default.pdf`, `OFK-ExcludedColumns.pdf`, `OFK-Copyright.xlsx`, and `OFK-Copyright.pdf` | +| `ExcelReportExportTests.swift` | Builds and writes `OFK-Default.xlsx`, `OFK-Full.xlsx`, `OFK-Default.pdf`, `OFK-ExcludedColumns.pdf`, `OFK-Copyright.xlsx`, `OFK-Copyright.pdf`, `OFK-OutsideClipBoundaries.xlsx` / `.pdf`, and `OFK-ProtectedSheets.xlsx` | | `Output/` | Generated workbooks and PDFs (created by tests; gitignored); may also hold local investigation fixtures such as `Sample.fcpxmld` | --- diff --git a/Tests/FCPXML Samples/FCPXML/HiddenMarkers.fcpxml b/Tests/FCPXML Samples/FCPXML/HiddenMarkers.fcpxml new file mode 100644 index 0000000..9c48e30 --- /dev/null +++ b/Tests/FCPXML Samples/FCPXML/HiddenMarkers.fcpxml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/OpenFCPXMLKitTests/FCPXMLMarkersKeywordsProjectionTests.swift b/Tests/OpenFCPXMLKitTests/FCPXMLMarkersKeywordsProjectionTests.swift index a8ae04b..39e0d08 100644 --- a/Tests/OpenFCPXMLKitTests/FCPXMLMarkersKeywordsProjectionTests.swift +++ b/Tests/OpenFCPXMLKitTests/FCPXMLMarkersKeywordsProjectionTests.swift @@ -19,6 +19,9 @@ final class FCPXMLMarkersKeywordsProjectionTests: XCTestCase { let fcpxml = try loadFCPXMLSample(named: FCPXMLSampleName.basicMarkers.rawValue) var options = FinalCutPro.FCPXML.ReportOptions.markersOnly options.includeChapterMarkersInMarkersReport = true + // BasicMarkers title markers lie outside the title media range (FCP-hidden); + // opt in so this sample still exercises Projection marker annotations. + options.includeMarkersOutsideClipBoundaries = true let report = try await fcpxml.buildReport(options: options) let rows = try XCTUnwrap(report.markers?.rows) diff --git a/Tests/OpenFCPXMLKitTests/FCPXMLMarkersReportTests.swift b/Tests/OpenFCPXMLKitTests/FCPXMLMarkersReportTests.swift index 6b46c28..187e97b 100644 --- a/Tests/OpenFCPXMLKitTests/FCPXMLMarkersReportTests.swift +++ b/Tests/OpenFCPXMLKitTests/FCPXMLMarkersReportTests.swift @@ -9,6 +9,7 @@ // import XCTest +import SwiftTimecode @testable import OpenFCPXMLKit @available(macOS 26.0, *) @@ -117,6 +118,86 @@ final class FCPXMLMarkersReportTests: XCTestCase, @unchecked Sendable { XCTAssertFalse(rows.contains { $0.markerName == "Chapter 4" }) } + func testHiddenMarkersSampleExcludesOutOfBoundsByDefault() async throws { + let fcpxml = try loadFCPXMLSample(named: FCPXMLSampleName.hiddenMarkers.rawValue) + var options = FinalCutPro.FCPXML.ReportOptions.markersOnly + options.workbookCoverSheet = nil + + let report = try await fcpxml.buildReport(options: options) + let section = try XCTUnwrap(report.markers) + let names = Set(section.rows.map(\.markerName)) + + XCTAssertEqual(names, ["Marker 1", "Marker 2"]) + XCTAssertFalse(section.showsHiddenColumn) + XCTAssertFalse( + section.columnHeaders().contains("Hidden"), + "Hidden column must be omitted when outside-bounds markers are excluded" + ) + XCTAssertEqual(section.rows.first?.columnValues.count, 9) + } + + func testHiddenMarkersSampleIncludesOutOfBoundsWithHiddenColumnWhenOptedIn() async throws { + let fcpxml = try loadFCPXMLSample(named: FCPXMLSampleName.hiddenMarkers.rawValue) + var options = FinalCutPro.FCPXML.ReportOptions.markersOnly + options.includeMarkersOutsideClipBoundaries = true + options.workbookCoverSheet = nil + + let report = try await fcpxml.buildReport(options: options) + let section = try XCTUnwrap(report.markers) + let byName = Dictionary(uniqueKeysWithValues: section.rows.map { ($0.markerName, $0) }) + + XCTAssertTrue(section.showsHiddenColumn) + XCTAssertEqual(section.columnHeaders().last, "Hidden") + XCTAssertEqual(Set(byName.keys), ["Marker 1", "Marker 2", "Marker 3", "Marker 4"]) + + XCTAssertEqual(byName["Marker 1"]?.isHidden, false) + XCTAssertEqual(byName["Marker 2"]?.isHidden, false) + XCTAssertEqual(byName["Marker 3"]?.isHidden, true) + XCTAssertEqual(byName["Marker 4"]?.isHidden, true) + + let values = section.columnValues(for: try XCTUnwrap(byName["Marker 3"])) + XCTAssertEqual(values.count, 10) + XCTAssertEqual(values.last, "✓") + XCTAssertEqual( + section.columnValues(for: try XCTUnwrap(byName["Marker 1"])).last, + "✗" + ) + } + + func testMarkerClipBoundaryDetectsOutsideHostMediaRange() { + let hostStart = Fraction(double: 3600) + let hostDuration = Fraction(double: 35.12) + + XCTAssertFalse( + FinalCutPro.FCPXML.MarkerClipBoundary.isOutsideHostMediaRange( + markerStart: Fraction(double: 3610.68), + hostStart: hostStart, + hostDuration: hostDuration + ) + ) + XCTAssertTrue( + FinalCutPro.FCPXML.MarkerClipBoundary.isOutsideHostMediaRange( + markerStart: Fraction(double: 3641.76), + hostStart: hostStart, + hostDuration: hostDuration + ) + ) + XCTAssertTrue( + FinalCutPro.FCPXML.MarkerClipBoundary.isOutsideHostMediaRange( + markerStart: Fraction(double: 3599), + hostStart: hostStart, + hostDuration: hostDuration + ) + ) + XCTAssertFalse( + FinalCutPro.FCPXML.MarkerClipBoundary.isOutsideHostMediaRange( + markerStart: Fraction(double: 3641.76), + hostStart: hostStart, + hostDuration: nil + ) + ) + } + func testMarkerOnAudioOnlyClipYieldsSingleDialogueRow() async throws { let fcpxml = try parseInlineFCPXML(audioOnlyClipMarkerFixture) var options = FinalCutPro.FCPXML.ReportOptions.markersOnly diff --git a/Tests/OpenFCPXMLKitTests/FCPXMLReportExcelExportTests.swift b/Tests/OpenFCPXMLKitTests/FCPXMLReportExcelExportTests.swift index 9095718..7e147d9 100644 --- a/Tests/OpenFCPXMLKitTests/FCPXMLReportExcelExportTests.swift +++ b/Tests/OpenFCPXMLKitTests/FCPXMLReportExcelExportTests.swift @@ -575,6 +575,49 @@ final class FCPXMLReportExcelExportTests: XCTestCase, @unchecked Sendable { XCTAssertNil(sheet?.getCellWithFormat("D5")?.format?.fontColor) } + @MainActor + func testMakeWorkbookLeavesSheetsUnprotectedByDefault() { + let report = FinalCutPro.FCPXML.Report( + projectName: "Demo", + markers: FinalCutPro.FCPXML.MarkersReportSection(rows: []), + workbookCoverSheet: .openFCPXMLKitDefault + ) + + let sheets = FinalCutPro.FCPXML.ReportExcelExport.makeWorkbook(from: report).getSheets() + XCTAssertFalse(sheets.isEmpty) + for sheet in sheets { + XCTAssertNil( + sheet.protection, + "Sheet '\(sheet.name)' should be unprotected by default" + ) + } + } + + @MainActor + func testMakeWorkbookProtectsAllSheetsWhenRequested() { + let report = FinalCutPro.FCPXML.Report( + projectName: "Demo", + markers: FinalCutPro.FCPXML.MarkersReportSection(rows: []), + workbookCoverSheet: .openFCPXMLKitDefault, + protectSheets: true + ) + + let sheets = FinalCutPro.FCPXML.ReportExcelExport.makeWorkbook(from: report).getSheets() + XCTAssertGreaterThanOrEqual(sheets.count, 2, "Cover + Markers expected") + for sheet in sheets { + XCTAssertNotNil( + sheet.protection, + "Sheet '\(sheet.name)' should be protected when protectSheets is true" + ) + XCTAssertEqual(sheet.protection?.sheet, true) + XCTAssertNil( + sheet.protection?.password, + "Default sheet protection must not set a password" + ) + XCTAssertNil(sheet.protection?.hashValue) + } + } + @MainActor private func sheetNames(from report: FinalCutPro.FCPXML.Report) -> [String] { FinalCutPro.FCPXML.ReportExcelExport.makeWorkbook(from: report).getSheets().map(\.name) diff --git a/Tests/OpenFCPXMLKitTests/FCPXMLReportObligationCorpusTests.swift b/Tests/OpenFCPXMLKitTests/FCPXMLReportObligationCorpusTests.swift index 2de6a4a..918b47c 100644 --- a/Tests/OpenFCPXMLKitTests/FCPXMLReportObligationCorpusTests.swift +++ b/Tests/OpenFCPXMLKitTests/FCPXMLReportObligationCorpusTests.swift @@ -107,12 +107,27 @@ final class FCPXMLReportObligationCorpusTests: XCTestCase { // MARK: - Near-zero-miss corpus func testObligationCorpusBasicMarkersHasMarkerRows() async throws { - let report = try await buildFullReport(sample: .basicMarkers) + // BasicMarkers title markers sit outside the title media range (FCP-hidden). + let defaultReport = try await buildFullReport(sample: .basicMarkers) + XCTAssertEqual( + defaultReport.markers?.rows.isEmpty, + true, + "Default Markers report must omit out-of-bounds BasicMarkers" + ) + + let fcpxml = try loadFCPXMLSample(named: FCPXMLSampleName.basicMarkers.rawValue) + var options = FinalCutPro.FCPXML.ReportOptions.full + options.mediaBaseURL = urlForFCPXMLSample(named: FCPXMLSampleName.basicMarkers.rawValue) + .deletingLastPathComponent() + options.includeMarkersOutsideClipBoundaries = true + let report = try await fcpxml.buildReport(options: options) let markers = try XCTUnwrap(report.markers) - XCTAssertFalse(markers.rows.isEmpty, "BasicMarkers must yield Markers sheet rows") + XCTAssertFalse(markers.rows.isEmpty, "Opt-in must yield Markers sheet rows for BasicMarkers") + XCTAssertTrue(markers.showsHiddenColumn) for row in markers.rows { XCTAssertFalse(row.markerName.isEmpty) XCTAssertFalse(row.position.isEmpty) + XCTAssertTrue(row.isHidden) } } diff --git a/Tests/OpenFCPXMLKitTests/FCPXMLReportPDFExportTests.swift b/Tests/OpenFCPXMLKitTests/FCPXMLReportPDFExportTests.swift index ee093dc..a5a3bfd 100644 --- a/Tests/OpenFCPXMLKitTests/FCPXMLReportPDFExportTests.swift +++ b/Tests/OpenFCPXMLKitTests/FCPXMLReportPDFExportTests.swift @@ -147,6 +147,7 @@ final class FCPXMLReportPDFExportTests: XCTestCase, @unchecked Sendable { includeRoleInventory: false ) options.projectName = fcpxml.allProjects().first?.name + options.includeMarkersOutsideClipBoundaries = true let report = try await fcpxml.buildReport(options: options) diff --git a/Tests/OpenFCPXMLKitTests/FCPXMLTestResources.swift b/Tests/OpenFCPXMLKitTests/FCPXMLTestResources.swift index 1846674..02ab60c 100644 --- a/Tests/OpenFCPXMLKitTests/FCPXMLTestResources.swift +++ b/Tests/OpenFCPXMLKitTests/FCPXMLTestResources.swift @@ -106,6 +106,7 @@ enum FCPXMLSampleName: String, CaseIterable { case empty1920x1080 = "1920x1080" case empty4096x2160 = "4096x2160" case empty5120x2160 = "5120x2160" + case hiddenMarkers = "HiddenMarkers" case keywords = "Keywords" case multicamMarkers = "MulticamMarkers" case multicamMarkers2 = "MulticamMarkers2" diff --git a/Tests/OpenFCPXMLKitTests/FileTests/FCPXMLFileTest_HiddenMarkers.swift b/Tests/OpenFCPXMLKitTests/FileTests/FCPXMLFileTest_HiddenMarkers.swift new file mode 100644 index 0000000..241786f --- /dev/null +++ b/Tests/OpenFCPXMLKitTests/FileTests/FCPXMLFileTest_HiddenMarkers.swift @@ -0,0 +1,23 @@ +// +// FCPXMLFileTest_HiddenMarkers.swift +// OpenFCPXMLKit • https://github.com/TheAcharya/OpenFCPXMLKit +// © 2026 • Licensed under MIT License +// + +// +// File tests for HiddenMarkers.fcpxml: markers outside host media range. +// + +import XCTest +@testable import OpenFCPXMLKit + +@available(macOS 26.0, *) +final class FCPXMLFileTest_HiddenMarkers: XCTestCase { + + func testParse() throws { + let fcpxml = try loadFCPXMLSample(named: FCPXMLSampleName.hiddenMarkers.rawValue) + XCTAssertEqual(fcpxml.root.element.name, "fcpxml") + XCTAssertEqual(fcpxml.version, .ver1_14) + XCTAssertFalse(fcpxml.allProjects().isEmpty) + } +} diff --git a/Tests/README.md b/Tests/README.md index f08472a..8ffb0ba 100644 --- a/Tests/README.md +++ b/Tests/README.md @@ -2,8 +2,8 @@ This directory contains the test suite for OpenFCPXMLKit, a Swift 6 framework for Final Cut Pro FCPXML processing with SwiftTimecode integration. The suite runs on **macOS** (Foundation XML backend). The library also supports **iOS 26+** (AEXML backend); CI builds for iOS Simulator; the same tests are not run on iOS because they rely on Foundation XML. -- **Test count:** **1076** tests listed in `swift test --list-tests` — **1072** in `OpenFCPXMLKitTests` (1069 XCTest `func test` methods + 3 Swift Testing `@Test` in `FCPXMLReportRoleExclusionTests`) and **4** in `ExcelReportTest` (optional integration; skips without a local fixture) -- **Scope:** Parsing, timecode, document operations, file loading, timeline export, validation (semantic, DTD, structural), timeline manipulation, media processing, typed models (adjustments, filters, captions/titles, keyframe animation), CMTime Codable, collections, Live Drawing (1.11+), HiddenClipMarker (1.13+), Format/Asset 1.13+ (heroEye, heroEyeOverride, mediaReps), SmartCollection match rules, 360 video (projection, stereoscopic), auditions, conform-rate, still images, multicam, secondary storylines, audio keyframes, keyword collections/folders, empty timeline creation at different sizes and frame rates, project-creation export at different sizes and frame rates (with DTD validation), FCPXMLExporter clip-level metadata export (markers, chapter-markers, keywords, ratings, metadata as asset-clip children; DTD and xmllint-compatible XML declaration), cross-platform XML (AEXML serialization parity, DTD validator behaviour, structural validator), Timeline Projection (`TimelineProjector` / `MediaUsageWindow` / `ReportProjectionContext`, project-once for report sections), Excel and PDF reporting (universal **Row** column on all tabular sheets via `ensuringRowColumn` / `allowsInjectedRowColumn`, role inventory columns, Summary sheet with project title in **B1**, Media Summary sheets, configurable `ReportTimecodeFormat` / DF·NDF notation, format-aware headers, Frames/Feet+Frames sort order, inventory-first `ReportBuildPhase` progress, global column exclusion, disabled-clip filtering, workbook export and cell formatting, PDF cover with black “About This PDF Export” header + `info.circle`, TOC with accent colour chips + content-tint washes keyed to sheet `colorIndex`, remaining columns expanded to fill A4 landscape width after exclusions, section pagination, shared `FCPXMLReportRowColorPolicy`, standalone compound-clip timelines via `allReportTimelineSources()` / `FCPXMLCompoundClipReportTests`), and all supported FCPXML versions and frame rates +- **Test count:** **1084** tests listed in `swift test --list-tests` — **1078** in `OpenFCPXMLKitTests` (1075 XCTest `func test` methods + 3 Swift Testing `@Test` in `FCPXMLReportRoleExclusionTests`) and **6** in `ExcelReportTest` (optional integration; skips without a local fixture) +- **Scope:** Parsing, timecode, document operations, file loading, timeline export, validation (semantic, DTD, structural), timeline manipulation, media processing, typed models (adjustments, filters, captions/titles, keyframe animation), CMTime Codable, collections, Live Drawing (1.11+), HiddenClipMarker (1.13+), Format/Asset 1.13+ (heroEye, heroEyeOverride, mediaReps), SmartCollection match rules, 360 video (projection, stereoscopic), auditions, conform-rate, still images, multicam, secondary storylines, audio keyframes, keyword collections/folders, empty timeline creation at different sizes and frame rates, project-creation export at different sizes and frame rates (with DTD validation), FCPXMLExporter clip-level metadata export (markers, chapter-markers, keywords, ratings, metadata as asset-clip children; DTD and xmllint-compatible XML declaration), cross-platform XML (AEXML serialization parity, DTD validator behaviour, structural validator), Timeline Projection (`TimelineProjector` / `MediaUsageWindow` / `ReportProjectionContext`, project-once for report sections), Excel and PDF reporting (universal **Row** column on all tabular sheets via `ensuringRowColumn` / `allowsInjectedRowColumn`, role inventory columns, Summary sheet with project title in **B1**, Media Summary sheets, configurable `ReportTimecodeFormat` / DF·NDF notation, format-aware headers, Frames/Feet+Frames sort order, inventory-first `ReportBuildPhase` progress, global column exclusion, disabled-clip filtering, Markers out-of-bounds filter / optional **Hidden** column (`includeMarkersOutsideClipBoundaries`), Excel `protectSheets` worksheet protection, workbook export and cell formatting, PDF cover with black “About This PDF Export” header + `info.circle`, TOC with accent colour chips + content-tint washes keyed to sheet `colorIndex`, remaining columns expanded to fill A4 landscape width after exclusions, section pagination, shared `FCPXMLReportRowColorPolicy`, standalone compound-clip timelines via `allReportTimelineSources()` / `FCPXMLCompoundClipReportTests`), and all supported FCPXML versions and frame rates - **Layout:** Shared utilities for sample paths; file tests per sample; logic/parsing tests for model types and structure; validation and cross-platform XML tests; optional Excel/PDF report integration tests under `ExcelReportTest/`; private investigation inbox under `Submitted FCPXML/` (gitignored contents) --- @@ -59,7 +59,7 @@ Tests/ │ ├── Inbox/ │ └── Notes/ ├── FCPXML Samples/ -│ └── FCPXML/ # 59 public .fcpxml fixtures (incl. GeneralDemo) +│ └── FCPXML/ # 60 public .fcpxml fixtures (incl. GeneralDemo) └── OpenFCPXMLKitTests/ ├── OpenFCPXMLKitTests.swift ├── FCPXMLTestResources.swift @@ -74,6 +74,7 @@ Tests/ │ ├── FCPXMLFileTest_Annotations.swift │ ├── FCPXMLFileTest_AuditionSample.swift │ ├── FCPXMLFileTest_BasicMarkers.swift + │ ├── FCPXMLFileTest_HiddenMarkers.swift │ ├── FCPXMLFileTest_Complex.swift │ ├── FCPXMLFileTest_CompoundClips.swift │ ├── FCPXMLFileTest_EmptyFormatProjects.swift @@ -194,9 +195,9 @@ swift test --filter OpenFCPXMLKitTests # By pattern To verify the documented test counts: ```bash -swift test --list-tests 2>/dev/null | grep -c '\.' # 1076 -swift test --list-tests 2>/dev/null | grep -c 'OpenFCPXMLKitTests\.' # 1072 -swift test --list-tests 2>/dev/null | grep -c 'ExcelReportTest\.' # 4 +swift test --list-tests 2>/dev/null | grep -c '\.' # 1084 +swift test --list-tests 2>/dev/null | grep -c 'OpenFCPXMLKitTests\.' # 1078 +swift test --list-tests 2>/dev/null | grep -c 'ExcelReportTest\.' # 6 ``` ### Xcode @@ -292,13 +293,13 @@ Tests are discovered automatically by Swift PM. Run `swift test` in an environme - **FCPXMLCompoundClipReportTests** — Standalone compound-clip FCPXML (event `ref-clip` → `media`/`sequence`, no ``): `allReportTimelineSources()`, role inventory / markers / summary via `buildReport`, project-name filter, and regression that normal project reports still resolve. - **FCPXMLRoleInventoryReportTests** — Role inventory section: Selected Roles Inventory rows and per-role sheets, categories, columns. - **FCPXMLRoleInventoryColumnLayoutTests** — Inventory column order (Row + 23 fixed columns), dynamic metadata key discovery, row index values, audio rate display. -- **FCPXMLMarkersReportTests** — Markers report rows (type, position, clip name, role ▸ subrole), chapter-marker inclusion. +- **FCPXMLMarkersReportTests** — Markers report rows (type, position, clip name, role ▸ subrole), chapter-marker inclusion; default omission of out-of-bounds markers; `includeMarkersOutsideClipBoundaries` + **Hidden** column (✓/✗); sample `HiddenMarkers.fcpxml`. - **FCPXMLKeywordsReportTests** — Keywords report rows (keyword, timeline in/out, duration, role ▸ subrole). - **FCPXMLTitlesReportTests** — Titles & Generators rows (clip name, Apple flag, role ▸ subrole, font, title text). - **FCPXMLTransitionsReportTests** — Transitions rows (transition, category, Apple flag, timeline in/out, duration). - **FCPXMLEffectsReportTests** / **FCPXMLSpeedChangeEffectsReportTests** — Video & Audio Effects and Speed Change Effects rows. - **FCPXMLSummaryReportTests** — Summary sheet: project metrics, per-role duration rows, percentage of total; Media Summary sheet: missing media paths; `.summaryOnly` and `.mediaSummaryOnly` presets. -- **FCPXMLReportExcelExportTests** — XLKit workbook export: Title Case sheet names, sheet ordering, sheet-name sanitisation, **Row** on section sheets (Markers … Media Summary) and Summary role table; Media Summary sheet (red missing-media paths); Summary sheet (project title in **B1**, narrow Row column A, black role-duration data, numeric `% of Total` cells); inventory/marker/section-sheet colour rules (role category, marker type, sheet-specific inference for Keywords/Effects/Titles/Transitions); cover sheet styling (including optional `copyrightLabel` in **A2**); black/white table headers. +- **FCPXMLReportExcelExportTests** — XLKit workbook export: Title Case sheet names, sheet ordering, sheet-name sanitisation, **Row** on section sheets (Markers … Media Summary) and Summary role table; Media Summary sheet (red missing-media paths); Summary sheet (project title in **B1**, narrow Row column A, black role-duration data, numeric `% of Total` cells); inventory/marker/section-sheet colour rules (role category, marker type, sheet-specific inference for Keywords/Effects/Titles/Transitions); cover sheet styling (including optional `copyrightLabel` in **A2**); black/white table headers; **`protectSheets`** applies XLKit `SheetProtection` to every sheet (default unprotected). - **FCPXMLReportPDFTableLayoutTests** — PDF column sizing: remaining columns expand to fill A4 landscape content width after exclusions / short content; pinned `Row` stays packed-width; `allowInjectedRowColumn: false` suppresses multi-page Row injection; wide tables still chunk horizontally with each part filling the page. - **FCPXMLReportPDFSheetPlanTests** — ordered sheet titles share sequential `colorIndex` values used by TOC colour chips and content-page tints; TOC entries preserve those indices when page numbers are filled in. - **FCPXMLReportPDFExportTests** — CoreGraphics PDF export: `%PDF` header, cover page (black “About This PDF Export” band + white `info.circle`, cover notes mentioning default/excludable Row) and custom branding, optional `copyrightLabel` on cover and centred running footer, table of contents for multi-section reports (TOC rows use sheet colour chips + tint washes), synthetic section content, wide-table multi-page pagination, full-workbook section parity (all nine sections in TOC/content), markers/media-summary/summary-only exports. @@ -354,6 +355,7 @@ File tests live under **OpenFCPXMLKitTests/FileTests/** and use samples from **T | **FCPXMLFileTest_Annotations** | Annotations.fcpxml | Root, events, projects | | **FCPXMLFileTest_AuditionSample** | AuditionSample.fcpxml | Root, ver1_13, audition element, active/inactive clips, adjust-colorConform, conform-rate, keywords | | **FCPXMLFileTest_BasicMarkers** | BasicMarkers.fcpxml | Root, ver1_9, root equality, resources, library; allEvents, allProjects | +| **FCPXMLFileTest_HiddenMarkers** | HiddenMarkers.fcpxml | Root, ver1_14; markers outside title media range (FCP-hidden) | | **FCPXMLFileTest_Complex** | Complex.fcpxml | Root, ver1_11, events, projects; version attribute; resources exist | | **FCPXMLFileTest_GeneralDemo** | GeneralDemo.fcpxml | ver1_14, multicam resources/clips, titles, filter-video; anonymized media paths | | **FCPXMLFileTest_CompoundClips** | CompoundClips.fcpxml, CompoundClipSample.fcpxml | Root, non-empty projects; compound clip resources | @@ -445,6 +447,7 @@ Document manager tests create documents for **FCPXML 1.5 through 1.14** and asse ## 11. Sample files +- **Count:** **60** public `.fcpxml` fixtures (including `HiddenMarkers.fcpxml` for out-of-bounds marker semantics). - **Location:** `Tests/FCPXML Samples/FCPXML/` (sibling of OpenFCPXMLKitTests). - **Path resolution:** At runtime via **packageRoot(relativeToFile: #file)** so tests work from Xcode and `swift test` without bundle resources. - **FCPXMLTestResources.swift** — `packageRoot`, `fcpxmlSamplesDirectory()`, `urlForFCPXMLSample(named:)`. @@ -459,13 +462,13 @@ The **`ExcelReportTest`** target (separate from `OpenFCPXMLKitTests`) builds rea | Item | Detail | |------|--------| | **Location** | `Tests/ExcelReportTest/` | -| **Test class** | `ExcelReportExportTests` (4 tests) — writes `Output/OFK-Default.xlsx`, `Output/OFK-Full.xlsx`, `Output/OFK-Default.pdf`, `Output/OFK-ExcludedColumns.pdf`, `Output/OFK-Copyright.xlsx`, and `Output/OFK-Copyright.pdf` | +| **Test class** | `ExcelReportExportTests` (**6** tests) — writes `Output/OFK-Default.xlsx`, `Output/OFK-Full.xlsx`, `Output/OFK-Default.pdf`, `Output/OFK-ExcludedColumns.pdf`, `Output/OFK-Copyright.xlsx` / `.pdf`, `Output/OFK-OutsideClipBoundaries.xlsx` / `.pdf`, and `Output/OFK-ProtectedSheets.xlsx` | | **Fixture** | Preferred `Sample.fcpxmld` / `Sample.fcpxml` under this folder or under `Output/`; else `OFK_REPORTING_FCPXML_BUNDLE`; else auto-discovery | | **Run** | `swift test --filter ExcelReportExportTests` | Full setup, output description, and CI notes: **[ExcelReportTest/README.md](ExcelReportTest/README.md)**. -Use this target for end-to-end workbook/PDF generation on a real fixture (open `Output/OFK-Full.xlsx`, `OFK-Default.pdf`, `OFK-ExcludedColumns.pdf`, or `OFK-Copyright.xlsx` / `OFK-Copyright.pdf` to visually verify layout and `--label-copyright` branding). Standalone compound-clip reporting (no ``) is covered in unit form by **`FCPXMLCompoundClipReportTests`** in `OpenFCPXMLKitTests`. Use **`FCPXMLReportPDFExportTests`**, **`FCPXMLReportPDFSheetPlanTests`**, **`FCPXMLReportPDFTableLayoutTests`**, and other **`OpenFCPXMLKitTests`** reporting files (listed under **Reporting & Excel/PDF export** in [§3.2](#32-dedicated-test-files-by-theme)) for unit and integration tests against bundled FCPXML samples and synthetic report structure. +Use this target for end-to-end workbook/PDF generation on a real fixture (open `Output/OFK-Full.xlsx`, `OFK-Default.pdf`, `OFK-ExcludedColumns.pdf`, `OFK-Copyright.xlsx` / `.pdf`, `OFK-OutsideClipBoundaries.xlsx` / `.pdf`, or `OFK-ProtectedSheets.xlsx` to visually verify layout, copyright branding, Markers **Hidden**, and sheet protection). Standalone compound-clip reporting (no ``) is covered in unit form by **`FCPXMLCompoundClipReportTests`** in `OpenFCPXMLKitTests`. Use **`FCPXMLReportPDFExportTests`**, **`FCPXMLReportPDFSheetPlanTests`**, **`FCPXMLReportPDFTableLayoutTests`**, **`FCPXMLReportExcelExportTests`** (incl. `protectSheets`), **`FCPXMLMarkersReportTests`**, and other **`OpenFCPXMLKitTests`** reporting files (listed under **Reporting & Excel/PDF export** in [§3.2](#32-dedicated-test-files-by-theme)) for unit and integration tests against bundled FCPXML samples and synthetic report structure. --- @@ -526,10 +529,14 @@ Add tests for new behaviour or edge cases; place them in the right file and MARK - **XCTest** (Apple documentation) - **Testing in Xcode** (Apple documentation) - **OpenFCPXMLKit README** (project root) — overview and API usage +- **[ARCHITECTURE.md](../ARCHITECTURE.md)** — layer stack, Mermaid codebase map +- **[GUARDRAILS.md](../GUARDRAILS.md)** — must / must-not (incl. never commit private FCPXML) - **Documentation/Manual** — full manual; [19 — Reporting, Excel & PDF Export](../Documentation/Manual/19-Reporting.md) for report API; [18 — Cross-Platform & iOS](../Documentation/Manual/18-Cross-Platform-iOS.md) for XML abstraction and iOS support - **Final Cut Pro XML (FCPXML)** — [fcp.cafe](https://fcp.cafe) for format reference - **SwiftTimecode** (GitHub) — timecode and frame rate types +**Keep counts in sync:** `swift test --list-tests` → **1084** total (**1078** OpenFCPXMLKitTests + **6** ExcelReportTest); **60** public samples. + --- ## 18. Resolving common test/build messages diff --git a/Tests/Submitted FCPXML/README.md b/Tests/Submitted FCPXML/README.md index 3806460..1c34cb3 100644 --- a/Tests/Submitted FCPXML/README.md +++ b/Tests/Submitted FCPXML/README.md @@ -1,8 +1,10 @@ -# Submitted FCPXML (private inbox) +# Submitted FCPXML (Private Inbox) Local-only drop zone for **private / user-supplied** FCPXML exports used when investigating parsing or Excel/PDF reporting edge cases. -**These files are never committed.** Contents are gitignored; only this README is tracked. +**These files are never committed.** Contents are gitignored; only this README (and `.gitkeep`) is tracked. See [GUARDRAILS.md](../../GUARDRAILS.md) (Sign: never-commit-submitted-fcpxml) and [ARCHITECTURE.md](../../ARCHITECTURE.md) §8. + +**Public suite counts (keep in sync):** **1084** tests listed (`1078` OpenFCPXMLKitTests + `6` ExcelReportTest); **60** public samples under `Tests/FCPXML Samples/FCPXML/` (e.g. `HiddenMarkers.fcpxml` was promoted from this workflow). --- @@ -85,8 +87,8 @@ This does **not** replace promoting anonymised fixtures into `FCPXML Samples/`. | Folder | Committed? | Purpose | |--------|------------|---------| -| `Tests/FCPXML Samples/FCPXML/` | Yes | Canonical public fixtures + CI | -| `Tests/ExcelReportTest/` | Fixture no / README yes | Local full workbook/PDF visual check | +| `Tests/FCPXML Samples/FCPXML/` | Yes | Canonical public fixtures + CI (**60** `.fcpxml` files) | +| `Tests/ExcelReportTest/` | Fixture no / README yes | Local full workbook/PDF visual check (**6** optional tests) | | `Tests/Submitted FCPXML/` | README only | Private investigation inbox | ---