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;
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なファイル名を持たせる必要がなければ、
の部分を
にするとか、あるいは短いファイル名を利用して最低限Unicodeなファイル名にも対応するという手もあります。
ドライブ名を返す関数。
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;