recover_itunes_ratings 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a5b5411273b36b55660fd6d23955d9b911d1af3b
4
+ data.tar.gz: 6d3956ae72f4f8022fd8dd04936c815ebdb66f89
5
+ SHA512:
6
+ metadata.gz: edffe849a9b824c30fd868b58c40942e0d4f726817966556e7f934f365689cda447beedf6475b94b45fc9a0b00291b2a251cf086c311f1b48829cb9c128ab11e
7
+ data.tar.gz: 7c4afb194f050800c394bb23523964ce7a9a71cd0e4f0ebc119816b71765a9ef1a70b78eca7dfe867938cefa0a58afa9cf9c5bd09847df2f10b836a2261808ee
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Milo Winningham
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,30 @@
1
+ # recover_itunes_ratings
2
+
3
+ Reads the song ratings from an iTunes library XML file and applies them to your current iTunes library via AppleScript. This may be useful to restore song ratings that were lost due to a bug in iTunes, Apple Music, or iCloud Music Library.
4
+
5
+ ## Installation
6
+
7
+ $ gem install recover_itunes_ratings
8
+
9
+ ## Usage
10
+
11
+ $ recover_itunes_ratings "iTunes Music Library.xml" [options]
12
+
13
+ By default, only prints the ratings that will be changed.
14
+
15
+ -a, --albums Reset all user-supplied album ratings
16
+ -r, --run Run instead of only printing changes
17
+
18
+ ## Development
19
+
20
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. Run `bundle exec recover_itunes_ratings` to use the gem in this directory, ignoring other installed copies of this gem.
21
+
22
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
23
+
24
+ ## Contributing
25
+
26
+ Bug reports and pull requests are welcome on GitHub at https://github.com/quadule/recover_itunes_ratings.
27
+
28
+ ## License
29
+
30
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "recover_itunes_ratings"
5
+
6
+ require "irb"
7
+ IRB.start
@@ -0,0 +1,5 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "optparse"
4
+ require "recover_itunes_ratings"
5
+
6
+ options = {
7
+ dry_run: true,
8
+ reset_album_ratings: false
9
+ }
10
+
11
+ optparse = OptionParser.new do |opts|
12
+ opts.banner = 'Usage: recover_itunes_ratings "iTunes Music Library.xml" [options]'
13
+
14
+ opts.separator ""
15
+ opts.separator "By default, only prints the ratings that will be changed."
16
+ opts.separator ""
17
+
18
+ opts.on("-a", "--albums", "Reset all user-supplied album ratings") do |v|
19
+ options[:reset_album_ratings] = true
20
+ end
21
+
22
+ opts.on("-r", "--run", "Run instead of only printing changes") do |v|
23
+ options[:dry_run] = false
24
+ end
25
+ end
26
+ optparse.parse!
27
+
28
+ if ARGV.empty?
29
+ puts optparse
30
+ exit(-1)
31
+ end
32
+
33
+ RecoverItunesRatings::Runner.new(ARGV[0], options).run
@@ -0,0 +1,2 @@
1
+ require "recover_itunes_ratings/runner"
2
+ require "recover_itunes_ratings/version"
@@ -0,0 +1,18 @@
1
+ require "rb-scpt"
2
+
3
+ module RecoverItunesRatings
4
+ # Matches saved ratings with current iTunes tracks.
5
+ class MatchedTracks
6
+ def initialize(ratings, app = ::Appscript.app("iTunes"))
7
+ @ratings = ratings
8
+ @app = app
9
+ end
10
+
11
+ def each
12
+ @ratings.each do |id, saved_rating|
13
+ track = @app.tracks[::Appscript.its.persistent_ID.eq(id)].get.first
14
+ yield track, saved_rating if track
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,32 @@
1
+ require "recover_itunes_ratings/matched_tracks"
2
+ require "recover_itunes_ratings/saved_ratings"
3
+
4
+ module RecoverItunesRatings
5
+ # Restores each rating found in the library XML to the current iTunes library.
6
+ class Runner
7
+ def initialize(library_xml_path, dry_run: true, reset_album_ratings: false)
8
+ @dry_run = dry_run
9
+ @reset_album_ratings = reset_album_ratings
10
+
11
+ @ratings = SavedRatings.new(library_xml_path)
12
+ @tracks = MatchedTracks.new(@ratings)
13
+ end
14
+
15
+ def run
16
+ @tracks.each do |track, saved_rating|
17
+ if @reset_album_ratings && track.album_rating_kind.get == :user
18
+ name = "#{track.artist.get} - #{track.album.get}"
19
+ puts "[#{track.album_rating.get/20} -> X] Album: #{name}"
20
+ track.album_rating.set(-1) unless @dry_run
21
+ end
22
+
23
+ current_rating = track.rating.get
24
+ next unless saved_rating && current_rating != saved_rating
25
+
26
+ name = "#{track.artist.get} - #{track.name.get}"
27
+ puts "[#{current_rating/20} -> #{saved_rating/20}] Song: #{name}"
28
+ track.rating.set(saved_rating) unless @dry_run
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,31 @@
1
+ require "itunes_parser/nokogiri_sax"
2
+
3
+ module RecoverItunesRatings
4
+ # Enumerates the track ratings in an iTunes Library XML file.
5
+ class SavedRatings
6
+ IGNORED_TYPES = ["Music Video", "Movie", "Home Video", "TV Show", "Podcast",
7
+ "iTunes U", "Audiobook", "Book", "Voice Memo"]
8
+
9
+ def initialize(library_xml_path)
10
+ @path = library_xml_path
11
+ end
12
+
13
+ def each
14
+ parse on_track: ->(data) {
15
+ next if data["Track Type"] == "Remote"
16
+ next if IGNORED_TYPES.any?(&data.method(:has_key?))
17
+ next if IGNORED_TYPES.any? { |t| data["Genre"] == t }
18
+
19
+ yield data["Persistent ID"], data["Rating"].to_i
20
+ }
21
+ end
22
+
23
+ private def library
24
+ open(@path, "r")
25
+ end
26
+
27
+ private def parse(*args)
28
+ ::ItunesParser::NokogiriSax.new(library, *args).parse
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ module RecoverItunesRatings
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'recover_itunes_ratings/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "recover_itunes_ratings"
8
+ spec.version = RecoverItunesRatings::VERSION
9
+ spec.authors = ["Milo Winningham"]
10
+ spec.email = ["milo@winningham.net"]
11
+
12
+ spec.summary = %q{Restores iTunes ratings from an iTunes library XML file.}
13
+ spec.homepage = "https://github.com/quadule/recover_itunes_ratings"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib", "vendor"]
20
+
21
+ spec.add_dependency "rb-scpt", "~> 1.0.1"
22
+ spec.add_dependency "nokogiri"
23
+ spec.add_development_dependency "bundler", "~> 1.10"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "rspec"
26
+ end
@@ -0,0 +1,155 @@
1
+ # From itunes_parser by Pete Higgins
2
+ # http://github.com/phiggins/itunes_parser
3
+
4
+ require 'nokogiri'
5
+
6
+ module ItunesParser
7
+ class NokogiriSax
8
+ class SaxDoc < Nokogiri::XML::SAX::Document
9
+ def initialize on_track, on_playlist
10
+ super()
11
+ @on_track, @on_playlist = on_track, on_playlist
12
+ @with_callbacks = on_track || on_playlist
13
+ @tracks, @playlists = [], [] unless @with_callbacks
14
+ @buffer = ""
15
+ end
16
+
17
+ def error(error_message)
18
+ raise error_message
19
+ end
20
+
21
+ #def start_element(name, attrs = [])
22
+ def start_element_namespace(name, *)
23
+ case name
24
+ when 'dict' ; start_dict
25
+ when 'array'
26
+ start_playlist_items if @key_string == 'Playlist Items'
27
+ end
28
+ end
29
+
30
+ #def end_element(name)
31
+ def end_element_namespace(name, *)
32
+ case name
33
+ when 'key'
34
+ @key_string = @buffer.strip
35
+ when 'dict'
36
+ end_dict
37
+ when 'array'
38
+ if @parsing_playlist_items
39
+ end_playlist_items
40
+ elsif @parsing_playlists
41
+ end_playlists
42
+ end
43
+ else
44
+ if @parsing_playlist_items
45
+ @current_playlist_items << @buffer.to_i
46
+ elsif @parsing_playlists || @parsing_tracks
47
+ if name == 'true'
48
+ @current << @key_string << true
49
+ elsif name == 'false'
50
+ @current << @key_string << false
51
+ else
52
+ @current << @key_string << @buffer.strip
53
+ end
54
+ end
55
+ end
56
+
57
+ @buffer = ""
58
+ end
59
+
60
+ def characters(string)
61
+ @buffer << string
62
+ end
63
+
64
+ def start_tracks
65
+ #puts " ** parsing tracks"
66
+ @parsing_tracks = true
67
+ @current = []
68
+ end
69
+
70
+ def end_tracks
71
+ #puts " ** end tracks"
72
+ @parsing_tracks = false
73
+ end
74
+
75
+ def start_playlists
76
+ #puts " ** parsing playlists"
77
+ @parsing_playlists = true
78
+ @current = []
79
+ end
80
+
81
+ def end_playlists
82
+ @parsing_playlists = false
83
+ end
84
+
85
+ def start_playlist_items
86
+ #puts " ** parsing playlist items"
87
+ @current_playlist_items = []
88
+ @parsing_playlist_items = true
89
+ end
90
+
91
+ def end_playlist_items
92
+ #puts " ** end playlist items"
93
+ @parsing_playlist_items = false
94
+ @current << "Playlist Items"
95
+ @current << @current_playlist_items
96
+ end
97
+
98
+ def start_dict
99
+ case @key_string
100
+ when "Tracks"
101
+ start_tracks
102
+ when "Playlists"
103
+ start_playlists
104
+ end
105
+ end
106
+
107
+ def end_dict
108
+ if @parsing_playlists && !@parsing_playlist_items
109
+ current = Hash[*@current]
110
+ if !@with_callbacks
111
+ @playlists.push(current)
112
+ elsif @on_playlist
113
+ @on_playlist.call(current)
114
+ end
115
+ @current = []
116
+ elsif @parsing_tracks
117
+ if @current == []
118
+ end_tracks
119
+ else
120
+ current = Hash[*@current]
121
+ if !@with_callbacks
122
+ @tracks.push(current)
123
+ elsif @on_track
124
+ @on_track.call(current)
125
+ end
126
+ @current = []
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ def initialize(xml, params={})
133
+ @xml = xml
134
+
135
+ @on_track = params[:on_track]
136
+ @on_playlist = params[:on_playlist]
137
+ end
138
+
139
+ def on_track &block
140
+ @on_track = block
141
+ end
142
+
143
+ def on_playlist &block
144
+ @on_playlist = block
145
+ end
146
+
147
+ def parse
148
+ document = SaxDoc.new(@on_track, @on_playlist)
149
+ parser = Nokogiri::XML::SAX::Parser.new(document)
150
+ parser.parse(@xml)
151
+
152
+ self
153
+ end
154
+ end
155
+ end
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: recover_itunes_ratings
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Milo Winningham
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-10-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rb-scpt
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.0.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.0.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: nokogiri
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.10'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.10'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description:
84
+ email:
85
+ - milo@winningham.net
86
+ executables:
87
+ - recover_itunes_ratings
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".gitignore"
92
+ - ".rspec"
93
+ - Gemfile
94
+ - LICENSE.txt
95
+ - README.md
96
+ - Rakefile
97
+ - bin/console
98
+ - bin/setup
99
+ - exe/recover_itunes_ratings
100
+ - lib/recover_itunes_ratings.rb
101
+ - lib/recover_itunes_ratings/matched_tracks.rb
102
+ - lib/recover_itunes_ratings/runner.rb
103
+ - lib/recover_itunes_ratings/saved_ratings.rb
104
+ - lib/recover_itunes_ratings/version.rb
105
+ - recover_itunes_ratings.gemspec
106
+ - vendor/itunes_parser/nokogiri_sax.rb
107
+ homepage: https://github.com/quadule/recover_itunes_ratings
108
+ licenses:
109
+ - MIT
110
+ metadata: {}
111
+ post_install_message:
112
+ rdoc_options: []
113
+ require_paths:
114
+ - lib
115
+ - vendor
116
+ required_ruby_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ required_rubygems_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ requirements: []
127
+ rubyforge_project:
128
+ rubygems_version: 2.4.8
129
+ signing_key:
130
+ specification_version: 4
131
+ summary: Restores iTunes ratings from an iTunes library XML file.
132
+ test_files: []