- Move phase/plan docs into docs/ - Move legacy node:test files into tests/legacy/ with README - Remove .backup file, test audit artifacts, and unused AI prompt/skill files - Remove broken iOS GitHub workflows (reference missing MovieMapper-iOS/)
11 KiB
11 KiB
MovieMapper iOS Implementation Plan
Architecture Overview
Since you want SwiftUI with Swift only (no Rust backend) and offline-first, this will be a complete rewrite in Swift using Apple frameworks.
Phase 1: Project Setup & Foundation
1.1 Create Xcode Project
# Create new iOS App project (iPad only)
xcodebuild -createProject MovieMapper -template iOS-App -destination ./MovieMapper-iOS
# Or using Swift Package Manager for more control
mkdir MovieMapper-iOS && cd MovieMapper-iOS
swift package init --type executable --name MovieMapper-iOS
1.2 Directory Structure
MovieMapper-iOS/
├── MovieMapper-iOS/ # Main app target
│ ├── ContentView.swift
│ ├── MovieMapperApp.swift
│ └── ...
├── SharedModels/ # Shared data models
│ ├── MediaFile.swift
│ ├── Show.swift
│ ├── Season.swift
│ ├── Episode.swift
│ └── TaggedFile.swift
├── Services/ # Business logic
│ ├── FileScanner.swift
│ ├── MetadataExtractor.swift
│ ├── TVDBClient.swift
│ ├── FileMapper.swift
│ └── AuditLogger.swift
├── UI/ # SwiftUI views
│ ├── BrowseView.swift
│ ├── SearchView.swift
│ ├── FileListView.swift
│ ├── SeasonDetailView.swift
│ └── ModalViews/
├── Utils/ # Helper functions
│ ├── Filesystem.swift
│ ├── FFmpegWrapper.swift
│ └── DateFormatters.swift
└── Resources/ # Assets
├── Assets.xcassets
└── Localizable.strings
Phase 2: Core Implementation
2.1 Data Models (SharedModels/)
struct MediaFile: Identifiable, Codable, Hashable {
let id: UUID
let name: String
let path: String
let size: Int64
let modified: Date
let duration: String // "mm:ss" format
let quality: String // "1080p", "4K", etc.
let fps: String // "24fps", "30fps", etc.
var isFolder: Bool
var tags: [TagType]
}
enum TagType: String, Codable, CaseIterable {
case extra = "extra"
case behindTheScenes = "behind-the-scenes"
case delete = "delete"
}
2.2 File Scanner Service
- Framework: Use
FileManagerwithURLQueryItemfor directory access - Permissions: Request
NSPhotoLibraryAddUsageDescriptionfor file access - Non-recursive scanning: Scan only current directory (match desktop behavior)
- Progress updates: Use Combine/Publishers for real-time UI updates
class FileScanner {
func scanDirectory(at path: URL, progress: @escaping (Int, Int, String) -> Void) async throws -> [MediaFile]
}
2.3 Metadata Extractor
- FFmpeg integration: Use
ffmpeg-kitorSwiftFFmpegpackage - Extract: duration, resolution, FPS
- Error handling: Gracefully handle corrupted files
class MetadataExtractor {
func extractDuration(from url: URL) async throws -> String
func extractQuality(from url: URL) async throws -> (quality: String, fps: String)
}
2.4 TVDB Client
- API: TheTVDB v4 REST API
- Authentication: Bearer token with caching
- Features: Search shows, get details, fetch episodes
- Offline caching: Store recent searches in
UserDefaultsor Core Data
class TVDBClient {
func authenticate() async throws
func search(query: String) async throws -> [Show]
func getShowDetails(id: Int) async throws -> ShowDetails
func getSeasonEpisodes(showId: Int, seasonNumber: Int) async throws -> [Episode]
}
2.5 File Mapper
- Jellyfin naming:
ShowName S01E01 - quality.ext - Episode ranges: Support
S01E01-E03format - Folder creation: Create
extras/,behind the scenes/,commentary/directories
class FileMapper {
func mapFiles(_ files: [MediaFile], to show: Show, season: Season) async throws -> MappingResult
}
2.6 Audit Logger
- Format: JSON lines in
.auditfiles - Storage: Write to same directory as files
- Content: Timestamp, action, details
class AuditLogger {
func log(action: String, details: [String: Any], in directory: URL) async throws
}
Phase 3: UI Implementation
3.1 Main Views
BrowseView.swift
- Directory picker (UIDocumentPickerViewController)
- Breadcrumb navigation (iPad multi-column)
- File list with:
- Folder icons 📁
- Media file details (duration, quality, FPS)
- Tag icons (clickable)
- Play button (moves tagged files)
SearchView.swift
- Search bar for TVDB
- Show results with thumbnails
- Season selection with episode lists
- Episode count badges
FileListView.swift
- Drag-and-drop reordering (iOS 17+)
- Episode number editing
- Tag toggling (extra, behind-the-scenes, delete)
- Floating action button for moving all tagged files
3.2 iPad-Specific Features
- Multi-column navigation: Use
NavigationSplitViewfor sidebar + content - Split view: Search on left, files on right
- Large screen optimization: Use more horizontal space
3.3 Modal Views
- Video preview modal
- Confirmation dialogs for destructive actions
- Loading indicators for async operations
Phase 4: Testing Strategy
4.1 Command Line Testing Tools
4.1.1 File Scanner Tests
# Create test directory structure
mkdir -p /tmp/moviemapper_test/{Season\ 01,Season\ 02}
touch /tmp/moviemapper_test/Season\ 01/episode1.mp4
touch /tmp/moviemapper_test/Season\ 01/episode2.mkv
# Run scanner test
swift test --filter FileScannerTests/testScanDirectory
4.1.2 Metadata Extraction Tests
# Create test video file (using ffmpeg)
ffmpeg -f lavfi -i testsrc=duration=5:size=1920x1080:rate=30 /tmp/test_video.mp4
# Run metadata extraction test
swift test --filter MetadataExtractorTests/testExtractDuration
swift test --filter MetadataExtractorTests/testExtractQuality
4.1.3 TVDB API Tests
# Set API key (from .env file)
export TVDB_API_KEY="your-api-key-here"
# Run TVDB integration tests
swift test --filter TVDBClientTests/testAuthenticate
swift test --filter TVDBClientTests/testSearchShows
swift test --filter TVDBClientTests/testGetShowDetails
4.1.4 File Mapping Tests
# Create test files
mkdir -p /tmp/moviemapper_test/Season\ 01
for i in {1..5}; do
ffmpeg -f lavfi -i testsrc=duration=1:size=1280x720:rate=24 \
/tmp/moviemapper_test/Season\ 01/video_${i}.mp4
done
# Run mapping test
swift test --filter FileMapperTests/testMapSingleEpisode
swift test --filter FileMapperTests/testMapEpisodeRange
4.2 UI Testing
# Run UI tests
xcodebuild test -project MovieMapper-iOS.xcodeproj \
-scheme MovieMapper-iOS \
-destination 'platform=iOS Simulator,name=iPad Pro (12.9-inch) (17th generation)' \
-destination-timeout 60
4.3 Performance Testing
# Test scanning performance with many files
xcodebuild test -project MovieMapper-iOS.xcodeproj \
-scheme MovieMapper-iOSPerformance \
-destination 'platform=iOS Simulator,name=iPad Pro' \
-enableCodeCoverage YES
Phase 5: Advanced Features
5.1 Offline-First Architecture
- Local caching: Store show search results in
UserDefaultsorCoreData - File scanning: Work completely offline
- TVDB features: Optional, only when internet available
5.2 File System Access
- Document Picker: Let user select media library location
- Security scoped bookmarks: Persist access across app launches
- iCloud integration: Optional (for backup)
5.3 Tagging System
struct TagManager {
func addTag(_ tag: TagType, to file: MediaFile, in directory: URL) async throws
func removeTag(_ tag: TagType, from file: MediaFile, in directory: URL) async throws
func moveTaggedFiles(_ files: [MediaFile], to folder: String) async throws
}
Phase 6: Build & Distribution
6.1 Build Commands
# Build for simulator
xcodebuild -project MovieMapper-iOS.xcodeproj \
-scheme MovieMapper-iOS \
-configuration Debug \
-sdk iphonesimulator
# Build for device
xcodebuild -project MovieMapper-iOS.xcodeproj \
-scheme MovieMapper-iOS \
-configuration Release \
-sdk iphoneos \
-archivePath MovieMapper-iOS.xcarchive \
archive
# Export IPA
xcodebuild -exportArchive \
-archivePath MovieMapper-iOS.xcarchive \
-exportOptionsPlist ExportOptions.plist
6.2 Testing Automation
#!/bin/bash
# run-tests.sh
# Set up test environment
export TVDB_API_KEY=$(cat .env | grep TVDB_API_KEY | cut -d'=' -f2)
# Run unit tests
swift test
# Run UI tests
xcodebuild test -project MovieMapper-iOS.xcodeproj \
-scheme MovieMapper-iOS \
-destination 'platform=iOS Simulator,name=iPad Pro'
# Generate test report
xcodebuild -project MovieMapper-iOS.xcodeproj \
-scheme MovieMapper-iOS \
-destination 'platform=iOS Simulator,name=iPad Pro' \
-quiet \
-testSummaryReport /tmp/test-results.xml
Phase 7: Dependencies & Packages
7.1 Swift Package Manager Dependencies
// swift-tools-version:5.9
import PackageDescription
let package = Package(
name: "MovieMapper-iOS",
platforms: [.iOS(.v17)],
dependencies: [
.package(url: "https://github.com/ffmpeg-kit/swift.git", from: "6.0.0"),
.package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.8.0"),
.package(url: "https://github.com/rnapier/ReactiveSwift.git", from: "7.0.0")
],
targets: [
.target(
name: "MovieMapper-iOS",
dependencies: [
.product(name: "FFmpegKitSwift", package: "swift-ffmpeg-kit"),
.product(name: "Alamofire", package: "Alamofire"),
.product(name: "ReactiveSwift", package: "ReactiveSwift")
]
)
]
)
Summary of Key Decisions
Architecture
- Pure SwiftUI with Swift only (no Rust backend as requested)
- Combine for async operations and state management
- Structured concurrency (
async/await) for all network/file operations
Offline-First Design
- File scanning works completely offline
- TVDB features are optional (require internet)
- Local caching of recent searches and show details
iPad-Specific Optimizations
- Multi-column navigation with
NavigationSplitView - Large screen layout with sidebar + content
- Touch-friendly UI elements (minimum 44pt tap targets)
Testing Approach
- Command line tests: Unit tests for all services
- UI tests: Simulator-based automation
- Manual testing: Test on actual iPad devices
Implementation Timeline
| Week | Task |
|---|---|
| 1-2 | Project setup, data models, basic file scanner |
| 3-4 | Metadata extractor, UI scaffolding |
| 5-6 | TVDB client, search functionality |
| 7-8 | File mapping, tagging system |
| 9 | Testing (unit + UI), bug fixes |
| 10 | iPad optimization, polishing |
Next Steps
- Create Xcode project using the structure above
- Implement core data models (MediaFile, Show, Season, Episode)
- Build FileScanner service with progress reporting
- Create SwiftUI views for directory browsing
- Implement metadata extraction with FFmpeg
- Add TVDB integration for show search
- Build tagging and file movement features
- Write comprehensive tests for all services
- Optimize for iPad with split views and large screen layouts
- Test on physical iPad device before App Store submission