Project

General

Profile

m3u.cc

Code patch - Jim Turner, July 12, 2022 21:33

 
1
/*
2
 * Audacious: A cross-platform multimedia player
3
 * Copyright (c) 2006-2010 William Pitcock, Tony Vroon, George Averill, Giacomo
4
 *  Lozito, Derek Pomery and Yoshiki Yazawa, and John Lindgren.
5
 *
6
 * This program is free software; you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; either version 2 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
 */
20

    
21
#include <stdlib.h>
22
#include <string.h>
23

    
24
#include <libaudcore/preferences.h>
25
#include <libaudcore/audstrings.h>
26
#include <libaudcore/i18n.h>
27
#include <libaudcore/plugin.h>
28
#include <libaudcore/runtime.h>
29

    
30
static const char * const m3u_exts[] = {"m3u", "m3u8"};
31

    
32
class M3ULoader : public PlaylistPlugin
33
{
34
public:
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
    };
43

    
44
    constexpr M3ULoader () : PlaylistPlugin (info, m3u_exts, true) {}
45

    
46
    bool load (const char * filename, VFSFile & file, String & title,
47
     Index<PlaylistAddItem> & items);
48
    bool save (const char * filename, VFSFile & file, const char * title,
49
     const Index<PlaylistAddItem> & items);
50
};
51

    
52
EXPORT M3ULoader aud_plugin_instance;
53

    
54
static char * split_line (char * line)
55
{
56
    char * feed = strchr (line, '\n');
57
    if (! feed)
58
        return nullptr;
59

    
60
    if (feed > line && feed[-1] == '\r')
61
        feed[-1] = 0;
62
    else
63
        feed[0] = 0;
64

    
65
    return feed + 1;
66
}
67

    
68
bool M3ULoader::load (const char * filename, VFSFile & file, String & title,
69
 Index<PlaylistAddItem> & items)
70
{
71
    enum extDataType {NA, ALB, ART, GENRE, INF};
72
    bool Extended_m3u = false;
73
    bool firstline = true;
74
    bool refreshTuple = true;
75
    Tuple tuple = Tuple ();
76

    
77
    Index<char> text = file.read_all ();
78
    if (! text.len ())
79
        return false;
80

    
81
    text.append (0);  /* null-terminate */
82

    
83
    char * parse = text.begin ();
84
    if (! strncmp (parse, "\xef\xbb\xbf", 3)) /* byte order mark */
85
        parse += 3;
86

    
87
    while (parse)
88
    {
89
        char * next = split_line (parse);
90

    
91
        while (* parse == ' ' || * parse == '\t')
92
            parse ++;
93

    
94
        if (* parse)
95
        {
96
            if (* parse != '#')
97
            {
98
                String s = String (uri_construct (parse, filename));
99

    
100
                if (s && s[0])
101
                {
102
                    if (Extended_m3u)
103
                    {
104
                        tuple.set_filename (s);
105
                        tuple.set_state (Tuple::Valid);
106
                        items.append (s, std::move (tuple));
107
                        refreshTuple = true;
108
                    }
109
                    else
110
                        items.append (s);
111
                }
112
            }
113
            else if (Extended_m3u)
114
            {
115
                if (! strncmp (parse, "#EXT-X-", 7))  // WE'RE AN "HLS" STREAM, STAND DOWN & LET ffaudio PLUGIN HANDLE!:
116
                {
117
                    AUDINFO ("i:HLS STREAM(%s) - STOP PARSING & JUST ADD PLAYLIST AS SINGLE ENTRY!\n", filename);
118
                    items.append (String (filename));
119
                    break;
120
                }
121
                else if (! strncmp (parse, "#EXT", 4))  // WE'RE A DATA LINE (EXTENDED M3U):
122
                {
123
                    extDataType extData = NA;
124
                    if (refreshTuple)
125
                    {
126
                        tuple = Tuple ();
127
                        refreshTuple = false;
128
                    }
129

    
130
                    if (! strncmp (parse, "#EXTINF", 7))        // SET [LENGTH,] TITLE
131
                        extData = INF;
132
                    else if (! strncmp (parse, "#EXTGENRE", 9)) // SET GENRE
133
                    {
134
                        parse += 2;
135
                        extData = GENRE;
136
                    }
137
                    else if (! strncmp (parse, "#EXTALB", 7))   // SET ALBUM
138
                        extData = ALB;
139
                    else if (! strncmp (parse, "#EXTART", 7))   // SET ARTIST
140
                        extData = ART;
141

    
142
                    parse += 7;
143
                    if (parse < next && * parse == ':')
144
                    {
145
                        ++parse;
146
                        while (parse < next && * parse == ' ')
147
                            ++parse;
148

    
149
                        if (* parse && parse < next)
150
                        {
151
                            Index<String> headerparts = str_list_to_index (parse, ",");
152
                            if (extData == INF && headerparts.len () > 1)
153
                            {
154
                                int tlen = atoi (headerparts[0]) * 1000;
155
                                if (tlen <= 0)
156
                                    tuple.unset (Tuple::Length);
157
                                else
158
                                    tuple.set_int (Tuple::Length, tlen);
159

    
160
                                // FIND THE TITLE AND MOVE PAST ANY LEADING SPACES IN IT:
161
                                char * c = parse;
162
                                while (c < next && * c != ',')
163
                                    ++c;
164
                                if (c < next && * c)
165
                                    ++c;
166
                                while (c < next && * c == ' ')
167
                                    ++c;
168
                                if (*c && c < next)
169
                                    tuple.set_str (Tuple::Title, c);
170
                            }
171
                            else if (headerparts.len () > 0)
172
                            {
173
                                if (extData == INF)
174
                                {
175
                                    tuple.unset (Tuple::Length);
176
                                    tuple.set_str (Tuple::Title, headerparts[0]);
177
                                }
178
                                else if (extData == ART)
179
                                    tuple.set_str (Tuple::Artist, headerparts[0]);
180
                                else if (extData == ALB)
181
                                    tuple.set_str (Tuple::Album, headerparts[0]);
182
                                else if (extData == GENRE)
183
                                    tuple.set_str (Tuple::Genre, headerparts[0]);
184
                            }
185
                        }
186
                    }
187
                }
188
            }
189
            else if (firstline && ! strncmp (parse, "#EXTM3U", 7))  // WE'RE AN EXTENDED M3U:
190
                Extended_m3u = true;
191
        }
192

    
193
        firstline = false;
194
        parse = next;
195
    }
196

    
197
    return true;
198
}
199

    
200
bool M3ULoader::save (const char * filename, VFSFile & file, const char * title,
201
 const Index<PlaylistAddItem> & items)
