プロフィール

髭山髭人(ひげひと)

自分の書いた記事が、一人でも誰かの役に立てば...
活動信条の一つとして「貴方のメモは、誰かのヒント」というのがあります。

このサイトについて

本家HP packetroom.net から切り離した いわゆる技術メモ用のブログで、無料レンタルサーバーにて運用しています。広告表示はその義務なのでご容赦。
XREA さんには長年お世話になっています

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

経緯とか

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

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

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

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

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

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

  • ルートパス(例えばC:\とか)から始まる絶対パス
  • ネットワーク上のパス
  • ストリーミングとしてのパス(URL)
  • プレイリストファイルの位置を起点とした相対パス

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" @""

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

先ほどの処理 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"

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