ルギア君の戯言

雑多な記事。

All to OGG

いままでのやつだと、MP3 しか変換できなかったのだが、今日、個人的な都合があったので、改良して全部変換するようにした。
というか拡張子が MP3 でなくても変換できるようになったというだけの話だ。
・・・書き込もうとしている名前のファイルがすでに存在している場合は書き込みませんので、自分自身どうしを変換してしまうと言うアホはしませんのでご安心を。


えっと、

(前略)
Out from: ./東方/Albums/[ZUN] 上海アリス幻樂団 - 幺樂団の歴史 其の参 〜 Akyu's Untouched Score vol.3
Into: ./東方/Albums/[ZUN] 蓮台野夜行 〜 Ghostly Field Club
Converting: ./東方/Albums/[ZUN] 蓮台野夜行 〜 Ghostly Field Club/03. 東方妖々夢 ~ Ancient Temple.mp3
        to: ./東方/Albums/[ZUN] 蓮台野夜行 〜 Ghostly Field Club/03. 東方妖々夢 ~ Ancient Temple.ogg
  Title : "東方妖々夢 ~ Ancient Temple"
 Artist : "ZUN"
  Album : "Perfect Cherry Blossom OST -  Rendai no Yakou ~ Ghostly Field Club"
Comment : "[OST]"
  Track : 3
  Genre : "Game"
Converting: ./東方/Albums/[ZUN] 蓮台野夜行 〜 Ghostly Field Club/cover.jpg
        to: ./東方/Albums/[ZUN] 蓮台野夜行 〜 Ghostly Field Club/cover.ogg
Couldn't convert: ./東方/Albums/[ZUN] 蓮台野夜行 〜 Ghostly Field Club/cover.jpg
Converting: ./東方/Albums/[ZUN] 蓮台野夜行 〜 Ghostly Field Club/08. 過去の花 ~ Fairy of Flower.mp3
        to: ./東方/Albums/[ZUN] 蓮台野夜行 〜 Ghostly Field Club/08. 過去の花 ~ Fairy of Flower.ogg
(後略)

のように面倒なので、メディアファイル以外も含め全て変換し(ようとし)ます。


で、ELF ファイル(実行可能ファイル)に関しては、ffmpegmpeg1 に誤認識するバグ(?)があるので混在していると変な ogg ファイルができる可能性があります。
ちなみに「ffmpeg で読めるファイル全て」なので、動画ファイルも変換しちゃいます。御注意を(笑)
あ、あと、英語がおかしいところがちらほらあるのでそこは気にせずに。


メッセージ:

Maybe aleady ogg file: hogehoge.ogg     # すでに拡張子として ogg を持っています。変換しません。
Skipping because not file: hogehoge     # hogehoge は通常のファイルで無い(リンク、パイプ・・・)ので変換しません。
Skipping already existing: hogehoge.ogg # hogehoge.ogg はすでに存在します。変換しません。
Couldn't convert: hogehoge.mp3          # hogehoge.mp3 を変換できませんでした。
No tags contains: hogehoge.mp3          # hogehoge.mp3 にはタグがありません。またはサポートされていない形式です。
Cannot write to: hogehoge.ogg           # hogehoge.ogg にタグを書き込めませんでした。


使い方: all_to_ogg [-v]

  • v ・・・ verbose mode. (ffmpeg、oggenc のログおよび実行しているコマンドを表示します。)


↓ソース

/* License is WTFPL or MIT License. Treat as free but no support.
* Copyright (c) 2009 Lugia Kun
*
* This program needs "oggenc" "ffmpeg"
* This program needs "taglib" to compile.
*
* ex: g++ all_to_ogg.cpp -o all_to_ogg -I{path-to-taglib-headers} -ltag \
*     -DPATH_OGGENC={path-to-oggenc} -DPATH_FFMPEG={path-to-ffmpeg}
*/

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <string>
#include <locale>
#include <sstream>
#include <iconv.h>
#include <stack>
#include <cerrno>

// taglib headers.
#include <tlist.h>
#include <fileref.h>
#include <tfile.h>
#include <tag.h>

using namespace std;

#ifndef PATH_OGGENC
#define PATH_OGGENC "/usr/bin/oggenc"
#endif

#ifndef PATH_FFMPEG
#define PATH_FFMPEG "/usr/bin/ffmpeg"
#endif

bool ifsjis(TagLib::String& input);
string ucs2utf(TagLib::String& input);
string iconv(iconv_t cd, TagLib::String& in);
TagLib::String utf2ucs(string& input);
string make_path(stack<dirent> list);

