Created
July 30, 2024 18:54
-
-
Save alastori/11afec679a520d3f968501867b96e080 to your computer and use it in GitHub Desktop.
This Python script processes referenced screenshots in Obsidian Markdown files, renaming them to include the Markdown file's name and a unique timestamp. It updates the Markdown files with the new filenames and generates a processing report. Create a config.ini file with your notes directory and maximum filename length, then run the script.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# org-obsidian-screenshots.py | |
""" | |
Script to Organize and Rename Screenshot Files in Obsidian Markdown Notes | |
Purpose: | |
This script processes referenced screenshots in Obsidian Markdown (`.md`) files. It renames the screenshot files to include the name of the Markdown file and a unique timestamp. Additionally, it updates the Markdown files to reference the new screenshot filenames and generates a processing report. | |
Usage: | |
1. Create a `config.ini` file in the same directory as the script with the following content: | |
[DEFAULT] | |
notes_dir = /path/to/your/obsidian/notes | |
max_filename_length = 255 | |
2. Run the script: | |
python org-obsidian-screenshots.py | |
Dependencies: | |
- Python 3.x | |
- No external dependencies are required (only Python standard library is used). | |
The script will: | |
- Generate a report listing files with no screenshot images. | |
- Ask for confirmation to rename screenshots for each `.md` file individually. | |
- Save the processing report to a text file with a timestamped filename. | |
""" | |
import os | |
import re | |
import shutil | |
from datetime import datetime, timedelta | |
import configparser | |
# Load configuration from config.ini file | |
config = configparser.ConfigParser() | |
config.read('config.ini') | |
notes_dir = config['DEFAULT']['notes_dir'] | |
MAX_FILENAME_LENGTH = int(config['DEFAULT']['max_filename_length']) | |
def list_screenshot_files_to_rename(notes_dir): | |
""" | |
Identify Markdown files and list the screenshots to be renamed. | |
Args: | |
notes_dir (str): The directory containing the Markdown files and screenshots. | |
Returns: | |
list: A list of tuples containing the Markdown file, original screenshot file, and new screenshot file. | |
list: A list of tuples indicating whether screenshots were found in each Markdown file. | |
""" | |
screenshots_to_rename = [] | |
markdown_files = [] | |
analyzed_files = [] | |
# Get all .md files and sort them by modification time (most recent first) | |
files = [f for f in os.listdir(notes_dir) if f.endswith('.md')] | |
files.sort(key=lambda f: os.path.getmtime(os.path.join(notes_dir, f)), reverse=True) | |
for file in files: | |
found_screenshot = False | |
base_name = os.path.splitext(file)[0] | |
analyzed_files.append((file, found_screenshot)) | |
with open(os.path.join(notes_dir, file), 'r') as md_file: | |
lines = md_file.readlines() | |
for line in lines: | |
match = re.search(r'!\[.*?\]\((Screenshot.*?|Pasted image.*?)\)|!\[\[.*?(Screenshot.*?|Pasted image.*?)\]\]', line) | |
if match: | |
found_screenshot = True | |
screenshot_file = match.group(1) or match.group(2) | |
if os.path.exists(os.path.join(notes_dir, screenshot_file)): | |
screenshot_path = os.path.join(notes_dir, screenshot_file) | |
timestamp = datetime.fromtimestamp(os.path.getmtime(screenshot_path)) | |
new_screenshot_file = f"{base_name}-Screenshot-{timestamp.strftime('%Y-%m-%d-%H%M%S')}{os.path.splitext(screenshot_file)[1]}" | |
# Ensure the new filename is unique by incrementing the timestamp if needed | |
while os.path.exists(os.path.join(notes_dir, new_screenshot_file)): | |
timestamp += timedelta(seconds=1) | |
new_screenshot_file = f"{base_name}-Screenshot-{timestamp.strftime('%Y-%m-%d-%H%M%S')}{os.path.splitext(screenshot_file)[1]}" | |
# Shorten the base name if necessary | |
if len(new_screenshot_file) > MAX_FILENAME_LENGTH: | |
excess_length = len(new_screenshot_file) - MAX_FILENAME_LENGTH | |
base_name = base_name[:-excess_length] | |
new_screenshot_file = f"{base_name}-Screenshot-{timestamp.strftime('%Y-%m-%d-%H%M%S')}{os.path.splitext(screenshot_file)[1]}" | |
screenshots_to_rename.append((file, screenshot_file, new_screenshot_file)) | |
analyzed_files[-1] = (file, found_screenshot) | |
return screenshots_to_rename, analyzed_files | |
def rename_screenshot_files(notes_dir, screenshots_to_rename, report_file): | |
""" | |
Rename screenshot files and update references in Markdown files. | |
Args: | |
notes_dir (str): The directory containing the Markdown files and screenshots. | |
screenshots_to_rename (dict): A dictionary of Markdown files and their associated screenshots to rename. | |
report_file (file): The file object to write the report to. | |
Returns: | |
set: A set of processed Markdown files. | |
""" | |
processed_files = set() | |
for md_file, screenshot_group in screenshots_to_rename.items(): | |
for original_screenshot, new_screenshot in screenshot_group: | |
original_screenshot_path = os.path.join(notes_dir, original_screenshot) | |
new_screenshot_path = os.path.join(notes_dir, new_screenshot) | |
if not os.path.exists(original_screenshot_path): | |
report_file.write(f"File {original_screenshot} does not exist. Skipping renaming.\n") | |
print(f"File {original_screenshot} does not exist. Skipping renaming.") | |
continue | |
if os.path.exists(new_screenshot_path): | |
report_file.write(f"File {new_screenshot} already exists. Skipping renaming for {original_screenshot}.\n") | |
print(f"File {new_screenshot} already exists. Skipping renaming for {original_screenshot}.") | |
continue | |
shutil.move(original_screenshot_path, new_screenshot_path) | |
update_markdown_references(notes_dir, md_file, original_screenshot, new_screenshot, report_file) | |
processed_files.add(md_file) | |
return processed_files | |
def update_markdown_references(notes_dir, md_file, original_screenshot, new_screenshot, report_file): | |
""" | |
Update screenshot references in Markdown files. | |
Args: | |
notes_dir (str): The directory containing the Markdown files and screenshots. | |
md_file (str): The Markdown file to update. | |
original_screenshot (str): The original screenshot file name. | |
new_screenshot (str): The new screenshot file name. | |
report_file (file): The file object to write the report to. | |
""" | |
report_file.write(f"**Updating:** {md_file}... references to {original_screenshot} --> {new_screenshot}\n") | |
print(f"**Updating:** {md_file}... references to {original_screenshot} --> {new_screenshot}") | |
md_file_path = os.path.join(notes_dir, md_file) | |
with open(md_file_path, 'r') as file: | |
content = file.read() | |
updated_content = content.replace(original_screenshot, new_screenshot) | |
with open(md_file_path, 'w') as file: | |
file.write(updated_content) | |
report_file.write(f"Updated references in {md_file}\n") | |
print(f"Updated references in {md_file}") | |
def organize_and_rename_screenshots(notes_dir, report_file): | |
""" | |
Main function to organize and rename screenshots in notes. | |
Args: | |
notes_dir (str): The directory containing the Markdown files and screenshots. | |
report_file (file): The file object to write the report to. | |
""" | |
screenshots_to_rename, analyzed_files = list_screenshot_files_to_rename(notes_dir) | |
report_file.write("\n**No screenshot found**\n") | |
print("\n**No screenshot found**") | |
for file, found_screenshot in analyzed_files: | |
if not found_screenshot: | |
report_file.write(f"- {file}\n") | |
print(f"- {file}") | |
if not screenshots_to_rename: | |
report_file.write("No screenshots to rename.\n") | |
print("No screenshots to rename.") | |
return | |
# Group screenshots by their markdown file | |
screenshots_by_md_file = {} | |
for md_file, original_screenshot, new_screenshot in screenshots_to_rename: | |
if md_file not in screenshots_by_md_file: | |
screenshots_by_md_file[md_file] = [] | |
screenshots_by_md_file[md_file].append((original_screenshot, new_screenshot)) | |
for md_file, screenshot_group in screenshots_by_md_file.items(): | |
report_file.write("\n**Renaming:**\n") | |
print("\n**Renaming:**") | |
# Reset the timestamp for each md_file to ensure unique filenames within the same md_file group | |
timestamp_increment = 0 | |
unique_new_screenshot_files = [] | |
for original_screenshot, new_screenshot in screenshot_group: | |
while any(new_screenshot == u for _, u in unique_new_screenshot_files): | |
timestamp_increment += 1 | |
base_name, rest = new_screenshot.rsplit('-Screenshot-', 1) | |
timestamp = datetime.strptime(rest.split('.')[0], '%Y-%m-%d-%H%M%S') | |
timestamp += timedelta(seconds=timestamp_increment) | |
new_screenshot = f"{base_name}-Screenshot-{timestamp.strftime('%Y-%m-%d-%H%M%S')}{os.path.splitext(original_screenshot)[1]}" | |
unique_new_screenshot_files.append((original_screenshot, new_screenshot)) | |
report_file.write(f"{original_screenshot} --> {new_screenshot}\n") | |
print(f"{original_screenshot} --> {new_screenshot}") | |
report_file.flush() | |
confirmation = input(f"Do you want to rename the screenshots in {md_file}? (yes/no): ").strip().lower() | |
if confirmation == 'yes': | |
rename_screenshot_files(notes_dir, {md_file: unique_new_screenshot_files}, report_file) | |
else: | |
report_file.write(f"Skipping renaming for {md_file}.\n") | |
print(f"Skipping renaming for {md_file}.") | |
report_file.flush() | |
# Prepare the report file | |
timestamp = datetime.now().strftime('%Y-%m-%d-%H%M%S') | |
report_filename = f"org-obsidian-screenshots-{timestamp}.txt" | |
report_file_path = os.path.join(notes_dir, report_filename) | |
report_file = open(report_file_path, 'w') | |
# Call the main function | |
organize_and_rename_screenshots(notes_dir, report_file) | |
# Close the report file | |
report_file.close() | |
print(f"\nProcessing report saved as {report_filename}") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment