19
19
20
20
from beets .plugins import BeetsPlugin
21
21
from beets .ui import Subcommand
22
+ from beets .ui .commands import PromptChoice
22
23
from beets import config
23
24
from beets import ui
24
25
from beets import util
25
26
from os .path import relpath
26
27
from tempfile import NamedTemporaryFile
28
+ import subprocess
27
29
28
30
# Indicate where arguments should be inserted into the command string.
29
31
# If this is missing, they're placed at the end.
30
32
ARGS_MARKER = '$args'
31
33
32
34
35
+ def play (command_str , selection , paths , open_args , log , item_type = 'track' ,
36
+ keep_open = False ):
37
+ """Play items in paths with command_str and optional arguments. If
38
+ keep_open, return to beets, otherwise exit once command runs.
39
+ """
40
+ # Print number of tracks or albums to be played, log command to be run.
41
+ item_type += 's' if len (selection ) > 1 else ''
42
+ ui .print_ (u'Playing {0} {1}.' .format (len (selection ), item_type ))
43
+ log .debug (u'executing command: {} {!r}' , command_str , open_args )
44
+
45
+ try :
46
+ if keep_open :
47
+ command = util .shlex_split (command_str )
48
+ command = command + open_args
49
+ subprocess .call (command )
50
+ else :
51
+ util .interactive_open (open_args , command_str )
52
+ except OSError as exc :
53
+ raise ui .UserError (
54
+ "Could not play the query: {0}" .format (exc ))
55
+
56
+
33
57
class PlayPlugin (BeetsPlugin ):
34
58
35
59
def __init__ (self ):
@@ -40,11 +64,14 @@ def __init__(self):
40
64
'use_folders' : False ,
41
65
'relative_to' : None ,
42
66
'raw' : False ,
43
- # Backwards compatibility. See #1803 and line 74
67
+ # Backwards compatibility. See #1803 and line 155
44
68
'warning_threshold' : - 2 ,
45
69
'warning_treshold' : 100 ,
46
70
})
47
71
72
+ self .register_listener ('before_choose_candidate' ,
73
+ self .before_choose_candidate_listener )
74
+
48
75
def commands (self ):
49
76
play_command = Subcommand (
50
77
'play' ,
@@ -56,44 +83,17 @@ def commands(self):
56
83
action = 'store' ,
57
84
help = u'add additional arguments to the command' ,
58
85
)
59
- play_command .func = self .play_music
86
+ play_command .func = self ._play_command
60
87
return [play_command ]
61
88
62
- def play_music (self , lib , opts , args ):
63
- """Execute query, create temporary playlist and execute player
64
- command passing that playlist, at request insert optional arguments .
89
+ def _play_command (self , lib , opts , args ):
90
+ """The CLI command function for `beet play`. Create a list of paths
91
+ from query, determine if tracks or albums are to be played .
65
92
"""
66
- command_str = config ['play' ]['command' ].get ()
67
- if not command_str :
68
- command_str = util .open_anything ()
69
93
use_folders = config ['play' ]['use_folders' ].get (bool )
70
94
relative_to = config ['play' ]['relative_to' ].get ()
71
- raw = config ['play' ]['raw' ].get (bool )
72
- warning_threshold = config ['play' ]['warning_threshold' ].get (int )
73
- # We use -2 as a default value for warning_threshold to detect if it is
74
- # set or not. We can't use a falsey value because it would have an
75
- # actual meaning in the configuration of this plugin, and we do not use
76
- # -1 because some people might use it as a value to obtain no warning,
77
- # which wouldn't be that bad of a practice.
78
- if warning_threshold == - 2 :
79
- # if warning_threshold has not been set by user, look for
80
- # warning_treshold, to preserve backwards compatibility. See #1803.
81
- # warning_treshold has the correct default value of 100.
82
- warning_threshold = config ['play' ]['warning_treshold' ].get (int )
83
-
84
95
if relative_to :
85
96
relative_to = util .normpath (relative_to )
86
-
87
- # Add optional arguments to the player command.
88
- if opts .args :
89
- if ARGS_MARKER in command_str :
90
- command_str = command_str .replace (ARGS_MARKER , opts .args )
91
- else :
92
- command_str = u"{} {}" .format (command_str , opts .args )
93
- else :
94
- # Don't include the marker in the command.
95
- command_str = command_str .replace (" " + ARGS_MARKER , "" )
96
-
97
97
# Perform search by album and add folders rather than tracks to
98
98
# playlist.
99
99
if opts .album :
@@ -117,13 +117,62 @@ def play_music(self, lib, opts, args):
117
117
paths = [relpath (path , relative_to ) for path in paths ]
118
118
item_type = 'track'
119
119
120
- item_type += 's' if len (selection ) > 1 else ''
121
-
122
120
if not selection :
123
121
ui .print_ (ui .colorize ('text_warning' ,
124
122
u'No {0} to play.' .format (item_type )))
125
123
return
126
124
125
+ open_args = self ._playlist_or_paths (paths )
126
+ command_str = self ._command_str (opts .args )
127
+
128
+ # Check if the selection exceeds configured threshold. If True,
129
+ # cancel, otherwise proceed with play command.
130
+ if not self ._exceeds_threshold (selection , command_str , open_args ,
131
+ item_type ):
132
+ play (command_str , selection , paths , open_args , self ._log ,
133
+ item_type )
134
+
135
+ def _command_str (self , args = None ):
136
+ """Create a command string from the config command and optional args.
137
+ """
138
+ command_str = config ['play' ]['command' ].get ()
139
+ if not command_str :
140
+ return util .open_anything ()
141
+ # Add optional arguments to the player command.
142
+ if args :
143
+ if ARGS_MARKER in command_str :
144
+ return command_str .replace (ARGS_MARKER , args )
145
+ else :
146
+ return u"{} {}" .format (command_str , args )
147
+ else :
148
+ # Don't include the marker in the command.
149
+ return command_str .replace (" " + ARGS_MARKER , "" )
150
+
151
+ def _playlist_or_paths (self , paths ):
152
+ """Return either the raw paths of items or a playlist of the items.
153
+ """
154
+ if config ['play' ]['raw' ]:
155
+ return paths
156
+ else :
157
+ return [self ._create_tmp_playlist (paths )]
158
+
159
+ def _exceeds_threshold (self , selection , command_str , open_args ,
160
+ item_type = 'track' ):
161
+ """Prompt user whether to abort if playlist exceeds threshold. If
162
+ True, cancel playback. If False, execute play command.
163
+ """
164
+ warning_threshold = config ['play' ]['warning_threshold' ].get (int )
165
+ # We use -2 as a default value for warning_threshold to detect if it is
166
+ # set or not. We can't use a falsey value because it would have an
167
+ # actual meaning in the configuration of this plugin, and we do not use
168
+ # -1 because some people might use it as a value to obtain no warning,
169
+ # which wouldn't be that bad of a practice.
170
+ if warning_threshold == - 2 :
171
+ # if warning_threshold has not been set by user, look for
172
+ # warning_treshold, to preserve backwards compatibility. See #1803.
173
+ # warning_treshold has the correct default value of 100.
174
+ warning_threshold = config ['play' ]['warning_treshold' ].get (int )
175
+
127
176
# Warn user before playing any huge playlists.
128
177
if warning_threshold and len (selection ) > warning_threshold :
129
178
ui .print_ (ui .colorize (
@@ -132,20 +181,9 @@ def play_music(self, lib, opts, args):
132
181
len (selection ), item_type )))
133
182
134
183
if ui .input_options ((u'Continue' , u'Abort' )) == 'a' :
135
- return
184
+ return True
136
185
137
- ui .print_ (u'Playing {0} {1}.' .format (len (selection ), item_type ))
138
- if raw :
139
- open_args = paths
140
- else :
141
- open_args = [self ._create_tmp_playlist (paths )]
142
-
143
- self ._log .debug (u'executing command: {} {!r}' , command_str , open_args )
144
- try :
145
- util .interactive_open (open_args , command_str )
146
- except OSError as exc :
147
- raise ui .UserError (
148
- "Could not play the query: {0}" .format (exc ))
186
+ return False
149
187
150
188
def _create_tmp_playlist (self , paths_list ):
151
189
"""Create a temporary .m3u file. Return the filename.
@@ -155,3 +193,21 @@ def _create_tmp_playlist(self, paths_list):
155
193
m3u .write (item + b'\n ' )
156
194
m3u .close ()
157
195
return m3u .name
196
+
197
+ def before_choose_candidate_listener (self , session , task ):
198
+ """Append a "Play" choice to the interactive importer prompt.
199
+ """
200
+ return [PromptChoice ('y' , 'plaY' , self .importer_play )]
201
+
202
+ def importer_play (self , session , task ):
203
+ """Get items from current import task and send to play function.
204
+ """
205
+ selection = task .items
206
+ paths = [item .path for item in selection ]
207
+
208
+ open_args = self ._playlist_or_paths (paths )
209
+ command_str = self ._command_str ()
210
+
211
+ if not self ._exceeds_threshold (selection , command_str , open_args ):
212
+ play (command_str , selection , paths , open_args , self ._log ,
213
+ keep_open = True )
0 commit comments