int main(int argc, char** argv) {
  bool verbose = false;
  if(argc >= 2 && (string)argv[1] == "-v") {
    verbose = true;
  }
  setlocale(LC_CTYPE, "");
  iconv_t sjis_to_utf8;
  sjis_to_utf8 = iconv_open("UTF-8", "SHIFT_JIS");
  
  stack<DIR*>   list;
  stack<dirent> path;
  DIR* d = opendir(".");    // current directory.
  if(d == NULL) {
    exit(EXIT_FAILURE);
  }
  list.push(d);
  bool first = true;
  string target;
  while(list.size() != 0) {
    dirent* dp = readdir(list.top());
    if(dp == NULL) {
      target = target.substr(0, target.rfind('/'));
      cout << "Out from: " << target << endl;
      closedir(list.top());
      list.pop();
      path.pop();
      continue;
    }
    if((!path.empty()) && (!first)) {
      path.pop();
    }
    path.push(*dp);
    if(dp->d_name[0] == '.') {
      first = false;
      continue;
    }
    target = make_path(path);
    if(dp->d_type == DT_DIR) {
      cout << "Into: " << target << endl;
      DIR* d = opendir(target.c_str());
      list.push(d);
      first = true;
      continue;
    }
    first = false;
    if(dp->d_type != DT_REG && dp->d_type != DT_UNKNOWN) {    // pipe, link, etc.
      cout << "Skipping because not file: " << target << endl;
      continue;
    }
    string fname = dp->d_name;
    string::size_type dot = fname.rfind('.');
    string suffix;
    string nosuffix_name;
    if(dot != 0 && dot != string::npos) {
      nosuffix_name = target.substr(0, target.length() - fname.length() + dot);
      suffix = fname.substr(dot + 1, string::npos);
    } else {
      nosuffix_name = fname;
      suffix = "";
    }
    if(suffix == "ogg") {
      cout << "Maybe aleady ogg file: " + target << endl;
      continue;
    }
    string out_file = nosuffix_name + ".ogg";
    struct stat buf;
    errno = 0;
    stat(out_file.c_str(), &buf);
    if(errno == 0) {
      cout << "Skipping already existing: " << out_file << endl;
      continue;
    }
    cout << "Converting: " << target << endl;
    cout << "        to: " << out_file << endl;
    string command = (string)(PATH_FFMPEG " -i \"") + target
                            + "\" -f flac -acodec flac -aq 100 -ac 2 -ar 48000 - ";
    if(!verbose)
      command += "2> /dev/null ";
    command += (string)("| " PATH_OGGENC " -q 10 -o \"") + out_file + "\" - ";
    if(!verbose)
      command += "1>/dev/null 2>/dev/null";
    
    int ret;
    if(verbose)
      cout << command << endl;
    ret = system(command.c_str());
    if(ret != 0) {
      cout << "Couldn\'t convert: " + target << endl;
      continue;
    }
    TagLib::FileRef i_file(target.c_str());
    TagLib::FileRef o_file(out_file.c_str());
    if(i_file.isNull() || !i_file.tag()) {
      cout << "No tags contains: " << target << endl;
      continue;
    }
    if(o_file.isNull() || !o_file.tag()) {
      cout << "Cannot write to: " << out_file << endl;
      continue;
    }
    TagLib::Tag* i_tag = i_file.tag();
    TagLib::Tag* o_tag = o_file.tag();
    TagLib::String i_str;
    TagLib::String o_str;
    string utf8;
    cout << "  Title : \"";
    i_str = i_tag->title();
    if(ifsjis(i_str)) {
      utf8 = iconv(sjis_to_utf8, i_str);
      cout << utf8;
      o_str = utf2ucs(utf8);
    } else {
      utf8 = ucs2utf(i_str);
      cout << utf8;
      o_str = i_str;
    }
    o_tag->setTitle(o_str);
    cout << "\"" << endl;
    cout << " Artist : \"";
    i_str = i_tag->artist();
    if(ifsjis(i_str)) {
      utf8 = iconv(sjis_to_utf8, i_str);
      cout << utf8;
      o_str = utf2ucs(utf8);
    } else {
      utf8 = ucs2utf(i_str);
      cout << utf8;
      o_str = i_str;
    }
    o_tag->setArtist(o_str);
    cout << "\"" << endl;
    cout << "  Album : \"";
    i_str = i_tag->album();
    if(ifsjis(i_str)) {
      utf8 = iconv(sjis_to_utf8, i_str);
      cout << utf8;
      o_str = utf2ucs(utf8);
    } else {
      utf8 = ucs2utf(i_str);
      cout << utf8;
      o_str = i_str;
    }
    o_tag->setAlbum(o_str);
    cout << "\"" << endl;
    cout << "Comment : \"";
    i_str = i_tag->comment();
    if(ifsjis(i_str)) {
      utf8 = iconv(sjis_to_utf8, i_str);
      cout << utf8;
      o_str = utf2ucs(utf8);
    } else {
      utf8 = ucs2utf(i_str);
      cout << utf8;
      o_str = i_str;
    }
    o_tag->setComment(o_str);
    cout << "\"" << endl;
    cout << "  Track : ";
    o_tag->setTrack(i_tag->track());
    cout << i_tag->track();
    cout << endl;
    cout << "  Genre : \"";
    i_str = i_tag->genre();
    if(ifsjis(i_str)) {
      utf8 = iconv(sjis_to_utf8, i_str);
      cout << utf8;
      o_str = utf2ucs(utf8);
    } else {
      utf8 = ucs2utf(i_str);
      cout << utf8;
      o_str = i_str;
    }
    o_tag->setGenre(o_str);
    cout << "\"" << endl;
    o_file.file()->save();
  }
  
  iconv_close(sjis_to_utf8);
  return EXIT_SUCCESS;
}

