Skip to content

Commit 34ed807

Browse files
committed
added base class for editmeta
1 parent 0d9dd4d commit 34ed807

File tree

4 files changed

+186
-26
lines changed

4 files changed

+186
-26
lines changed

clid/_const.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,3 +213,26 @@
213213
}
214214

215215
FORMAT_PAT = re.compile(r'%.')
216+
217+
TAG_FIELDS = {
218+
'tit': 'title',
219+
'alb': 'album',
220+
'gen': 'genre',
221+
'tno': 'track',
222+
'art': 'artist',
223+
'com': 'comment',
224+
'ala': 'album_artist'
225+
}
226+
227+
228+
DATE_PATTERN = re.compile(r"""(?x)\s*
229+
((?P<year>[0-9]{4}) # YYYY
230+
(-(?P<month>[01][0-9]) # -MM
231+
(-(?P<day>[0-3][0-9]) # -DD
232+
)?)?)?
233+
[ T]?
234+
((?P<hour>[0-2][0-9]) # HH
235+
(:(?P<min>[0-6][0-9]) # :MM
236+
(:(?P<sec>[0-6][0-9]) # :SS
237+
)?)?)?\s*
238+
""")

clid/base.py

Lines changed: 133 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44

55
import curses
66

7+
import stagger
78
import npyscreen as npy
89

10+
from . import _const
11+
912
class ClidActionController(npy.ActionControllerSimple):
1013
"""Base class for the command line at the bottom of the screen"""
1114

@@ -30,10 +33,10 @@ def set_up_handlers(self):
3033
self.handlers[curses.KEY_END] = self.h_end
3134
self.handlers[curses.KEY_HOME] = self.h_home
3235

33-
def h_home(self, input):
36+
def h_home(self, char):
3437
self.cursor_position = 0
3538

36-
def h_end(self, input):
39+
def h_end(self, char):
3740
self.cursor_position = len(self.value)
3841

3942

@@ -75,30 +78,30 @@ def set_up_handlers(self):
7578
self.vim_add_handlers()
7679
self.handlers[curses.ascii.ESC] = self.h_vim_normal_mode # is a bit slow
7780

78-
def h_addch(self, input):
81+
def h_addch(self, char):
7982
if self.parent.in_insert_mode: # add characters only if in insert mode
80-
super().h_addch(input)
83+
super().h_addch(char)
8184

82-
def h_vim_insert_mode(self, input):
85+
def h_vim_insert_mode(self, char):
8386
"""Enter insert mode"""
8487
self.parent.in_insert_mode = True
8588
self.vim_remove_handlers() # else `k`, j`, etc will not be added to text(will still act as keybindings)
8689

87-
def h_vim_normal_mode(self, input):
90+
def h_vim_normal_mode(self, char):
8891
"""Exit insert mode by pressing Esc"""
8992
self.parent.in_insert_mode = False
9093
self.cursor_position -= 1 # just like in vim
9194
self.vim_add_handlers() # removed earlier when going to insert mode
9295

93-
def h_vim_append_char(self, input):
96+
def h_vim_append_char(self, char):
9497
"""Append characters, like `a` in vim"""
95-
self.h_vim_insert_mode(input)
98+
self.h_vim_insert_mode(char)
9699
self.cursor_position += 1
97100

98-
def h_vim_append_char_at_end(self, input):
101+
def h_vim_append_char_at_end(self, char):
99102
"""Add characters to the end of the line, like `A` in vim"""
100-
self.h_vim_insert_mode(input)
101-
self.h_end(input) # go to the end
103+
self.h_vim_insert_mode(char)
104+
self.h_end(char) # go to the end
102105

