Chienomi

RipCD Evo

開発::util

RipCD EvoはCDリッピングのためのシェルスクリプトである。 リッピング自体はripitとcdrdaoを使用する。

これは私のケースにおいて最適化し、コマンド一発で通るようにしたものだ。 コード自体は大したものではないが、作業フロー的にはかなり有効なものとして機能している。

私のケースにおける前提は次のとおりである。

  • CDはリッピングし、特定ディレクトリ下に $artist/$album ディレクトリを作り、以下にFLACで保存する
  • CD自体はイメージ化してアーカイブする
  • アートワークはアルバムディレクトリ下にcover.jpgとして保存する

なお、もともとはripcdというシェル関数として定義して使っていたのだが、一般化するとともにユーティリティを加え、さらにfreedbの終了に合わせてgnudbを使うようにするなどの変更を加えており、“RipCD Evo”の名称で開発していた。 だが、公開するにあたっては過去のRipCDが公開されていたという事実がないため、ripcdという名前で公開している。

また、このスクリプトは過去の記事で存在自体は触れたことがあるものだ。

コード

ripcdのコードは、意外と複雑である。 現行aa082baのコードは次の通りだ。

#!/bin/zsh
setopt EXTENDED_GLOB
setopt BARE_GLOB_QUAL

if [[ ${XDG_CONFIG_DIR:-$HOME/.config}/reasonset/ripcd.zsh ]]
then
  source ${XDG_CONFIG_DIR:-$HOME/.config}/reasonset/ripcd.zsh
fi

if [[ -z $RIPCD_OUTDIR ]]
then
  typeset -g RIPCD_OUTDIR=$(xdg-user-dir MUSIC)
fi

print $RIPCD_OUTDIR
cd $RIPCD_OUTDIR

# Get albums before rip.
print -l */*(/) > /tmp/ripcd.$$.current

# rip
ripit -C gnudb.org -o "$RIPCD_OUTDIR" -D '"$artist/$album"' -c 2 --quality 8

# Get albums after rip.
print -l */*(/) > /tmp/ripcd.$$.next

album_list=(${(f)"$(sort /tmp/ripcd.$$.current /tmp/ripcd.$$.next | uniq -u)"})

