diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c7e64f3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2013 Ushi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..dae01b8 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module m3u + +go 1.19 + +require github.com/ushis/m3u v0.0.0-20150127162843-94396b784733 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..0129d6b --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/ushis/m3u v0.0.0-20150127162843-94396b784733 h1:m4zGEkIeft/gfUs469WS/gB6NT3RtkG8zQrOvCOzovE= +github.com/ushis/m3u v0.0.0-20150127162843-94396b784733/go.mod h1:/w56gU05vgM74JSy2/xFy6tUQ9vJBMiciHNvyIEU1UY= diff --git a/m3u.go b/m3u.go index 368bd7d..184fb40 100644 --- a/m3u.go +++ b/m3u.go @@ -20,18 +20,15 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -// This package implements a fault tolerant m3u parser and functions to write -// simple and extended m3u files. -// -// The spec can be found at http://www.scvi.net/pls.htm package m3u import ( - "bufio" - "fmt" - "io" - "strconv" - "strings" + "bufio" + "bytes" + "fmt" + "io" + "strconv" + "strings" ) // Represents a list of tracks. @@ -39,89 +36,98 @@ type Playlist []Track // Represents a single track. type Track struct { - Path string // path to the file - Title string // title of the track - Time int64 // duration of the track + Path string // path to the file + Title string // title of the track + Time int64 // duration of the track } // Parses simple and extended m3u files. Returns the playlist. func Parse(r io.Reader) (Playlist, error) { - br := bufio.NewReader(r) - pl := Playlist{} - - for { - line, err := br.ReadString('\n') - - if err != nil { - if err == io.EOF { - return pl, nil - } - return pl, err - } - line = line[:len(line)-1] - - if len(line) > 0 && line[0] != '#' { - pl = append(pl, Track{Path: line, Title: "", Time: -1}) - continue - } - - if len(line) > 8 && line[:8] == "#EXTINF:" { - i := strings.Index(line[8:], ",") - - if i < 0 { - return pl, fmt.Errorf("unexpected line: %q", line) - } - ftime, err := strconv.ParseFloat(line[8:i+8], 64) - - if err != nil { - return pl, err - } - time := int64(ftime) - path, err := br.ReadString('\n') - - if err != nil { - return pl, err - } - pl = append(pl, Track{Path: path[:len(path)-1], Title: line[i+9:], Time: time}) - } - } + scanner := bufio.NewScanner(r) + pl := Playlist{} + + for scanner.Scan() { + line := scanner.Text() + + if len(line) > 0 && line[0] != '#' { + pl = append(pl, Track{Path: line, Title: "", Time: -1}) + continue + } + + if len(line) > 8 && line[:8] == "#EXTINF:" { + i := strings.Index(line[8:], ",") + + if i < 0 { + return pl, fmt.Errorf("unexpected line: %q", line) + } + ftime, err := strconv.ParseFloat(line[8:i+8], 64) + + if err != nil { + return pl, err + } + time := int64(ftime) + scanner.Scan() + path := scanner.Text() + + if err := scanner.Err(); err != nil { + return pl, err + } + pl = append(pl, Track{Path: path, Title: line[i+9:], Time: time}) + } + } + if err := scanner.Err(); err != nil { + return pl, err + } + return pl, nil } // Writes the playlist to a writer in the extended m3u format. Returns the // number of written bytes. -func (pl Playlist) WriteTo(w io.Writer) (n int, err error) { - if n, err = fmt.Fprintln(w, "#EXTM3U"); err != nil { - return - } - - for _, t := range pl { - time := t.Time - - if time < 1 { - time = -1 - } - m, err := fmt.Fprintf(w, "#EXTINF:%d,%s\n%s\n", time, t.Title, t.Path) - - if err != nil { - return n, err - } - n += m - } - return +func (pl Playlist) WriteTo(w io.Writer) (int64, error) { + var buf bytes.Buffer + buf.WriteString("#EXTM3U\n") + + for _, t := range pl { + time := t.Time + + if time < 1 { + time = -1 + } + var b bytes.Buffer + strconv.AppendInt(b.Bytes(), time, 10) + buf.WriteString("#EXTINF:") + buf.Write(b.Bytes()) + buf.WriteString(",") + buf.WriteString(t.Title) + buf.WriteString("\n") + buf.WriteString(t.Path) + buf.WriteString("\n") + } + + // Write the buffer to the output. + n, err := buf.WriteTo(w) + if err != nil { + return 0, err + } + + return n, nil } // Writes the playlist to a writer in the simple m3u format. Returns the number // of written bytes. -func (pl Playlist) WriteSimpleTo(w io.Writer) (n int, err error) { - n = 0 - - for _, t := range pl { - m, err := fmt.Fprintln(w, t.Path) - - if err != nil { - return n, err - } - n += m - } - return +func (pl Playlist) WriteSimpleTo(w io.Writer) (int64, error) { + var buf bytes.Buffer + + for _, t := range pl { + buf.WriteString(t.Path) + buf.WriteString("\n") + } + + // Write the buffer to the output. + n, err := buf.WriteTo(w) + if err != nil { + return 0, err + } + + return n, nil } diff --git a/m3u_test.go b/m3u_test.go index 294f0cd..1c8726b 100644 --- a/m3u_test.go +++ b/m3u_test.go @@ -23,102 +23,102 @@ package m3u import ( - "bytes" - "io" - "os" - "testing" + "bytes" + "io" + "os" + "testing" ) var extended = Playlist{ - Track{ - Path: "Alternative\\everclear_SMFTA.mp3", - Title: "Everclear - So Much For The Afterglow", - Time: 233, - }, - Track{ - Path: "Comedy/Weird_Al_Everything_You_Know_Is_Wrong.mp3", - Title: "", - Time: 227, - }, - Track{ - Path: "Weird_Al_This_Is_The_Life.mp3", - Title: "Weird Al Yankovic - This is the Life", - Time: 187, - }, - Track{ - Path: "http://www.site.com/~user/gump.mp3", - Title: "Weird Al: Bad Hair Day - Gump", - Time: 129, - }, - Track{ - Path: "http://www.site.com:8000/listen.pls", - Title: "My Cool Stream", - Time: -1, - }, + Track{ + Path: "Alternative\\everclear_SMFTA.mp3", + Title: "Everclear - So Much For The Afterglow", + Time: 233, + }, + Track{ + Path: "Comedy/Weird_Al_Everything_You_Know_Is_Wrong.mp3", + Title: "", + Time: 227, + }, + Track{ + Path: "Weird_Al_This_Is_The_Life.mp3", + Title: "Weird Al Yankovic - This is the Life", + Time: 187, + }, + Track{ + Path: "http://www.site.com/~user/gump.mp3", + Title: "Weird Al: Bad Hair Day - Gump", + Time: 129, + }, + Track{ + Path: "http://www.site.com:8000/listen.pls", + Title: "My Cool Stream", + Time: -1, + }, } var simple = Playlist{ - Track{Time: -1, Title: "", Path: "Alternative\\everclear_SMFTA.mp3"}, - Track{Time: -1, Title: "", Path: "Comedy/Weird_Al_Everything_You_Know_Is_Wrong.mp3"}, - Track{Time: -1, Title: "", Path: "Weird_Al_This_Is_The_Life.mp3"}, - Track{Time: -1, Title: "", Path: "http://www.site.com/~user/gump.mp3"}, - Track{Time: -1, Title: "", Path: "http://www.site.com:8000/listen.pls"}, + Track{Time: -1, Title: "", Path: "Alternative\\everclear_SMFTA.mp3"}, + Track{Time: -1, Title: "", Path: "Comedy/Weird_Al_Everything_You_Know_Is_Wrong.mp3"}, + Track{Time: -1, Title: "", Path: "Weird_Al_This_Is_The_Life.mp3"}, + Track{Time: -1, Title: "", Path: "http://www.site.com/~user/gump.mp3"}, + Track{Time: -1, Title: "", Path: "http://www.site.com:8000/listen.pls"}, } func assertPlaylist(t *testing.T, a, b Playlist) { - if len(a) != len(b) { - t.Fatalf("Result: %v\nExpected: %v\n", a, b) - } - - for i, _ := range a { - if a[i].Path != b[i].Path || a[i].Title != b[i].Title || a[i].Time != b[i].Time { - t.Fatalf("\nResult: %v\nExpected: %v\n", a, b) - } - } + if len(a) != len(b) { + t.Fatalf("Result: %v\nExpected: %v\n", a, b) + } + + for i := range a { + if a[i].Path != b[i].Path || a[i].Title != b[i].Title || a[i].Time != b[i].Time { + t.Fatalf("\nResult: %v\nExpected: %v\n", a, b) + } + } } func parse(t *testing.T, path string) Playlist { - f, err := os.Open(path) + f, err := os.Open(path) - if err != nil { - t.Fatal(err) - } - defer f.Close() + if err != nil { + t.Fatal(err) + } + defer f.Close() - pl, err := Parse(f) + pl, err := Parse(f) - if err != nil { - t.Fatal(err) - } - return pl + if err != nil { + t.Fatal(err) + } + return pl } -func writeAndParse(t *testing.T, w func(io.Writer) (int, error)) Playlist { - var buf bytes.Buffer +func writeAndParse(t *testing.T, w func(io.Writer) (int64, error)) Playlist { + var buf bytes.Buffer - if _, err := w(&buf); err != nil { - t.Fatal(err) - } - pl, err := Parse(&buf) + if _, err := w(&buf); err != nil { + t.Fatal(err) + } + pl, err := Parse(&buf) - if err != nil { - t.Fatal(err) - } - return pl + if err != nil { + t.Fatal(err) + } + return pl } func TestParse(t *testing.T) { - assertPlaylist(t, parse(t, "testdata/extended.m3u"), extended) + assertPlaylist(t, parse(t, "testdata/extended.m3u"), extended) } func TestParseSimple(t *testing.T) { - assertPlaylist(t, parse(t, "testdata/simple.m3u"), simple) + assertPlaylist(t, parse(t, "testdata/simple.m3u"), simple) } func TestWriteTo(t *testing.T) { - assertPlaylist(t, writeAndParse(t, extended.WriteTo), extended) + assertPlaylist(t, writeAndParse(t, extended.WriteTo), extended) } func TestWriteSimpleTo(t *testing.T) { - assertPlaylist(t, writeAndParse(t, extended.WriteSimpleTo), simple) + assertPlaylist(t, writeAndParse(t, extended.WriteSimpleTo), simple) }