C# .m3u .m3u8 プレイリスト内から得た相対ファイルパスを(も)絶対パス形式にしたかった

経緯とか

この規格の仕様とかって無いのかなーと思って調べたら、それっぽい情報がwikiにありました。

Wikipedia - M3U (日本語訳版) によると

正式な仕様は存在せず対応状況はまちまちである

との事 …orz
※ 今回 #EXTINF については省きます。

メディアファイルとして存在しうるpath

プレイリストに書かれたファイルパスの種類は、いくつかに分類できます。

1~3は絶対パスと考えれば、まぁ同義かもしれません。
問題は相対パスです

プレイリスト自身が置かれているパスを得る

プレイリストに書いてあるすべての相対パスは 、そのプレイリストファイルの位置を起点とする
といったルールが確立されているようなので、
まずはプレイリストファイル自身が置かれている位置から、配置ディレクトリ位置を取り出しておきます

string CurrentDirectoryPath = System.IO.Path.GetDirectoryName(PathPlayListFile);

|プレイリストファイルパス|System.IO.Path.GetDirectoryName() で得られる結果| |—-|—-| |@”D:\More Music\Foo.m3u”|@”D:\More Music”| |@”C:\music\favorite.m3u8”|@”C:\music”| |@”C:\any\bgm.m3u”|@”C:\any”| |@”E:\music\playlist\rock.m3u8”|@”E:\music\playlist”|

記載されたメディアファイルパスからルートパスを調べる

次に、プレイリスト内に記載されているメディアファイル行から、
書かれているメディアファイルのルートパスが得られるかどうかを確認します

string PathRoot = System.IO.Path.GetPathRoot(PathExtinfMedia);

ここでは通常、変数PathRoot には @"c:\"@"d:\" といった値が入ることが期待されますが、
相対パスが与えられた場合はこれが取得できません。

逆に言えば、得られた文字列内に [a-zA-Z] が含まれているルートパスが得られた
…として、そのまま読み込めるメディアパスとして扱えるとの判断もできます

メディアパス System.IO.Path.GetPathRoot() で得られる結果
@”D:\More Music\Foo.mp3” @”D:\”
@”Alternative\Band - Song.mp3” @””
@”\any\sound.mp3” @”\”
@”..\..\music\Hoge.mp3” @””
@”http://example.com/streaming_song.mp3” @””

参考 (詳しい例が見られます)
curict.com - [C#][VB.NET]ファイルのパスから、ルートディレクトリを取得する

メディアの相対パスとプレイリストディレクトリ位置から、絶対パスを導く

先ほどの処理 System.IO.Path.GetPathRoot() でメディアファイルのルートパスが得られなかった場合は
予め System.IO.Path.GetDirectoryName() で得た、プレイリストファイルのディレクトリ位置と、
記載されている相対パスの2つから、新たにUriクラスを用いて、絶対パスを生成します

参考
DOBON.NET - 任意のディレクトリを基準にして相対パスから絶対パスを取得する

String TargetPlayListPath = @"C:\playlist\myplaylist.m3u";
String CurrentDirectoryPath = System.IO.Path.GetDirectoryName(TargetPlayListPath);
// @"C:\playlist" が得られる

String ReadMediaFilePath = @"..\any\sound.mp3";

Uri u1 = new Uri(CurrentDirectoryPath);
Uri u2 = new Uri(u1, ReadMediaFilePath);

Console.WriteLine(u2.LocalPath); // @"C:\any\sound.mp3"

雑な検証・変換メソッド

public static Hoge(String CurrentDirectoryPath , String ReadMediaFilePath){

	string AbsolutePath = "";

	//ルートパスが得られる場合
	if (System.Text.RegularExpressions.Regex.IsMatch(System.IO.Path.GetPathRoot(ReadMediaFilePath), "[a-zA-Z]"))
	{
		AbsolutePath = ReadMediaFilePath;
	}
	//ルートパスが得られない場合
	else
	{
		if (Uri.IsWellFormedUriString(ReadMediaFilePath, UriKind.Absolute))
		{
			AbsolutePath = ReadMediaFilePath;
		}
		else
		{
			Uri u1 = new Uri(CurrentDirectoryPath);
			Uri u2 = new Uri(u1, ReadMediaFilePath);
			AbsolutePath = u2.LocalPath;
		}
	}
	//Console.WriteLine("算出前{0}", ReadMediaFilePath);
	//Console.WriteLine("算出後{0}", AbsolutePath);
	return AbsolutePath;
}

例として、このような Hoge() メソッドを作り、

をそれぞれ与えると、以下のような結果が返ってくると思います

Hoge(@"C:\playlist" , @"music.mp3");
// @"C:\playlist\music.mp3"

Hoge(@"C:\playlist" , @"..\sound.wav");
// @"C:\sound.wav"

Hoge(@"D:\music" , @"\fooArtist\01_title.m4a");
// @"D:\music\fooArtist\01_title.m4a"

Hoge(@"D:\media\playlist" , @"C:\My Music\rockband\title\01_music.mp3");
// @"C:\My Music\rockband\title\01_music.mp3"

Hoge(@"D:\media\netradio" , @"http://example.com/streaming_song.mp3");
// @"http://example.com/streaming_song.mp3"

例外などは考えていませんし、運用レベルで組むならプレイリストのパス自体は、
クラスのフィールドに持たせておいたほうが良さそうな気がします。