옵시디언에서 불필요한 태그와 메타데이터 삭제하기

연결문서

[!warning]
이 코드를 실행하고자 한다면, 반드시 백업을 해주세요! 이 코드는 제가 쓰기 위해 간단히 작성된 코드이므로, 예상치 못한 오류가 발생할 수 있습니다. 실행 시 옵시디언 파일이 손상될 수 있으니 주의가 필요합니다.

수많은 책들

옵시디언 메타데이터 한번에 수정하기

옵시디언에서 태그 형식을 폴더 형식으로 바꾸고 나서 기존에 작성한 태그들과 충돌이 발생했습니다. 이에 따라, 이전에 작성한 태그들을 삭제하고, 불필요한 메타데이터를 가진 마크다운 파일들의 내부 메타데이터 항목을 정리하려고 합니다. 이 작업은 주로 자동화된 스크립트를 활용해 처리합니다.

1. 메타데이터 항목 지우기

이 코드는 불필요한 메타데이터 항목을 자동으로 삭제합니다.

  • 각 파일을 열고 YAML 프론트매터를 확인한 후, 필요한 메타데이터 항목만 남기고 나머지는 삭제합니다.
  • 필요한 메타데이터는 'metadata_fields'와 'optional_fields' 목록에 포함된 항목들 입니다.
import os
import re

def process_markdown_file(file_path, metadata_fields, optional_fields):
    try:
        with open(file_path, 'r', encoding='utf-8') as file:
            content = file.read()

        # 파일 시작부분에 YAML 프론트매터가 있는지 확인
        yaml_pattern = r'^---\n(.*?)\n---\n'
        yaml_match = re.search(yaml_pattern, content, re.DOTALL)
        
        if not yaml_match:
            return  # YAML 메타데이터가 없으면 파일 건너뛰기
            
        yaml_content = yaml_match.group(1)
        rest_content = content[yaml_match.end():]
        
        # 현재 줄과 다음 줄을 함께 처리하기 위한 변수들
        allowed_fields = metadata_fields + optional_fields
        new_yaml_lines = []
        current_field = None
        
        lines = yaml_content.split('\n')
        i = 0
        while i < len(lines):
            line = lines[i].strip()
            if not line:  # 빈 줄 건너뛰기
                i += 1
                continue
                
            # 새로운 필드인지 확인
            is_new_field = False
            for field in allowed_fields:
                if line.lower().startswith(field.lower()):
                    current_field = field
                    is_new_field = True
                    new_yaml_lines.append(line)
                    break
            
            # 현재 필드의 다중 라인 값 처리
            if not is_new_field:
                # 이전 필드의 연속된 데이터라면 (들여쓰기 되어 있거나 - 로 시작하는 경우)
                if current_field and (line.startswith(' ') or line.startswith('-') or line.startswith('  ')):
                    new_yaml_lines.append(line)
            
            i += 1
        
        # 새로운 YAML 프론트매터 생성
        if new_yaml_lines:
            new_content = f"---\n{'\n'.join(new_yaml_lines)}\n---\n{rest_content}"
            
            # 파일 내용이 변경되었는지 확인
            if new_content != content:
                # 파일 덮어쓰기
                with open(file_path, 'w', encoding='utf-8') as file:
                    file.write(new_content)
                return True
                
        return False
        
    except Exception as e:
        print(f"Error processing {file_path}: {str(e)}")
        return False

def process_vault(vault_path, metadata_fields, optional_fields):
    processed_files = 0
    modified_files = 0
    
    # 재귀적으로 모든 .md 파일 처리
    for root, _, files in os.walk(vault_path):
        for file in files:
            if file.endswith('.md'):
                file_path = os.path.join(root, file)
                processed_files += 1
                
                if process_markdown_file(file_path, metadata_fields, optional_fields):
                    modified_files += 1
                    print(f"Modified: {file}")
                    
                if processed_files % 100 == 0:
                    print(f"Processed {processed_files} files...")
    
    return processed_files, modified_files

