Skip to content

Commit

Permalink
implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeferson Tenorio committed Dec 21, 2017
1 parent eb99f0f commit 2283de7
Show file tree
Hide file tree
Showing 23 changed files with 814 additions and 0 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
# m3uParser
A dotnet parser for M3U file format

# Thanks
https://github.com/sprache/Sprache
53 changes: 53 additions & 0 deletions m3uParser.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.10
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{19BA96D9-4DB8-4361-A592-EEBD390650D6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "m3uParser", "src\m3uParser\m3uParser.csproj", "{40E280F5-E597-4B6F-977E-29C01E2FD3AD}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{570D28BF-6E44-4FE4-9FC0-770099C0F283}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "m3uParser.tool", "src\m3uParser.tool\m3uParser.tool.csproj", "{29FC7F81-20FC-4C79-95B0-BAE3E0B9F9CD}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6E62EFF6-EFCE-45C2-A785-27851E19307D}"
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
LICENSE = LICENSE
README.md = README.md
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "m3uParser.Test", "test\m3uParser.Test\m3uParser.Test.csproj", "{8A14FB47-E478-4622-B2F3-325A2CB964CC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{40E280F5-E597-4B6F-977E-29C01E2FD3AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{40E280F5-E597-4B6F-977E-29C01E2FD3AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{40E280F5-E597-4B6F-977E-29C01E2FD3AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{40E280F5-E597-4B6F-977E-29C01E2FD3AD}.Release|Any CPU.Build.0 = Release|Any CPU
{29FC7F81-20FC-4C79-95B0-BAE3E0B9F9CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{29FC7F81-20FC-4C79-95B0-BAE3E0B9F9CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{29FC7F81-20FC-4C79-95B0-BAE3E0B9F9CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{29FC7F81-20FC-4C79-95B0-BAE3E0B9F9CD}.Release|Any CPU.Build.0 = Release|Any CPU
{8A14FB47-E478-4622-B2F3-325A2CB964CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8A14FB47-E478-4622-B2F3-325A2CB964CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8A14FB47-E478-4622-B2F3-325A2CB964CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8A14FB47-E478-4622-B2F3-325A2CB964CC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{40E280F5-E597-4B6F-977E-29C01E2FD3AD} = {19BA96D9-4DB8-4361-A592-EEBD390650D6}
{29FC7F81-20FC-4C79-95B0-BAE3E0B9F9CD} = {19BA96D9-4DB8-4361-A592-EEBD390650D6}
{8A14FB47-E478-4622-B2F3-325A2CB964CC} = {570D28BF-6E44-4FE4-9FC0-770099C0F283}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FD8A92CB-30FC-4366-A370-F1EB643B211D}
EndGlobalSection
EndGlobal
6 changes: 6 additions & 0 deletions src/m3uParser.tool/App.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7" />
</startup>
</configuration>
18 changes: 18 additions & 0 deletions src/m3uParser.tool/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace m3uParser.tool
{
internal class Program
{
private static void Main(string[] args)
{
}
}
}
36 changes: 36 additions & 0 deletions src/m3uParser.tool/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("m3uParser.tool")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("m3uParser.tool")]
[assembly: AssemblyCopyright("Copyright © 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("29fc7f81-20fc-4c79-95b0-bae3e0b9f9cd")]

// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
58 changes: 58 additions & 0 deletions src/m3uParser.tool/m3uParser.tool.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{29FC7F81-20FC-4C79-95B0-BAE3E0B9F9CD}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>m3uParser.tool</RootNamespace>
<AssemblyName>m3uParser.tool</AssemblyName>
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\m3uParser\m3uParser.csproj">
<Project>{40e280f5-e597-4b6f-977e-29c01e2fd3ad}</Project>
<Name>m3uParser</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
46 changes: 46 additions & 0 deletions src/m3uParser/M3U.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Sprache;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace m3uParser
{
public static class M3U
{
public static Root ParseText(string text)
{
// Clear comments
Regex commentRegex = new Regex("##{1,}.*");
text = commentRegex.Replace(text, string.Empty);

// Parse EXTINF
return ParseSpecification.root.Parse(text);
}

public static Root ParseBytes(byte[] byteArr)
{
return ParseText(Encoding.Default.GetString(byteArr));
}

public static Root ParseFromFile(string file)
{
return ParseText(System.IO.File.ReadAllText(file));
}

public static async Task<Root> ParseFromUrlAsync(string requestUri)
{
return await ParseFromUrlAsync(requestUri, new HttpClient());
}

public static async Task<Root> ParseFromUrlAsync(string requestUri, HttpClient client)
{
var get = await client.GetAsync(requestUri);
var content = await get.Content.ReadAsStringAsync();
return ParseText(content);
}
}
}
26 changes: 26 additions & 0 deletions src/m3uParser/Model/Attributes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace m3uParser
{
/// <summary>
/// https://en.wikipedia.org/wiki/M3U
/// https://github.com/sprache/Sprache
/// https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.2
/// http://ss-iptv.com/en/users/documents/m3u
/// https://developer.apple.com/library/content/technotes/tn2288/_index.html
/// </summary>
public class AttributeInfo
{
public IEnumerable<KeyValuePair<string, string>> Attributes { get; set; }
public string GroupTitle { get { return this.Attributes.FirstOrDefault(w => w.Key.ToLower().Trim() == "group-title").Value; } }
public string GuideTimeShiftingTV { get { return this.Attributes.FirstOrDefault(w => w.Key.ToLower().Trim() == "tvg-shift").Value; } }
public string GuideIdentifierTV { get { return this.Attributes.FirstOrDefault(w => w.Key.ToLower().Trim() == "tvg-name").Value; } }
public string Logo { get { return this.Attributes.FirstOrDefault(w => w.Key.ToLower().Trim() == "tvg-logo").Value; } }
public string AudioTrack { get { return this.Attributes.FirstOrDefault(w => w.Key.ToLower().Trim() == "audio-track").Value; } }
public string AspectRatio { get { return this.Attributes.FirstOrDefault(w => w.Key.ToLower().Trim() == "aspect-ratio").Value; } }
public string Id { get { return this.Attributes.FirstOrDefault(w => w.Key.ToLower().Trim() == "tvg-id").Value; } }
}
}
28 changes: 28 additions & 0 deletions src/m3uParser/Model/Media.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace m3uParser
{
public class Media
{
public decimal Duration { get; set; }

public Title Title { get; set; }

public string MediaFile { get; set; }

public bool IsStream { get { return this.Duration <= 0; } }

public AttributeInfo AttributeInfo { get; set; }

internal Media(decimal duration, IEnumerable<KeyValuePair<string, string>> attributes, Title title, string mediafile)
{
AttributeInfo = new AttributeInfo();
Duration = duration;
Title = title;
MediaFile = mediafile;
AttributeInfo.Attributes = attributes;
}
}
}
21 changes: 21 additions & 0 deletions src/m3uParser/Model/Root.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Sprache;
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace m3uParser
{
public class Root
{
public string Header { get; set; }

public IEnumerable<Media> Media { get; set; }

internal Root(string header, IEnumerable<Media> media)
{
Header = header;
Media = media;
}
}
}
18 changes: 18 additions & 0 deletions src/m3uParser/Model/Title.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace m3uParser
{
public class Title
{
public string RawTitle { get; set; }
public string InnerTitle { get; set; }

public Title(string title, string innerTitle)
{
RawTitle = title;
InnerTitle = innerTitle;
}
}
}
57 changes: 57 additions & 0 deletions src/m3uParser/ParseSpecification.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using Sprache;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace m3uParser
{
internal static class ParseSpecification
{
public static readonly Parser<Root> root =
from header in Parse.String("#EXTM3U").Text()
from endOfLine in Parse.LineEnd.Once().Optional()
from blankLine in Parse.String(Environment.NewLine).Optional()
from info in extinf.AtLeastOnce()
select new Root(header, info);

// https://stackoverflow.com/questions/21414309/sprache-parse-signed-integer
public static readonly Parser<Media> extinf =
from open in Parse.String("#EXTINF:").Text()
from duration in decimalSigned.Once()
from space in Parse.WhiteSpace.Once()
from param in attributes.Many()
from space1 in Parse.Char(',').Once()
from title in title.Once()
from newLine1 in Parse.LineEnd.Once()
from media in Parse.CharExcept(Environment.NewLine).Many().Text()
from ignore in Parse.CharExcept('#').Many()
select new Media(duration.First(), param, title.First(), media);

public static readonly Parser<Title> title =
from raw in Parse.CharExcept(Environment.NewLine).Many().Text()
select new Title(raw, raw);

public static readonly Parser<string> key =
from key in Parse.AnyChar.Except(Parse.Char('=').Or(Parse.Char(','))).Many().Text()
select key.Trim();

public static readonly Parser<string> value =
from open in Parse.Char('"').Once()
from value in Parse.CharExcept('"').Many().Text()
from close in Parse.Char('"').Once()
select value;

// https://gitter.im/sprache/Sprache/archives/2017/02/20
public static readonly Parser<KeyValuePair<string, string>> attributes =
from key in key
from assignment in Parse.Char('=')
from value in value
select new KeyValuePair<string, string>(key, value);

public static readonly Parser<decimal> decimalSigned =
from op in Parse.Optional(Parse.Char('-').Token())
from num in Parse.Decimal
select decimal.Parse(num) * (op.IsDefined ? -1 : 1);
}
}
Loading

0 comments on commit 2283de7

Please sign in to comment.