※旧タイトル:MacのプレイリストをWALKMAN NW-A306で再生する方法【完全解決】
Macのミュージックアプリ(旧iTunes)で作成したプレイリストをそのままSONY WALKMAN NW-A306(Android) に転送して再生しようとコピーしてみましたが、再生どころかプレイリスト自体が表示されません。
調べてみるとAppleとSONYではプレイリストの仕様が複数違っており、それぞれ変換する必要がありました。

原因は単純ではなく、Apple独自のプレイリスト仕様とWindows環境、さらにWALKMAN側が期待する.m3u8仕様がかみ合っていないためです。
当初は「プレイリストのパスを書き換えるだけ」の小さなPowerShellスクリプトとして書き始めました。しかし動かしては失敗を繰り返すうちに、PowerShell 7専用APIへの依存、Unicode正規化、URLエンコード、ローマ数字など、実用面では無視できない問題が次々と見つかりました。
結果として、スクリプトは全部書き直すことになり、設計から変え、どうせならWindows PowerShell 5.1とPowerShell 7の両方で動作することを前提とした方向で勉強しなおしました。
この記事ではコードの解説だけではなく、なぜその設計へ変更しなければならなかったかを中心にまとめます。
結論
絶対に直さなければならなかった点と改善した内容はほぼ同じなので、改善したところと変更内容をまとめました。
- macOSの絶対パスをWALKMAN用の相対パスへ変換
- %20などURLエンコードを復元
- Unicode NFDをNFCへ正規化
- Unicodeローマ数字を通常ASCIIへ変換
- UTF-8(BOM付き)の.m3u8として保存
| 改善点 | 変更点 |
| PowerShell互換性 | 5.1と7の両対応 |
| パス抽出 | 固定文字列から正規表現へ変更 |
| プレイリスト | 絶対パスから相対パスへ変更 |
| URLエンコード | %20などを元の文字へ復元 |
| Unicode | NFDをNFCへ正規化 |
| ローマ数字 | Unicode装飾文字をASCIIへ変換 |
| 安全性 | DryRun、ShouldProcess、例外処理を追加 |
| 運用性 | 入力チェックとサマリーを表示してみた |
やっていることはほぼ同じなのですが、新しく作り直したことで中身はほぼ別物です。
こちらが書き直したスクリプトです。(2026年7月24日)
[strings]を書き換えることで汎用性が高まるかなと思います。
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
# [strings]は環境に合わせて変更してください。
param(
[string]$PlaylistDir = "E:\Music\Playlists",
[string]$MacRootRegex = '^/Users/[^/]+/Music/Music/Media\.localized/Music/',
[string]$WalkmanMusicRoot = "E:\Artist",
[string]$WalkmanPlaylists = "E:\Music\Playlists",
[switch]$DryRun,
[switch]$Force
)
$ErrorActionPreference = 'Stop'
try {
$MacRootRegexObj = [regex]::new($MacRootRegex)
}
catch {
throw "MacRootRegex が正しい表現ではありません: $MacRootRegex ($($_.Exception.Message))"
}
$Utf8NoBom = New-Object System.Text.UTF8Encoding($false)
# --- 装飾ローマ数字をASCIIへ ---
$RomanMap = [System.Collections.Specialized.OrderedDictionary]::new([StringComparer]::Ordinal)
$RomanPairs = @(
@("Ⅰ","I"), @("Ⅱ","II"), @("Ⅲ","III"), @("Ⅳ","IV"), @("Ⅴ","V"), @("Ⅵ","VI"),
@("Ⅶ","VII"), @("Ⅷ","VIII"), @("Ⅸ","IX"), @("Ⅹ","X"), @("Ⅺ","XI"), @("Ⅻ","XII"),
@("Ⅼ","L"), @("Ⅽ","C"), @("Ⅾ","D"), @("Ⅿ","M"),
@("ⅰ","i"), @("ⅱ","ii"), @("ⅲ","iii"), @("ⅳ","iv"), @("ⅴ","v"), @("ⅵ","vi"),
@("ⅶ","vii"), @("ⅷ","viii"), @("ⅸ","ix"), @("ⅹ","x"), @("ⅺ","xi"), @("ⅻ","xii"),
@("ⅼ","l"), @("ⅽ","c"), @("ⅾ","d"), @("ⅿ","m")
)
foreach ($pair in $RomanPairs) { $RomanMap.Add($pair[0], $pair[1]) }
function Convert-RomanToAscii {
param([string]$Text)
if ([string]::IsNullOrEmpty($Text)) { return $Text }
foreach ($k in $RomanMap.Keys) {
if ($Text.Contains($k)) {
$Text = $Text.Replace($k, $RomanMap[$k])
}
}
return $Text
}
function Normalize-Text {
param([string]$Text)
if ([string]::IsNullOrEmpty($Text)) { return $Text }
$normalized = $Text.Normalize([Text.NormalizationForm]::FormC)
$normalized = Convert-RomanToAscii $normalized
return $normalized
}
function Convert-UrlDecode {
param([string]$Text)
return [Uri]::UnescapeDataString($Text)
}
function Get-RelativePathCompat {
param(
[string]$From,
[string]$To
)
$fromUri = [Uri]::new(($From.TrimEnd('\') + '\'))
$toUri = [Uri]::new($To)
$relUri = $fromUri.MakeRelativeUri($toUri)
$rel = [Uri]::UnescapeDataString($relUri.ToString())
return ($rel -replace '/', '\')
}
# --- 事前チェック ---
if (-not (Test-Path -LiteralPath $PlaylistDir)) {
throw "プレイリストフォルダが見つかりません: $PlaylistDir"
}
if (-not (Test-Path -LiteralPath $WalkmanPlaylists)) {
if ($DryRun) {
Write-Host " [DryRun] フォルダ作成予定地: $WalkmanPlaylists"
}
elseif ($PSCmdlet.ShouldProcess($WalkmanPlaylists, "フォルダ作成")) {
New-Item -ItemType Directory -Path $WalkmanPlaylists -Force | Out-Null
}
}
$m3uFiles = Get-ChildItem -LiteralPath $PlaylistDir -Filter *.m3u -File
if (-not $m3uFiles) {
Write-Warning "m3u ファイルが見つかりません: $PlaylistDir"
return
}
Write-Host "=== 変換する対象は: $($m3uFiles.Count) 件です ==="
$m3uFiles | ForEach-Object { Write-Host " - $($_.Name)" }
# --- 実行前の確認 (y/n) ---
if (-not $Force -and -not $DryRun) {
$answer = Read-Host "`nこれらのファイルを変換し、'$WalkmanPlaylists' に書き込みます。よろしいですか? (y/n)"
if ($answer -notin @('y', 'Y', 'yes', 'Yes')) {
Write-Host "中断します。ファイルは変更していません。"
return
}
}
$summary = [System.Collections.Generic.List[string]]::new()
foreach ($m3u in $m3uFiles) {
try {
$inputM3U = $m3u.FullName
$baseName = $m3u.BaseName
$outputM3U8 = Join-Path $WalkmanPlaylists ($baseName + ".m3u8")
# --- 既にあるファイルの上書き確認 (y/n) ---
if ((Test-Path -LiteralPath $outputM3U8) -and -not $Force -and -not $DryRun) {
$ow = Read-Host " '$($baseName).m3u8' は既に存在しています。上書き保存をしますか? (y/n)"
if ($ow -notin @('y', 'Y', 'yes', 'Yes')) {
Write-Host " スキップ: $($m3u.Name)"
continue
}
}
Write-Host "=== 変換中: $($m3u.Name) -> $($baseName).m3u8 ==="
# 日本語ファイル名を含むm3uはUTF-8(BOM付き)を前提。
$lines = Get-Content -LiteralPath $inputM3U -Encoding UTF8
$out = [System.Collections.Generic.List[string]]::new()
$unmatched = 0
foreach ($line in $lines) {
if ($line.StartsWith("#") -or [string]::IsNullOrWhiteSpace($line)) {
$out.Add($line)
continue
}
$work = $line.Trim()
# ユーザー名や私の環境の "Music/Music" 二重フォルダの揺れ対策のために正規表現でマッチ
$match = $MacRootRegexObj.Match($work)
if ($match.Success) {
$relative = $work.Substring($match.Index + $match.Length)
$relative = $relative -replace "/", "\"
$relative = Convert-UrlDecode $relative
$relative = Normalize-Text $relative
$walkmanAbs = Join-Path $WalkmanMusicRoot $relative
$relativeForPlaylist = Get-RelativePathCompat -From $WalkmanPlaylists -To $walkmanAbs
if (-not (Test-Path -LiteralPath $walkmanAbs)) {
Write-Warning " 実体ファイルなし: $relative"
}
$out.Add($relativeForPlaylist)
}
else {
# ルートに一致しない行は変換されずそのまま残る。壊れたパスになるかもしれないです。
$unmatched++
Write-Warning " ルート不一致(未変換): $work"
$out.Add((Normalize-Text $work))
}
}
if ($DryRun) {
Write-Host " [DryRun] $($out.Count) 行を出力予定。書き込みはしません。"
}
elseif ($PSCmdlet.ShouldProcess($outputM3U8, "書き込み")) {
[System.IO.File]::WriteAllLines($outputM3U8, $out, $Utf8NoBom)
Write-Host " -> 出力: $outputM3U8"
}
$summary.Add("$($m3u.Name): 総行数 $($lines.Count) / 未変換 $unmatched 件です")
}
catch {
Write-Warning " エラー: $($m3u.Name) の処理中に失敗しました - $($_.Exception.Message)"
$summary.Add("$($m3u.Name): エラーで中断")
}
}
Write-Host "`n=== 完了 ==="
$summary | ForEach-Object { Write-Host " $_" }
検証環境
次の環境で検証をしました。
- macOS ミュージック.appで出力したプレイリスト(m3u)
- ミュージックライブラリ
- Windows 11
- Windows PowerShell 5.1
- PowerShell 7
- SONY WALKMAN NW-A306
目的は、Macで管理しているプレイリストをWindows経由でWALKMANでも使いまわせるようにするということです。

なぜWALKMANでは読み込めないのか
ここが結構闇が深く、調べるだけでどんどん気力がそがれるような感覚に陥りました。
macOSで出力したプレイリストには次のように書かれています。
- macOSの絶対パス
- URLエンコード
- Unicode NFD
- Apple独自のフォルダ構造
これらがそのまま記録されていました。
一方WALKMANが欲しいものはWindows形式の相対パスで保存された.m3u8です。
つまり、プレイリストを書き換えるのではなく、Apple仕様をWALKMAN仕様へ変換する処理が必要でした。
設計変更 1:PowerShell 5.1でも動くようにした
今回もっとも大きな変更点です。前回のスクリプトはPowerShell 7でしか動きませんでした。しかし、このブログでは誰でも再現できるようにしたいと思っているため、この記事だけインストールが必要なPowerShell 7を使っていることに違和感を感じていました。
PowerShell 7を使っていた理由はいくつかありますが、一番は次のものです。
System.IO.Path::GetRelativePath()
これは.NET Core以降で追加されたAPIであり、Windows PowerShell 5.1(.NET Framework)には存在しません。
そこで相対パス生成をPowerShell 5.1でも使えるようにしました。
Uri.MakeRelativeUri()
このAPIは.NET Framework時代から存在しているため、PowerShell 5.1でもPowerShell 7でも同じ結果を得られます。
長く使うAPIを選ぶことはブログのテーマとしては正しいのでしょうが、実際問題新しいAPIを導入した方がいいとは思っています。
設計変更 2:固定文字列検索をやめて正規表現へ
初期版ではあまり考えずに/Users/username/という固定文字列で検索していました。
しかし実際にはユーザー名は環境ごとに異なります。そこで固定文字列の比較をやめて次のように正規表現へ変更しました。
^/Users/[^/]+/Music/Music/Media\.localized/Music/
これによりユーザ名の違いをなんとかすることができるようになり、環境依存が大幅に減ったと思います。
設計変更 3:Unicodeを正しく扱う
macOSの.m3uには濁点付きの日本語がNFD形式で保存されています。一方Windowsでは通常NFCが使われています。そのままではうまくいきません。「が」と「か+゛」別文字です。
そこですべてNFCへ正規化してから処理をするように変更しました。
見た目は同じ文字でも内部コードが異なれば違う文字です。この処理を行わないとファイルが見つからないケースがあります。
設計変更 4:URLエンコードを復元する
macOSの.m3uではスペースなどが%20として保存されていました。
前回は.NET Frameworkの HttpUtility.UrlDecode() を使用していました。しかし、互換性を考えて次のように変更しました。
これによりPowerShell 5.1 / 7のどちらでも同じ処理を行うことができるようになりました。
設計変更 5:Unicodeローマ数字への対応
クラシック音楽では当たり前のようにⅣ、Ⅸ、ⅫといったUnicodeローマ数字が使われています。初期版では12種類程度で疲れてしまい、それ以降には対応しませんでした。
今回は気合をいれなおして、大文字、小文字、Unicode装飾ローマ数字、ここまで対応させました。
さらにPowerShellのハッシュテーブルでは「Ⅰ」と「ⅰ」が同一のものとして扱われる問題があります。そこでOrderedDictionaryとStringComparer.Ordinalを使用し、Unicodeのコードポイント単位で比較できるよう設計を変形しています。
見た目ではわからないところですが、動作の安定にはなかなかに重要なところだったのではないかと思っています。
設計変更 6:動くより安全に働くを意識した
初期版は実行すると即変換を開始していました。今回の書き直しでは次の要素をいれて見ました。
- DryRun
- ShouldProcess
- Force
- 入力チェック
- 上書き確認
- try / catch
- エラーサマリー
意味合いとしては、誤って大量のプレイリストを上書きしてしまうリスクを減らすこと、失敗したファイルだけを確認できるようにしたこと、そのような感じで付け加えました。
このおかげというかせいというかで、スクリプトの規模は大きくなりましたが、ブログテーマである安全のことを考えると付けるべき改善だったと思います。
変更前と変更後
今回は、この記事だけPowerShell 7が必要だということが気になっていたため、PowerShell 5.1でも動くように前回のスクリプトから大幅に設計段階から丸ごと変えました。内容は次の表のとおりです。
| 項目 | 前回(2025年4月21日) | 最新版(2026年7月24日) |
| PowerShell 5.1 | × | ○ |
| PowerShell 7 | ○ | ○ |
| 相対パス生成 | GetRelativePath | MakeRelativeUri |
| URLデコード | HttpUtility | Uri |
| ローマ数字 | 一部のみ | Unicode全対応 |
| 入力チェック | なし | あり |
| DryRun | なし | あり |
| 例外処理 | なし | あり |
| 処理結果表示 | なし | あり |
もはや前回の原型をとどめていないスクリプトになりました。
結果
最終的に、macOSで管理しているプレイリストをWindows PowerShell 5.1 / PowerShell 7のどちらでも変換できるようになりました。
WALKMAN側でもプレイリストが正常に認識され、クラシック音楽で多用されるローマ数字や日本語ファイル名も問題なく再生できています。
単純な文字列置き換えスクリプトから始まりましたが、いろいろ試行錯誤して互換性を求めた結果、保守性や安全性も付け加えることができ、進化した設計になったのではないかと思います。
よくある質問(FAQ)
- QPowerShell 7だけならもっと簡単に書けると思うのですが?
- A
はい。GetRelativePath()など便利なAPIが使えるため、コード量はかなり減らすことができます。しかし、あくまでWindows標準機能だけで完結させたいため、PowerShell 5.1と互換性がある書き方をしています。
- QなぜPowerShell 5.1へ対応したのですか?
- A
Windowsには現在もPowerShell 5.1が標準搭載され、PowerShell 7はインストールする必要があります。ブログのテーマ上、「あるものでなんとかする」ため、PowerShell 5.1を利用することを決めました。
- QこのスクリプトはmacOSのミュージック.app専用ですか?
- A
ミュージック.appから書き出した.m3uプレイリストを前提としていますが、同じような形式であれば応用はできると思います。
まとめ
今回は完全リライトという形となり、いろいろ変わりました。一番変わったのはコードの長さではありません。
前回の「動けばいい」という雑な方法から「仕様が変わるまで使える」ように設計自体を変更したことです。
この記事で紹介した変換スクリプトは、単にm3uをm3u8へ変換するだけではありません。
macOSのミュージック.appとWALKMANのプレイリストの仕様差を一つずつ検証し、PowerShell 5.1でも動作するよう設計を見直しながら改善を重ねた結果です。
同じような文字コードやパス変換の問題に直面したとき、この記事の考え方が別のスクリプト設計にも役立てば幸いです。


リファレンス
Microsoft公式:
Windows PowerShell 5.1 Documentation
https://learn.microsoft.com/powershell/scripting/
about_Functions
https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_functions
ShouldProcess (SupportsShouldProcess)
https://learn.microsoft.com/powershell/scripting/developer/cmdlet/requesting-confirmation-from-cmdlets
String.Normalize()…NFDからNFCの正規化
https://learn.microsoft.com/dotnet/api/system.string.normalize
NormalizationForm…Unicode正規化形式(FormC / FormD)関係
https://learn.microsoft.com/dotnet/api/system.text.normalizationform
Uri.UnescapeDataString()…PowerShell 5.1 / 7共通で%20や日本語URLエンコードを戻す処理関係
https://learn.microsoft.com/ja-jp/dotnet/api/system.uri.unescapedatastring?view=net-10.0
Uri.MakeRelativeUri()…Path.GetRelativePathの代替
https://learn.microsoft.com/dotnet/api/system.uri.makerelativeuri
Path.GetRelativePath()…PowerShell 7(.NET Core)専用APIとして比較できる
https://learn.microsoft.com/dotnet/api/system.io.path.getrelativepath
Unicode Normalization Forms…NFD / NFCの正式仕様
https://www.unicode.org/reports/tr15/
Unicode Character Database…ローマ数字(Ⅰ〜Ⅻ)や結合文字の仕様を調べる際の資料
https://www.unicode.org/ucd/
更新履歴
- 2026年7月24日
- 2025年4月21日


コメント