2016-10-26

Template: Work.html

Work+Movementのテンプレートを作成しました。
Works.html
「~/Library/Containers/com.me.oyasuhisa.sirabesirabe/Data/Library/Application Support/SirabeSirabe/Templates」
に追加して再起動してください。
「MenuBar->View->Work」を選択してください。

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()
}