novel-doomsday-resurgence/skills/fanfic-writer/scripts/chapter_manager.py

219 lines
6.6 KiB
Python
Raw Permalink Normal View History

"""
Chapter Content Manager
Handles chapter file operations and content integrity
"""
import os
import re
from pathlib import Path
from datetime import datetime
def get_chapter_path(book_dir, chapter_num, chapter_title=None):
"""Get path for a chapter file"""
chapters_dir = Path(book_dir) / "chapters"
# Convert title to safe filename
if chapter_title:
safe_title = "".join(c for c in chapter_title if c.isalnum() or c in "-_").strip()
filename = f"{chapter_num:03d}章_{safe_title}.txt"
else:
filename = f"{chapter_num:03d}章.txt"
return chapters_dir / filename
def save_chapter(book_dir, chapter_num, chapter_title, content, is_draft=False):
"""Save a chapter to file"""
if is_draft:
save_dir = Path(book_dir) / "drafts"
else:
save_dir = Path(book_dir) / "chapters"
save_dir.mkdir(exist_ok=True)
# Construct filename
safe_title = "".join(c for c in chapter_title if c.isalnum() or c in "-_").strip() if chapter_title else ""
if safe_title:
filename = f"{chapter_num:03d}章_{safe_title}.txt"
else:
filename = f"{chapter_num:03d}章.txt"
filepath = save_dir / filename
# Write with metadata header
metadata = f"""[Chapter Metadata]
Number: {chapter_num}
Title: {chapter_title}
Word Count: {len(content)}
Created: {datetime.now().isoformat()}
Status: {'draft' if is_draft else 'final'}
[End Metadata]
---
{content}
"""
with open(filepath, 'w', encoding='utf-8') as f:
f.write(metadata)
return filepath
def load_chapter(book_dir, chapter_num):
"""Load a chapter from file"""
chapters_dir = Path(book_dir) / "chapters"
# Find chapter file
pattern = f"{chapter_num:03d}章*.txt"
matches = list(chapters_dir.glob(pattern))
if not matches:
# Try drafts
drafts_dir = Path(book_dir) / "drafts"
matches = list(drafts_dir.glob(pattern))
if not matches:
raise FileNotFoundError(f"Chapter {chapter_num} not found")
filepath = matches[0]
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
# Extract metadata and content
metadata_match = re.search(r'\[Chapter Metadata\](.+?)\[End Metadata\]', content, re.DOTALL)
if metadata_match:
metadata_text = metadata_match.group(1)
main_content = content[metadata_match.end():].strip()
# Remove --- separator
if main_content.startswith('---'):
main_content = main_content[3:].strip()
else:
metadata_text = ""
main_content = content
# Parse metadata
metadata = {}
for line in metadata_text.strip().split('\n'):
if ':' in line:
key, value = line.split(':', 1)
metadata[key.strip()] = value.strip()
return {
'filepath': str(filepath),
'metadata': metadata,
'content': main_content,
'word_count': len(main_content)
}
def load_previous_chapters(book_dir, current_chapter, count=3):
"""Load previous N chapters for context"""
chapters = []
for i in range(max(1, current_chapter - count), current_chapter):
try:
chapter = load_chapter(book_dir, i)
chapters.append(chapter)
except FileNotFoundError:
continue
return chapters
def list_chapters(book_dir):
"""List all chapters in the book"""
chapters_dir = Path(book_dir) / "chapters"
chapters = []
for f in sorted(chapters_dir.glob("第*.txt")):
# Parse chapter number from filename
match = re.match(r'第(\d+)章', f.stem)
if match:
chapter_num = int(match.group(1))
# Get title from filename after chapter number
title = f.stem.split('_', 1)[1] if '_' in f.stem else ""
# Get actual content for word count
try:
chapter_data = load_chapter(book_dir, chapter_num)
word_count = chapter_data['word_count']
except:
word_count = 0
chapters.append({
'number': chapter_num,
'title': title,
'filename': f.name,
'word_count': word_count
})
return sorted(chapters, key=lambda x: x['number'])
def count_total_words(book_dir):
"""Count total words across all chapters"""
chapters = list_chapters(book_dir)
return sum(c['word_count'] for c in chapters)
def merge_chapters(book_dir, output_format='txt'):
"""Merge all chapters into final book"""
chapters = list_chapters(book_dir)
if not chapters:
raise ValueError("No chapters found")
# Load book config
import json
config_path = Path(book_dir) / "0-book-config.json"
with open(config_path, 'r', encoding='utf-8') as f:
config = json.load(f)
book_title = config.get('title', 'Untitled')
# Build merged content
lines = [f"# {book_title}", "", "---", ""]
for chapter in chapters:
try:
chapter_data = load_chapter(book_dir, chapter['number'])
lines.append(f"\n{chapter['number']}{chapter['title']}\n")
lines.append(chapter_data['content'])
lines.append("\n---\n")
except Exception as e:
lines.append(f"\n[Error loading chapter {chapter['number']}: {e}]\n")
merged_content = '\n'.join(lines)
# Save to final directory
final_dir = Path(book_dir) / "final"
final_dir.mkdir(exist_ok=True)
safe_title = "".join(c for c in book_title if c.isalnum() or c == '-').strip()
output_path = final_dir / f"{safe_title}.txt"
with open(output_path, 'w', encoding='utf-8') as f:
f.write(merged_content)
return output_path, len(merged_content)
if __name__ == "__main__":
import sys
import json
if len(sys.argv) < 2:
print("Usage: chapter_manager.py <command> [args]")
print(" list <book_dir> - List all chapters")
print(" merge <book_dir> - Merge into final book")
print(" count <book_dir> - Count total words")
sys.exit(1)
cmd = sys.argv[1]
if cmd == "list":
chapters = list_chapters(sys.argv[2])
for c in chapters:
print(f"{c['number']}章: {c['title']} ({c['word_count']} words)")
elif cmd == "merge":
path, words = merge_chapters(sys.argv[2])
print(f"Merged to: {path}")
print(f"Total words: {words}")
elif cmd == "count":
words = count_total_words(sys.argv[2])
print(f"Total words written: {words}")