2016-10-03

Swift - ScriptingBridge - iTunes

RegularExpression/main.swift
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
let rxMovement = NSRegularExpression(regex: "^\\d\\d .+? - (\\d+)?\\.? ?(.+)")

guard let  tracks = Tracks.whose("albumArtist beginswith \"Trifonov\"")
    , (0 < tracks.count)
    else {
        print("Error: No Track")
        exit(1)
}
//let update = query(text:"count = \(tracks.count), first track = '\(tracks[0]!.name!)'\nUpdate")
let update = false
tracks.enumerate { i, track in
    let name = track.name!
    guard let strings = rxMovement.strings(firstMatchIn: name) else {
        print("Patttern: '\(rxMovement.pattern)'")
        print("    Text: '\(name)'")
        exit(1)
    }
    let number   = Int(movementNumber:strings[1])
    let movement = strings[2]!
    guard update else {
        assert(name.hasSuffix(movement))
        print(number, movement)
        return true
    }
    if 0 < number {
        track.movementNumber = number
    }
    track.movement = movement
    print("\t", track.movementNumber, track.movement)
    return true
}



iTunes.xcodeproj















Objective-C/iTunesSB.sh
1
2
3
4
5
6
7
8
#!/bin/sh

app=iTunes.app
path=$(mdfind "kMDItemFSName == '"$app"'")
name=$(mdls -name kMDItemDisplayName -raw $path)
id=$(mdls -name kMDItemCFBundleIdentifier -raw $path)

sdef "$path" | sdp -fh --basename "${name}" -o "${name}SB".h

Objective-C/iTunes.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#ifndef iTunes_h
#define iTunes_h

#import "iTunesSB.h"
iTunesApplication *_Nullable GetiTunesApplication();
iTunesTrack       *_Nullable getTrack(iTunesTrack *_Null_unspecified track);

@interface Tracks: NSObject
+ (instancetype _Null_unspecified) selection;
+ (instancetype _Null_unspecified) whose:(NSString * _Null_unspecified) clause;
- (instancetype _Null_unspecified) initWithPredicate:(NSPredicate * _Null_unspecified) predicate;
- (iTunesTrack *_Nullable) objectAtIndexedSubscript:(NSUInteger) i;
- (void)enumerate:(BOOL (^_Null_unspecified)( NSInteger index, iTunesTrack *_Null_unspecified track))block;

@property (readonly) NSUInteger count;
@end

#endif /* iTunes_h */

Objective-C/iTunes.m
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#import "iTunes.h"

//MARK: iTunes
static iTunesApplication *iTunes = NULL;
iTunesApplication *GetiTunesApplication() {
    if (iTunes == NULL) {
        iTunes = (iTunesApplication *)[SBApplication applicationWithBundleIdentifier:@"com.apple.iTunes"];
    }
    return iTunes;
}
//MARK: - Track
iTunesTrack * getTrack(iTunesTrack *track) {
    return track.get;
}
static SBObject *selection = NULL;
SBElementArray<iTunesTrack *> *GetSelection() {
    if (selection == NULL) {
        iTunesApplication *app = GetiTunesApplication();
        selection = app.selection;
    }
    SBElementArray<iTunesTrack *> *tracks = selection.get;
    return tracks;
}
iTunesTrack * getTrackAt(SBElementArray<iTunesTrack*>* tracks, NSUInteger i) {
    return [tracks objectAtIndex:i];
}
@implementation Tracks
{
        SBElementArray<iTunesTrack *> *tracks;
}
+ (instancetype)selection {
    return [[Tracks alloc] initBySelection];
}
+ (instancetype)whose:(NSString *)clause {
    @try {
        NSPredicate *predicate = [NSPredicate predicateWithFormat:clause];
        //NSLog(@"'%@'", predicate);
        return [[Tracks alloc] initWithPredicate:predicate];
    }
    @catch (NSException *exception) {
        NSLog(@"Error: '%@'\n\t%@", clause, exception.reason);
        exit(-1);
    }
    @finally {

    }
}
- (NSUInteger) count {
    return tracks.count;
}
- (instancetype)initBySelection {
    tracks = GetSelection();
    return self;
}
- (instancetype)initWithPredicate:(NSPredicate *)predicate {
    iTunesApplication * app = GetiTunesApplication();
    tracks = (SBElementArray<iTunesTrack *> *)[app.tracks filteredArrayUsingPredicate:predicate];
    return self;
}
- (iTunesTrack *) objectAtIndexedSubscript:(NSUInteger) i {
    if (self.count <= i) return nil;
    return getTrackAt(tracks, i).get;
}
- (void)enumerate:(BOOL (^)(NSInteger index, iTunesTrack * _Null_unspecified track))block {
    NSUInteger count = tracks.count;
    for (NSInteger i = 0 ; i < count; i++) { // unsigned -> signed
        if (!block(i, tracks[i])) break;
    }
}
@end


