drum 0.1.14 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da330d9d023cd2cab59a1e210a1363fa00569fed1c5edcf382424112ba2e5bc3
4
- data.tar.gz: 7d339df36d5438a64ef33887ccfe084a350d8737cf0444fbe95adb901e1f6bd3
3
+ metadata.gz: 744d7de8c05fe7f7dc8f843b04b2ccd1a77a2688228369706fa4fca79ddf6c07
4
+ data.tar.gz: c72fc4a18d59cb12017692e1a6fad3cb73cbec24ea73a64adf68279331c3eafb
5
5
  SHA512:
6
- metadata.gz: f023760cc5107baf38d87c60cba3d404732d14552c50a904358abd8153d28861386e53f359f4ba3735e7e66b6566dac529706ed29b15853a18c7c3c10ba53302
7
- data.tar.gz: fdd9371a7f21d5a879444f4d2c39552e5b15af27459c0126264171633a86b4c7f5c460405a9af63b3758aa130cf9cea919e9878c77b6b2d8726eccd2f3c1a439
6
+ metadata.gz: 9cd77d6cf666ef8cd90709b357b59994da0eb20f2a7afaea68f5a60c3aae909d4acf49b407426dd21af4e56caf542e2906dabae73e9270e8221272d543a02e5a
7
+ data.tar.gz: 488848553a20609b7f72bbd11431c46ba03e8d20db4ad428cca5d34df434a061a1ae55a668ee3a304836f42eb84ba5af84bf84e3a5ab0168271f1b86fd3e8f71
@@ -4,13 +4,18 @@ on: [push]
4
4
 
5
5
  jobs:
6
6
  test:
7
- runs-on: ubuntu-latest
7
+ strategy:
8
+ matrix:
9
+ os: ['ubuntu-latest', 'macos-latest']
10
+ ruby: ['2.7.0', '3.2.0']
11
+
12
+ runs-on: '${{ matrix.os }}'
8
13
  steps:
9
14
  - uses: actions/checkout@v2
10
15
  - name: Set up Ruby
11
16
  uses: ruby/setup-ruby@v1
12
17
  with:
13
- ruby-version: 2.7.0
18
+ ruby-version: '${{ matrix.ruby }}'
14
19
  - name: Install dependencies
15
20
  run: |
16
21
  gem install bundler -v 2.2.0
data/Gemfile CHANGED
@@ -3,6 +3,19 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in drum.gemspec
4
4
  gemspec
5
5
 
6
+ # Install AppleScript bridge on macOS
7
+ #
8
+ # Note: End-users installing drum via 'gem install'
9
+ # will need to install rb-scpt manually to use the integration.
10
+ #
11
+ # TODO: Investigate whether this could be done automatically
12
+ # See https://stackoverflow.com/a/10249133 for more info.
13
+ # Unfortunately checking the OS in the gemspec is insufficient,
14
+ # since the file is evaluated at packaging time.
15
+ if RUBY_PLATFORM =~ /\bdarwin/
16
+ gem 'rb-scpt', '~> 1.0'
17
+ end
18
+
6
19
  group :development, :test do
7
20
  gem 'rake', '~> 13.0'
8
21
  gem 'yard', '~> 0.9'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- drum (0.1.14)
4
+ drum (0.2.0)
5
5
  highline (~> 2.0)
6
6
  jwt (~> 2.2)
7
7
  launchy (~> 2.4)
@@ -9,55 +9,55 @@ PATH
9
9
  rest-client (~> 2.0)
10
10
  rspotify (~> 2.10)
11
11
  ruby-limiter (~> 2.1)
12
- thor (~> 0.20)
12
+ thor (~> 1.2)
13
13
  webrick (~> 1.7)
14
14
 
15
15
  GEM
16
16
  remote: https://rubygems.org/
17
17
  specs:
18
- addressable (2.8.0)
19
- public_suffix (>= 2.0.2, < 5.0)
20
- diff-lcs (1.4.4)
18
+ addressable (2.8.4)
19
+ public_suffix (>= 2.0.2, < 6.0)
20
+ diff-lcs (1.5.0)
21
21
  domain_name (0.5.20190701)
22
22
  unf (>= 0.0.5, < 1.0.0)