# 실행
if __name__ == "__main__":
    metadata_fields = ['created:', 'updated:', 'date:', 'tags:', 'aliases:
                      'links:', 'title:', 'weight:', 'description:', 'labels:']
    optional_fields = ['profileName:', 'postId:', 'author:', 'source:']
    
    vault_path = r"자신의 옵시디언 파일 경로"
    
    print("Starting to process markdown files...")
    total_files, modified = process_vault(vault_path, metadata_fields, optional_fields)
    
    print(f"\nProcessing complete!")
    print(f"Total files processed: {total_files}")
    print(f"Files modified: {modified}")

위 코드에서 아래의 리스트는 제가 자주 활용하는 메타데이터 항목입니다. 이 외의 항목은 삭제됩니다.

    metadata_fields = ['created:', 'updated:', 'date:', 'tags:', 'aliases:
                      'links:', 'title:', 'weight:', 'description:', 'labels:']
    optional_fields = ['profileName:', 'postId:', 'author:', 'source:']

#### 2. 이전 태그들 지우기

이 코드는 tags 항목에서 사용했던 이전의 태그들을 모두 삭제합니다. 특히, /가 포함되지 않은 태그는 메타데이터까지 모두 삭제됩니다.

import os
import re


def process_markdown_file(file_path, metadata_fields, optional_fields):
    try:
        with open(file_path, 'r', encoding='utf-8') as file:
            content = file.read()

        # 파일 시작부분에 YAML 프론트매터가 있는지 확인
        yaml_pattern = r'^---\n(.*?)\n---\n'
        yaml_match = re.search(yaml_pattern, content, re.DOTALL)

        if not yaml_match:
            return  # YAML 메타데이터가 없으면 파일 건너뛰기

        yaml_content = yaml_match.group(1)
        rest_content = content[yaml_match.end():]

        allowed_fields = metadata_fields + optional_fields
        new_yaml_lines = []
        current_field = None
        found_tags = False
        valid_tags = []

        lines = yaml_content.split('\n')
        i = 0
        while i < len(lines):
            line = lines[i].strip()

            # 새로운 필드 시작 확인
            is_new_field = False
            for field in allowed_fields:
                if line.lower().startswith(field.lower()):
                    # 이전에 수집한 태그가 있으면 처리
                    if found_tags and current_field and current_field.lower() == 'tags:':
                        if valid_tags:  # 유효한 태그가 있는 경우만 추가
                            new_yaml_lines.append('tags:')
                            for tag in valid_tags:
                                new_yaml_lines.append(f'  {tag}')
                        valid_tags = []

                    current_field = field
                    found_tags = field.lower() == 'tags:'
                    if not found_tags:
                        new_yaml_lines.append(line)
                    is_new_field = True
                    break

            if not is_new_field:
                if found_tags:
                    # 태그 라인 처리
                    if line.startswith('-'):
                        tag = line.strip()
                        if '/' in line:  # '/'가 있는 태그만 보존
                            valid_tags.append(tag)
                else:
                    # 태그가 아닌 다른 필드의 값은 그대로 보존
                    if current_field and (line.startswith(' ') or line.startswith('-') or line.startswith('  ')):
                        new_yaml_lines.append(line)

            i += 1

        # 마지막 태그 처리
        if found_tags and valid_tags:
            new_yaml_lines.append('tags:')
            for tag in valid_tags:
                new_yaml_lines.append(f'  {tag}')

        # 새로운 YAML 프론트매터 생성
        if new_yaml_lines:
            new_content = f"---\n{'\n'.join(new_yaml_lines)}\n---\n{rest_content}"

            # 파일 내용이 변경되었는지 확인
            if new_content != content:
                # 파일 덮어쓰기
                with open(file_path, 'w', encoding='utf-8') as file:
                    file.write(new_content)
                print(f"Modified {file_path}")
                return True

        return False

    except Exception as e:
        print(f"Error processing {file_path}: {str(e)}")
        return False


def process_vault(vault_path, metadata_fields, optional_fields):
    processed_files = 0
    modified_files = 0

    for root, _, files in os.walk(vault_path):
        for file in files:
            if file.endswith('.md'):
                file_path = os.path.join(root, file)
                processed_files += 1

                if process_markdown_file(file_path, metadata_fields, optional_fields):
                    modified_files += 1

                if processed_files % 100 == 0:
                    print(f"Processed {processed_files} files...")

    return processed_files, modified_files


