#Region " Imports "
Imports System.Collections.ObjectModel
Imports System.IO
Imports System.Text
Imports ElektroKit.Core.Multimedia.Enums
#End Region
#Region " Playlist Editor "
Namespace ElektroKit.Core.Multimedia.Types
''' <summary>
''' Contains methods to create and or manage the tracks defined in a multimedia playlist file.
''' </summary>
Public NotInheritable Class PlaylistEditor
#Region " Properties "
''' <summary>
''' Gets the playlist filepath.
''' </summary>
Public ReadOnly Property FilePath As String
<DebuggerStepThrough>
Get
Return Me.filepathB
End Get
End Property
''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' ( Backing field )
''' The playlist filepath.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
Private ReadOnly filepathB As String
''' <summary>
''' Gets the playlist type.
''' </summary>
Public ReadOnly Property PlaylistType As PlaylistType
Get
Return Me.playlistTypeB
End Get
End Property
Private ReadOnly playlistTypeB As PlaylistType
''' <summary>
''' Gets the text encoding of the playlist file.
''' </summary>
Public ReadOnly Property FileEncoding As Encoding
Get
Return Me.encodingB
End Get
End Property
Private ReadOnly encodingB As Encoding
''' <summary>
''' Gets a value indicating whether the text will be appended at the bottom of the playlist file.
''' </summary>
Public ReadOnly Property Append As Boolean
<DebuggerStepThrough>
Get
Return Me.appendB
End Get
End Property
Private ReadOnly appendB As Boolean
''' <summary>
''' Gets a collection that represents the tracks defined (if any) in the playlist.
''' </summary>
Public ReadOnly Property Tracks As ReadOnlyCollection(Of PlaylistTrackInfo)
<DebuggerStepThrough>
Get
Return New ReadOnlyCollection(Of PlaylistTrackInfo)(Me.GetTracks)
End Get
End Property
''' <summary>
''' Gets the amount of tracks defined in the playlist.
''' </summary>
Public ReadOnly Property Count As Integer
<DebuggerStepThrough>
Get
Return Me.GetTracks.Count
End Get
End Property
#End Region
#Region " Constructors "
<DebuggerNonUserCode>
Private Sub New()
End Sub
''' <summary>
''' Initializes a new instance of the <see cref="PlaylistEditor"/> class.
''' </summary>
'''
''' <param name="playlistFile">
''' The playlist file path.
''' </param>
''' <param name="playlistType">
''' The type of the playlist.
''' </param>
''' <param name="createNew">
''' If set to <see langword="True"/>,
''' the <see cref="PlaylistEditor"/> instance will assume that the playlist file already exist,
''' and it will append any new track entries to the bottom of the playlist file.
''' <para></para>
''' If set to <see langword="False"/>,
''' the <see cref="PlaylistEditor"/> instance will assume that the playlist file does not exist,
''' so then it will create a new playlist file.
''' </param>
''' <param name="fileEncoding">
''' Optionally indicates the file encoding to perform write and read operations to the playlist file.
''' <para></para>
''' Default value is: <see cref="Encoding.Default"/>
''' </param>
<DebuggerStepThrough>
Public Sub New(ByVal playlistFile As String,
ByVal playlistType As PlaylistType,
ByVal createNew As Boolean,
Optional ByVal fileEncoding As Encoding = Nothing)
Me.filepathB = playlistFile
Me.playlistTypeB = playlistType
Me.encodingB = If(fileEncoding IsNot Nothing, fileEncoding, Encoding.Default)
Me.appendB = Not createNew
If Not (appendB) Then
Me.AddHeaders()
End If
End Sub
#End Region
#Region " Public Methods "
''' <summary>
''' Adds a new track entry in the playlist.
''' </summary>
<DebuggerStepThrough>
Public Sub Add(ByVal filepath As String,
Optional ByVal throwOnDuplicate As Boolean = False)
If Not (throwOnDuplicate) AndAlso Me.Exist(filepath) Then
Throw New ArgumentException("The specified file path already exists in the playlist.",
paramName:="filepath")
End If
Dim sb As New StringBuilder
Select Case Me.playlistTypeB
Case PlaylistType.M3U
sb.AppendLine()
sb.AppendLine(filepath)
File.
AppendAllText(Me.
filepathB, sb.
ToString(),
Me.
encodingB)
Case PlaylistType.PLS
Dim tacksCount As Integer = Me.GetPlsTrackCount()
sb.
AppendLine(File.
ReadAllText(Me.
filepathB,
Me.
encodingB).
Replace("NumberOfEntries=" & CStr(tacksCount),
"NumberOfEntries=" & CStr(tacksCount + 1)))
sb.AppendLine(String.Format("File{0}={1}", CStr(tacksCount + 1), filepath.Replace("\", "/")))
File.
WriteAllText(Me.
filepathB, sb.
ToString(),
Me.
encodingB)
End Select
sb.Clear()
End Sub
''' <summary>
''' Adds a new track in the playlist, with extended track information.
''' </summary>
<DebuggerStepThrough>
Public Sub Add(ByVal filepath As String, ByVal title As String, ByVal length As TimeSpan,
Optional ByVal throwOnDuplicate As Boolean = False)
If Not (throwOnDuplicate) AndAlso Me.Exist(filepath) Then
Throw New ArgumentException("The specified file path already exists in the playlist.",
paramName:="filepath")
End If
Dim sb As New StringBuilder
Select Case Me.playlistTypeB
Case PlaylistType.M3U
sb.AppendLine()
sb.AppendLine(String.Format("#EXTINF:{0},{1}", CStr(Math.Truncate(length.TotalSeconds)), title))
sb.AppendLine(filepath)
File.
AppendAllText(Me.
filepathB, sb.
ToString(),
Me.
encodingB)
Case PlaylistType.PLS
Dim tacksCount As Integer = Me.GetPlsTrackCount()
sb.
AppendLine(File.
ReadAllText(Me.
filepathB,
Me.
encodingB).
Replace("NumberOfEntries=" & CStr(tacksCount),
"NumberOfEntries=" & CStr(tacksCount + 1I)))
sb.AppendLine(String.Format("File{0}={1}", CStr(tacksCount + 1I), filepath.Replace("\", "/")))
sb.AppendLine(String.Format("Title{0}={1}", CStr(tacksCount + 1I), title))
sb.AppendLine(String.Format("Length{0}={1}", CStr(tacksCount + 1I), CStr(Math.Truncate(length.TotalSeconds))))
File.
WriteAllText(Me.
filepathB, sb.
ToString(),
Me.
encodingB)
End Select
sb.Clear()
End Sub
''' <summary>
''' Adds a new track in the playlist, with extended track information.
''' </summary>
<DebuggerStepThrough>
Public Sub Add(ByVal trackInfo As PlaylistTrackInfo,
Optional ByVal throwOnDuplicate As Boolean = False)
Me.Add(trackInfo.Path, trackInfo.Title, trackInfo.Length, throwOnDuplicate)
End Sub
''' <summary>
''' Removes the specified track from the playlist.
''' </summary>
<DebuggerStepThrough>
Public Sub RemoveTrack(ByVal filepath As String)
If Not Me.Exist(filepath) Then
Throw New ArgumentException("The specified file path does not exists in the playlist.",
paramName:="filepath")
End If
Dim playlistContent
As List
(Of String) = File.
ReadLines(Me.
filepathB,
Me.
encodingB).
ToList()
Select Case Me.playlistTypeB
Case PlaylistType.M3U
Dim entryIndex As Integer =
playlistContent.FindIndex(
Function(item As String)
Return item.Equals(filepath, StringComparison.OrdinalIgnoreCase)
End Function)
playlistContent.RemoveAt(entryIndex)
If playlistContent(entryIndex - 1).StartsWith("#EXTINF", StringComparison.OrdinalIgnoreCase) Then
playlistContent.RemoveAt(entryIndex - 1)
End If
File.
WriteAllLines(Me.
filepathB, playlistContent,
Me.
encodingB)
Case PlaylistType.PLS
Dim entryIndex As Integer =
playlistContent.FindIndex(
Function(item As String)
Return item.ToLower Like "file#*" & filepath.Replace("\", "/").ToLower
End Function)
Dim trackIndexDelimStartIndex As Integer =
playlistContent(entryIndex).IndexOf("e", StringComparison.OrdinalIgnoreCase) + 1I
Dim trackIndexDelimEndIndex As Integer =
playlistContent(entryIndex).IndexOf("=", StringComparison.OrdinalIgnoreCase)
Dim trackIndex As Integer =
CInt(playlistContent(entryIndex).Substring(trackIndexDelimStartIndex,
trackIndexDelimEndIndex - trackIndexDelimStartIndex))
playlistContent.RemoveAt(entryIndex)
Dim titleEntryIndex As Integer =
playlistContent.FindIndex(Function(item As String)
Return item.ToLower Like String.Format("title{0}=*", CStr(trackIndex))
End Function)
If titleEntryIndex <> -1 Then
playlistContent.RemoveAt(titleEntryIndex)
End If
Dim lengthEntryIndex As Integer =
playlistContent.FindIndex(Function(item As String)
Return item.ToLower Like String.Format("length{0}=*", CStr(trackIndex))
End Function)
If lengthEntryIndex <> -1I Then
playlistContent.RemoveAt(lengthEntryIndex)
End If
Dim numberOfEntriesEntryIndex As Integer =
playlistContent.FindIndex(Function(item As String)
Return item.ToLower Like "numberofentries=#*"
End Function)
playlistContent(numberOfEntriesEntryIndex) =
String.Format("NumberOfEntries={0}", CStr(Me.GetPlsTrackCount() - 1))
File.
WriteAllLines(Me.
filepathB, playlistContent,
Me.
encodingB)
Me.FixPlsTrackIndices()
End Select
End Sub
''' <summary>
''' Removes the specified track from the playlist.
''' </summary>
<DebuggerStepThrough>
Public Sub RemoveTrack(ByVal trackIndex As Integer)
Dim track As PlaylistTrackInfo = Me.GetTrackInfo(trackIndex)
If track IsNot Nothing Then
Me.RemoveTrack(track.Path)
Else
Throw New IndexOutOfRangeException("Track index is out of range") With {.Source = "trackIndex"}
End If
End Sub
''' <summary>
''' Sets the track info of the specified track.
''' </summary>
<DebuggerStepThrough>
Public Sub SetTrackInfo(ByVal filepath As String, ByVal trackInfo As PlaylistTrackInfo)
If Not Me.Exist(filepath) Then
Throw New ArgumentException("The specified file path does not exists in the playlist.",
paramName:="filepath")
End If
Dim track As PlaylistTrackInfo = Me.GetTrackInfo(filepath)
With track
.Path = trackInfo.Path
.Title = trackInfo.Title
.Length = trackInfo.Length
End With
Dim playlistContent
As List
(Of String) = File.
ReadLines(Me.
filepathB,
Me.
encodingB).
ToList()
Select Case Me.playlistTypeB
Case PlaylistType.M3U
Dim trackIndex As Integer =
playlistContent.FindIndex(
Function(item As String)
Return item.Equals(filepath, StringComparison.OrdinalIgnoreCase)
End Function)
playlistContent(trackIndex) = String.Format("#EXTINF:{0},{1}",
CStr(Math.Truncate(track.Length.TotalSeconds)),
track.Title) & Environment.NewLine & track.Path
If playlistContent(trackIndex - 1I).StartsWith("#EXTINF", StringComparison.OrdinalIgnoreCase) Then
playlistContent.RemoveAt(trackIndex - 1I)
End If
File.
WriteAllLines(Me.
filepathB, playlistContent,
Me.
encodingB)
Case PlaylistType.PLS
track.Path = track.Path.Replace("\", "/")
Dim trackIndex As Integer =
playlistContent.FindIndex(
Function(item As String)
Return item.ToLower Like "file#*" & filepath.Replace("\", "/").ToLower
End Function)
playlistContent(trackIndex) = String.Format("File{0}={1}", CStr(track.Index), track.Path) & Environment.NewLine &
String.Format("Title{0}={1}", CStr(track.Index), track.Title) & Environment.NewLine &
String.Format("Length{0}={1}", CStr(track.Index), CStr(Math.Truncate(track.Length.TotalSeconds)))
If playlistContent.Count > (trackIndex + 1) Then
If playlistContent(trackIndex + 2).StartsWith("Title", StringComparison.OrdinalIgnoreCase) _
OrElse playlistContent(trackIndex + 2).StartsWith("Length", StringComparison.OrdinalIgnoreCase) Then
playlistContent.RemoveAt(trackIndex + 2)
End If
End If
If playlistContent.Count > trackIndex Then
If playlistContent(trackIndex + 1).StartsWith("Title", StringComparison.OrdinalIgnoreCase) _
OrElse playlistContent(trackIndex + 1).StartsWith("Length", StringComparison.OrdinalIgnoreCase) Then
playlistContent.RemoveAt(trackIndex + 1)
End If
End If
File.
WriteAllLines(Me.
filepathB, playlistContent,
Me.
encodingB) End Select
End Sub
''' <summary>
''' Sets the track info of the specified track.
''' </summary>
<DebuggerStepThrough>
Public Sub SetTrackInfo(ByVal trackIndex As Integer, ByVal trackInfo As PlaylistTrackInfo)
If Not Me.Exist(trackIndex) Then
Throw New IndexOutOfRangeException("Track index is out of range.") With {.Source = "trackIndex"}
End If
Me.SetTrackInfo(Me.GetTrackInfo(trackIndex).Path, trackInfo)
End Sub
''' <summary>
''' Finds the specified track in the playlist and returns a
''' <see cref="PlaylistTrackInfo"/> instance that represents the track.
''' </summary>
<DebuggerStepThrough>
Public Function GetTrackInfo(ByVal filepath As String) As PlaylistTrackInfo
Dim playlistContent
As List
(Of String) = File.
ReadLines(Me.
filepathB,
Me.
encodingB).
ToList() Dim tInfo As New PlaylistTrackInfo
Select Case Me.playlistTypeB
Case PlaylistType.M3U
Dim trackIndex As Integer = playlistContent.FindIndex(
Function(item As String)
Return item.Equals(filepath, StringComparison.OrdinalIgnoreCase)
End Function) - 1
If playlistContent(trackIndex).StartsWith("#EXTINF", StringComparison.OrdinalIgnoreCase) Then
Dim titleDelimIndex As Integer = playlistContent(trackIndex).IndexOf(","c) + 1
Dim lengthDelimIndex As Integer = playlistContent(trackIndex).IndexOf(":"c) + 1
With tInfo
.Index = trackIndex
.Path = filepath
.Title = playlistContent(trackIndex).Substring(titleDelimIndex)
.Length = TimeSpan.FromSeconds(CDbl(playlistContent(trackIndex).Substring(lengthDelimIndex, (titleDelimIndex - lengthDelimIndex))))
End With
End If
Case PlaylistType.PLS
filepath = filepath.Replace("\", "/")
Dim entry As String = (From Item As String In playlistContent
Where Item.ToLower Like String.Format("file#*={0}", filepath.ToLower())).SingleOrDefault
If Not String.IsNullOrEmpty(entry) Then
Dim indexDelimStartIndex As Integer =
entry.IndexOf("e", StringComparison.OrdinalIgnoreCase) + 1I
Dim indexDelimEndIndex As Integer =
entry.IndexOf("=", StringComparison.OrdinalIgnoreCase)
Dim trackIndex As Integer = CInt(entry.Substring(indexDelimStartIndex,
indexDelimEndIndex - indexDelimStartIndex))
Dim titleEntry As String = (From Item As String In playlistContent
Where Item.StartsWith(String.Format("Title{0}=", CStr(trackIndex)), StringComparison.OrdinalIgnoreCase)).
FirstOrDefault
Dim lengthEntry As String = (From Item As String In playlistContent
Where Item.StartsWith(String.Format("Length{0}=", CStr(trackIndex)), StringComparison.OrdinalIgnoreCase)).
FirstOrDefault
With tInfo
.Index = trackIndex
.Path = filepath
.Title = If(Not String.IsNullOrEmpty(titleEntry),
titleEntry.Substring(titleEntry.IndexOf("=") + 1),
Nothing)
.Length = If(Not String.IsNullOrEmpty(titleEntry),
TimeSpan.FromSeconds(CDbl(lengthEntry.Split("="c).LastOrDefault)),
Nothing)
End With
End If
End Select
Return tInfo
End Function
''' <summary>
''' Finds the specified track in the playlist and returns a
''' <see cref="PlaylistTrackInfo"/> instance that represents the track.
''' </summary>
<DebuggerStepThrough>
Public Function GetTrackInfo(ByVal trackIndex As Integer) As PlaylistTrackInfo
Dim playlistContent
As List
(Of String) = File.
ReadLines(Me.
filepathB,
Me.
encodingB).
ToList()
Select Case Me.playlistTypeB
Case PlaylistType.M3U
Dim trackCount As Integer = 0
For index As Integer = 0 To (playlistContent.Count - 1)
If Not String.IsNullOrEmpty(playlistContent(index)) _
AndAlso Not playlistContent(index).StartsWith("#EXT", StringComparison.OrdinalIgnoreCase) Then
trackCount += 1
If (trackCount = trackIndex) Then
Dim tInfo As PlaylistTrackInfo = Me.GetTrackInfo(playlistContent(index))
With tInfo
.Index = trackIndex
.Path = playlistContent(index)
End With
Return tInfo
End If
End If
Next index
Case PlaylistType.PLS
For index As Integer = 0 To (playlistContent.Count - 1)
If playlistContent(index).StartsWith(String.Format("File{0}=", CStr(trackIndex)),
StringComparison.OrdinalIgnoreCase) Then
Return Me.GetTrackInfo(playlistContent(index).Substring(playlistContent(index).IndexOf("="c) + 1I))
End If
Next index
End Select
Return Nothing
End Function
''' <summary>
''' Determines whether the specified track exists in the playlist.
''' </summary>
<DebuggerStepThrough>
Public Function Exist(ByVal filepath As String) As Boolean
Dim returnValue As Boolean = False
Select Case Me.playlistTypeB
Case PlaylistType.M3U
returnValue
= (From Item
As String In
File.
ReadLines(Me.
filepathB,
Me.
encodingB) Where Item.StartsWith(filepath, StringComparison.OrdinalIgnoreCase)).
Any()
Case PlaylistType.PLS
returnValue
= (From Item
As String In
File.
ReadLines(Me.
filepathB,
Me.
encodingB) Where Item.ToLower() Like "file#*" & filepath.Replace("\", "/").ToLower()).
Any()
End Select
Return returnValue
End Function
''' <summary>
''' Determines whether the specified track exists in the playlist.
''' </summary>
<DebuggerStepThrough>
Public Function Exist(ByVal trackIndex As Integer) As Boolean
If (trackIndex <= 0) Then
Throw New IndexOutOfRangeException("TrackIndex should be greater than 0.") With {.Source = "trackIndex"}
End If
Return (Me.Count >= trackIndex)
End Function
#End Region
#Region " Private Methods "
''' <summary>
''' Collects all the tracks defined in a playlist file and returns a <see cref="List(Of PlaylistTrackInfo)"/>.
''' </summary>
<DebuggerStepThrough>
Private Function GetTracks() As List(Of PlaylistTrackInfo)
Dim playlistContent
As List
(Of String) = File.
ReadLines(Me.
filepathB,
Me.
encodingB).
ToList() Dim tInfo As New List(Of PlaylistTrackInfo)
Dim trackCount As Integer = 0
Select Case Me.playlistTypeB
Case PlaylistType.M3U
For index As Integer = 0 To (playlistContent.Count - 1)
If Not String.IsNullOrEmpty(playlistContent(index)) _
AndAlso Not playlistContent(index).StartsWith("#EXT", StringComparison.OrdinalIgnoreCase) Then
trackCount += 1
tInfo.Add(Me.GetTrackInfo(trackCount))
End If
Next
Case PlaylistType.PLS
For index As Integer = 0 To (playlistContent.Count - 1)
If playlistContent(index).StartsWith("File", StringComparison.OrdinalIgnoreCase) Then
trackCount += 1
tInfo.Add(Me.GetTrackInfo(trackCount))
End If
Next index
End Select
Return tInfo
End Function
''' <summary>
''' Adds the playlist headers at the top of the playlist file.
''' <para></para>
''' This method should always be called before adding the very first entry in a new (empty) playlist.
''' </summary>
<DebuggerStepThrough>
Private Sub AddHeaders()
Dim sb As New StringBuilder
Select Case Me.playlistTypeB
Case PlaylistType.M3U
sb.AppendLine("#EXTM3U")
Case PlaylistType.PLS
With sb
.AppendLine("[playlist]")
.AppendLine("NumberOfEntries=0")
.AppendLine("Version=2")
End With
End Select
File.
WriteAllText(Me.
filepathB, sb.
ToString(),
Me.
encodingB) sb.Clear()
End Sub
''' <summary>
''' Gets the amount of total tracks defined in a PLS playlist file.
''' </summary>
<DebuggerStepThrough>
Private Function GetPlsTrackCount() As Integer
Dim playlistContent
As String = File.
ReadAllText(Me.
filepathB,
Me.
encodingB)
Dim startIndex As Integer =
playlistContent.IndexOf("=") + 1I
Dim endIndex As Integer =
playlistContent.IndexOf(ControlChars.NewLine, startIndex) - startIndex
Return CInt(playlistContent.Substring(startIndex, playlistContent.IndexOf(String.Empty, endIndex)))
End Function
''' <summary>
''' Fixes the track indices of a PLS playlist file.
''' <para></para>
''' This method shoould always be called after removing a track from the playlist.
''' </summary>
<DebuggerStepThrough>
Private Sub FixPlsTrackIndices()
Dim playlistContent
As List
(Of String) = File.
ReadLines(Me.
filepathB,
Me.
encodingB).
ToList() Dim trackCount As Integer = 0I
For index As Integer = 0 To (playlistContent.Count - 1I)
If playlistContent(index).StartsWith("File", StringComparison.OrdinalIgnoreCase) Then
trackCount += 1I
playlistContent(index) = String.Format("File{0}={1}",
CStr(trackCount),
playlistContent(index).Substring(playlistContent(index).IndexOf("="c) + 1I))
ElseIf playlistContent(index).StartsWith("Title", StringComparison.OrdinalIgnoreCase) Then
playlistContent(index) = String.Format("Title{0}={1}",
CStr(trackCount),
playlistContent(index).Substring(playlistContent(index).IndexOf("="c) + 1I))
ElseIf playlistContent(index).StartsWith("Length", StringComparison.OrdinalIgnoreCase) Then
playlistContent(index) = String.Format("Length{0}={1}",
CStr(trackCount),
playlistContent(index).Substring(playlistContent(index).IndexOf("="c) + 1I))
End If
Next index
Dim numberOfEntriesEntryIndex As Integer =
playlistContent.FindIndex(Function(item As String)
Return item.ToLower Like "numberofentries=#*"
End Function)
playlistContent(numberOfEntriesEntryIndex) =
String.Format("NumberOfEntries={0}", CStr(trackCount))
File.
WriteAllLines(Me.
filepathB, playlistContent,
Me.
encodingB) End Sub
#End Region
End Class
End Namespace
#End Region