if (( ${#album_list} == 1 ))
then
  album="${album_list[1]}"
else

  select album in $album_list "Manual Input"
  do
    if [[ -n "$album" && -e "$album" ]]
    then
      perl -pi -e 'if (/^\//) { s@^.*/@@ }' $album/*.m3u
      break
    elif [[ "$album" == "Manual Input" ]]
    then
      read "album?artist/album-> "
      if [[ -n "$album" && -e "$album" ]]
      then
        perl -pi -e 'if (/^\//) { s@^.*/@@ }' $album/*.m3u
        break
      else
        print "NO ALBUM DIRECTORY FOUND." >&2
      fi
    else
      print "NO ALBUM DIRECTORY FOUND." >&2
    fi
  done
fi


if [[ -z $RIPCD_IMGDIR ]]
then
  typeset -g RIPCD_IMGDIR=$(xdg-user-dir MUSIC)/rip
fi

[[ -e "$RIPCD_IMGDIR/${album:h}" ]] || mkdir -p "$RIPCD_IMGDIR/${album:h}"
cdrdao read-cd --read-raw --datafile "$RIPCD_IMGDIR/${album}.bin" --driver generic-mmc-raw "$RIPCD_IMGDIR/${album}.toc" && eject

rm /tmp/ripcd.$$.*

手順を大まかに分けると

  1. リッピング
  2. ripitによって生成されたm3uの修正
  3. cdrdaoによるイメージ化

というステップになる。

問題はripitによってリッピングされたアルバムのタイトルをどうやってcdrdaoに渡すか、だ。 こうした情報は基本的に外部データベースから得られるが、その取得はripitによって行われる。 手入力ではなく、これを使いたい。

ここでは、コレクションディレクトリの差分を取る、という方法を取っている。 複数候補がある場合はインタラクティブに選択させる。

ベストとは言い難いが、それなりに良い挙動になったと思う。

ウォークマンへの変換

#!/bin/zsh
setopt EXTENDED_GLOB

export AAC_BITRATE=320k

typeset -gx SOURCE_DIR=$1
typeset -gx DEST_DIR=$2

fixm3u() {
  (
    print "Fixing and copy m3u"
    cd $SOURCE_DIR
    for i in */*/*.m3u
    do
      [[ -e $DEST_DIR/$i ]] && continue
      print $DEST_DIR/$i
      perl -p -e 'if (/^[^#]/ && ! /^$/) { tr/!?"\\<>*|:/_________/; s/(\.[a-zA-Z0-9]+)? *$/.m4a/ }' $i >| $DEST_DIR/$i
    done
  )
}

fdkaac() {
  print "Convert AAC with libfdk_aac..."
  (
    cd $SOURCE_DIR
    ruby -rfileutils <<EOF
    sources = []
    Dir.glob("*/*").each {|artalbm|
      oartalbm = artalbm.split('/').map {|x| x.tr('!?\"\\<>*|:', '_') }.join("/")
      if File.exist?(%Q%#{ENV['DEST_DIR']}/#{oartalbm}%)
        if File.exist?(%Q%#{artalbm}/cover.jpg%) && ! File.exist?("#{ENV['DEST_DIR']}/#{oartalbm}/cover.jpg")
          FileUtils.cp(%Q%#{artalbm}/cover.jpg%, %Q%#{ENV['DEST_DIR']}/#{oartalbm}/%)
        end
        STDERR.puts "#{artalbm} is already exist. Skipping..."
        next
      else
        FileUtils.mkdir_p(%Q%#{ENV['DEST_DIR']}/#{oartalbm}%)
        if File.exist? %Q%#{artalbm}/cover.jpg%
          FileUtils.cp(%Q%#{artalbm}/cover.jpg%, %Q%#{ENV['DEST_DIR']}/#{oartalbm}/%)
        end
      end
      songfiles = Dir.entries(artalbm).select {|i| File.extname(i) == ".flac" }
      songfiles.sort.each do |songfile|
        params = { source: "#{artalbm}/#{songfile}" }
        params[:dest] = "#{ENV['DEST_DIR']}/#{oartalbm}/#{songfile.tr('!?\"\\<>*|:', '_').sub(/.[a-zA-Z0-9]+$/, '.m4a') }"
        sources.push params
      end
    }
    sources.each { |elm| system('ffmpeg', '-nostdin', '-i', elm[:source], '-c:a', 'libfdk_aac', '-b:a', ENV['AAC_BITRATE'], '-cutoff', '18000', elm[:dest] ) }
EOF
  )
}

cover4walkman() {
  (
    cd $DEST_DIR
    for i in *
    do
      print For "$i" ...
      (
        cd $i
        for j in *
        do
          if [[ -e $j/cover.jpg && ! -e $j/$j.jpg ]]
          then
            cp -v $j/cover.jpg $j/$j.jpg
          fi
        done
      )
    done
  )
}

if [[ -z $SOURCE_DIR ]]
then
  print SOURCE_DIR is not set.
  exit 1
fi
if [[ -z $DEST_DIR ]]
then
  print DEST_DIR is not set.
  exit 1
fi

print source: $SOURCE_DIR
print dest: $DEST_DIR

fdkaac
fixm3u
cover4walkman

ZshスクリプトだけどAAC変換部分はRubyというもの。

Walkman用変換としての注意点は以下の3つ

  • サポート形式はMP3, AAC
  • FAT系ファイルシステム
  • カバーアートは$albumtitle.jpg形式の名前である必要がある

まず、音楽形式の変換はFFmpeg+libfdk_aacを用いている。 Manjaroの標準FFmpegはlibfdk_aacが有効になっていないのでAUR版を使う必要がある。

変換部分が複雑だが、大きいのはFAT系ファイルシステムに対応したパス変換が必要であること、それに合わせて(また拡張子が変わることにも合わせて)プレイリストを変換する必要があること、カバーアートについては変換後の名前になること、に合わせたものだ。

もっとも、これは必要以上に複雑で、もともとは別の考え方をしていたために素直にtrできなかったからこうなったのだが、この状態だと別にtrで良い。 だから、Rubyパートを外してもっとスッキリしたものになるだろう。

動作確認の都合もあるが、そのうちやりたい。