--- m3u.cc.org	2019-03-06 18:15:40.124197628 -0600
+++ m3u.cc	2019-03-06 21:35:06.823531414 -0600
@@ -18,18 +18,28 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  */
 
+#include <stdlib.h>
 #include <string.h>
 
+#include <libaudcore/preferences.h>
 #include <libaudcore/audstrings.h>
 #include <libaudcore/i18n.h>
 #include <libaudcore/plugin.h>
+#include <libaudcore/runtime.h>
 
 static const char * const m3u_exts[] = {"m3u", "m3u8"};
 
 class M3ULoader : public PlaylistPlugin
 {
 public:
-    static constexpr PluginInfo info = {N_("M3U Playlists"), PACKAGE};
+    static const PreferencesWidget widgets[];
+    static const PluginPreferences prefs;
+    static constexpr PluginInfo info = {
+        N_("M3U Playlists"),
+        PACKAGE,
+        nullptr,
+        & prefs
+    };
 
     constexpr M3ULoader () : PlaylistPlugin (info, m3u_exts, true) {}
 
@@ -37,6 +47,10 @@ public:
      Index<PlaylistAddItem> & items);
     bool save (const char * filename, VFSFile & file, const char * title,
      const Index<PlaylistAddItem> & items);
+
+private:
+    bool Extended_m3u = false;
+    Tuple tuple;
 };
 
 EXPORT M3ULoader aud_plugin_instance;
@@ -58,6 +72,9 @@ static char * split_line (char * line)
 bool M3ULoader::load (const char * filename, VFSFile & file, String & title,
  Index<PlaylistAddItem> & items)
 {
+    Extended_m3u = false;
+    tuple = Tuple ();
+
     Index<char> text = file.read_all ();
     if (! text.len ())
         return false;
@@ -68,6 +85,7 @@ bool M3ULoader::load (const char * filen
     if (! strncmp (parse, "\xef\xbb\xbf", 3)) /* byte order mark */
         parse += 3;
 
+    bool firstline = true;
     while (parse)
     {
         char * next = split_line (parse);
@@ -75,13 +93,71 @@ bool M3ULoader::load (const char * filen
         while (* parse == ' ' || * parse == '\t')
             parse ++;
 
-        if (* parse && * parse != '#')
+        if (* parse)
         {
-            StringBuf s = uri_construct (parse, filename);
-            if (s)
-                items.append (String (s));
+            if (* parse != '#')
+            {
+                String s = String (uri_construct (parse, filename));
+
+                if (s && s[0])
+                {
+                    if (! strncmp (s, "file://", 7) && strstr (parse, "cue?"))  // CUESHEET:
+                        s = String (str_decode_percent (s));  // UNESCAPE THE "?" ESCAPED BY uri_construct ().
+
+                    if (Extended_m3u)
+                    {
+                        tuple.set_filename (s);
+                        items.append (String (s), std::move (tuple));  // NOTE:NEVER SET TUPLE VALID (FORCE RESCAN)!
+                    }
+                    else
+                        items.append (String (s));
+                }
+            }
+            else if (Extended_m3u && ! strncmp (parse, "#EXTINF", 7))  // WE'RE A TITLE LINE (EXTENDED M3U):
+            {
+                tuple = Tuple ();
+                parse += 7;
+                if (parse < next && * parse == ':')
+                {
+                    ++parse;
+                    while (parse < next && * parse == ' ')
+                        ++parse;
+
+                    if (* parse && parse < next)
+                    {
+                        Index<String> headerparts = str_list_to_index (parse, ",");
+                        if (headerparts.len () > 1)
+                        {
+                            int tlen = atoi (headerparts[0]) * 1000;
+                            if (tlen <= 0)
+                                tuple.unset (Tuple::Length);
+                            else
+                                tuple.set_int (Tuple::Length, tlen);
+
+                            // FIND THE TITLE AND MOVEPAST ANY LEADING SPACES IN IT:
+                            char * c = parse;
+                            while (c < next && * c != ',')
+                                ++c;
+                            if (c < next && * c)
+                                ++c;
+                            while (c < next && * c == ' ')
+                                ++c;
+                            if (* c)
+                                tuple.set_str (Tuple::Title, c);
+                        }
+                        else if (headerparts.len () > 0)
+                        {
+                            tuple.unset (Tuple::Length);
+                            tuple.set_str (Tuple::Title, headerparts[0]);
+                        }
+                    }
+                }
+            }
+            else if (firstline && ! strncmp (parse, "#EXTM3U", 7))  // WE'RE AN EXTENDED M3U:
+                Extended_m3u = true;
         }
 
+        firstline = false;
         parse = next;
     }
 
@@ -91,9 +167,25 @@ bool M3ULoader::load (const char * filen
 bool M3ULoader::save (const char * filename, VFSFile & file, const char * title,
  const Index<PlaylistAddItem> & items)
 {
+    Extended_m3u = aud_get_bool ("m3u", "saveas_extended_m3u");
+    if (Extended_m3u && file.fwrite (str_copy("#EXTM3U\n"), 1, 8) != 8)
+        return false;
+
     for (auto & item : items)
     {
         StringBuf path = uri_deconstruct (item.filename, filename);
+        if (Extended_m3u && item.tuple.state () == Tuple::Valid)
+        {
+            int tuplen = item.tuple.get_int (Tuple::Length);
+            if (tuplen >= 0)
+                tuplen /= 1000;
+            String tupstr = item.tuple.get_str (Tuple::Title);
+            if (!tupstr)
+                tupstr = String (filename_get_base (item.filename));
+            StringBuf line = str_printf ("#EXTINF:%d, %s\n", tuplen, (const char *) tupstr);
+            if (file.fwrite (line, 1, line.len ()) != line.len ())
+                return false;
+        }
         StringBuf line = str_concat ({path, "\n"});
         if (file.fwrite (line, 1, line.len ()) != line.len ())
             return false;
@@ -101,3 +193,10 @@ bool M3ULoader::save (const char * filen
 
     return true;
 }
+
+const PreferencesWidget M3ULoader::widgets[] = {
+    WidgetLabel(N_("<b>M3U Configuration</b>")),
+    WidgetCheck(N_("Save in Extended M3U format?"), WidgetBool("m3u", "saveas_extended_m3u")),
+};
+
+const PluginPreferences M3ULoader::prefs = {{widgets}};
