18 |
18 |
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
19 |
19 |
*/
|
20 |
20 |
|
|
21 |
#include <stdlib.h>
|
21 |
22 |
#include <string.h>
|
22 |
23 |
|
|
24 |
#include <libaudcore/preferences.h>
|
23 |
25 |
#include <libaudcore/audstrings.h>
|
24 |
26 |
#include <libaudcore/i18n.h>
|
25 |
27 |
#include <libaudcore/plugin.h>
|
|
28 |
#include <libaudcore/runtime.h>
|
26 |
29 |
|
27 |
30 |
static const char * const m3u_exts[] = {"m3u", "m3u8"};
|
28 |
31 |
|
29 |
32 |
class M3ULoader : public PlaylistPlugin
|
30 |
33 |
{
|
31 |
34 |
public:
|
32 |
|
static constexpr PluginInfo info = {N_("M3U Playlists"), PACKAGE};
|
|
35 |
static const PreferencesWidget widgets[];
|
|
36 |
static const PluginPreferences prefs;
|
|
37 |
static constexpr PluginInfo info = {
|
|
38 |
N_("M3U Playlists"),
|
|
39 |
PACKAGE,
|
|
40 |
nullptr,
|
|
41 |
& prefs
|
|
42 |
};
|
33 |
43 |
|
34 |
44 |
constexpr M3ULoader () : PlaylistPlugin (info, m3u_exts, true) {}
|
35 |
45 |
|
... | ... | |
37 |
47 |
Index<PlaylistAddItem> & items);
|
38 |
48 |
bool save (const char * filename, VFSFile & file, const char * title,
|
39 |
49 |
const Index<PlaylistAddItem> & items);
|
|
50 |
|
|
51 |
private:
|
|
52 |
bool Extended_m3u = false;
|
|
53 |
Tuple tuple;
|
40 |
54 |
};
|
41 |
55 |
|
42 |
56 |
EXPORT M3ULoader aud_plugin_instance;
|
... | ... | |
58 |
72 |
bool M3ULoader::load (const char * filename, VFSFile & file, String & title,
|
59 |
73 |
Index<PlaylistAddItem> & items)
|
60 |
74 |
{
|
|
75 |
Extended_m3u = false;
|
|
76 |
tuple = Tuple ();
|
|
77 |
|
61 |
78 |
Index<char> text = file.read_all ();
|
62 |
79 |
if (! text.len ())
|
63 |
80 |
return false;
|
... | ... | |
68 |
85 |
if (! strncmp (parse, "\xef\xbb\xbf", 3)) /* byte order mark */
|
69 |
86 |
parse += 3;
|
70 |
87 |
|
|
88 |
bool firstline = true;
|
71 |
89 |
while (parse)
|
72 |
90 |
{
|
73 |
91 |
char * next = split_line (parse);
|
... | ... | |
75 |
93 |
while (* parse == ' ' || * parse == '\t')
|
76 |
94 |
parse ++;
|
77 |
95 |
|
78 |
|
if (* parse && * parse != '#')
|
|
96 |
if (* parse)
|
79 |
97 |
{
|
80 |
|
StringBuf s = uri_construct (parse, filename);
|
81 |
|
if (s)
|
82 |
|
items.append (String (s));
|
|
98 |
if (* parse != '#')
|
|
99 |
{
|
|
100 |
String s = String (uri_construct (parse, filename));
|
|
101 |
|
|
102 |
if (s && s[0])
|
|
103 |
{
|
|
104 |
if (! strncmp (s, "file://", 7) && strstr (parse, "cue?")) // CUESHEET:
|
|
105 |
s = String (str_decode_percent (s)); // UNESCAPE THE "?" ESCAPED BY uri_construct ().
|
|
106 |
|
|
107 |
if (Extended_m3u)
|
|
108 |
{
|
|
109 |
tuple.set_filename (s);
|
|
110 |
items.append (String (s), std::move (tuple)); // NOTE:NEVER SET TUPLE VALID (FORCE RESCAN)!
|
|
111 |
}
|
|
112 |
else
|
|
113 |
items.append (String (s));
|
|
114 |
}
|
|
115 |
}
|
|
116 |
else if (Extended_m3u && ! strncmp (parse, "#EXTINF", 7)) // WE'RE A TITLE LINE (EXTENDED M3U):
|
|
117 |
{
|
|
118 |
tuple = Tuple ();
|
|
119 |
parse += 7;
|
|
120 |
if (parse < next && * parse == ':')
|
|
121 |
{
|
|
122 |
++parse;
|
|
123 |
while (parse < next && * parse == ' ')
|
|
124 |
++parse;
|
|
125 |
|
|
126 |
if (* parse && parse < next)
|
|
127 |
{
|
|
128 |
Index<String> headerparts = str_list_to_index (parse, ",");
|
|
129 |
if (headerparts.len () > 1)
|
|
130 |
{
|
|
131 |
int tlen = atoi (headerparts[0]) * 1000;
|
|
132 |
if (tlen <= 0)
|
|
133 |
tuple.unset (Tuple::Length);
|
|
134 |
else
|
|
135 |
tuple.set_int (Tuple::Length, tlen);
|
|
136 |
|
|
137 |
// FIND THE TITLE AND MOVEPAST ANY LEADING SPACES IN IT:
|
|
138 |
char * c = parse;
|
|
139 |
while (c < next && * c != ',')
|
|
140 |
++c;
|
|
141 |
if (c < next && * c)
|
|
142 |
++c;
|
|
143 |
while (c < next && * c == ' ')
|
|
144 |
++c;
|
|
145 |
if (* c)
|
|
146 |
tuple.set_str (Tuple::Title, c);
|
|
147 |
}
|
|
148 |
else if (headerparts.len () > 0)
|
|
149 |
{
|
|
150 |
tuple.unset (Tuple::Length);
|
|
151 |
tuple.set_str (Tuple::Title, headerparts[0]);
|
|
152 |
}
|
|
153 |
}
|
|
154 |
}
|
|
155 |
}
|
|
156 |
else if (firstline && ! strncmp (parse, "#EXTM3U", 7)) // WE'RE AN EXTENDED M3U:
|
|
157 |
Extended_m3u = true;
|
83 |
158 |
}
|
84 |
159 |
|
|
160 |
firstline = false;
|
85 |
161 |
parse = next;
|
86 |
162 |
}
|
87 |
163 |
|
... | ... | |
91 |
167 |
bool M3ULoader::save (const char * filename, VFSFile & file, const char * title,
|
92 |
168 |
const Index<PlaylistAddItem> & items)
|
93 |
169 |
{
|
|
170 |
Extended_m3u = aud_get_bool ("m3u", "saveas_extended_m3u");
|
|
171 |
if (Extended_m3u && file.fwrite (str_copy("#EXTM3U\n"), 1, 8) != 8)
|
|
172 |
return false;
|
|
173 |
|
94 |
174 |
for (auto & item : items)
|
95 |
175 |
{
|
96 |
176 |
StringBuf path = uri_deconstruct (item.filename, filename);
|
|
177 |
if (Extended_m3u && item.tuple.state () == Tuple::Valid)
|
|
178 |
{
|
|
179 |
int tuplen = item.tuple.get_int (Tuple::Length);
|
|
180 |
if (tuplen >= 0)
|
|
181 |
tuplen /= 1000;
|
|
182 |
String tupstr = item.tuple.get_str (Tuple::Title);
|
|
183 |
if (!tupstr)
|
|
184 |
tupstr = String (filename_get_base (item.filename));
|
|
185 |
StringBuf line = str_printf ("#EXTINF:%d, %s\n", tuplen, (const char *) tupstr);
|
|
186 |
if (file.fwrite (line, 1, line.len ()) != line.len ())
|
|
187 |
return false;
|
|
188 |
}
|
97 |
189 |
StringBuf line = str_concat ({path, "\n"});
|
98 |
190 |
if (file.fwrite (line, 1, line.len ()) != line.len ())
|
99 |
191 |
return false;
|
... | ... | |
101 |
193 |
|
102 |
194 |
return true;
|
103 |
195 |
}
|
|
196 |
|
|
197 |
const PreferencesWidget M3ULoader::widgets[] = {
|
|
198 |
WidgetLabel(N_("<b>M3U Configuration</b>")),
|
|
199 |
WidgetCheck(N_("Save in Extended M3U format?"), WidgetBool("m3u", "saveas_extended_m3u")),
|
|
200 |
};
|
|
201 |
|
|
202 |
const PluginPreferences M3ULoader::prefs = {{widgets}};
|