23
- faraday (2.3.0)
24
- faraday-net_http (~> 2.0)
23
+ faraday (2.7.4)
24
+ faraday-net_http (>= 2.0, < 3.1)
25
25
  ruby2_keywords (>= 0.0.4)
26
- faraday-net_http (2.0.2)
27
- ffi (1.15.5-x64-mingw32)
26
+ faraday-net_http (3.0.2)
27
+ ffi (1.15.5)
28
28
  hashie (5.0.0)
29
- highline (2.0.3)
30
- http-cookie (1.0.4)
29
+ highline (2.1.0)
30
+ http-cookie (1.0.5)
31
31
  domain_name (~> 0.5)
32
- jwt (2.3.0)
33
- launchy (2.5.0)
34
- addressable (~> 2.7)
32
+ jwt (2.7.0)
33
+ launchy (2.5.2)
34
+ addressable (~> 2.8)
35
35
  mime-types (3.4.1)
36
36
  mime-types-data (~> 3.2015)
37
- mime-types-data (3.2022.0105)
38
- multi_json (1.15.0)
37
+ mime-types-data (3.2023.0218.1)
39
38
  multi_xml (0.6.0)
40
39
  netrc (0.11.0)
41
- oauth2 (1.4.9)
40
+ oauth2 (2.0.9)
42
41
  faraday (>= 0.17.3, < 3.0)
43
42
  jwt (>= 1.0, < 3.0)
44
- multi_json (~> 1.3)
45
43
  multi_xml (~> 0.5)
46
- rack (>= 1.2, < 3)
47
- omniauth (2.1.0)
44
+ rack (>= 1.2, < 4)
45
+ snaky_hash (~> 2.0)
46
+ version_gem (~> 1.1)
47
+ omniauth (2.1.1)
48
48
  hashie (>= 3.4.6)
49
49
  rack (>= 2.2.3)
50
50
  rack-protection
51
- omniauth-oauth2 (1.7.2)
52
- oauth2 (~> 1.4)
53
- omniauth (>= 1.9, < 3)
51
+ omniauth-oauth2 (1.8.0)
52
+ oauth2 (>= 1.4, < 3)
53
+ omniauth (~> 2.0)
54
54
  options (2.3.2)
55
55
  progress_bar (1.3.3)
56
56
  highline (>= 1.6, < 3)
57
57
  options (~> 2.3.0)
58
- public_suffix (4.0.7)
59
- rack (2.2.3)
60
- rack-protection (2.2.0)
58
+ public_suffix (5.0.1)
59
+ rack (3.0.7)
60
+ rack-protection (3.0.6)
61
61
  rack
62
62
  rake (13.0.6)
63
63
  rest-client (2.0.2)
@@ -69,31 +69,35 @@ GEM
69
69
  http-cookie (>= 1.0.2, < 2.0)
70
70
  mime-types (>= 1.16, < 4.0)
71
71
  netrc (~> 0.8)
72
- rspec (3.10.0)
73
- rspec-core (~> 3.10.0)
74
- rspec-expectations (~> 3.10.0)
75
- rspec-mocks (~> 3.10.0)
76
- rspec-core (3.10.1)
77
- rspec-support (~> 3.10.0)
78
- rspec-expectations (3.10.1)
72
+ rspec (3.12.0)
73
+ rspec-core (~> 3.12.0)
74
+ rspec-expectations (~> 3.12.0)
75
+ rspec-mocks (~> 3.12.0)
76
+ rspec-core (3.12.2)
77
+ rspec-support (~> 3.12.0)
78
+ rspec-expectations (3.12.3)
79
79
  diff-lcs (>= 1.2.0, < 2.0)
80
- rspec-support (~> 3.10.0)
81
- rspec-mocks (3.10.2)
80
+ rspec-support (~> 3.12.0)
81
+ rspec-mocks (3.12.5)
82
82
  diff-lcs (>= 1.2.0, < 2.0)
83
- rspec-support (~> 3.10.0)
84
- rspec-support (3.10.2)
83
+ rspec-support (~> 3.12.0)
84
+ rspec-support (3.12.0)
85
85
  rspotify (2.11.1)
86
86
  addressable (~> 2.8.0)
