1 /***************************************************************************
2 * Copyright (C) 2008-2021 by Andrzej Rybczak *
3 * andrzej@rybczak.net *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
20
21 #include "screens/tiny_tag_editor.h"
22
23 #ifdef HAVE_TAGLIB_H
24
25 #include <boost/locale/conversion.hpp>
26
27 // taglib includes
28 #include <fileref.h>
29 #include <tag.h>
30
31 #include "curses/menu_impl.h"
32 #include "screens/browser.h"
33 #include "charset.h"
34 #include "display.h"
35 #include "helpers.h"
36 #include "global.h"
37 #include "screens/song_info.h"
38 #include "screens/playlist.h"
39 #include "screens/search_engine.h"
40 #include "statusbar.h"
41 #include "screens/tag_editor.h"
42 #include "title.h"
43 #include "tags.h"
44 #include "screens/screen_switcher.h"
45 #include "utility/string.h"
46
47 using Global::MainHeight;
48 using Global::MainStartY;
49
50 TinyTagEditor *myTinyTagEditor;
51
TinyTagEditor()52 TinyTagEditor::TinyTagEditor()
53 : Screen(NC::Menu<NC::Buffer>(0, MainStartY, COLS, MainHeight, "", Config.main_color, NC::Border()))
54 {
55 w.setHighlightPrefix(Config.current_item_prefix);
56 w.setHighlightSuffix(Config.current_item_suffix);
57 w.cyclicScrolling(Config.use_cyclic_scrolling);
58 w.centeredCursor(Config.centered_cursor);
59 w.setItemDisplayer([](NC::Menu<NC::Buffer> &menu) {
60 menu << menu.drawn()->value();
61 });
62 }
63
resize()64 void TinyTagEditor::resize()
65 {
66 size_t x_offset, width;
67 getWindowResizeParams(x_offset, width);
68 w.resize(width, MainHeight);
69 w.moveTo(x_offset, MainStartY);
70 hasToBeResized = 0;
71 }
72
switchTo()73 void TinyTagEditor::switchTo()
74 {
75 using Global::myScreen;
76 if (itsEdited.isStream())
77 {
78 Statusbar::print("Streams can't be edited");
79 }
80 else if (getTags())
81 {
82 m_previous_screen = myScreen;
83 SwitchTo::execute(this);
84 drawHeader();
85 }
86 else
87 {
88 std::string full_path;
89 if (itsEdited.isFromDatabase())
90 full_path += Config.mpd_music_dir;
91 full_path += itsEdited.getURI();
92
93 const char msg[] = "Couldn't read file \"%1%\"";
94 Statusbar::printf(msg, wideShorten(full_path, COLS-const_strlen(msg)));
95 }
96 }
97
title()98 std::wstring TinyTagEditor::title()
99 {
100 return L"Tiny tag editor";
101 }
102
mouseButtonPressed(MEVENT me)103 void TinyTagEditor::mouseButtonPressed(MEVENT me)
104 {
105 if (w.empty() || !w.hasCoords(me.x, me.y) || size_t(me.y) >= w.size())
106 return;
107 if (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED))
108 {
109 if (!w.Goto(me.y))
110 return;
111 if (me.bstate & BUTTON3_PRESSED)
112 {
113 w.refresh();
114 runAction();
115 }
116 }
117 else
118 Screen<WindowType>::mouseButtonPressed(me);
119 }
120
121 /**********************************************************************/
122
actionRunnable()123 bool TinyTagEditor::actionRunnable()
124 {
125 return !w.empty();
126 }
127
runAction()128 void TinyTagEditor::runAction()
129 {
130 size_t option = w.choice();
131 if (option < 19) // separator after comment
132 {
133 Statusbar::ScopedLock slock;
134 size_t pos = option-8;
135 Statusbar::put() << NC::Format::Bold << SongInfo::Tags[pos].Name << ": " << NC::Format::NoBold;
136 itsEdited.setTags(SongInfo::Tags[pos].Set, Global::wFooter->prompt(
137 itsEdited.getTags(SongInfo::Tags[pos].Get)));
138 w.at(option).value().clear();
139 w.at(option).value() << NC::Format::Bold << SongInfo::Tags[pos].Name << ':' << NC::Format::NoBold << ' ';
140 ShowTag(w.at(option).value(), itsEdited.getTags(SongInfo::Tags[pos].Get));
141 }
142 else if (option == 20)
143 {
144 Statusbar::ScopedLock slock;
145 Statusbar::put() << NC::Format::Bold << "Filename: " << NC::Format::NoBold;
146 std::string filename = itsEdited.getNewName().empty() ? itsEdited.getName() : itsEdited.getNewName();
147 size_t dot = filename.rfind(".");
148 std::string extension = filename.substr(dot);
149 filename = filename.substr(0, dot);
150 std::string new_name = Global::wFooter->prompt(filename);
151 if (!new_name.empty())
152 {
153 itsEdited.setNewName(new_name + extension);
154 w.at(option).value().clear();
155 w.at(option).value() << NC::Format::Bold << "Filename:" << NC::Format::NoBold << ' ' << (itsEdited.getNewName().empty() ? itsEdited.getName() : itsEdited.getNewName());
156 }
157 }
158
159 if (option == 22)
160 {
161 Statusbar::print("Updating tags...");
162 if (Tags::write(itsEdited))
163 {
164 Statusbar::print("Tags updated");
165 if (itsEdited.isFromDatabase())
166 Mpd.UpdateDirectory(itsEdited.getDirectory());
167 else
168 {
169 if (m_previous_screen == myPlaylist)
170 myPlaylist->main().current()->value() = itsEdited;
171 else if (m_previous_screen == myBrowser)
172 myBrowser->requestUpdate();
173 }
174 }
175 else
176 Statusbar::printf("Error while writing tags: %1%", strerror(errno));
177 }
178 if (option > 21)
179 m_previous_screen->switchTo();
180 }
181
182 /**********************************************************************/
183
SetEdited(const MPD::Song & s)184 void TinyTagEditor::SetEdited(const MPD::Song &s)
185 {
186 if (auto ms = dynamic_cast<const MPD::MutableSong *>(&s))
187 itsEdited = *ms;
188 else
189 itsEdited = s;
190 }
191
getTags()192 bool TinyTagEditor::getTags()
193 {
194 std::string path_to_file;
195 if (itsEdited.isFromDatabase())
196 path_to_file += Config.mpd_music_dir;
197 path_to_file += itsEdited.getURI();
198
199 TagLib::FileRef f(path_to_file.c_str());
200 if (f.isNull())
201 return false;
202
203 std::string ext = itsEdited.getURI();
204 ext = boost::locale::to_lower(ext.substr(ext.rfind(".")+1));
205
206 w.clear();
207 w.reset();
208
209 w.resizeList(24);
210
211 for (size_t i = 0; i < 7; ++i)
212 w[i].setInactive(true);
213
214 w[7].setSeparator(true);
215 w[19].setSeparator(true);
216 w[21].setSeparator(true);
217
218 if (!Tags::extendedSetSupported(f.file()))
219 {
220 w[10].setInactive(true);
221 for (size_t i = 15; i <= 17; ++i)
222 w[i].setInactive(true);
223 }
224
225 w.highlight(8);
226
227 auto print_key_value = [](NC::Buffer &buf, const char *key, const auto &value) {
228 buf << NC::Format::Bold
229 << Config.color1
230 << key
231 << ":"
232 << NC::FormattedColor::End<>(Config.color1)
233 << NC::Format::NoBold
234 << " "
235 << Config.color2
236 << value
237 << NC::FormattedColor::End<>(Config.color2);
238 };
239
240 print_key_value(w[0].value(), "Filename", itsEdited.getName());
241 print_key_value(w[1].value(), "Directory", ShowTag(itsEdited.getDirectory()));
242 print_key_value(w[3].value(), "Length", itsEdited.getLength());
243 print_key_value(
244 w[4].value(),
245 "Bitrate",
246 boost::lexical_cast<std::string>(f.audioProperties()->bitrate()) + " kbps");
247 print_key_value(
248 w[5].value(),
249 "Sample rate",
250 boost::lexical_cast<std::string>(f.audioProperties()->sampleRate()) + " Hz");
251 print_key_value(
252 w[6].value(),
253 "Channels",
254 channelsToString(f.audioProperties()->channels()));
255
256 unsigned pos = 8;
257 for (const SongInfo::Metadata *m = SongInfo::Tags; m->Name; ++m, ++pos)
258 {
259 w[pos].value() << NC::Format::Bold
260 << m->Name
261 << ":"
262 << NC::Format::NoBold
263 << " ";
264 ShowTag(w[pos].value(), itsEdited.getTags(m->Get));
265 }
266
267 w[20].value() << NC::Format::Bold
268 << "Filename:"
269 << NC::Format::NoBold
270 << " "
271 << itsEdited.getName();
272
273 w[22].value() << "Save";
274 w[23].value() << "Cancel";
275 return true;
276 }
277
278 #endif // HAVE_TAGLIB_H
279
280