#!/usr/bin/env python # -*- coding: utf-8 -*- """ Scribus Script: Export text per layer to separate .txt files For each layer creates a file: -.txt Content structure: Page 1: text from frame 1 -------------------- text from frame 2 -------------------- ... Page 2: ... Text frames are sorted by position on the page: top → bottom, then left → right. Output folder = same folder as the .sla file (no dialog needed). """ import scribus import os import re import traceback def safe_filename(name): """Make a string safe for use as filename.""" name = name.strip() name = re.sub(r'[\\/:*?"<>|]', '_', name) name = re.sub(r'\s+', '_', name) return name or "unnamed" def get_document_path_and_basename(): """Return (directory, basename without extension) of current document.""" doc = scribus.getDocName() if not doc: return os.path.expanduser("~"), "untitled" directory = os.path.dirname(doc) if not directory: directory = os.path.expanduser("~") base = os.path.basename(doc) name, _ = os.path.splitext(base) return directory, (safe_filename(name) if name else "untitled") def get_text_from_frame(frame_name): """Get text from a text frame. Prefer getAllText for linked frames.""" try: text = scribus.getAllText(frame_name) if text is None: text = "" return text.strip() except Exception: try: text = scribus.getFrameText(frame_name) if text is None: text = "" return text.strip() except Exception: return "" def get_frame_position(frame_name): """Return (x, y) of the frame. Used for sorting.""" try: return scribus.getPosition(frame_name) except Exception: return (0.0, 0.0) def sort_frames_by_position(frame_names): """ Sort text frames top-to-bottom, then left-to-right. Primary key: Y (ascending = top first) Secondary key: X (ascending = left first) """ def sort_key(name): x, y = get_frame_position(name) return (y, x) return sorted(frame_names, key=sort_key) def get_textframes_on_page_layer(page_nr, layer_name): """ Robustly get text frames on a given page (0-based) and layer. Tries several calling conventions of getAllObjects. """ TEXTFRAME_TYPE = 4 objects = [] # Method 1: keyword arguments (newer Scribus) try: objects = scribus.getAllObjects(type=TEXTFRAME_TYPE, page=page_nr, layer=layer_name) if objects: return objects except Exception: pass # Method 2: positional try: objects = scribus.getAllObjects(TEXTFRAME_TYPE, page_nr, layer_name) if objects: return objects except Exception: pass # Method 3: get all objects on page, then filter by layer + type try: all_on_page = scribus.getAllObjects(page=page_nr) for obj in all_on_page: try: if scribus.getObjectType(obj) != "TextFrame": continue # Check layer of the object if possible # (some versions have no direct getLayer, so we try sendToLayer trick or just accept) objects.append(obj) except Exception: continue # If we got objects this way we cannot reliably filter by layer, # so return empty and let the caller know – better than wrong content. # We only use this as last resort if the layer parameter failed. if objects and not layer_name: return objects except Exception: pass return [] def main(): if not scribus.haveDoc(): scribus.messageBox("Error", "No document open.", scribus.ICON_WARNING) return layers = scribus.getLayers() if not layers: scribus.messageBox("Error", "No layers found.", scribus.ICON_WARNING) return page_count = scribus.pageCount() if page_count < 1: scribus.messageBox("Error", "Document has no pages.", scribus.ICON_WARNING) return # Always use the same folder as the .sla file out_dir, doc_base = get_document_path_and_basename() if not os.path.isdir(out_dir): scribus.messageBox("Error", "Cannot find document folder:\n" + out_dir, scribus.ICON_WARNING) return # Confirm with the user confirm = scribus.messageBox( "Export text per layer", "Output folder:\n" + out_dir + "\n\n" "Files will be named:\n" + doc_base + "-.txt\n\n" "Continue?", scribus.ICON_INFORMATION, scribus.BUTTON_OK, scribus.BUTTON_CANCEL ) if confirm != scribus.BUTTON_OK and confirm != 1: # 1 is sometimes returned for OK return created_files = [] debug_info = [] for layer_name in layers: safe_layer = safe_filename(layer_name) out_path = os.path.join(out_dir, f"{doc_base}-{safe_layer}.txt") lines = [] has_any_text = False frames_found = 0 for page_nr in range(page_count): # 0-based page_display = page_nr + 1 objects = get_textframes_on_page_layer(page_nr, layer_name) if not objects: continue frames_found += len(objects) # Sort frames: top → bottom, left → right objects = sort_frames_by_position(objects) page_texts = [] for obj in objects: txt = get_text_from_frame(obj) if txt: page_texts.append(txt) has_any_text = True if page_texts: lines.append(f"Page {page_display}:") lines.append("") for i, t in enumerate(page_texts): lines.append(t) if i < len(page_texts) - 1: lines.append("--------------------") lines.append("") lines.append("") debug_info.append(f"Layer '{layer_name}': {frames_found} text frames found") if has_any_text: content = "\n".join(lines).rstrip() + "\n" try: with open(out_path, "w", encoding="utf-8") as f: f.write(content) created_files.append(out_path) except Exception as e: scribus.messageBox("Error writing file", f"{out_path}\n\n{str(e)}", scribus.ICON_WARNING) else: # Create an empty marker file so the user sees the layer was processed try: with open(out_path, "w", encoding="utf-8") as f: f.write(f"# No text content found on layer '{layer_name}'\n") created_files.append(out_path + " (empty)") except Exception: pass # Final report if created_files: msg = "Created / updated files:\n\n" + "\n".join(created_files) msg += "\n\n--- Debug ---\n" + "\n".join(debug_info) scribus.messageBox("Export finished", msg, scribus.ICON_INFORMATION) else: msg = "No files written.\n\n" + "\n".join(debug_info) scribus.messageBox("Info", msg, scribus.ICON_INFORMATION) if __name__ == "__main__": try: main() except Exception as e: # Catch any unexpected crash and show it scribus.messageBox("Script Error", str(e) + "\n\n" + traceback.format_exc()[:1500], scribus.ICON_WARNING)