NNDD で重複取得されたコメントを削除するスクリプトを作りました

ニコニコ動画ダウンロード&ビデオ再生ソフトの NNDD(v1_27_6) を愛用しているのですが、Player 設定で「コメントを再生のたびに更新する - コメント更新時にローカルに保存済みのコメントに追記」を有効にしていると、NNDD でコメント投稿後に再度メインウィンドウでその動画を再生した際に、コメントが重複して取得されてしまうバグがあるようです。

この時、コメント .xmlファイルでは、NNDD から投稿したコメントの 要素に no 属性がないことが分かります。 これが原因で no="1" からコメントが再取得されてしまうのでしょう。

<packet>
  <thread ... />
  <view_counter ... />
  <chat thread="..." no="1" vpos="..." date="..." mail="..." user_id="..." anonymity="...">コメント</chat>
  (略)
  <chat thread="..." no="70" vpos="..." date="..." mail="..." user_id="..." anonymity="...">コメント</chat>

  <chat thread="..." vpos="..." date="..." mail="..." user_id="..." anonymity="...">NNDDからコメント</chat>

  <chat thread="..." no="1" vpos="..." date="..." mail="..." user_id="..." anonymity="...">コメント</chat>
  (略)
  <chat thread="..." no="71" vpos="..." date="..." mail="..." user_id="..." anonymity="...">NNDDからコメント</chat>
  (略)
</packet>


今まではコメントが保存された .xml ファイルを直接編集して、重複箇所を削除していたのですが、めんどうになったので Python スクリプトを作成しました(要 BeautifulSoup)。
このスクリプトは引数に動画が保存してある NNDD フォルダを指定して実行すると、フォルダ内部の全てのコメント .xml ファイルを解析して重複部分があれば削除します。

Usage:
    python nndd.py [options] NNDD_DIR

Options:
    -b/--backup  : backup mode (書き換えたファイルのバックアップ .xml~ を作成する)
    -s/--speed   : high speed mode (CPU をフルに使って解析する)
    -h/--help    : display help (使用法を表示)


ファイル名を nndd.py として、以下のプログラムを保存してください。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import os
import fnmatch
import time
import getopt
from BeautifulSoup import BeautifulStoneSoup  # For processing XML


backup_mode = False
speed_mode = False

def valid_xml(path):
    global backup_mode
    global speed_mode
    file_object = open(path, 'r+')
    try:
        contents = file_object.read()
        # BeautifulStoneSoup
        soup = BeautifulStoneSoup(contents)
        chat_list = soup('chat')  # <chat>
        last_no = 0
        duplications = []
        for chat in chat_list:
            if not chat.has_key('no'):  # エラーの原因
                duplications.append(str(chat))
                continue
            no = chat['no']
            if not no.isdigit():
                continue
            no = int(no)
            if no <= last_no:
                duplications.append(str(chat))
                continue
            last_no = no
            
            # high-speed mode off
            if not speed_mode:
                time.sleep(0.01)
                
        if len(duplications) > 0:  # 重複あり
            # backup mode on
            if backup_mode:
                try:
                    backup_file = open(path + '~', 'w')
                    backup_file.write(contents)
                finally:
                    backup_file.close()
                    
            # 重複箇所を削除する
            for dup in duplications:
                contents = contents.replace('  ' + dup + '\n', '', 1)
                    
            # XML ファイルの更新
            file_object.seek(0)
            file_object.write(contents)
            file_object.truncate()
            print '    rewrited!'
    finally:
        file_object.close()

def all_files(root, patterns='*', single_level=False, yield_folders=False):
    # セミコロンで区切られた文字列からパターンを展開してリストにする
    patterns = patterns.split(';')
    for path, subdirs, files in os.walk(root):
        if yield_folders:
            files.extend(subdirs)
        files.sort()
        for name in files:
            for pattern in patterns:
                if fnmatch.fnmatch(name, pattern):
                    yield os.path.join(path, name)
                    break
        if single_level:
            break

def valid_nndd(nndd_dir):
    # |dir|以下に含まれる .xml ファイルパスを取得
    for path in all_files(nndd_dir, '*].xml'):
        # [Owner].xml と [ThumbInfo].xml ファイルを無視
        if path.endswith('[Owner].xml') or path.endswith('[ThumbInfo].xml'):
            continue
        print path
        valid_xml(path)

def usage():
    print 'Usage:'
    print '    python nndd.py [options] NNDD_DIR'
    print '    -b/--backup  : backup mode'
    print '    -s/--speed   : high speed mode'
    print '    -h/--help    : display help'

def main():
    global backup_mode
    global speed_mode
    if len(sys.argv) >= 2:
        # オプションを解析
        try:
            opts, args = getopt.getopt(sys.argv[1:], 'bsh', ['backup', 'speed', 'help'])
        except getopt.GetoptError, err:
            # ヘルプメッセージを出力して終了
            print str(err)
            usage()
            sys.exit(2)
        for opt, arg in opts:
            if opt in ('-b', '--backup'):  # バックアップモード
                backup_mode = True
            elif opt in ('-s', '--speed'):  # ハイスピードモード
                speed_mode = True
            elif opt in ('-h', '--help'):  # ヘルプ表示
                usage()
                sys.exit()
            else:
                assert False, 'unhandled option'
        nndd_dir = args[0]
        valid_nndd(nndd_dir)

if __name__ == '__main__':
    main()


WindowsPython を使用する方法(パス設定等)はこちらを参考に。
NNDD フォルダは、Windows であれば /Users/アカウント名/Documents/NNDD にあります。