| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- import XCTest
- @testable import MixBoard
- /// Tests for all DAW exporters.
- final class ExporterTests: XCTestCase {
- private var playlist: Playlist!
- private var outputDir: URL!
- override func setUp() {
- super.setUp()
- outputDir = FileManager.default.temporaryDirectory
- .appendingPathComponent("MixBoardExportTests", isDirectory: true)
- try? FileManager.default.createDirectory(at: outputDir, withIntermediateDirectories: true)
- playlist = Playlist(name: "Test Mix")
- let t1 = Track(title: "Track One", artist: "Artist A", album: "Album X", filePath: "/tmp/t1.mp3", duration: 180, fileFormat: "MP3")
- let t2 = Track(title: "Track Two", artist: "Artist B", album: "Album Y", filePath: "/tmp/t2.wav", duration: 240, fileFormat: "WAV")
- let t3 = Track(title: "Track Three", artist: "Artist A", filePath: "/tmp/t3.flac", duration: 300, fileFormat: "FLAC")
- t1.bpm = 128
- t1.musicalKey = "Am"
- t2.bpm = 130
- playlist.addTrack(t1, crossfadeDuration: 0)
- playlist.addTrack(t2, crossfadeDuration: 2.0)
- playlist.addTrack(t3, crossfadeDuration: 1.5)
- }
- override func tearDown() {
- super.tearDown()
- try? FileManager.default.removeItem(at: outputDir)
- }
- // MARK: - Audition Exporter
- func testAuditionExport() throws {
- let url = outputDir.appendingPathComponent("test.sesx")
- var options = ExportOptions.default
- options.copyAudioFiles = false
- try AuditionExporter.export(playlist: playlist, to: url, options: options)
- let content = try String(contentsOf: url, encoding: .utf8)
- // Verify XML structure
- XCTAssertTrue(content.contains("<!DOCTYPE sesx>"))
- XCTAssertTrue(content.contains("<sesx version=\"1.9\">"))
- XCTAssertTrue(content.contains("<session"))
- XCTAssertTrue(content.contains("sampleRate="))
- XCTAssertTrue(content.contains("<audioTrack"))
- XCTAssertTrue(content.contains("<audioClip"))
- XCTAssertTrue(content.contains("Track One"))
- XCTAssertTrue(content.contains("Track Two"))
- XCTAssertTrue(content.contains("Track Three"))
- XCTAssertTrue(content.contains("<files>"))
- XCTAssertTrue(content.contains("absolutePath="))
- XCTAssertTrue(content.contains("</sesx>"))
- }
- func testAuditionExportFileReferences() throws {
- let url = outputDir.appendingPathComponent("test_refs.sesx")
- var options = ExportOptions.default
- options.copyAudioFiles = false
- try AuditionExporter.export(playlist: playlist, to: url, options: options)
- let content = try String(contentsOf: url, encoding: .utf8)
- // Should have file references with media handlers
- XCTAssertTrue(content.contains("mediaHandler=\"AmioMP3\""))
- XCTAssertTrue(content.contains("mediaHandler=\"AmioWav\""))
- XCTAssertTrue(content.contains("mediaHandler=\"AmioLSF\""))
- }
- func testAuditionExportMasterTrack() throws {
- let url = outputDir.appendingPathComponent("test_master.sesx")
- var options = ExportOptions.default
- options.copyAudioFiles = false
- try AuditionExporter.export(playlist: playlist, to: url, options: options)
- let content = try String(contentsOf: url, encoding: .utf8)
- XCTAssertTrue(content.contains("<masterTrack"))
- XCTAssertTrue(content.contains("Audition.Fader"))
- }
- // MARK: - CUE Sheet Exporter
- func testCueSheetExport() throws {
- let url = outputDir.appendingPathComponent("test.cue")
- var options = ExportOptions.default
- options.copyAudioFiles = false
- try CueSheetExporter.export(playlist: playlist, to: url, options: options)
- let content = try String(contentsOf: url, encoding: .utf8)
- XCTAssertTrue(content.contains("TITLE \"Test Mix\""))
- XCTAssertTrue(content.contains("TRACK 01 AUDIO"))
- XCTAssertTrue(content.contains("TRACK 02 AUDIO"))
- XCTAssertTrue(content.contains("TRACK 03 AUDIO"))
- XCTAssertTrue(content.contains("TITLE \"Track One\""))
- XCTAssertTrue(content.contains("PERFORMER \"Artist A\""))
- XCTAssertTrue(content.contains("INDEX 01"))
- }
- func testCueSheetBPMAndKey() throws {
- let url = outputDir.appendingPathComponent("test_bpm.cue")
- var options = ExportOptions.default
- options.copyAudioFiles = false
- try CueSheetExporter.export(playlist: playlist, to: url, options: options)
- let content = try String(contentsOf: url, encoding: .utf8)
- XCTAssertTrue(content.contains("REM BPM 128.0"))
- XCTAssertTrue(content.contains("REM KEY Am"))
- }
- // MARK: - EDL Exporter
- func testEDLExport() throws {
- let url = outputDir.appendingPathComponent("test.edl")
- var options = ExportOptions.default
- options.copyAudioFiles = false
- try EDLExporter.export(playlist: playlist, to: url, options: options)
- let content = try String(contentsOf: url, encoding: .utf8)
- XCTAssertTrue(content.contains("TITLE: Test Mix"))
- XCTAssertTrue(content.contains("FCM: NON-DROP FRAME"))
- XCTAssertTrue(content.contains("001"))
- XCTAssertTrue(content.contains("FROM CLIP NAME: Track One"))
- XCTAssertTrue(content.contains("ARTIST: Artist A"))
- }
- func testEDLTimecodes() throws {
- let url = outputDir.appendingPathComponent("test_tc.edl")
- var options = ExportOptions.default
- options.copyAudioFiles = false
- try EDLExporter.export(playlist: playlist, to: url, options: options)
- let content = try String(contentsOf: url, encoding: .utf8)
- // Should contain valid HH:MM:SS:FF timecodes
- let timecodePattern = #"\d{2}:\d{2}:\d{2}:\d{2}"#
- let regex = try NSRegularExpression(pattern: timecodePattern)
- let matches = regex.numberOfMatches(in: content, range: NSRange(content.startIndex..., in: content))
- XCTAssertGreaterThan(matches, 0, "Should contain SMPTE timecodes")
- }
- // MARK: - M3U Exporter
- func testM3UExport() throws {
- let url = outputDir.appendingPathComponent("test.m3u")
- var options = ExportOptions.default
- options.copyAudioFiles = false
- try M3UExporter.export(playlist: playlist, to: url, options: options)
- let content = try String(contentsOf: url, encoding: .utf8)
- XCTAssertTrue(content.contains("#EXTM3U"))
- XCTAssertTrue(content.contains("#PLAYLIST:Test Mix"))
- XCTAssertTrue(content.contains("#EXTINF:180,Artist A - Track One"))
- XCTAssertTrue(content.contains("#EXTINF:240,Artist B - Track Two"))
- }
- func testM3UBPMMetadata() throws {
- let url = outputDir.appendingPathComponent("test_meta.m3u")
- var options = ExportOptions.default
- options.copyAudioFiles = false
- try M3UExporter.export(playlist: playlist, to: url, options: options)
- let content = try String(contentsOf: url, encoding: .utf8)
- XCTAssertTrue(content.contains("#EXTBPM:128.0"))
- XCTAssertTrue(content.contains("#EXTKEY:Am"))
- }
- // MARK: - DAWproject Exporter
- func testDAWProjectExport() throws {
- let url = outputDir.appendingPathComponent("test.dawproject")
- var options = ExportOptions.default
- options.copyAudioFiles = false
- try DAWProjectExporter.export(playlist: playlist, to: url, options: options)
- // DAWProjectExporter writes to .dawproject.xml
- let xmlURL = url.deletingPathExtension().appendingPathExtension("dawproject.xml")
- let content = try String(contentsOf: xmlURL, encoding: .utf8)
- XCTAssertTrue(content.contains("<Project version=\"1.0\">"))
- XCTAssertTrue(content.contains("<Application name=\"MixBoard\""))
- XCTAssertTrue(content.contains("<Clip"))
- XCTAssertTrue(content.contains("Track One"))
- }
- // MARK: - MixExporter Dispatcher
- func testExportFormatProperties() {
- XCTAssertEqual(MixExporter.ExportFormat.allCases.count, 5)
- for format in MixExporter.ExportFormat.allCases {
- XCTAssertFalse(format.name.isEmpty)
- XCTAssertFalse(format.fileExtension.isEmpty)
- XCTAssertFalse(format.description.isEmpty)
- }
- }
- }
|