if __name__ == "__main__":
    metadata_fields = ['created:', 'updated:', 'date:', 'tags:', 'aliases:
                       'links:', 'title:', 'weight:', 'description:', 'labels:']
    optional_fields = ['profileName:', 'postId:', 'author:', 'source:']

    vault_path = r"파일 경로"

    print("Starting to process markdown files...")
    total_files, modified = process_vault(vault_path, metadata_fields, optional_fields)

    print(f"\nProcessing complete!")
    print(f"Total files processed: {total_files}")
    print(f"Files modified: {modified}")

실행 확인

태그가 완전히 삭제된 것을 확인했습니다. 이후, 옵시디언의 tags 항목을 확인하면서 글 내부에 #tags 형식으로 포함된 부분들을 찾아 수정했습니다. 이 부분들은 글 내부에 존재하기 때문에 따로 수정이 필요합니다. 특히 컬러 HEX 코드 같은 부분들이 태그로 인식이 되더군요.

설정의 메타데이터 기록 지우기

그 이후 옵시디언 설정에서의 메타데이터 기록도 추가적으로 삭제합니다.

  1. .obsidian 폴더 접근

    • 숨김 폴더인 .obsidian 폴더로 이동하여, types.json 파일을 텍스트 에디터로 열었습니다.
  2. 불필요한 프로퍼티 삭제

    • 파일 내에서 불필요한 프로퍼티를 하나하나 삭제했습니다. 이 과정에서 이전에 실수로 등록된 불필요한 메타데이터, 예를 들어 ‘ㅠ’ 같은 잘못된 값도 제거 가능합니다.

태그 되돌리기

사라진 태그 프로퍼티를 되살리기 위해 작성한 파이썬 코드.

import os
import re

def add_missing_tags(directory_path):
    """
    디렉토리 내의 모든 마크다운 파일을 검사하여 누락된 tags: 필드를 추가합니다.
    
    Args:
        directory_path (str): 마크다운 파일들이 있는 디렉토리 경로
    """
    # 마크다운 파일 찾기
    for root, dirs, files in os.walk(directory_path):
        for file in files:
            if file.endswith('.md'):
                file_path = os.path.join(root, file)
                
                # 파일 읽기
                with open(file_path, 'r', encoding='utf-8') as f:
                    content = f.read()
                
                # 프론트매터 확인
                frontmatter_pattern = r'^---\s*\n(.*?)\n---'
                match = re.search(frontmatter_pattern, content, re.DOTALL)
                
                if match:
                    frontmatter = match.group(1)
                    # tags: 필드가 없는 경우
                    if 'tags:' not in frontmatter:
                        # 프론트매터의 마지막 줄을 찾아서 그 다음에 tags: 추가
                        new_frontmatter = frontmatter.rstrip() + '\ntags: []\n'
                        new_content = content.replace(frontmatter, new_frontmatter)
                        
                        # 파일 쓰기
                        with open(file_path, 'w', encoding='utf-8') as f:
                            f.write(new_content)
                        print(f"Added tags: to {file_path}")

# 사용 예시
if __name__ == "__main__":
    # 마크다운 파일들이 있는 디렉토리 경로 지정
    markdown_directory = "./your_markdown_directory"
    add_missing_tags(markdown_directory)

결론

이번 작업을 통해 옵시디언에서 불필요한 메타데이터와 태그들을 효율적으로 삭제할 수 있었습니다. 많은 파일들을 다루면서 수작업으로 처리하기 어려운 부분들을 파이썬 스크립트를 활용하여 자동화함으로써 시간을 절약할 수 있었고, 데이터의 일관성을 유지하는 데도 큰 도움이 되었습니다.

그러나 태그 항목을 수정할 때 태그가 그대로 지워지는 점 등은 아쉬움이 남습니다. 시간이 좀 있었다면 수정할 수 있었을텐데, 오늘따라 잘 안되네요. 계속 사용할 게 아닌 한번만 사용해도 되니까 편하게 다뤄서 그런것도 있는 것 같습니다. 최종적으로 다듬어서 좋은 프로세스를 만들면 좋은데..흠 나중에 더 필요한 순간이 오면 좀 다듬어서 만들어봐야겠습니다.

참조

댓글 쓰기