iTunes-Bridging-Header.h
1
#import "iTunes.h"

iTunes/main.swift
1
2
3
4
let iTunes = GetiTunesApplication()!
print(iTunes.name!)
iTunes.playOnce(true)
print(iTunes.currentTrack.name!)

Library/iTunes.swift
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
//MARK: iTunes
public var iTunes:iTunesApplication = {
    guard let app = GetiTunesApplication() else {
        fatalError("Can't connect.")
    }
    return app
}()
public enum playerState {
    case Stopped
    case Playing
    case Paused
    init() {
        switch iTunes.playerState {
        case iTunesEPlSPlaying:
            self =    .Playing
        case iTunesEPlSPaused:
            self =    .Paused
        case iTunesEPlSStopped:
            self =    .Stopped
        default:
            fatalError("iTunes.playerState")
        }}
}
//MARK: iTunesTrack
public func currentTrack() ->iTunesTrack? {
    guard let track = iTunes.currentTrack, 0 < track.id() else {
        return nil
    }
    return get(track:track)
}
private func get(track:iTunesTrack?) -> iTunesTrack? {
    return getTrack(track)
}
//MARK: Functions
public func query(text: String = "Update") ->Bool {
    while true {
        print("\(text)? y/N/^D : ", terminator:"")
        guard let ans = readLine(strippingNewline: true) else {
            print("Canceled.")
            exit(-1)
        }
        switch ans.lowercased() {
        case "y", "yes":
            return true
        case "n", "no":
            return false
        case "":
            return false
        default:
            break
        }}
}
public func rewindAndStepForward(
    while ok:@escaping (_ track:iTunesTrack)->Bool,
    do execute:(_ track:iTunesTrack)->Bool // true: next, false: break
    ){
    func _ok(_ _track:iTunesTrack?)->Bool {
        guard let track = _track else {
            return false
        }
        return (0 < track.id()
            && ok(track))
    }
    while _ok(currentTrack()) {
        iTunes.previousTrack()
    }
    iTunes.nextTrack()
    while true {
        guard     let track = currentTrack()
            ,     _ok(track)
            , execute(track)
            else {
                break
        }
        iTunes.nextTrack()
    }
}
//MARK: - NSRegularExpression
private extension String {
    var nsRange:NSRange {
        return NSRange(location:0, length: (self as NSString).length)
    }
}
public extension NSRegularExpression {
    convenience init(regex:String, options:NSRegularExpression.Options = []) {
        do {
            try self.init(pattern: regex, options: options)
        }
        catch let error as NSError {
            print(error.localizedDescription)
            exit(-1)
        }}
    func string(byReplacingMatchesIn string: String,
                options: NSRegularExpression.MatchingOptions = [],
                withTemplate templ: String) -> String {
        return self.stringByReplacingMatches(in: string,
                                             options: options,
                                             range: string.nsRange,
                                             withTemplate: templ)
    }
    func strings(firstMatchIn string: String) ->[String?]? {
        let nsstring = string as NSString
        guard let match = self.firstMatch(in: string, range: string.nsRange)
            else {
                return nil
        }
        var strings:[String?] = []
        for i in 0 ..< match.numberOfRanges {
            let range = match.rangeAt(i)
            strings.append(
                range.location == NSNotFound
                    ? nil
                    : nsstring.substring(with: range))
        }
        return strings
    }
}
//MARK: - Int
private let RomanNumbers:[String] = {
    return "I II III IV V VI VII VIII IX X XI XII XIII XIV XV XVI XVII XVIII XIX XX".components(separatedBy:" ")
}()
private var R2N:[String:Int] = {
    var table:[String:Int] = [:]
    for (i, r) in RomanNumbers.enumerated() {
        table[r] = i + 1
    }
    return table
}()
public extension Int {
    init?(roman:String) {
        guard let i = R2N[roman] else {
            return nil
        }
        self.init(i)
    }
    init(movementNumber string:String?) {
        self.init(
            string == nil
             ? 0
             : Int(string!) ?? Int(roman:string!) ?? 0)
    }
    var roman:String? {
        guard 0 < self && self <= RomanNumbers.count else {
            return nil
        }
        return RomanNumbers[self - 1]
    }
}