bool ifsjis(TagLib::String& input) {
  bool only_ASCII = true;
  for(unsigned int i = 0; i < input.length(); i++) {
    unsigned short int tmp[2];
    tmp[0] = input[i];
    if(tmp[0] < 0x0080) {
      continue;    // no problem.
    } else if(tmp[0] > 0x00A0 && tmp[0] < 0x00E0) {
      only_ASCII = false;
      continue;
    } else {
      only_ASCII = false;
      i++;
      tmp[1] = input[i];
      if(((tmp[0] > 0x0080 && tmp[0] < 0x00A0) || (tmp[0] >= 0x00E0 && tmp[0] < 0x00FD)) &&
         ((tmp[1] > 0x003F && tmp[1] < 0x007F) || (tmp[1] >= 0x0080 && tmp[1] < 0x00FD))) {
        continue;    // noproblem.
      } else {
        return false;
      }
    }
  }
  if(only_ASCII)
    return false;
  else
    return true;
}

string ucs2utf(TagLib::String& input) {
  string out;
  out = "";
  for(unsigned int i = 0; i < input.length(); i++) {
    unsigned short int tmp = input[i];
    if(tmp < 0x0080) {
      out += (unsigned char) tmp & 0x7F;
    } else if(tmp < 0x0800) {
      out += (unsigned char) ((tmp >> 6) | 0xC0);
      out += (unsigned char) ((tmp & 0x003F) | 0x80);
    } else {
      out += (unsigned char) ((tmp >> 12) | 0xE0);
      out += (unsigned char) (((tmp >> 6) & 0x003F) | 0x80);
      out += (unsigned char) ((tmp & 0x003F) | 0x80);
    }
  }
  return out;
}

TagLib::String utf2ucs(string& input){
  TagLib::String out;
  out = L"";
  for(unsigned int i = 0, r = 1; i < input.length(); i += r) {
    unsigned char tmp[3];
    tmp[0] = input[i];
    tmp[1] = input[i + 1];
    tmp[2] = input[i + 2];
    TagLib::wchar ucs = 0x0000;
    if(tmp[0] < 0x80) {
      ucs = tmp[0];
      r = 1;
    } else if(tmp[0] > 0xC0 && tmp[0] < 0xE0) {
      ucs  = (TagLib::wchar)(tmp[0] & 0x3F) << 6;
      ucs |= (TagLib::wchar)(tmp[1] & 0x3F);
      r = 2;
    } else if(tmp[0] > 0xE0) {
      ucs  = (TagLib::wchar)(tmp[0] & 0x0F) << 12;
      ucs |= (TagLib::wchar)(tmp[1] & 0x3F) << 6;
      ucs |= (TagLib::wchar)(tmp[2] & 0x3F);
      r = 3;
    }
    out += ucs;
  }
  return out;
}

#define S_SIZE 1024

string iconv(iconv_t cd, TagLib::String& in) {
  string s;
  for(unsigned int i = 0; i < in.length(); i++) {
    s += (unsigned char) in[i] & 0xFF;
  }
  istringstream si;
  ostringstream so;
  si.str(s);
  char* s_src = new char[S_SIZE];
  char* s_dst = new char[S_SIZE];
  char* p_src;
  char* p_dst;
  size_t n_src;
  size_t n_dst;
  while(!si.eof()) {
    si.getline(s_src, S_SIZE);
    p_src = s_src;
    p_dst = s_dst;
    n_src = strlen(s_src);
    n_dst = S_SIZE-1;
    while(0 < n_src){
      errno = 0;
      iconv(cd, &p_src, &n_src, &p_dst, &n_dst);
      if(errno != 0) {
        break;
      }
    }
    *p_dst = '\0';
    so << s_dst;
  }
  delete [] s_src;
  delete [] s_dst;
  return so.str();
}

string make_path(stack<dirent> list) {
  string ret = "";
  while(!list.empty()) {
    ret = (string)list.top().d_name + ((ret != "") ? ("/" + ret) : (""));
    list.pop();
  }
  return "./" + ret;
}

ffmperg や ogginfo は termcap や terminfo、ncurses 自身を使っているわけではないが似たようなことをしているので、all2ogg が終了した後、文字がエコーバックされなくなることがあります。そのときは reset (ncurses に入っています)で直してください。
なお、エコーバックされなくなるだけで文字は打ててますので、変なコマンドを実行しないよう御注意を。

追記: GPL の要件を満たしていないので、ライセンスを WTFPL v2 または MIT License に変更しました。どちらのライセンスで利用していただいてもかまいません。中身は変更していません。また、GPLと書かれたソースのコピーを もし 持っているならば、上記のように変更していください。

The MIT License (MIT)
Copyright (c) 2009 Lugia Kun

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.