103106
class ClidVimTitleText(npy.TitleText):
104107
_entry_type = ClidVimTextfield
@@ -122,3 +125,122 @@ class ClidCommandLine(npy.fmFormMuttActive.TextCommandBoxTraditional, ClidTextfi
122125
# # self.color = 'DEFAULT'
123126
# # self.show_bold = False
124127
pass
128+
129+
130+
class ClidEditMeta(npy.ActionFormV2):
131+
"""Edit the metadata of a track.
132+
133+
Attributes:
134+
files(list): List of files whose tags are being edited.
135+
_label_textbox(ClidTextfield):
136+
Text box which acts like a label(cannot be edited).
137+
_title_textbox(ClidTextfield):
138+
Text box with a title, to be used as input field for tags.
139+
in_insert_mode(bool):
140+
Used to decide whether the form is in insert/normal
141+
mode(if vi_keybindings are enabled). This is actually
142+
set as an attribute of the parent form so that all
143+
text boxes in the form are in the same mode.
144+
"""
145+
def __init__(self, *args, **kwags):
146+
super().__init__(*args, **kwags)
147+
self.handlers.update({
148+
'^S': self.h_ok,
149+
'^Q': self.h_cancel
150+
})
151+
self.in_insert_mode = False
152+
self.files = self.parentApp.current_file
153+
154+
def set_textbox(self):
155+
"""Set the text boxes to be used(with or without vim-bindings).
156+
Called by child classes.
157+
"""
158+
if self.parentApp.settings['vim_mode'] == 'true':
159+
self._title_textbox = ClidVimTitleText # vim keybindings if enabled
160+
self._label_textbox = ClidVimTextfield
161+
else:
162+
self._title_textbox = ClidTitleText
163+
self._label_textbox = ClidTextfield
164+
165+
def create(self):
166+
self.set_textbox()
167+
self.tit = self.add(self._title_textbox, name='Title')
168+
self.nextrely += 1
169+
self.alb = self.add(self._title_textbox, name='Album')
170+
self.nextrely += 1
171+
self.art = self.add(self._title_textbox, name='Artist')
172+
self.nextrely += 1
173+
self.ala = self.add(self._title_textbox, name='Album Artist')
174+
self.nextrely += 2
175+
self.gen = self.add(self._title_textbox, name='Genre')
176+
self.nextrely += 1
177+
self.dat = self.add(self._title_textbox, name='Date/Year')
178+
self.nextrely += 1
179+
self.tno = self.add(self._title_textbox, name='Track Number')
180+
self.nextrely += 2
181+
self.com = self.add(self._title_textbox, name='Comment')
182+
183+
def resolve_genre(self, num_gen):
184+
"""Convert numerical genre values to readable values. Genre may be
185+
saved as a str of the format '(int)' by applications like EasyTag.
186+
187+
Args:
188+
num_gen (str): str representing the genre.
189+
190+
Returns:
191+
str: Name of the genre (Electronic, Blues, etc). Returns
192+
num_gen itself if it doesn't match the format.
193+
"""
194+
match = _const.GENRE_PAT.findall(num_gen)
195+
196+
if match:
197+
try:
198+
return _const.GENRES[int(match[0])]
199+
except IndexError:
200+
return ''
201+
else:
202+
return num_gen
203+
204+
def h_ok(self, char):
205+
"""Handler to save the tags"""
206+
self.on_ok()
207+
208+
def h_cancel(self, char):
209+
"""Handler to cancel the operation"""
210+
self.on_cancel()
211+
212+
def on_cancel(self): # char is for handlers
213+
"""Switch to standard view at once without saving"""
214+
self.editing = False
215+
self.parentApp.switchForm("MAIN")
216+
217+
def on_ok(self): # char is for handlers
218+
"""Save and switch to standard view"""
219+
# date format check
220+
m = _const.DATE_PATTERN.match(self.dat.value)
221+
if m is None or m.end() != len(self.dat.value):
222+
npy.notify_confirm(message='Date should be of the form YYYY-MM-DD HH:MM:SS',
223+
title='Invalid Date Format', editw=1)
224+
return None
225+
# track number check
226+
track = self.tno.value or '0' # automatically converted to int by stagger
227+
if not track.isnumeric():
228+
npy.notify_confirm(message='Track number can only take integer values',
229+
title='Invalid Track Number', editw=1)
230+
return None
231+
# FIXME: values of tags are reset to initial when ok is pressed(no prob with ^S)
232+
233+
for mp3 in self.files:
234+
meta = stagger.read_tag(mp3)
235+
for tbox, field in _const.TAG_FIELDS:
236+
# equivalent to `meta.title = self.tit.value`...
237+
tag = getattr(self, tbox).value # get value to be written to file
238+
setattr(meta, field, tag)
239+
meta.write()
240+
241+
main_form = self.parentApp.getForm("MAIN")
242+
# show the new tags in the status line
243+
main_form.wMain.set_status(filename=main_form.wMain.get_selected(), force=True)
244+
245+
self.editing = False
246+
self.parentApp.switchForm("MAIN")

clid/database.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,16 @@ def load_files_and_set_values(self):
7474
self.meta_cache = dict()
7575

7676

77-
def parse_meta_for_status(self, filename):
77+
def parse_meta_for_status(self, filename, force=False):
7878
"""Make a string like 'artist - album - track_number. title' from a filename
7979
(using file_dict and data[attributes])
8080
8181
Args:
8282
filename: the filename(*not* the absolute path)
83+
force: reconstruct the string even if it has already been made
8384
"""
8485
temp = self.pre_format # make a copy of format and replace specifiers with tags
85-
if not filename in self.meta_cache:
86+
if not filename in self.meta_cache or force:
8687
try:
8788
meta = stagger.read_tag(self.file_dict[filename])
8889
for spec in self.specifiers: # str to convert track number to str if given

clid/main.py

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,16 @@ class ClidMultiline(npy.MultiLine):
5555
"""MultiLine class to be used by clid. `Esc` has been modified to revert
5656
the screen back to the normal view after a searh has been performed
5757
(the search results will be shown; blank if no matches are found)
58+
or if files have been selected. If files are selected *and* search
59+
has been performed, selected files will be kept intact and search will
60+
be reverted
5861
5962
Attributes:
6063
space_selected_values(list):
6164
Stores list of files which was selected for batch tagging using <Space>
65+
_space_selected_values(list):
66+
(property) List of indexes of space selected files *in context with
67+
self.parent.wMain.values*
6268
6369
Note:
6470
self.parent refers to ClidInterface -> class
@@ -73,16 +79,20 @@ def __init__(self, *args, **kwargs):
7379
curses.ascii.SP: self.h_multi_select,
7480
curses.ascii.ESC: self.h_revert_escape,
7581
})
76-
82+
7783
self.allow_filtering = False # does NOT refer to search invoked with '/'
7884
self.space_selected_values = []
79-
85+
8086
smooth = self.parent.parentApp.settings['smooth_scroll'] # is smooth scroll enabled ?
8187
self.slow_scroll = True if smooth == 'true' else False
8288

83-
def set_status(self, filename):
89+
@property
90+
def _space_selected_values(self):
91+
return [self.values.index(file) for file in self.space_selected_values if file in self.values]
92+
93+
def set_status(self, filename, **kwargs):
8494
"""Set the the value of self.parent.wStatus2 with metadata of file under cursor."""
85-
self.parent.wStatus2.value = self.parent.value.parse_meta_for_status(filename=filename)
95+
self.parent.wStatus2.value = self.parent.value.parse_meta_for_status(filename=filename, **kwargs)
8696
self.parent.display()
8797

8898
def get_selected(self):
@@ -101,16 +111,15 @@ def h_revert_escape(self, char):
101111
if self.parent.after_search_now_filter_view: # if screen is showing search results
102112
self.values = self.parent.value.get_all_values() # revert
103113
self.parent.after_search_now_filter_view = False
114+
elif len(self.space_selected_values):
115+
self.space_selected_values = []
104116

105117
self.display()
106118

107119
# TODO: make it faster
108120

109121
def filter_value(self, index):
110-
if self._filter in self.display_value(self.values[index]).lower(): # ignore case
111-
return True
112-
else:
113-
return False
122+
return self._filter in self.display_value(self.values[index]).lower # ignore case
114123

115124
def h_switch_to_settings(self, char):
116125
self.parent.parentApp.switchForm("SETTINGS")
@@ -149,17 +158,22 @@ def h_cursor_page_up(self, char):
149158
self.set_status(self.get_selected())
150159

151160
def h_select(self, char):
152-
self.parent.parentApp.current_file = self.parent.value.file_dict[self.get_selected()]
161+
self.parent.parentApp.current_file = [self.parent.value.file_dict[self.get_selected()]]
153162
self.parent.parentApp.switchForm("EDIT")
154163

155164
def h_multi_select(self, char):
156-
if self.cursor_line in self.space_selected_values:
157-
self.space_selected_values.remove(self.cursor_line)
165+
"""Add or remove current line from list of lines
166+
to be highlighted, when <Space> is pressed.
167+
"""
168+
current = self.get_selected()
169+
if current in self.space_selected_values:
170+
self.space_selected_values.remove(current)
158171
else:
159-
self.space_selected_values.append(self.cursor_line)
172+
self.space_selected_values.append(current)
160173

161174
def _set_line_highlighting(self, line, value_indexer):
162-
if value_indexer in self.space_selected_values:
175+
"""Highlight files which were selected with <Space>"""
176+
if value_indexer in self._space_selected_values:
163177
self.set_is_line_important(line, True) # mark as important
164178
else:
165179
self.set_is_line_important(line, False)

0 commit comments

Comments
 (0)