iTunesSwift/main.swift
1
2
3
print(iTunes.name!)
iTunes.playOnce(true)
print(currentTrack()!.name!)

Selection/main.swift
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
if let savedTrack = currentTrack() {
    print(savedTrack.name)
}
else {
    print("No current track.")
}
print("----")
if let tracks = Tracks.selection() {
    print("count = \(tracks.count)")
    for i in 0 ..< tracks.count {
        let track = tracks[i]!
        print(track.name)
    }}
print("----")
guard let selection = Tracks.whose("composer contains \"Bach\""),
    0 < selection.count else {
        exit(1)
}
print("count = \(selection.count)")
selection.enumerate { i, track in
    let trackNumber = i + 1
    print("\(trackNumber)\t: \(track.name!)")
    return trackNumber < 5
}

SelectSameGrouping/main.swift
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Play another track, then target track
guard let track = currentTrack() else {
    print("No Current Track.")
    exit(1)
}
let state    = playerState(); iTunes.pause()
let position = iTunes.playerPosition
let album    = track.album!
let grouping = track.grouping!
let update   = query(text:"\(album) | \(grouping)\nUpdate")
rewindAndStepForward(
    while: { track in
        (  album    == track.album!
     && grouping == track.grouping!
        )},
    do: { track in
        print(track.grouping!, "|", track.name!)
        guard update else {
            return true
        }
        print("<<< Updating >>>")
        return true
})
track.playOnce(true)
iTunes.playerPosition = position
if state == .Paused {
    iTunes.pause()
}

1 件のコメント:

  1. - Guide -
    Xcode 8, Swift 3

    New Project
    1 File > New > Project...
    2 macOS > macOS > Command Line Tool
    3 Product Name: iTunes
    Language: Swift

    Headers Group
    1 File > New > Group
    2 rename: Headers

    iTunesSB.h
    1 File > New > File
    2 macOS > Shell Script
    3 NewFolder: Objective-C
    Save As: iTunesSB.sh
    Uncheck Target Membership
    4 Edit Objectiv-C/iTunesSB.sh
    5 File > New > Target
    6 Cross-platform > External Buld System
    7 Project Name: iTunesSB.h
    8 Select iTunes.project > TARGETS:iTunesSB.h > Info
    9 Build Tool: /bin/sh
    Arguments: iTunesSB.sh
    Directory: Objective-C
    off: Pass build settings in environment
    10 Select iTunesSB.h from scheme pop up menu or Produce > Scheme
    11 Push ▶︎
    12 Select Headers group
    13 File > Add Files to
    14 Select Objective-C/iTunesSB.h

    iTunes.h, iTunes.m
    1 Create Library Group
    2 New Objective-C/iTunes.m(group:Library)
    3 Select Creating Birdiging Header
    4 New Objective-C/iTunes.h(group:Header)
    5 Edit iTunes.h and iTunes.m

    iTunes-Bridging-Header.h
    1 Select iTunes.xcodeproj > PROJECT:iTunes
    2 Build Setting > Objective-C Bridge Header > iTunes = iTunes-Bridging-Header.h
    3 Edit iTunes-Bridging-Header.h

    iTunes/main.swift
    1 Select scheme: iTunes
    2 Edit iTunes/main.swift
    3 Build and Run

    iTunesSwift/main.swift
    1 New Target iTunesSwift
    2 Select scheme: iTunesSwift
    3 New File Library/iTunes.swift(group:Library)
    4 Edit Library/iTunes.swift
    5 Edit iTunesSwift/main.swift
    6 View > Utilities > Show File Inspector
    7 iTumes.m, iTunes.swift: check Taget Menbership:iTunesSwift
    8 Build and Run

    New Targets
    Selection
    SelectGrouping
    RegularExpression

    返信削除