1/*
2 * PlaylistFileReader.cpp - Media Player for the Haiku Operating System
3 *
4 * Copyright (C) 2006 Marcus Overhagen <marcus@overhagen.de>
5 * Copyright (C) 2007-2009 Stephan A��mus <superstippi@gmx.de> (MIT ok)
6 * Copyright (C) 2008-2009 Fredrik Mod��en <[FirstName]@[LastName].se> (MIT ok)
7 *
8 * Released under the terms of the MIT license.
9 */
10
11#include "PlaylistFileReader.h"
12
13#include <cctype>
14#include <new>
15#include <stdio.h>
16#include <strings.h>
17
18#include <Path.h>
19#include <Url.h>
20
21#include "FileReadWrite.h"
22#include "Playlist.h"
23
24using std::isdigit;
25using std::nothrow;
26
27
28/*static*/ PlaylistFileReader*
29PlaylistFileReader::GenerateReader(const entry_ref& ref)
30{
31	PlaylistFileType type = _IdentifyType(ref);
32	PlaylistFileReader* reader = NULL;
33	if (type == m3u)
34		reader = new (nothrow) M3uReader;
35	else if (type == pls)
36		reader = new (nothrow) PlsReader;
37	return reader;
38}
39
40
41int32
42PlaylistFileReader::_AppendItemToPlaylist(const BString& entry, Playlist* playlist)
43{
44	bool validItem = true;
45	int32 assignedIndex = -1;
46	BPath path(entry.String());
47	entry_ref refPath;
48	status_t err = get_ref_for_path(path.Path(), &refPath);
49	PlaylistItem* item = NULL;
50
51	// Create a PlaylistItem if entry is a valid file path or URL
52	if (err == B_OK)
53		item = new (nothrow) FilePlaylistItem(refPath);
54	else {
55		BUrl url(entry);
56		if (url.IsValid())
57			item = new (nothrow) UrlPlaylistItem(url);
58		else {
59			printf("Error - %s: [%" B_PRIx32 "]\n", strerror(err), err);
60			validItem = false;
61		}
62	}
63
64	// If creation of a PlaylistItem was attempted, try to add it to the Playlist
65	if (validItem) {
66		if (item == NULL)
67			delete item;
68		else {
69			bool itemAdded = playlist->AddItem(item);
70			if (!itemAdded)
71				delete item;
72			else
73				assignedIndex = playlist->IndexOf(item);
74		}
75	}
76
77	return assignedIndex;
78}
79
80
81/*static*/ PlaylistFileType
82PlaylistFileReader::_IdentifyType(const entry_ref& ref)
83{
84	BString path(BPath(&ref).Path());
85	PlaylistFileType type = unknown;
86	if (path.FindLast(".m3u") == path.CountChars() - 4
87		|| path.FindLast(".m3u8") == path.CountChars() - 5)
88		type = m3u;
89	else if (path.FindLast(".pls") == path.CountChars() - 4)
90		type = pls;
91	return type;
92}
93
94
95/*virtual*/ void
96M3uReader::AppendToPlaylist(const entry_ref& ref, Playlist* playlist)
97{
98	BFile file(&ref, B_READ_ONLY);
99	FileReadWrite lineReader(&file);
100
101	BString line;
102	while (lineReader.Next(line)) {
103		if (line.FindFirst("#") != 0)
104			// Current version of this function ignores all comment lines
105			_AppendItemToPlaylist(line, playlist);
106		line.Truncate(0);
107	}
108}
109
110
111/*virtual*/ void
112PlsReader::AppendToPlaylist(const entry_ref& ref, Playlist* playlist)
113{
114	BString plsEntries;
115		// The total number of tracks in the PLS file, taken from the NumberOfEntries line.
116		// This variable is not used for anything at this time.
117	BString plsVersion;
118		// The version of the PLS standard used in this playlist file, taken from the Version line.
119		// This variable is not used for anything at this time.
120	int32 lastAssignedIndex = -1;
121		// The index that MediaPlayer assigned to the most recently added PlaylistItem.
122		// If an attempted assignment fails, this will be set to -1 again.
123
124	BFile file(&ref, B_READ_ONLY);
125	FileReadWrite lineReader(&file);
126	BString line;
127
128	// Check for the "[playlist]" header on the first line
129	lineReader.Next(line);
130	if (line != "[playlist]") {
131		printf("Error - Invalid .pls file\n");
132		return;
133	}
134	line.Truncate(0);
135
136	while (true) {
137		bool lineRead = lineReader.Next(line);
138		if (lineRead == false)
139			break;
140
141		// All valid PLS lines after the header contain an equal sign
142		int32 equalIndex = line.FindFirst("=");
143		if (equalIndex == B_ERROR) {
144			line.Truncate(0);
145			continue;
146		}
147
148		BString lineType;
149			// Will be set for each line to one of: File, Title, Length, NumberOfEntries, Version
150		BString trackNumber;
151			// Number of the track being processed, using the (one-based) explicit numbering
152			// from the .pls file
153		if (isdigit(line[equalIndex - 1])) {
154			// Distinguish between lines that specify a track number, and those that don't
155			int32 trackIndex = equalIndex - 1;
156				// The string index where the track number begins
157			while (isdigit(line[trackIndex - 1]))
158				trackIndex--;
159			line.CopyInto(lineType, 0, trackIndex);
160			line.CopyInto(trackNumber, trackIndex, equalIndex - trackIndex);
161		} else {
162			line.CopyInto(lineType, 0, equalIndex);
163		}
164
165		BString lineContent;
166			// Everything after the equal sign
167		line.CopyInto(lineContent, equalIndex + 1, line.Length() - (equalIndex + 1));
168
169		if (lineType == "File") {
170			lastAssignedIndex = _AppendItemToPlaylist(lineContent, playlist);
171		// A File line may be followed by optional Title and/or Length lines.
172		} else if (lineType == "Title") {
173			_ParseTitleLine(lineContent, playlist, lastAssignedIndex);
174		} else if (lineType == "Length") {
175			_ParseLengthLine(lineContent, playlist, lastAssignedIndex);
176		// The file should include one NumberOfEntries line and one Version line.
177		} else if (lineType == "NumberOfEntries") {
178			plsEntries = lineContent;
179		} else if (lineType == "Version") {
180			plsVersion = lineContent;
181		} else {
182			// Ignore the line
183		}
184
185	line.Truncate(0);
186	}
187}
188
189
190status_t
191PlsReader::_ParseTitleLine(const BString& title, Playlist* playlist,
192	const int32 lastAssignedIndex)
193{
194	status_t err;
195	if (lastAssignedIndex != -1) {
196		// Only use this Title if most recent File was successfully added to the playlist.
197		err = playlist->ItemAt(lastAssignedIndex)->SetAttribute(
198			PlaylistItem::ATTR_STRING_TITLE, title);
199	} else
200		err = B_CANCELED;
201
202	if (err != B_OK)
203		printf("Error - %s: [%" B_PRIx32 "]\n", strerror(err), err);
204
205	return err;
206}
207
208
209status_t
210PlsReader::_ParseLengthLine(const BString& length, Playlist* playlist,
211	const int32 lastAssignedIndex)
212{
213	status_t err;
214	if (lastAssignedIndex != -1)
215	{
216		int64 lengthInt = strtoll(length.String(), NULL, 10);
217			// Track length in seconds, or -1 for an infinite streaming track.
218
219		err = playlist->ItemAt(lastAssignedIndex)->SetAttribute(
220			PlaylistItem::ATTR_INT64_DURATION, lengthInt);
221			// This does nothing if the track in question is streaming, because
222			// UrlPlaylistItem::SetAttribute(const Attribute&, const int32&) is not implemented.
223	} else
224		err = B_CANCELED;
225
226	if (err != B_OK)
227		printf("Error - %s: [%" B_PRIx32 "]\n", strerror(err), err);
228
229	return err;
230}
231