//------------------------------------------------ //--- 010 Editor v9.0.2 Script File // // File: SplitMiffHeaders.1sc // Authors: David C. Huffman // Version: 1.0.0 // Purpose: Cut (or copy, if source file is read-only) MIFF header(s) to new file(s). // Not fully compatible with all valid MIFF files, but should work with those generated by ImageMagick. // See https://imagemagick.org/www/script/miff.php // Category: Format Specific // File Mask: *.miff // History: // 1.0.0 2019-10-13 David C. Huffman: Initial release. Based on SplitFile.1sc by SweetScape Software. //------------------------------------------------ RequiresVersion(4);// todo: is this right? RequiresFile(); //------------------------------------------------ //--- Helper Functions // It doesn't look like there's an `IsCharCtrl()` function provided :( // Since we're already writing one, mind as well just ignore `IsCharWhitespace()` and write them both, but merged. int IsCharCtrlOrWhitespace(char c) { // control chars are: [0,31] U 127 // whitespace chars are: [9,13] U 32 return c >= 0 && c <= 32 || c == 127; } // return index of first character of the first header at or after `start` // if not found, return -1 int64 FindNextMiffHeader(int64 start) { // start-of-header magic constants const string MAGIC_STR = "id=ImageMagick"; const string MAGIC_RGX = MAGIC_STR + "(?=\W),a"; const int64 MAGIC_STR_LEN = Strlen(MAGIC_STR); // note: headers must contain id=ImageMagick, but need not *start* with it (those created by ImageMagick always seem to, however). // note: multiple MIFF headers are separated by arbitrary binary data, potentially including id=ImageMagick. // fixme: don't use id=ImageMagick to determine the first (or any) header's starting position // fixme: use preceding headers (and, in some cases, the image data) to determine the location of successive headers. // note: length of separating data determined by: rows, columns, depth, colorspace, and compression (and therefore the data itself), is that all? const int64 pos = FindFirst(MAGIC_RGX, true, false, FINDMETHOD_REGEX, 0.0, 1, start); if (pos >= 0) { // the magic ascii must be followed by a control or whitespace character to be valid const int64 after = pos + MAGIC_STR_LEN; if (!IsCharCtrlOrWhitespace(ReadByte(after))) { // what we've found isn't actually the start of a header, try again return FindNextMiffHeader(after);// hopefully this won't overflow... } } return pos; } // return index of first character following the first end-of-header byte-sequence at or after `start` // if not found, return -1 int FindEndOfMiffHeader(int64 start) { // note: the end-of-header sequence is commonly (seemingly always by ImageMagick) prefixed by '\x0C\n' const string MAGIC = "0x3A1A,h"; const int64 MAGIC_LEN = 2; const int64 pos = FindFirst(MAGIC, true, false, FINDMETHOD_NORMAL, 0.0, 1, start); return pos >= 0 ? pos + MAGIC_LEN : -1; } //------------------------------------------------ //--- Preparation // used to track the absolute byte positions for the beginning and ending of the current header int64 beg;// used in main loop and immediately below int64 end;// used (and set) only in main loop // locate first (or only) MIFF header beg = FindNextMiffHeader(0); if (beg < 0) { // this is either not a MIFF file or a corrupt one MessageBox(idOk, GetScriptName(), "MIFF header not found."); return -1; } // if the file does not start with a MIFF header, it's probably not a MIFF file... if (beg > 0 && MessageBox(idYes|idCancel, GetScriptName(), "MIFF header found but not located at start of file. Proceed anyways?") != idYes) { return 0; } // Obtain current state information so we can restore it upon script completion const int32 origFileNum = GetFileNum(); int64 origSelStart = GetSelStart(); int64 origSelSize = GetSelSize(); const int32 origClipboard = GetClipboardIndex(); // clipboards are indexed 0-9 with 0 being the system clipboard // we'll be clobbering one of them, so pick one that is neither 0 nor currently active SetClipboardIndex(origClipboard % 9 + 1); // if the source file is read-only, we'll use CopyToClipboard() rather than CutToClipboard() const int readOnly = GetReadOnly(); // if we create exactly 1 file, end the script with it focused; otherwise focus the source file. // valid files have integers >= 0; we'll use -1->not yet set and -2->use `origFileNum` int firstNewFileNum = -1; //------------------------------------------------ //--- Main while (beg >= 0) { // find the index of the next byte that is not part of the current header end = FindEndOfMiffHeader(beg); if (end < 0 && MessageBox(idYes|idNo, GetScriptName(), "MIFF end-of-header mark not found. Take rest of file?") != idYes) { break; } // cut or copy header to a new file SetSelection(beg, (end >= 0 ? end : FileSize()) - beg); if (readOnly) { CopyToClipboard(); } else { CutToClipboard(); // adjust positions for removed bytes if (origSelStart >= end) { origSelStart -= end - beg; } else if (origSelStart >= beg) { origSelSize = Max(0, origSelStart + origSelSize - end); origSelStart = beg; } end = beg; } FileNew("Text", true);// MIFF headers are ISO-8859-1, so using Unicode here would be bad firstNewFileNum = firstNewFileNum == -1 ? GetFileNum() : -2;// record or clear new file num PasteFromClipboard(); FileSelect(origFileNum);// deactive the new file, and reactivate the source file // find next header, if any, and continue beg = FindNextMiffHeader(end); } //------------------------------------------------ //--- Clean-up // Clean up our unwanted UI state changes ClearClipboard(); SetClipboardIndex(origClipboard); SetSelection(origSelStart, origSelSize); if (firstNewFileNum >= 0) { // Then, make a final--hopefully wanted--state change FileSelect(firstNewFileNum); }