87
87
  omniauth-oauth2 (>= 1.6)
88
88
  rest-client (~> 2.0.2)
89
89
  ruby-limiter (2.2.2)
90
90
  ruby2_keywords (0.0.5)
91
- thor (0.20.3)
91
+ snaky_hash (2.0.1)
92
+ hashie
93
+ version_gem (~> 1.1, >= 1.1.1)
94
+ thor (1.2.1)
92
95
  unf (0.1.4)
93
96
  unf_ext
94
- unf_ext (0.0.8.1)
95
- webrick (1.7.0)
96
- yard (0.9.26)
97
+ unf_ext (0.0.8.2)
98
+ version_gem (1.1.2)
99
+ webrick (1.8.1)
100
+ yard (0.9.34)
97
101
 
98
102
  PLATFORMS
99
103
  ruby
@@ -107,4 +111,4 @@ DEPENDENCIES
107
111
  yard (~> 0.9)
108
112
 
109
113
  BUNDLED WITH
110
- 2.2.29
114
+ 2.4.12
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Drum
2
2
 
3
+ [![Test](https://github.com/fwcd/drum/actions/workflows/test.yml/badge.svg)](https://github.com/fwcd/drum/actions/workflows/test.yml)
4
+ [![Documentation](https://github.com/fwcd/drum/actions/workflows/documentation.yml/badge.svg)](https://fwcd.github.io/drum)
5
+
3
6
  A small tool for copying your playlists across music streaming services. Think `rsync`, but for playlists.
4
7
 
5
8
  ![Icon](artwork/icon128.png)
@@ -21,6 +24,7 @@ The basic usage pattern is always `drum cp [source] [destination]` where `source
21
24
  * `@spotify/playlists`
22
25
  * `@spotify/tracks`
23
26
  * `@applemusic/playlists`
27
+ * `@music/playlists` (referring to the local Apple Music (`Music.app`) library on macOS)
24
28
  * `@stdin`
25
29
  * `@stdout`
26
30
  * A dash `-`, synonymous with `@stdin` and `@stdout`, depending on usage
@@ -67,6 +71,8 @@ To run the application, run `bundle exec bin/drum`. You can also run `bin/consol
67
71
 
68
72
  To package the application into a gem, run `bundle exec rake build`. The built gem should then be located in `pkg`.
69
73
 
74
+ > Note: If you wish to install `drum` using `gem install`, you may need to install additional gems such as `rb-scpt` on macOS to use platform-specific integrations. See [the `Gemfile`](Gemfile) for more information.
75
+
70
76
  To install the gem, run `bundle exec rake install`.
71
77
 
72
78
  To generate the documentation, run `bundle exec rake yard`.
data/drum.gemspec CHANGED
@@ -23,8 +23,8 @@ Gem::Specification.new do |spec|
23
23
  spec.bindir = 'bin'
24
24
  spec.executables = ['drum']
25
25
  spec.require_paths = ['lib']
26
-
27
- spec.add_dependency 'thor', '~> 0.20'
26
+
27
+ spec.add_dependency 'thor', '~> 1.2'
28
28
  spec.add_dependency 'rspotify', '~> 2.10'
29
29
  spec.add_dependency 'launchy', '~> 2.4'
30
30
  spec.add_dependency 'ruby-limiter', '~> 2.1'
@@ -2,6 +2,7 @@ require 'drum/model/playlist'
2
2
  require 'drum/model/ref'
3
3
  require 'drum/service/service'
4
4
  require 'drum/utils/log'
5
+ require 'drum/utils/yaml'
5
6
  require 'pathname'
6
7
  require 'uri'
7
8
  require 'yaml'
@@ -9,7 +10,7 @@ require 'yaml'
9
10
  module Drum
10
11
  # A service that reads/writes playlists to/from YAML files.
11
12
  class FileService < Service
12
- include Log
13
+ include Log, YAMLUtils
13
14
 
14
15
  def name
15
16
  'file'
@@ -45,12 +46,12 @@ module Drum
45
46
  if base_path.directory?
46
47
  Dir.glob("#{base_path}/**/*.{yaml,yml}").map do |p|
47
48
  path = Pathname.new(p)
48
- playlist = Playlist.deserialize(YAML.load(path.read))
49
+ playlist = Playlist.deserialize(from_yaml(path.read))
49
50
  playlist.path = path.relative_path_from(base_path).parent.each_filename.to_a
50
51
  playlist
51
52
  end
52
53
  else
53
- [Playlist.deserialize(YAML.load(base_path.read))]
54
+ [Playlist.deserialize(from_yaml(base_path.read))]
54
55
  end
55
56
  end
56
57
 
@@ -74,7 +75,7 @@ module Drum
74
75
  end
75
76
 
76
77
  length = 6
77
- while playlist_path[length].exist? && Playlist.deserialize(YAML.load(playlist_path[length].read)).id != playlist.id
78
+ while playlist_path[length].exist? && Playlist.deserialize(from_yaml(playlist_path[length].read)).id != playlist.id
78
79
  length += 1
79
80
  end
80
81
 
@@ -0,0 +1,117 @@
1
+ require 'drum/model/album'
2
+ require 'drum/model/artist'
3
+ require 'drum/model/ref'
4
+ require 'drum/model/playlist'
5
+ require 'drum/model/track'
6
+ require 'drum/service/service'
7
+ require 'drum/utils/log'
8
+
9
+ module Drum
10
+ # A service that uses AppleScript to interact with the local Apple Music (Music.app) library.
11
+ #
12
+ # Useful resources:
13
+ # - https://github.com/BrendanThompson/rb-scpt/tree/develop/sample
14
+ # - https://stackoverflow.com/questions/12964766/create-playlist-in-itunes-with-python-and-scripting-bridge
15
+ # - https://dougscripts.com/itunes/itinfo/info01.php
16
+ # - https://dougscripts.com/itunes/itinfo/info02.php
17
+ # - https://dougscripts.com/itunes/itinfo/info03.php
18
+ # - https://github.com/sorah/jockey/blob/master/add.rb
19
+ class MusicService < Service
20
+ include Log
21
+
22
+ def name
23
+ 'music'
24
+ end
25
+
26
+ def require_rb_scpt
27
+ begin
28
+ require 'rb-scpt'
29
+ rescue LoadError
30
+ raise "Using the local Apple Music importer requires the 'rb-scpt' gem (which requires macOS)"
31
+ end
32
+ end
33
+
34
+ def get_library_proxy
35
+ self.require_rb_scpt
36
+ music = Appscript.app('Music')
37
+ source = music.sources[1]
38
+ unless source.kind.get == :library
39
+ raise 'Could not get music library source'
40
+ end
41
+ return source
42
+ end
43
+
44
+ # Ref parsing
45
+
46
+ def parse_ref(raw_ref)
47
+ if raw_ref.is_token
48
+ location = case raw_ref.text
49
+ when "#{self.name}/playlists" then :playlists
50
+ else return nil
51
+ end
52
+ Ref.new(self.name, :special, location)
53
+ else
54
+ nil
55
+ end
56
+ end
57
+
58
+ # Upload helpers
59
+
60
+ def to_track_proxy(library_proxy, playlist, track)
61
+ # Get track metadata
62
+ name = track.name
63
+ artists = track.artist_ids.map { |id| playlist.artists[id].name }
64
+
65
+ # Match the track with a track in the local library
66
+ # TODO: Don't require exact 100% matches
67
+ name_query = Appscript.its.name.eq(name)
68
+ artists_query = artists
69
+ .map { |a| Appscript.its.artist.contains(a) }
70
+ .reduce { |q1, q2| q1.or(q2) }
71
+ query = name_query.and(artists_query)
72
+
73
+ # Find a matching track in the track
74
+ # TODO: Instead of choosing the first, prefer iTunes-matched/higher bitrate ones etc.
75
+ track_proxy = library_proxy.tracks[query].get.first
76
+
77
+ if track_proxy.nil?
78
+ log.warn "No match found for '#{track.name}' by '#{artists.first}'"
79
+ else
80
+ log.info "Matched '#{track.name}' with '#{track_proxy.name.get}' by '#{track_proxy.artist.get}'"
81
+ end
82
+ track_proxy
83
+ end
84
+
85
+ def upload_playlist(library_proxy, playlist)
86
+ playlist_proxy = library_proxy.make(new: :playlist, with_properties: {
87
+ name: playlist.name,
88
+ description: playlist.description,
89
+ }.compact)
90
+
91
+ playlist.tracks.each do |track|
92
+ track_proxy = self.to_track_proxy(library_proxy, playlist, track)
93
+ unless track_proxy.nil?
94
+ track_proxy.duplicate(to: playlist_proxy)
95
+ end
96
+ end
97
+ end
98
+
99
+ # Service
100
+
101
+ def download(ref)
102
+ raise 'Downloading is not implemented yet'
103
+ end
104
+
105
+ def upload(ref, playlists)
106
+ library_proxy = self.get_library_proxy
107
+
108
+ unless ref.resource_type == :special && ref.resource_location == :playlists
109
+ raise "Cannot upload to anything other than @#{self.name}/playlists yet!"
110
+ end
111
+
112
+ playlists.each do |playlist|
113
+ self.upload_playlist(library_proxy, playlist)
114
+ end
115
+ end
116
+ end
117
+ end
@@ -2,12 +2,13 @@ require 'drum/model/playlist'
2
2
  require 'drum/model/ref'
3
3
  require 'drum/service/service'
4
4
  require 'drum/utils/log'
5
+ require 'drum/utils/yaml'
5
6
  require 'yaml'
6
7
 
7
8
  module Drum
8
9
  # A service that reads from stdin and writes to stdout.
9
10
  class StdioService < Service
10
- include Log
11
+ include Log, YAMLUtils
11
12
 
12
13
  def name
13
14
  'stdio'
@@ -31,7 +32,7 @@ module Drum
31
32
  def download(playlist_ref)
32
33
  if playlist_ref.resource_location.include?(:stdin)
33
34
  # TODO: Support multiple, --- delimited playlists?
34
- [Playlist.deserialize(YAML.load(STDIN.read))]
35
+ [Playlist.deserialize(from_yaml(STDIN.read))]
35
36
  else
36
37
  []
37
38
  end
@@ -1,50 +1,58 @@
1
+ require 'drum/utils/log'
2
+ require 'drum/utils/yaml'
1
3
  require 'yaml'
2
4
 
3
- # A wrapper around a hash that stores values persistently in a YAML.
4
- #
5
- # @!attribute value
6
- # @return [Hash] The wrapped hash
7
- class Drum::PersistentHash
8
- attr_reader :value
9
-
10
- # Creates a new persistent hash.
5
+ module Drum
6
+ # A wrapper around a hash that stores values persistently in a YAML.
11
7
  #
12
- # @param [String] file_path The path to the stored YAML file (may be non-existent).
13
- # @param [Hash] value The initial default value, if the file doesn't exist yet or is malformed
14
- def initialize(file_path, value={})
15
- @file_path = file_path
16
- begin
17
- self.load
18
- rescue StandardError => e
19
- @value = value
20
- self.store
8
+ # @!attribute value
9
+ # @return [Hash] The wrapped hash
10
+ class PersistentHash
11
+ include Log, YAMLUtils
12
+ attr_reader :value
13
+
14
+ # Creates a new persistent hash.
15
+ #
16
+ # @param [String] file_path The path to the stored YAML file (may be non-existent).
17
+ # @param [Hash] value The initial default value, if the file doesn't exist yet or is malformed
18
+ def initialize(file_path, value={})
19
+ @file_path = file_path
20
+ begin
21
+ self.load
22
+ rescue StandardError => e
23
+ unless e.is_a?(Errno::ENOENT)
24
+ log.warn "Could not load #{file_path}: #{e.inspect}... creating from scratch"
25
+ end
26
+ @value = value
27
+ self.store
28
+ end
21
29
  end
22
- end
23
30
 
24
- # Loads the hash from the file.
25
- def load
26
- @value = YAML.load(File.read(@file_path))
27
- end
31
+ # Loads the hash from the file.
32
+ def load
33
+ @value = from_yaml(File.read(@file_path))
34
+ end
28
35
 
29
- # Saves the hash to the file.
30
- def store
31
- File.write(@file_path, @value.to_yaml)
32
- end
36
+ # Saves the hash to the file.
37
+ def store
38
+ File.write(@file_path, @value.to_yaml)
39
+ end
33
40
 
34
- # Writes a mapping to the hash and stores it on disk.
35
- #
36
- # @param [Object] key The key to use.
37
- # @param [Object] value The value to map the key to.
38
- def []=(key, value)
39
- @value[key] = value
40
- store
41
- end
41
+ # Writes a mapping to the hash and stores it on disk.
42
+ #
43
+ # @param [Object] key The key to use.
44
+ # @param [Object] value The value to map the key to.
45
+ def []=(key, value)
46
+ @value[key] = value
47
+ store
48
+ end
42
49
 
43
- # Reads a mapping from the hash.
44
- #
45
- # @param [Object] key The key to use.
46
- # @return [Object] The value the key is mapped to.
47
- def [](key)
48
- @value[key]
50
+ # Reads a mapping from the hash.
51
+ #
52
+ # @param [Object] key The key to use.
53
+ # @return [Object] The value the key is mapped to.
54
+ def [](key)
55
+ @value[key]
56
+ end
49
57
  end
50
58
  end
@@ -0,0 +1,11 @@
1
+ require 'date'
2
+ require 'yaml'
3
+
4
+ module Drum
5
+ module YAMLUtils
6
+ # Deserializes YAML with a number of standard permitted classes.
7
+ def from_yaml(yaml)
8
+ YAML.load(yaml, permitted_classes: [Date, DateTime, Symbol, Time])
9
+ end
10
+ end
11
+ end
data/lib/drum/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Drum
2
- VERSION = '0.1.14'
2
+ VERSION = '0.2.0'
3
3
  end
data/lib/drum.rb CHANGED
@@ -2,6 +2,7 @@ require 'drum/model/raw_ref'
2
2
  require 'drum/service/applemusic'
3
3
  require 'drum/service/file'
4
4
  require 'drum/service/mock'
5
+ require 'drum/service/music'
5
6
  require 'drum/service/service'
6
7
  require 'drum/service/spotify'
7
8
  require 'drum/service/stdio'
@@ -25,8 +26,8 @@ module Drum
25
26
 
26
27
  @hl = HighLine.new
27
28
 
28
- # Set up .drum directory
29
- @dot_dir = Pathname.new(Dir.home) / '.drum'
29
+ # Set up directory for persisted state
30
+ @dot_dir = Pathname.new(Dir.home) / '.local' / 'state' / 'drum'
30
31
  @dot_dir.mkdir unless @dot_dir.directory?
31
32
 
32
33
  @cache_dir = @dot_dir / 'cache'
@@ -35,6 +36,7 @@ module Drum
35
36
  # Declare services in descending order of parse priority
36
37
  @services = [
37
38
  MockService.new,
39
+ MusicService.new,
38
40
  StdioService.new,
39
41
  AppleMusicService.new(@cache_dir),
40
42
  SpotifyService.new(@cache_dir),
@@ -130,7 +132,6 @@ module Drum
130
132
  method_option :recase_paths,
131
133
  type: :string,
132
134
  enum: ['kebabcase', 'startcase', 'camelcase', 'pascalcase'],
133
- default: false,
134
135
  desc: "Convert each playlist's path segments to a specific case."
135
136
 
136
137
  method_option :flatten,
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: drum
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.14
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - fwcd
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-05-08 00:00:00.000000000 Z
11
+ date: 2024-01-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.20'
19
+ version: '1.2'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0.20'
26
+ version: '1.2'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rspotify
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -171,12 +171,14 @@ files:
171
171
  - lib/drum/service/applemusic.rb
172
172
  - lib/drum/service/file.rb
173
173
  - lib/drum/service/mock.rb
174
+ - lib/drum/service/music.rb
174
175
  - lib/drum/service/service.rb
175
176
  - lib/drum/service/spotify.rb
176
177
  - lib/drum/service/stdio.rb
177
178
  - lib/drum/utils/ext.rb
178
179
  - lib/drum/utils/log.rb
179
180
  - lib/drum/utils/persist.rb
181
+ - lib/drum/utils/yaml.rb
180
182
  - lib/drum/version.rb
181
183
  - userdoc/playlist-format.md
182
184
  homepage: https://github.com/fwcd/drum.git