202
{
203
    bool Extended_m3u = aud_get_bool ("m3u", "saveas_extended_m3u");
204

    
205
    if (Extended_m3u && file.fwrite (str_copy("#EXTM3U\n"), 1, 8) != 8)
206
        return false;
207

    
208
    for (auto & item : items)
209
    {
210
        StringBuf path = uri_deconstruct (item.filename, filename);
211
        if (Extended_m3u && item.tuple.state () == Tuple::Valid)
212
        {
213
            int tuplen = item.tuple.get_int (Tuple::Length);
214
            if (tuplen >= 0)
215
                tuplen /= 1000;
216

    
217
            {
218
                String tupstr = item.tuple.get_str (Tuple::Title);
219
                if (! tupstr)
220
                    tupstr = String (filename_get_base (item.filename));
221
                StringBuf line = str_printf ("#EXTINF:%d, %s\n", tuplen, (const char *) tupstr);
222
                if (file.fwrite (line, 1, line.len ()) != line.len ())
223
                    return false;
224
            }
225
            {
226
                String tupstr = item.tuple.get_str (Tuple::Artist);
227
                if (tupstr && tupstr[0])
228
                {
229
                    StringBuf line = str_printf ("#EXTART:%s\n", (const char *) tupstr);
230
                    if (file.fwrite (line, 1, line.len ()) != line.len ())
231
                        AUDERR ("m3u: could not write artist to extended m3u file?!\n");
232
                }
233
            }
234
            {
235
                String tupstr = item.tuple.get_str (Tuple::Album);
236
                if (tupstr && tupstr[0])
237
                {
238
                    StringBuf line = str_printf ("#EXTALB:%s\n", (const char *) tupstr);
239
                    if (file.fwrite (line, 1, line.len ()) != line.len ())
240
                        AUDERR ("m3u: could not write album to extended m3u file?!\n");
241
                }
242
            }
243
            {
244
                String tupstr = item.tuple.get_str (Tuple::Genre);
245
                if (tupstr && tupstr[0])
246
                {
247
                    StringBuf line = str_printf ("#EXTGENRE:%s\n", (const char *) tupstr);
248
                    if (file.fwrite (line, 1, line.len ()) != line.len ())
249
                        AUDERR ("m3u: could not write genre to extended m3u file?!\n");
250
                }
251
            }
252
        }
253
        StringBuf line = str_concat ({path, "\n"});
254
        if (file.fwrite (line, 1, line.len ()) != line.len ())
255
            return false;
256
    }
257

    
258
    return true;
259
}
260

    
261
const PreferencesWidget M3ULoader::widgets[] = {
262
    WidgetLabel(N_("<b>M3U Configuration</b>")),
263
    WidgetCheck(N_("Save in Extended M3U format?"), WidgetBool("m3u", "saveas_extended_m3u")),
264
};
265

    
266
const PluginPreferences M3ULoader::prefs = {{widgets}};