drum 0.1.12 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/deploy.yml +1 -1
- data/.github/workflows/test.yml +7 -2
- data/Gemfile +13 -2
- data/Gemfile.lock +53 -73
- data/README.md +6 -0
- data/drum.gemspec +2 -2
- data/lib/drum/service/file.rb +5 -4
- data/lib/drum/service/music.rb +117 -0
- data/lib/drum/service/spotify.rb +6 -2
- data/lib/drum/service/stdio.rb +3 -2
- data/lib/drum/utils/persist.rb +48 -40
- data/lib/drum/utils/yaml.rb +11 -0
- data/lib/drum/version.rb +1 -1
- data/lib/drum.rb +14 -4
- metadata +7 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 744d7de8c05fe7f7dc8f843b04b2ccd1a77a2688228369706fa4fca79ddf6c07
|
|
4
|
+
data.tar.gz: c72fc4a18d59cb12017692e1a6fad3cb73cbec24ea73a64adf68279331c3eafb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9cd77d6cf666ef8cd90709b357b59994da0eb20f2a7afaea68f5a60c3aae909d4acf49b407426dd21af4e56caf542e2906dabae73e9270e8221272d543a02e5a
|
|
7
|
+
data.tar.gz: 488848553a20609b7f72bbd11431c46ba03e8d20db4ad428cca5d34df434a061a1ae55a668ee3a304836f42eb84ba5af84bf84e3a5ab0168271f1b86fd3e8f71
|
|
@@ -26,7 +26,7 @@ jobs:
|
|
|
26
26
|
touch ~/.gem/credentials
|
|
27
27
|
chmod 600 ~/.gem/credentials
|
|
28
28
|
echo ":github: Bearer ${{ secrets.GITHUB_TOKEN }}" >> ~/.gem/credentials
|
|
29
|
-
echo ":rubygems:
|
|
29
|
+
echo ":rubygems: ${{ secrets.RUBYGEMS_TOKEN }}" >> ~/.gem/credentials
|
|
30
30
|
- name: Push gem to GitHub Packages
|
|
31
31
|
run: bundle exec gem push --key github --host "https://rubygems.pkg.github.com/fwcd" pkg/drum-*.gem
|
|
32
32
|
- name: Push gem to RubyGems.org
|
data/.github/workflows/test.yml
CHANGED
|
@@ -4,13 +4,18 @@ on: [push]
|
|
|
4
4
|
|
|
5
5
|
jobs:
|
|
6
6
|
test:
|
|
7
|
-
|
|
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:
|
|
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,10 +3,21 @@ 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'
|
|
9
22
|
gem 'rspec', '~> 3.10'
|
|
10
|
-
gem 'ruby-debug-ide', '~> 0.7'
|
|
11
|
-
gem 'debase', '~> 0.2' # seems to be required by ruby-debug-ide
|
|
12
23
|
end
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
drum (0.
|
|
4
|
+
drum (0.2.0)
|
|
5
5
|
highline (~> 2.0)
|
|
6
6
|
jwt (~> 2.2)
|
|
7
7
|
launchy (~> 2.4)
|
|
@@ -9,74 +9,55 @@ PATH
|
|
|
9
9
|
rest-client (~> 2.0)
|
|
10
10
|
rspotify (~> 2.10)
|
|
11
11
|
ruby-limiter (~> 2.1)
|
|
12
|
-
thor (~>
|
|
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.
|
|
19
|
-
public_suffix (>= 2.0.2, <
|
|
20
|
-
|
|
21
|
-
debase-ruby_core_source (>= 0.10.2)
|
|
22
|
-
debase-ruby_core_source (0.10.12)
|
|
23
|
-
diff-lcs (1.4.4)
|
|
18
|
+
addressable (2.8.4)
|
|
19
|
+
public_suffix (>= 2.0.2, < 6.0)
|
|
20
|
+
diff-lcs (1.5.0)
|
|
24
21
|
domain_name (0.5.20190701)
|
|
25
22
|
unf (>= 0.0.5, < 1.0.0)
|
|
26
|
-
faraday (
|
|
27
|
-
faraday-
|
|
28
|
-
faraday-em_synchrony (~> 1.0)
|
|
29
|
-
faraday-excon (~> 1.1)
|
|
30
|
-
faraday-httpclient (~> 1.0.1)
|
|
31
|
-
faraday-net_http (~> 1.0)
|
|
32
|
-
faraday-net_http_persistent (~> 1.1)
|
|
33
|
-
faraday-patron (~> 1.0)
|
|
34
|
-
faraday-rack (~> 1.0)
|
|
35
|
-
multipart-post (>= 1.2, < 3)
|
|
23
|
+
faraday (2.7.4)
|
|
24
|
+
faraday-net_http (>= 2.0, < 3.1)
|
|
36
25
|
ruby2_keywords (>= 0.0.4)
|
|
37
|
-
faraday-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
faraday-net_http_persistent (1.2.0)
|
|
43
|
-
faraday-patron (1.0.0)
|
|
44
|
-
faraday-rack (1.0.0)
|
|
45
|
-
ffi (1.15.4-x64-mingw32)
|
|
46
|
-
hashie (4.1.0)
|
|
47
|
-
highline (2.0.3)
|
|
48
|
-
http-cookie (1.0.4)
|
|
26
|
+
faraday-net_http (3.0.2)
|
|
27
|
+
ffi (1.15.5)
|
|
28
|
+
hashie (5.0.0)
|
|
29
|
+
highline (2.1.0)
|
|
30
|
+
http-cookie (1.0.5)
|
|
49
31
|
domain_name (~> 0.5)
|
|
50
|
-
jwt (2.
|
|
51
|
-
launchy (2.
|
|
52
|
-
addressable (~> 2.
|
|
53
|
-
mime-types (3.
|
|
32
|
+
jwt (2.7.0)
|
|
33
|
+
launchy (2.5.2)
|
|
34
|
+
addressable (~> 2.8)
|
|
35
|
+
mime-types (3.4.1)
|
|
54
36
|
mime-types-data (~> 3.2015)
|
|
55
|
-
mime-types-data (3.
|
|
56
|
-
multi_json (1.15.0)
|
|
37
|
+
mime-types-data (3.2023.0218.1)
|
|
57
38
|
multi_xml (0.6.0)
|
|
58
|
-
multipart-post (2.1.1)
|
|
59
39
|
netrc (0.11.0)
|
|
60
|
-
oauth2 (
|
|
61
|
-
faraday (>= 0.
|
|
40
|
+
oauth2 (2.0.9)
|
|
41
|
+
faraday (>= 0.17.3, < 3.0)
|
|
62
42
|
jwt (>= 1.0, < 3.0)
|
|
63
|
-
multi_json (~> 1.3)
|
|
64
43
|
multi_xml (~> 0.5)
|
|
65
|
-
rack (>= 1.2, <
|
|
66
|
-
|
|
44
|
+
rack (>= 1.2, < 4)
|
|
45
|
+
snaky_hash (~> 2.0)
|
|
46
|
+
version_gem (~> 1.1)
|
|
47
|
+
omniauth (2.1.1)
|
|
67
48
|
hashie (>= 3.4.6)
|
|
68
|
-
rack (>=
|
|
49
|
+
rack (>= 2.2.3)
|
|
69
50
|
rack-protection
|
|
70
|
-
omniauth-oauth2 (1.
|
|
71
|
-
oauth2 (
|
|
72
|
-
omniauth (
|
|
51
|
+
omniauth-oauth2 (1.8.0)
|
|
52
|
+
oauth2 (>= 1.4, < 3)
|
|
53
|
+
omniauth (~> 2.0)
|
|
73
54
|
options (2.3.2)
|
|
74
55
|
progress_bar (1.3.3)
|
|
75
56
|
highline (>= 1.6, < 3)
|
|
76
57
|
options (~> 2.3.0)
|
|
77
|
-
public_suffix (
|
|
78
|
-
rack (
|
|
79
|
-
rack-protection (
|
|
58
|
+
public_suffix (5.0.1)
|
|
59
|
+
rack (3.0.7)
|
|
60
|
+
rack-protection (3.0.6)
|
|
80
61
|
rack
|
|
81
62
|
rake (13.0.6)
|
|
82
63
|
rest-client (2.0.2)
|
|
@@ -88,34 +69,35 @@ GEM
|
|
|
88
69
|
http-cookie (>= 1.0.2, < 2.0)
|
|
89
70
|
mime-types (>= 1.16, < 4.0)
|
|
90
71
|
netrc (~> 0.8)
|
|
91
|
-
rspec (3.
|
|
92
|
-
rspec-core (~> 3.
|
|
93
|
-
rspec-expectations (~> 3.
|
|
94
|
-
rspec-mocks (~> 3.
|
|
95
|
-
rspec-core (3.
|
|
96
|
-
rspec-support (~> 3.
|
|
97
|
-
rspec-expectations (3.
|
|
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)
|
|
98
79
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
99
|
-
rspec-support (~> 3.
|
|
100
|
-
rspec-mocks (3.
|
|
80
|
+
rspec-support (~> 3.12.0)
|
|
81
|
+
rspec-mocks (3.12.5)
|
|
101
82
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
102
|
-
rspec-support (~> 3.
|
|
103
|
-
rspec-support (3.
|
|
104
|
-
rspotify (2.
|
|
105
|
-
addressable (~> 2.
|
|
83
|
+
rspec-support (~> 3.12.0)
|
|
84
|
+
rspec-support (3.12.0)
|
|
85
|
+
rspotify (2.11.1)
|
|
86
|
+
addressable (~> 2.8.0)
|
|
106
87
|
omniauth-oauth2 (>= 1.6)
|
|
107
88
|
rest-client (~> 2.0.2)
|
|
108
|
-
ruby-
|
|
109
|
-
rake (>= 0.8.1)
|
|
110
|
-
ruby-limiter (2.1.0)
|
|
89
|
+
ruby-limiter (2.2.2)
|
|
111
90
|
ruby2_keywords (0.0.5)
|
|
112
|
-
|
|
91
|
+
snaky_hash (2.0.1)
|
|
92
|
+
hashie
|
|
93
|
+
version_gem (~> 1.1, >= 1.1.1)
|
|
94
|
+
thor (1.2.1)
|
|
113
95
|
unf (0.1.4)
|
|
114
96
|
unf_ext
|
|
115
|
-
unf_ext (0.0.8)
|
|
116
|
-
|
|
117
|
-
webrick (1.
|
|
118
|
-
yard (0.9.
|
|
97
|
+
unf_ext (0.0.8.2)
|
|
98
|
+
version_gem (1.1.2)
|
|
99
|
+
webrick (1.8.1)
|
|
100
|
+
yard (0.9.34)
|
|
119
101
|
|
|
120
102
|
PLATFORMS
|
|
121
103
|
ruby
|
|
@@ -123,12 +105,10 @@ PLATFORMS
|
|
|
123
105
|
x86_64-linux
|
|
124
106
|
|
|
125
107
|
DEPENDENCIES
|
|
126
|
-
debase (~> 0.2)
|
|
127
108
|
drum!
|
|
128
109
|
rake (~> 13.0)
|
|
129
110
|
rspec (~> 3.10)
|
|
130
|
-
ruby-debug-ide (~> 0.7)
|
|
131
111
|
yard (~> 0.9)
|
|
132
112
|
|
|
133
113
|
BUNDLED WITH
|
|
134
|
-
2.
|
|
114
|
+
2.4.12
|
data/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# Drum
|
|
2
2
|
|
|
3
|
+
[](https://github.com/fwcd/drum/actions/workflows/test.yml)
|
|
4
|
+
[](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
|

|
|
@@ -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', '~>
|
|
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'
|
data/lib/drum/service/file.rb
CHANGED
|
@@ -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(
|
|
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(
|
|
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(
|
|
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
|
data/lib/drum/service/spotify.rb
CHANGED
|
@@ -555,8 +555,12 @@ module Drum
|
|
|
555
555
|
log.info 'Fetching playlists...'
|
|
556
556
|
Enumerator.new(sp_playlists.length) do |enum|
|
|
557
557
|
sp_playlists.each do |sp_playlist|
|
|
558
|
-
|
|
559
|
-
|
|
558
|
+
begin
|
|
559
|
+
new_playlist = self.from_sp_playlist(sp_playlist)
|
|
560
|
+
enum.yield new_playlist
|
|
561
|
+
rescue StandardError => e
|
|
562
|
+
log.info "Could not download playlist '#{sp_playlist.name}': #{e}"
|
|
563
|
+
end
|
|
560
564
|
end
|
|
561
565
|
end
|
|
562
566
|
when :tracks
|
data/lib/drum/service/stdio.rb
CHANGED
|
@@ -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(
|
|
35
|
+
[Playlist.deserialize(from_yaml(STDIN.read))]
|
|
35
36
|
else
|
|
36
37
|
[]
|
|
37
38
|
end
|
data/lib/drum/utils/persist.rb
CHANGED
|
@@ -1,50 +1,58 @@
|
|
|
1
|
+
require 'drum/utils/log'
|
|
2
|
+
require 'drum/utils/yaml'
|
|
1
3
|
require 'yaml'
|
|
2
4
|
|
|
3
|
-
|
|
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
|
-
#
|
|
13
|
-
#
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
31
|
+
# Loads the hash from the file.
|
|
32
|
+
def load
|
|
33
|
+
@value = from_yaml(File.read(@file_path))
|
|
34
|
+
end
|
|
28
35
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
# Saves the hash to the file.
|
|
37
|
+
def store
|
|
38
|
+
File.write(@file_path, @value.to_yaml)
|
|
39
|
+
end
|
|
33
40
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
data/lib/drum/version.rb
CHANGED
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
|
|
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),
|
|
@@ -125,13 +127,17 @@ module Drum
|
|
|
125
127
|
method_option :group_by_author,
|
|
126
128
|
type: :boolean,
|
|
127
129
|
default: false,
|
|
128
|
-
desc: "Prepend the author name to each playlist's path"
|
|
130
|
+
desc: "Prepend the author name to each playlist's path."
|
|
129
131
|
|
|
130
132
|
method_option :recase_paths,
|
|
131
133
|
type: :string,
|
|
132
134
|
enum: ['kebabcase', 'startcase', 'camelcase', 'pascalcase'],
|
|
135
|
+
desc: "Convert each playlist's path segments to a specific case."
|
|
136
|
+
|
|
137
|
+
method_option :flatten,
|
|
138
|
+
type: :boolean,
|
|
133
139
|
default: false,
|
|
134
|
-
desc:
|
|
140
|
+
desc: 'Clear all playlist paths. Note that flags such as --group-by-author will still be applied.'
|
|
135
141
|
|
|
136
142
|
method_option :note_date,
|
|
137
143
|
type: :boolean,
|
|
@@ -176,6 +182,10 @@ module Drum
|
|
|
176
182
|
playlists = playlists.map do |playlist|
|
|
177
183
|
bar&.increment!
|
|
178
184
|
|
|
185
|
+
if options[:flatten]
|
|
186
|
+
playlist.path = []
|
|
187
|
+
end
|
|
188
|
+
|
|
179
189
|
if options[:group_by_author]
|
|
180
190
|
author_name = playlist.author_id.try { |id| playlist.users[id] }&.display_name || 'Other'
|
|
181
191
|
playlist.path.unshift(author_name)
|
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.
|
|
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:
|
|
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: '
|
|
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: '
|
|
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
|
|
@@ -200,7 +202,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
200
202
|
- !ruby/object:Gem::Version
|
|
201
203
|
version: '0'
|
|
202
204
|
requirements: []
|
|
203
|
-
rubygems_version: 3.2
|
|
205
|
+
rubygems_version: 3.1.2
|
|
204
206
|
signing_key:
|
|
205
207
|
specification_version: 4
|
|
206
208
|
summary: Playlist manager
|