No internet connection
  1. Home
  2. How to

Script that can Extract timecode stamps from a table in a word document and create markers!

By Dane Butler @Dane_Butler
    2024-02-26 16:11:50.134Z

    Hi @Chad !

    I have a higher priority request than my previous (Sorry for blowing you up lately) and this one is definitely going to be well beyond my knowledge. I think perhaps it will require some of the (Regex?) magic from the excel code you helped provide me with.

    I can submit an example through the email portal, but basically it's just this....

    I have a word document that contains a table with multiple columns and rows.

    The columns from left to right are row number, character, timecode, line!

    The script I'm looking for should be able to navigate the columns and create markers based on a specific character name.

    So if I launch the script, it would ask me for a name, I would type "Suzie" and then it would scan the names column for every instance of "Suzie" in that specific column, and then return a marker at its corresponding time code.

    Bonus points if the marker name says "Suzie 3" if the row/line number is 3 :D

    If you could help me figure this out as soon as possible that be incredible. Just picked up a new gig and I'm trying to get my ducks in a row.

    • 25 replies

    There are 25 replies. Estimated reading time: 25 minutes

    1. D
      Dane Butler @Dane_Butler
        2024-02-26 16:21:00.074Z

        Hi @Chad ! It just occurred to me that I can also just take the table and paste it into an excel document easy peasy! Do you think that would help for this script to run better?

        1. RRandall Smith @Randall_Smith
            2024-02-26 21:32:28.624Z

            Here is a script I created for taking a FrameIO comments CSV and creating markers.

            
            function openCsvAsText() {
            
                var startOfClip = getCurrentTimeCode() //get start timecode
            
                var path = sf.interaction.selectFile({
                    prompt: 'Please Select File to Open',
                }).path;
            
                var contents = sf.file.readText({ path }).text;
                var arr = contents.trim() //gets rid of empty cells
                var arr = arr.split('\n'); //splits on lines
            
            
                for (var i = 1; i < arr.length; i++) {
                    var data = arr[i].split(',');
                    var frameIOTeam = data[0];
                    var frameIOProject = data[1];
                    var frameIOFile = data[2];
                    var frameIOFileUrl = data[3];
                    var frameIOVersion = data[4];
                    var frameIOCommenter = data[5];
                    var frameIOCommentID = data[6];
                    var frameIOComment = data[7];
                    var frameIOReply = data[8];
                    var frameIOCommented = data[9];
                    var frameIOComplete = data[10];
                    var frameIOAnnotation = data[11];
                    var frameIOFrame = data[12];
                    var frameIODuration = data[13];
                    var frameIOTimecode = data[14];
                    var frameIOTimecodeIn = data[15];
                    var frameIOTimecodeOut = data[16];
                    var frameIOTCSource = data[17];
                    var frameIOTCSourceIn = data[18];
                    var frameIOTCSourceOut = data[19];
                    var frameIOTimeIn = data[20];
                    var frameIOTimeOut = data[21];
                   // alert(frameIOComment);
            
            
                    var clipTC = convertMStoSamples(startOfClip, frameIOTimeIn)
                    goToTimeCode(clipTC)
                    var commentText = data[2].replace(/"/g, '');
                    //  addMarkerComment(commentText)
            
                    var commenter = frameIOCommenter.replace(/"/g, '');
                    var commentText = frameIOComment.replace(/"/g, '');
                    var commentFieldText = ("Version:" + frameIOVersion + "  From:" + commenter);
            
            
            
            
                    //  addMarkerBaseSpot(baseSpot);
            
                    // sf.ui.proTools.waitForNoModals();
            
                   //  if (isNaN(durationNum)) continue;
                    //goToTimeCode(timeCodeSource);
            
                    addMarkerIsci(commentText, commentFieldText);
                    //alert(baseSpot);
                    //addTimeCode(durationNum);
            
                    //    alert(isci);
                }
            
            
            }
            
            
            function getCurrentTimeCode() {
                sf.ui.proTools.mainCounterSetValue({
                    targetValue: "Samples",
                });
                var currentTC = sf.ui.proTools.selectionGet().mainCounter.trim();
                return currentTC
            
            }
            
            function goToTimeCode(timeCodeSource) {
            
                var timeCodeSource = timeCodeSource + ""
                sf.ui.proTools.selectionSet({
                    selectionStart: timeCodeSource,
                });
            
            }
            
            function convertMStoSamples(startTime, timeFromSource) {
                var spc = 48048
                var a = timeFromSource.split(':');
                //alert(a.length + "")
                if (a.length > 2) {
                    var seconds = (+a[0]) * 60 * 60 + (+a[1]) * 60 + (a[2]);
                    //  alert (seconds)
                } else {
                    var seconds = (+a[0]) * 60 + (a[1]);
                    //alert (seconds)
                }
                var startTime2 = (Number(startTime))
                var tfs = ((Number(seconds)) * spc);
                var offsetTime = (startTime2 + tfs)
                // alert (startTime2+"")
                //  alert (tfs+"")
                //  alert(offsetTime+"")
                return offsetTime
            
            }
            
            function returnDisplay() {
                sf.ui.proTools.mainCounterSetValue({
                    targetValue: "Timecode",
                });
            }
            
            function addMarkerIsci(commentText, commentFieldText) {
            
                sf.keyboard.press({
                    keys: "numpad enter",
                });
            
                sf.keyboard.type({
                    text: commentText,
                });
            
                sf.keyboard.press({
                    keys: "tab, tab",
                });
            
                sf.keyboard.type({
                    text: commentFieldText,
                });
            
                //sf.keyboard.press({
                //    keys: "cmd+v",
                //});
            
            
                sf.keyboard.press({
                    keys: "numpad enter",
                });
            
            
            }
            
            function addMarkerComment(markerDescription) {
            
                sf.keyboard.press({
                    keys: "numpad enter",
                });
            
                sf.keyboard.type({
                    text: markerDescription,
                });
            
            
                sf.keyboard.press({
                    keys: "numpad enter",
                });
            }
            
            
            //start of script
            
            sf.ui.proTools.appActivateMainWindow();
            
            const areYouReadyToStart = sf.interaction.displayDialog({
                prompt: 'The ProTools Playhead needs to be at FFOP for the reviewed version. Is it there now?',
                buttons: ['Yes', 'No'],
                defaultButton: 'Yes',
                cancelButton: 'No'
            
            }).button
            
            if (areYouReadyToStart === 'Yes') {
                log("Let's Go")
                openCsvAsText();
            } else {
                alert("Move playhead to start of file and try again")
            }
            
            
            returnDisplay();
            
            
            
            1. In reply toDane_Butler:
              Dustin Harris @Dustin_Harris
                2024-02-27 06:57:18.588Z

                Excel is definitely better to work with. I’ll play with the idea in the morning (GMT-5) and see what I can come up with. What version of Pro Tools are you on?

                1. DDane Butler @Dane_Butler
                    2024-02-27 07:35:53.782Z

                    Hey Dustin! Thanks for hopping in! I'm on Pro Tools 2023.9

                    1. Dustin Harris @Dustin_Harris
                        2024-02-27 19:46:48.854Z

                        OK Dane! Give this one a shot. Prerequisites are: the excel has to have headers, like this: it doesn't matter what they're called as long as they're in this order: line or row number (you will need to create this), character, timecode, and line (line is the line to be performed, which I'm copying to the marker comments.)

                        Example:

                        You then need to export from Excel as 'Tab delimited Text (txt)'

                        select the newly created text file when prompted by running this script below:

                        function tabDelimitedToJson(rawTxt) {
                        
                            const lines = rawTxt.split("\n");
                            const events = [];
                            const headers = lines[0].replace(/\r/g, "").split("\t");
                        
                            for (let i = 1; i < lines.length; i++) {
                        
                                let obj = {};
                                let currentline = lines[i].replace(/\r/g, "").split("\t");
                        
                                for (let j = 0; j < headers.length; j++) {
                                    obj[headers[j]] = currentline[j];
                                }
                        
                                events.push(obj);
                        
                            }
                            return {headers, events};
                        }
                        
                        
                        
                        function main() {
                            const csvFilePath = sf.interaction.selectFile({
                                allowedFileTypes: ["txt"],
                                prompt: 'please select a Tab-Delimited text file',
                                defaultLocation: "~/Desktop/"
                        
                            }).path
                        
                            const rawText = sf.file.readText({ path: csvFilePath }).text
                            const {headers, events} = tabDelimitedToJson(rawText)
                        
                            sf.ui.proTools.appActivateMainWindow();
                        
                            events.forEach(event => {
                                const markerName = event[headers[1]] + " " + event[headers[0]];
                                const timecode = event[headers[2]];
                                const scriptLine = event[headers[3]];
                        
                                sf.app.proTools.createMemoryLocation({
                                    reference: "Absolute",
                                    timeProperties: "Marker",
                                    startTime: timecode,
                                    name: markerName,
                                    comments: scriptLine,
                                    colorIndex: 7
                                })
                        
                            })
                        
                            log('done')
                        
                        }
                        
                        main();
                        

                        Let me know how it goes :) I've tested with the latest SoundFlow and PT2023.12.1

                        1. DDane Butler @Dane_Butler
                            2024-02-27 22:05:39.512Z

                            Thank you for this! Big step in the right direction and I'm sure the problem is something I'm not doing right, but I'm getting this error....

                            TypeError: Object has no method 'createMemoryLocation'
                            (Anime-Excel-Rev1 line 42)

                            1. DDane Butler @Dane_Butler
                                2024-02-27 22:09:13.051Z

                                The original creator of the table I copy and pasted used empty rows as a formatting technique, do you thing that's the problem?

                                1. Dustin Harris @Dustin_Harris
                                    2024-02-27 22:10:07.468Z

                                    as long as you format the excel doc like my example it should be ok

                                    1. In reply toDane_Butler:
                                      DDane Butler @Dane_Butler
                                        2024-02-27 22:14:37.976Z

                                        Dang, I tried deleting all of the empty rows and still nothing.

                                        1. Dustin Harris @Dustin_Harris
                                            2024-02-27 22:19:32.844Z

                                            If you're not on SoundFlow v5.6.1, update to that then try again. If it still doesn't work, it might require updating Pro Tools 2023.12.1, but I don't think that's the case, hard to keep all the versions straight in my head :)

                                        2. In reply toDane_Butler:
                                          Dustin Harris @Dustin_Harris
                                            2024-02-27 22:09:25.229Z

                                            You might need to update your SoundFlow version... try that and see if it helps?

                                            1. DDane Butler @Dane_Butler
                                                2024-02-28 01:24:34.425Z

                                                Trying it now!

                                                1. In reply toDustin_Harris:
                                                  DDane Butler @Dane_Butler
                                                    2024-02-28 01:58:32.470Z

                                                    Ok that seems to work! By Filtering and pasting to a new sheet seems to be a good way to make sure the characters only go one at a time, unless you have a more elegant method! Question, is there a script in the community that presses cmd v at every marker location? I think that is the missing piece. These two things together should give me the ability to prep these massive ADR sessions. Big thank you Dustin!!

                                                    1. Dustin Harris @Dustin_Harris
                                                        2024-02-28 02:03:32.296Z

                                                        What do you mean by ‘the characters only go one at a time?’ Like you only want to drop markers for one character at a time? I can edit the script to give you a pop up list of character names and drop only those markers. I assume you want paste to paste beeps or wipe triggers?

                                                        1. DDane Butler @Dane_Butler
                                                            2024-02-28 02:42:45.136Z

                                                            Hey Dustin!

                                                            Yes sorry if I phrased that in a weird way. Yes I would like to set it up so that I can choose which character I'd like to hone in on, as each dubbing session will only ever host one actor per role/character!

                                                            Also yes, I would like to paste an audio file containing beeps at each marker location! :D

                                                            1. Dustin Harris @Dustin_Harris
                                                                2024-02-28 04:18:17.405Z

                                                                this one should allow you to pick a character to drop markers for. I haven't tested it thoroughly so it's possible weird things can happen, like "Dane" and "Dane " would show up as different characters if the excel doc wasn't formatted well, but we'll cross that bridge when we get to it. Let me know how this one goes:

                                                                function tabDelimitedToJson(rawTxt) {
                                                                
                                                                    const lines = rawTxt.split("\n");
                                                                    const events = [];
                                                                    const headers = lines[0].replace(/\r/g, "").split("\t");
                                                                
                                                                    for (let i = 1; i < lines.length; i++) {
                                                                
                                                                        let obj = {};
                                                                        let currentline = lines[i].replace(/\r/g, "").split("\t");
                                                                
                                                                        for (let j = 0; j < headers.length; j++) {
                                                                            obj[headers[j]] = currentline[j];
                                                                        }
                                                                
                                                                        events.push(obj);
                                                                
                                                                    }
                                                                    return {headers, events};
                                                                }
                                                                
                                                                
                                                                function main() {
                                                                    const csvFilePath = sf.interaction.selectFile({
                                                                        allowedFileTypes: ["txt"],
                                                                        prompt: 'please select a Tab-Delimited text file',
                                                                        defaultLocation: "~/Desktop/"
                                                                
                                                                    }).path
                                                                
                                                                    const rawText = sf.file.readText({ path: csvFilePath }).text
                                                                    const {headers, events} = tabDelimitedToJson(rawText)
                                                                
                                                                    //sf.ui.proTools.appActivateMainWindow();
                                                                
                                                                    const characterNames = [];
                                                                    events.forEach(event=> {
                                                                        if (!characterNames.includes(event[headers[1]])) {
                                                                            characterNames.push(event[headers[1]])
                                                                        }
                                                                    })
                                                                
                                                                    const characterChoice = sf.interaction.popupSearch({
                                                                        items: characterNames.map(c => ({name: c})),
                                                                        title: `Choose a Character`,
                                                                        onCancel: "Abort",
                                                                    }).item.name
                                                                
                                                                    const filteredEvents = events.filter(e => e[headers[1]] === characterChoice)
                                                                
                                                                    filteredEvents.forEach(event => {
                                                                        const markerName = event[headers[1]] + " " + event[headers[0]];
                                                                        const timecode = event[headers[2]];
                                                                        const scriptLine = event[headers[3]];
                                                                
                                                                        sf.app.proTools.createMemoryLocation({
                                                                            reference: "Absolute",
                                                                            timeProperties: "Marker",
                                                                            startTime: timecode,
                                                                            name: markerName,
                                                                            comments: scriptLine,
                                                                            colorIndex: 7
                                                                        })
                                                                    })
                                                                
                                                                    log('done')
                                                                
                                                                }
                                                                
                                                                main();
                                                                
                                                                1. DDane Butler @Dane_Butler
                                                                    2024-03-01 04:12:48.277Z

                                                                    A very sincere thank you Dustin, I can't tell you how happy it makes me to use this script. You just saved my bacon in a big way. I hope you have a wonderful Friday mate! Thank you so much!!!!!

                                                                    1. Dustin Harris @Dustin_Harris
                                                                        2024-03-01 04:16:23.654Z

                                                                        With a name Dane and the fact that to you it’s Friday, I reckon you’re an Aussie? :) it’s these kinds of scripts that make SoundFlow a can’t-live-without tool. Glad it’s working for you 🤟

                                                                        1. In reply toDane_Butler:
                                                                          Dustin Harris @Dustin_Harris
                                                                            2024-03-02 18:09:54.134Z2024-03-04 04:29:52.828Z

                                                                            hey @Dane_Butler Since this is a mission critical script, I added some code to double check that the TCs don't have formatting typos and inadvertently get skipped:
                                                                            Edit: Added paste from clipboard

                                                                            function proToolsPasteFromClipBoard() {
                                                                                if (sf.ui.proTools.getMenuItem('Edit', 'Paste').isEnabled) {
                                                                                    sf.ui.proTools.menuClick({
                                                                                        menuPath: ["Edit", "Paste"]
                                                                                    });
                                                                                };
                                                                            }
                                                                            
                                                                            /**@param {string} rawTxt */
                                                                            function tabDelimitedToJson(rawTxt) {
                                                                            
                                                                                const lines = rawTxt.split("\n");
                                                                                const events = [];
                                                                                const headers = lines[0].replace(/\r/g, "").split("\t").map(h => h.trim());
                                                                            
                                                                                for (let i = 1; i < lines.length; i++) {
                                                                            
                                                                                    let obj = {};
                                                                                    let currentline = lines[i].replace(/\r/g, "").split("\t").map(x => x.trim());
                                                                            
                                                                                    for (let j = 0; j < headers.length; j++) {
                                                                                        obj[headers[j]] = currentline[j];
                                                                                    }
                                                                                    events.push(obj);
                                                                                }
                                                                                return { headers, events };
                                                                            }
                                                                            
                                                                            
                                                                            function main() {
                                                                            
                                                                                if (!sf.ui.proTools.exists) throw 'Pro Tools Not Open';
                                                                                if (!sf.app.proTools.hasOpenSession) throw 'No Pro Tools Session Open'
                                                                                if (sf.ui.proTools.selectedTrackNames.length == 0) throw 'No Track Selected'
                                                                                if (sf.ui.proTools.selectedTrackNames.length > 0
                                                                                    && !sf.ui.proTools.getMenuItem('Edit', 'Paste').isEnabled) {
                                                                                    throw 'No track selected or nothing in clipboard'
                                                                                }
                                                                            
                                                                                const tabDelimFilePath = sf.interaction.selectFile({
                                                                                    allowedFileTypes: ["txt"],
                                                                                    prompt: 'please select a Tab-Delimited text file',
                                                                                    defaultLocation: "~/Desktop/",
                                                                                    onCancel: "Abort"
                                                                                }).path
                                                                            
                                                                                const rawText = sf.file.readText({ path: tabDelimFilePath }).text
                                                                                const { headers, events } = tabDelimitedToJson(rawText)
                                                                            
                                                                                sf.ui.proTools.appActivateMainWindow();
                                                                            
                                                                                const characterNames = [... new Set(events.map(e => e[headers[1]]))];
                                                                            
                                                                                const characterChoice = sf.interaction.popupSearch({
                                                                                    items: characterNames.map(c => ({ name: c })),
                                                                                    title: `Choose a Character`,
                                                                                    onCancel: "Abort",
                                                                                }).item.name
                                                                            
                                                                                const filteredEvents = events.filter(e => e[headers[1]] === characterChoice);
                                                                            
                                                                                const eventsToCheck = [];
                                                                                const TIMECODE_REGEX = /(\d{2})[:](\d{2})[:](\d{2})[:;](\d{2})/;
                                                                            
                                                                            
                                                                                filteredEvents.forEach(event => {
                                                                                    const markerName = event[headers[1]] + " " + event[headers[0]];
                                                                                    const timecode = event[headers[2]];
                                                                                    const scriptLine = event[headers[3]];
                                                                            
                                                                                    if (!TIMECODE_REGEX.test(event[headers[2]])) {
                                                                            
                                                                                        const eventToCheck = `Number: ${event[headers[0]]}, Character: ${event[headers[1]]}, TC: ${event[headers[2]]}`;
                                                                                        eventsToCheck.push(eventToCheck);
                                                                                        return
                                                                                    }
                                                                            
                                                                                    sf.app.proTools.createMemoryLocation({
                                                                                        reference: "Absolute",
                                                                                        timeProperties: "Marker",
                                                                                        startTime: timecode,
                                                                                        name: markerName,
                                                                                        comments: scriptLine,
                                                                                        colorIndex: 7
                                                                                    });
                                                                            
                                                                                    sf.wait({ intervalMs: 50 })
                                                                            
                                                                                    sf.app.proTools.setTimelineSelection({
                                                                                        inTime: timecode,
                                                                                        outTime: timecode,
                                                                                    });
                                                                            
                                                                                    proToolsPasteFromClipBoard();
                                                                                })
                                                                            
                                                                                if (eventsToCheck.length > 0) {
                                                                                    eventsToCheck.unshift('These Events Needs Checking:\n');
                                                                                    alert(eventsToCheck.join("\n"));
                                                                                }
                                                                            
                                                                                log('done');
                                                                            
                                                                            }
                                                                            
                                                                            main();
                                                                            
                                                                            
                                                                            1. DDane Butler @Dane_Butler
                                                                                2024-03-04 02:31:43.845Z

                                                                                Thank you so much for this king! Testing now, I'll let you know if I run into any issues! Additionally, might I request a far more basic companion script for this? I just need something that we CMD V paste what every have copied at every marker in the list. So If choose some beeps, cmd C, and then run the script it will paste all of the beeps to the markers generated by the former script! I think having this script be its own thing is a good call. If you can't get to it no biggie, I can just manually paste the beeps, but sometimes I miss one so a script might help me with quality control!

                                                                                1. Dustin Harris @Dustin_Harris
                                                                                    2024-03-04 04:26:15.842Z

                                                                                    Edited the post above to add 'paste from clipboard' :)

                                                                                    1. DDane Butler @Dane_Butler
                                                                                        2024-03-05 02:14:35.566Z

                                                                                        Thank you so much!!!!

                                                            2. R
                                                              In reply toDane_Butler:
                                                              Randall Smith @Randall_Smith
                                                                2024-02-26 21:34:21.258Z

                                                                Here is one that works with excel. You will have to modify either, but they might be starting points.

                                                                
                                                                function getExcelTimeValue() {
                                                                    sf.ui.app('com.microsoft.Excel').appActivateMainWindow();
                                                                
                                                                    sf.wait({
                                                                        intervalMs: 250,
                                                                    });
                                                                
                                                                
                                                                    sf.ui.app('com.microsoft.Excel').menuClick({
                                                                        menuPath: ["Edit", "Copy"],
                                                                    });
                                                                
                                                                    issueName = sf.clipboard.getText().text.split("\r")
                                                                
                                                                    sf.keyboard.press({
                                                                        keys: "left",
                                                                    });
                                                                
                                                                    sf.ui.app('com.microsoft.Excel').menuClick({
                                                                        menuPath: ["Edit", "Copy"],
                                                                    });
                                                                
                                                                
                                                                    issueTimeCode = sf.clipboard.getText().text.split("\r")
                                                                
                                                                    sf.keyboard.press({
                                                                        keys: "down, right",
                                                                    });
                                                                
                                                                
                                                                    log(issueName)
                                                                
                                                                    goToLocation(issueTimeCode)
                                                                
                                                                }
                                                                
                                                                function goToLocation(timeCodeLocation) {
                                                                    timeCodeLocation = ("" + timeCodeLocation);
                                                                    //alert(timeCodeLocation);
                                                                    sf.ui.proTools.appActivateMainWindow();
                                                                    sf.ui.proTools.mainWindow.groups.whoseTitle.is("Counter Display Cluster").first.textFields.whoseTitle.is("Main Counter").first.elementSetTextAreaValue({
                                                                        value: timeCodeLocation,
                                                                    });
                                                                    sf.keyboard.press({
                                                                        keys: "numpad multiply, numpad clear",
                                                                    });
                                                                    sf.keyboard.type({ text: timeCodeLocation });
                                                                
                                                                    sf.keyboard.press({
                                                                        keys: "numpad enter",
                                                                    });
                                                                
                                                                }
                                                                
                                                                getExcelTimeValue();
                                                                //sf.ui.proTools.transportBackAndPlayKeepSelection();
                                                                
                                                                1. DDane Butler @Dane_Butler
                                                                    2024-02-27 05:21:48.968Z

                                                                    Thanks for sending me something to work with. I'm not sure under what conditions this script is supposed to work with excel, but unfortunately it doesn't seem to interact with the time code written as a number value like so 01:01:19:21.

                                                                    I may need way more than a starting place :D

                                                                  • In reply toDane_Butler:
                                                                    Kitch Membery @Kitch2024-02-28 00:04:48.739Z

                                                                    Hi Dane,

                                                                    I'm happy to see that the community chimed in here and you've been able to get some help. It's great to see your excitement about getting SoundFlow working well for you.

                                                                    Just a quick note related to tagging the SoundFlow team members.
                                                                    We generally prefer questions in the forum to be phrased and addressed to the larger community first, and SF team member tags to be used only once it becomes known that the person in question is the only one who might be able to help.
                                                                    This allows our team to spend their time more efficiently, thus allowing us to help more users have a good experience with SoundFlow. Hope you understand - we just want as many people to be able to get good quality help as possible :-)

                                                                    If you're ever looking for more 1:1 private help, please always feel free to reach out on support@soundflow.org to enquire about our Script Services program.