ホーム >プログラム >Delphi 6 ローテクTips >m3u8 プレイリストの読み込み

m3u8 形式のプレイリストを読み込もうというページ。



m3u8プレイリストはm3uプレイリストがUTF-8で保存されたものという理解で良いと思います。
UTF-8なのでUnicodeな文字も保存可能ということなのだと思います。

Unicodeな文字も保存可能ということて、せっかくなのでUnicodeに非対応なTStringsをUnicodeに対応させるため短いファイル名を利用する方法UTF-7を利用する方法も活用します。


簡単な例

まず、相対パスやコメントなどに対処しない一番簡単な読み込みの例です。
ただ読み込むだけでもm3uプレイリストの時と違い、TStringListを介して読み込んだりBOMを取り除いたりUTF-8の変換処理が必要になるのでやや複雑になります。

procedure ReadM3u8List(sFile: WideString; slList: TStrings);
//m3u8プレイリストを読み込む。
var
  lsl_Playlist: TStringList;
  i: Integer;
begin
  slList.Clear;

  //リストを読み込む。
  //TListBoxやTComboBoxのItemsはUTF-8をうまく読み込めないのでTStringListを利用。
  lsl_Playlist := TStringList.Create;
  try
    lsl_Playlist.LoadFromFile(sFile);
    if  (lsl_Playlist[0][1] = #$EF)
    and (lsl_Playlist[0][2] = #$BB)
    and (lsl_Playlist[0][3] = #$BF)
    then begin
      //BOMつきだったので取り除く。
      lsl_Playlist[0] := Copy(lsl_Playlist[0], 4, MAXINT);
    end;

    for i := 0 to lsl_Playlist.Count-1 do begin
      //Unicodeな文字があると代替文字に変換される。
      slList.Add(Utf8Decode(lsl_Playlist[i]));
    end;
  finally
    lsl_Playlist.Free;
  end;
end;
Unicode対応の例 - (短いファイル名を利用)

UTF-8は ã や ü などのUnicodeな文字を扱えます。
つまりm3u8プレイリストにはUnicodeな文字が含まれている可能性があるということです。
これをUnicodeに非対応なDelphi 6 のTStringsにそのまま代入するとUnicodeな文字が代替文字に変換されてしまうのでプレーヤーで再生できなくなってしまいます。
そこでUnicodeに非対応なTStringsをUnicodeに対応させるための例を二つ。

まず完全な対応ではないですが、簡便な短いファイル名を利用する方法です。
この方法だと後述するUTF-7を利用する方法に比べ、表示やTStringsからの取り出し方は通常のものをそのまま使えるので手間がかかりません。
またプレーヤーがUnicodeなファイル名に対応していない場合(例えばWindows Media Player の6.4など)も短いファイル名に変換することでUnicodeな文字を含むファイル名であっても再生できる(場合がある)ようになります。

表示の際に見慣れない短いファイル名に変わってしまう点とUnicodeなファイル名が8.3形式の短いファイル名に合致してしまう場合に対応できない点が欠点です。

procedure ReadM3u8List(sFile: WideString; slList: TStrings);
//m3u8プレイリストを読み込む。
var
  lsl_Playlist: TStringList;
  i: Integer;
  ls_File: WideString;
begin
  slList.Clear;

  //リストを読み込む。
  //TListBoxやTComboBoxのItemsはUTF-8をうまく読み込めないのでTStringListを利用。
  lsl_Playlist := TStringList.Create;
  try
    lsl_Playlist.LoadFromFile(sFile);
    if  (lsl_Playlist[0][1] = #$EF)
    and (lsl_Playlist[0][2] = #$BB)
    and (lsl_Playlist[0][3] = #$BF)
    then begin
      //BOMつきだったので取り除く。
      lsl_Playlist[0] := Copy(lsl_Playlist[0], 4, MAXINT);
    end;

    for i := 0 to lsl_Playlist.Count-1 do begin
      ls_File := Utf8Decode(lsl_Playlist[i]);
      if (gfnbIsUnicode(ls_File)) then begin
        //Unicodeな文字があるので短いファイル名に変換して問題を回避する。
        slList.Add(gfnsShortFileNameGet(ls_File));
      end else begin
        slList.Add(ls_File);
      end;
    end;
  finally
    lsl_Playlist.Free;
  end;
end;
Unicode対応の例 - (UTF-7を利用)

UTF-7を利用した変換処理を行ってUnicodeに非対応なTStringsにUnicodeな文字のあるファイル名を保存する例です。
この例ではTListBoxやTComboBoxなどでの表示の際にはOnDrawItemイベントを使って自前で表示させないといけなかったり、TStringsから取り出すときにいちいちUTF-7からWideStringに戻す関数を使わないといけなかったりと、手間がかかります。

その代わり先述の短いファイル名を利用する方法では対応できない8.3形式の短いファイル名に合致するUnicodeなファイル名も問題なく扱えます。
ただしプレーヤーがUnicodeなファイル名に対応していない場合(例えばWindows Media Player の6.4など)は上記の短いファイル名を利用しなければなりません。

procedure ReadM3u8List(sFile: WideString; slList: TStrings);
//m3u8プレイリストを読み込む。
var
  lsl_Playlist: TStringList;
  i: Integer;
begin
  slList.Clear;

  //リストを読み込む。
  //TListBoxやTComboBoxのItemsはUTF-8をうまく読み込めないのでTStringListを利用。
  lsl_Playlist := TStringList.Create;
  try
    lsl_Playlist.LoadFromFile(sFile);
    if  (lsl_Playlist[0][1] = #$EF)
    and (lsl_Playlist[0][2] = #$BB)
    and (lsl_Playlist[0][3] = #$BF)
    then begin
      //BOMつきだったので取り除く。
      lsl_Playlist[0] := Copy(lsl_Playlist[0], 4, MAXINT);
    end;

    for i := 0 to lsl_Playlist.Count-1 do begin
      //WideStringをUTF-7に変換してUnicodeに非対応なTStringsに保存。
      slList.Add(gfnsWideToUtf7(Utf8Decode(lsl_Playlist[i])));
    end;
  finally
    lsl_Playlist.Free;
  end;
end;
コメントやプレイリスト、相対パスに対処した例

コメントとプレイリスト、相対パスに対処した例です。
リストはUTF-7を利用してUnicodeに非対応なTStringsにUnicodeなファイル名を持たせています。

procedure ReadM3u8List(sFile: WideString; slList: TStrings);
//m3u8プレイリストを読み込む。
var
  lsl_Playlist: TStringList;
  i: Integer;
  ls_File, ls_Path: WideString;
begin
  slList.Clear;

  //リストを読み込む。
  //TListBoxやTComboBoxのItemsはUTF-8をうまく読み込めないのでTStringListを利用。
  lsl_Playlist := TStringList.Create;
  try
    lsl_Playlist.LoadFromFile(sFile);
    if  (lsl_Playlist[0][1] = #$EF)
    and (lsl_Playlist[0][2] = #$BB)
    and (lsl_Playlist[0][3] = #$BF)
    then begin
      //BOMつきだったので取り除く。
      lsl_Playlist[0] := Copy(lsl_Playlist[0], 4, MAXINT);
    end;

    ls_Path := gfnsFilePathGet(sFile); //相対パスを絶対パスに変換するため。
    for i := 0 to lsl_Playlist.Count-1 do begin
      ls_File := Trim(Utf8Decode(lsl_Playlist[i]));
      if (ls_File = '')                       //空行。
      or ((i = 0) and (ls_File = '#EXTM3U')) //先頭行が#EXTM3U。
      or (Pos('#EXTINF:', ls_File) = 1)       //追加情報。残すならコメントアウト。
      or (IsPlaylist(ls_File))                //プレイリスト。
      then begin
        //リストに追加しないもの。
      end else begin
        //相対パスから絶対パスに変換。
        ls_File := gfnsAbsolutePathGet(ls_File, ls_Path);
//        if (gfnbFileExists(ls_File)) then begin //ファイルが存在する場合のみ追加。
          //WideStringをUTF-7に変換してUnicodeに非対応なTStringsに保存。
          slList.Add(gfnsWideToUtf7(ls_File));
//        end;
      end;
    end;
  finally
    lsl_Playlist.Free;
  end;
end;
Unicodeなファイル名を持たせる必要がなければ、
        //相対パスから絶対パスに変換。
        ls_File := gfnsAbsolutePathGet(ls_File, ls_Path);
//        if (gfnbFileExists(ls_File)) then begin //ファイルが存在する場合のみ追加。
          //WideStringをUTF-7に変換してUnicodeに非対応なTStringsに保存。
          slList.Add(gfnsWideToUtf7(ls_File));
//        end;
の部分を
        //相対パスから絶対パスに変換。
        ls_File := gfnsAbsolutePathGet(ls_File, ls_Path);
//        if (gfnbFileExists(ls_File)) then begin //ファイルが存在する場合のみ追加。
          slList.Add(ls_File);
//        end;
にするとか、あるいは短いファイル名を利用して最低限Unicodeなファイル名にも対応するという手もあります。
        //相対パスから絶対パスに変換。
        ls_File := gfnsAbsolutePathGet(ls_File, ls_Path);
//        if (gfnbFileExists(ls_File)) then begin //ファイルが存在する場合のみ追加。
          if (gfnbIsUnicode(ls_File)) then begin
            //Unicodeな文字があるので短いファイル名に変換して問題を回避する。
            slList.Add(gfnsShortFileNameGet(ls_File));
          end else begin
            slList.Add(ls_File);
          end;
//        end;
汎用ルーチン
ドライブ名を返す関数。
function gfnsFileDriveGet(sFile: WideString): WideString;
//Unicode対応ExtractFileDrive。
const
  lcs_DRIVEDELIM = WideString(':');
  lcs_PATHDELIM  = WideString('\');

  function lfns_DriveGet(sDrive: WideString; iIndex: Integer): WideString;
  begin
    if (WideUpperCase(sDrive[iIndex])[1] in [WideChar('A')..WideChar('Z')]) then begin
      Result := Copy(sFile, iIndex, 2);
    end else begin
      Result := '';
    end;
  end;
  function lfns_UncGet(sDrive: WideString; iIndex, iLen: Integer): WideString;
  //UNC名を返す。
  var
    i: Integer;
    iServer: Integer;
  begin
    Result := '';
    i := iIndex;
    iServer := -1;
    while (i < iLen) do begin
      if (sDrive[i] = lcs_PATHDELIM) then begin
        if (i = iIndex) then begin
          Exit; //サーバー名が''。
        end else begin
          if (iServer <> -1) then begin
            if ((iServer + 1) = i) then begin
              Exit; //共有フォルダ名が''。
            end else begin
              Break;
            end;
          end else begin
            iServer := i;
          end;
        end;
      end;
      Inc(i);
    end;
    if (iServer <> -1) and (iServer < iLen) then begin
      if (sDrive[i] = lcs_PATHDELIM) then Dec(i);
      Result := Copy(sDrive, iIndex, i - iIndex + 1);
      if (Result <> '') then Result := '\\' + Result;
    end;
  end;
var
  li_Len: Integer;
begin
  Result := '';
  li_Len := Length(sFile);
  if (li_Len >= 2) then begin
    //2文字以上。
    //1文字以下の場合はドライブ名とはみなさない(A-Zならドライブとみなす方が良いか?)
    if (sFile[2] = lcs_DRIVEDELIM) then begin
      //C:〜など。
      Result := lfns_DriveGet(sFile, 1);
    end else if (sFile[1] = lcs_PATHDELIM) and (sFile[2] = lcs_PATHDELIM) then begin
      if (li_Len >= 8) and (WideUpperCase(Copy(sFile, 1, 8)) = '\\?\UNC\') then begin
        //\\?\UNC\〜。
        Result := lfns_UncGet(sFile, 9, li_Len);
      end else if (li_Len >= 3) and (sFile[3] = '?') then begin
        //\\?\〜。
        if (li_Len >= 6) and (sFile[6] = lcs_DRIVEDELIM) then begin
          Result := lfns_DriveGet(sFile, 5);
        end;
      end else begin
        //\\〜 UNC。
        Result := lfns_UncGet(sFile, 3, li_Len);
      end;
    end;
  end;
end;
パス名を返す関数。
function gfnsFilePathGet(sFile: WideString): WideString;
{
Unicode対応ExtractFilePath。
ドライブ名も含む。
末尾の'\'はつく。
ドライブ名のみの場合も'\'はつく。
ただしパスが空文字の場合のみ'\'はつかない。
}

var
  i: Integer;
begin
  Result := '';
  if (sFile <> '') then begin
    for i := Length(sFile) downto 1 do begin
      if (sFile[i] = '\') or (sFile[i] = '/') then begin
        Result := Copy(sFile, 1, i);
        Break;
      end else if (sFile[i] = ':') then begin
        Result := Copy(sFile, 1, i) + '\';
        Break;
      end;
    end;
  end;
end;
拡張子を返す関数。
function gfnsFileExtGet(sFile: WideString): WideString;
//Unicode対応ExtractFileExt。
//'.'は返る。
var
  i: Integer;
begin
  Result := '';
  for i := Length(sFile) downto 1 do begin
    if (sFile[i] = '\') or (sFile[i] = '/') or (sFile[i] = ':') then begin
      //拡張子なし。
      Break;
    end else if (sFile[i] = '.') then begin
      //拡張子あり。
      Result := Copy(sFile, i, MaxInt);
      Break;
    end;
  end;
end;
短いファイル名を返す関数。
Unicode非対応のコンポーネントや関数にファイルを渡す場合の(完全ではないけれど)逃げ道。
function gfnsShortFileNameGet(sFile: WideString): WideString;
//短いファイル名を返す。
//戻り値がWideStringであることに注意。

var
  lp_Buff: PWideChar;
begin
  Result := sFile;

  lp_Buff := AllocMem((MAX_PATH + 1) * 2);
  try
    if (GetShortPathNameW(PWideChar(sFile), lp_Buff, (MAX_PATH + 1) * 2) > 0) then begin
      Result := WideString(lp_Buff);
    end;
  finally
    FreeMem(lp_Buff);
  end;
end;
ファイルが存在するかを返す関数。
function gfnbFileExists(sFile: WideString): Boolean;
//Unicode対応FileExists。
var
  li_Attr: DWORD;
begin
  li_Attr := GetFileAttributesW(PWideChar(sFile));
  Result := (li_Attr <> $FFFFFFFF) and ((li_Attr and FILE_ATTRIBUTE_DIRECTORY) = 0);
end;
WideStringをUTF-7に変換して返す関数。
Unicodeな文字をUnicodeに非対応なTStringsに持たせるための小手先の技。
function gfnsWideToUtf7(sSrc: WideString): AnsiString;
//WideStringをUTF-7にエンコードして返す。
var
  li_Len:  Integer;
  lp_Buff: PAnsiChar;
begin
  //WC_COMPOSITECHECKはNG。
  li_Len  := WideCharToMultiByte(CP_UTF7, 0, PWideChar(sSrc), -1, nil, 0, nil, nil);
  lp_Buff := AllocMem(li_Len + 1);
  try
    WideCharToMultiByte(CP_UTF7, 0, PWideChar(sSrc), -1, lp_Buff, li_Len, nil, nil);
    Result := AnsiString(lp_Buff);
  finally
    FreeMem(lp_Buff);
  end;
end;
WideStringをUTF-7に変換したものを元に戻す関数。
Unicodeな文字をUnicodeに非対応なTStringsに持たせるための小手先の技。
function gfnsUtf7ToWide(sSrc: AnsiString): WideString;
//UTF-7でエンコードされている文字列をWideStringにして返す。
var
  li_Len:  Integer;
  lp_Buff: PWideChar;
begin
  li_Len  := MultiByteToWideChar(CP_UTF7, 0, PAnsiChar(sSrc), -1, nil, 0);
  lp_Buff := AllocMem((li_Len + 1) * 2);
  try
    MultiByteToWideChar(CP_UTF7, 0, PAnsiChar(sSrc), -1, lp_Buff, li_Len);
    Result := WideString(lp_Buff);
  finally
    FreeMem(lp_Buff);
  end;
end;
文字列中にUnicodeな文字があるかを返す関数。
const
  WC_NO_BEST_FIT_CHARS = $00000400;

function gfnbIsUnicode(sSrc: WideString): Boolean;
//sSrcにウムラウトのようなUnicode文字があればTrueを返す。
var
  lb_Bool: LongBool;
begin
  WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, PWideChar(sSrc), -1, nil, 0, nil, @lb_Bool);
  Result := lb_Bool;
end;

2010-09-01: