Previous 1 2 3 4 Next 128 Replies Latest reply: Apr 9, 2015 7:14 PM by bighop Go to original post Branched to a new discussion.
  • Niccum Level 1 Level 1 (0 points)


    I am doing a similar routine as I also like to manually tweak the commercial markers.  I have found on my system that I can skip the compress step and go straight to the export.  The export to AppleTV recognizes the commercial markers on my setup.  So at least you can eliminate the compress step if you want to.


    For those of us that are anal about fine tuning the markers, it would be nice to be able to be able to check them off as prepared for export and then have an export routine kick in automatically in the middle of the night to do the longer export work.  That is about as automated as I can think of if still needing to fine tune the commercial marks.



  • essandess Level 1 Level 1 (15 points)

    Yes, every step works great in Mountain Lion and previous OS X versions. A Turbo.264 HD dongle isn't necessary (I believe), as I wrote the RecordingDone script above so that commercial marking waits for this process to complete:


    set ProcessName to "Elgato H.264 Decoder"


    I believe that EyeTV runs this whether a Turbo.264 dongle is attached or not. Someone without a dongle—please confirm that this is correct. If that process isn't there, or isn't running at more than 2 percent CPU load, then commercial marking kicks in.


    That said, I sure need a dongle for a mid-2010 Mac Mini, and though the latest Mac Mini probably wont see major speed improvements from a dongle, it's nice to offload transcoding from the CPU and save it for other major tasks, like running a server and recording new shows. Elgato may be exploiting the GPU on newer models, but I've not heard this.


    An EyeTV HD box can be set up to save all recorded shows for streaming to iPad (720p). If you do this, then export "iPad" to iTunes (automatically or manually), then EyeTV will just copy this file without an additional transcoding step. Commercial marks in .edl files are cut out at the transcoding step. I've found that it's better to add chapter headings to h.264 files rather than delete content automatically. The steps look like:


    Record -> Transcode -> Mark Commercials [ -> Export to iTunes]


    Finally, I'd like to remind people to ask Elgato to add chapter navigation to their iOS streaming app, which will remove the extra iTunes step in many cases. Submit a ticket and make the feature request:

  • gardnern Level 1 Level 1 (0 points)

    I have an HDHomeRun which outputs MPEG-2.  The process that runs on my machine from EyeTV is "Elgato MPEG-2 Decoder" so I had to tweak the script to check that process for pausing to wait for the export to finish.


    Below is the with Growl fixed up.  I know it works with Growl 2.0 but I think it should work back to 1.3.  The script has to be called with --growl on the command line.


    I also added a --m4vonly that will just chapter the m4v files on stuff that has been previously marked.


    This is the first time I've coded in python, it all works but it might be a tad crude.

    #!/usr/bin/env python
    #, EyeTV3 version
    # Copyright (c) 2008, Jon A. Christopher
    # Copyright (c) 2008, TeamSTARS Dick Gordon and Rick Kier
    # Licensed under the GNU General Public License, version 2
    # Run comskip on the specified file and replace the markers in the .eyetvr
    # file with the new markers.
    # if no arguments show id, recording name...
    # If argument is 'all', process all recordings which don't
    # have markers and do not match any exclude information from the cfg file.
    # If argument is'forceall', process all recordings except those that match
    # any exclude information in the cfg file.
    # Otherwise, argument is treated as an EyeTV recording id, and that
    # recording is processed if it is not excluded and it does NOT have any markers.
    # To be done:
    #  exporting - not needed??
    #  exclude channels
    #   Issue: some channels are 0
    #  exclude titles
    #  exclude station names
    #   Issue: some station names are blank
    #  Catch ^c when in c program - comskip throws SIGINT's away?? (see mpeg2dec.c)
    #  Catch ^c when in python program - coded
    #  Handle multiple video pids.
    # Added argument for PID - Ben Blake September 2009
    # STS
    # Added macports wine, nice-ness, mp4chaps chapters headings at commercials
    import sys, os, string, os.path
    import time
    import math
    import traceback
    from optparse import OptionParser
    from ConfigParser import SafeConfigParser
    from os import listdir
    from os.path import isfile, join
    # Exit Codes
    #  Everything worked ok
    successExitCode = 0
    #  Unable to import appscript
    importExitCode = 1
    #  Unable to find the recoring specified
    noRecordingExitCode = 2
    #  Error getting recordings from EyeTV
    getRecordingsErrorExitCode = 3
    #  Unknown exit code from comskip
    unknownComskipErrorExitCode = 4
    #  Error when accessing plist file
    accessPlistFileErrorExitCode = 5
    #  Error when accessing config file
    accessConfigFileErrorExitCode = 6
    #  Keyboard interrupt (^c)
    keyboardInterruptExitCode = 7
    #  Unable to communicate with the application
    communicationsErrorExitCode = 8
    # provided with appscript package
        import aem
        from appscript import *
    except ImportError, e:
        sys.stderr.write('Error: importing appscript\n%s\n' % e)
    version = '0.4.0'
    # Cfg file definitions and variables
    userSectionName = 'User Section'
    listDelimiterName = 'LIST_DELIMITER'
    excludedTitlesName = 'EXCLUDED_TITLES'
    excludedChannelsName = 'EXCLUDED_CHANNELS'
    excludedStationNamesName = 'EXCLUDED_STATION_NAMES'
    excludedTitles = []
    excludedChannels = []
    excludedStationNames = []
    # General variables
    options = None
    args = None
    recordingCount = 0
    comskipLogPathName = None
    log = None
    growl = None
    eyeTV = None
    pathToComskip = None
    nameOfComskip = 'comskip'
    comskipLocations = ['.', r'/Library/Application Support/ETVComskip']
    # for debugging. when False, will not actually run comskip, but will do everything else
    RUN_COMSKIP = True
    # Get the executable directory
    ETVComskipDir = os.path.abspath(os.path.dirname(__file__))
    # Growl support
    commercialStart = 'Start'
    commercialStartDescription = 'Start detecting commercials'
    commercialStop = 'Stop'
    commercialStopDescription = 'Stop detecting commercials'
    programName = 'Mark Commercials'
    allNotificationsList = [commercialStart, commercialStop]
    def InitGrowl():
        """docstring for InitGrowl"""
        global growl
        global allNotificationsList
        # Should we use growl?
        if not options.growl:
            # No
        growl = app('Growl')
        enabledNotificationsList = allNotificationsList
        WriteToLog('Registering with growl\n')
        except Exception, e:
            WriteToLog('Error: registering with growl\n  %s\n' % e)
            growl = None
    def sendGrowlNotification(name, title, description):
        """docstring for sendGrowlNotification"""
        # Send a Notification...
        if growl is not None:
            WriteToLog('Sending notification via growl (%s, %s, %s)\n' % (programName, title,description))
            except Exception, e:
                WriteToLog('Error: growl notify\n  %s\n' % e)
    # Create the log file
    def GetLog(name=None):
        """docstring for GetLog"""
        global log
        global comskipLogPathName
        # Should we log?
        if not options.log:
        # Is the log directory created?
        fullPath = os.path.expanduser('~/Library/Logs/ETVComskip')
        if not os.path.isdir(fullPath):
            # No, create it.
        # Create the log
        if name is None:
            name = time.strftime('%m-%d-%Y %H-%M-%S')
        comskipLogPathName = os.path.join(fullPath, name + '_comskip.log')
        log = open(os.path.join(fullPath, name + '.log'), 'w')
    def WriteToLog(message):
        """docstring for WriteToLog"""
        if options.log:
            if type(message) == type(u""):
            log.write('%s - %s' % (time.asctime(), message))
    def CheckForApplicationCommunications(retries=3):
        """docstring for CheckForApplicationCommunications"""
        global EyeTV
        # launch the application
        WriteToLog('Checking communications to %s with %d retries\n' % (, retries))
        for attempt in range(retries):
                # Get the recordings
                # It worked - break out of here
                WriteToLog('  Attempt %d worked\n' % (attempt + 1))
            except Exception, e:
                WriteToLog('  Attempt %d failed\n    %s\n' % ((attempt + 1), e))
                EyeTV = app(
            msg = 'Error: unable to communicate with %s\n' %
    def GetRecordings(retries=0):
        """docstring for GetRecordings"""
        global EyeTV
        WriteToLog('Getting recordings\n')
            recordings = EyeTV.recordings.get()
        except Exception, e:
            msg = 'Error: unable to get recordings\n%s\n' % e
        WriteToLog('Recordings: %s\n' % recordings)
        return recordings
    # Possibly run comskip and return the name of a plist file with commercial markers in it
    def GetPlistFile(etvr_file, run_comskip=True):
        FileDir = os.path.dirname(etvr_file)
        dir, fil = os.path.split(etvr_file)
        FileRoot, ext = os.path.splitext(fil)
        MpgFile = FileRoot + ".mpg"
        PlistFile = FileRoot + ".edl"
        #cmd = '"/Library/Application Support/ETVComskip/" "/Library/Application Support/ETVComskip/comskip/comskip.exe" --ini="/Library/Application Support/ETVComskip/comskip/comskip.ini" "%s"' % MpgFile
        # MacPorts 64-bit wine
        #cmd = '"/Applications/" "/Library/Application Support/ETVComskip/comskip/comskip.exe" --ini="/Library/Application Support/ETVComskip/comskip/comskip.ini" "%s"' % MpgFile
        cmd = '"/opt/local/bin/wine" "/Library/Application Support/ETVComskip/comskip/comskip.exe" --ini="/Library/Application Support/ETVComskip/comskip/comskip.ini" "%s"' % MpgFile
        if <> "":
             cmd += " --pid=" +
        outputName = '/dev/null'
        if options.log:
            cmd += ' > %s 2>&1' % comskipLogPathName
            cmd += ' > %s 2>&1' % '/dev/null'
        if options.verbose:
            cmd += ' --verbose=%d' % options.verbose
        # nice the wine command
        cmd = "/usr/bin/nice -n 14 " + cmd
        WriteToLog('Changing directory to %s\n' % FileDir)
        if run_comskip:
            #get the show name from the Directory Path
            # Notify the user
            sendGrowlNotification(commercialStart, commercialStart + " " + showName, commercialStartDescription + " on " + showName)
            # TBD stop comskip when ^c happens
            if not options.m4vonly:
                WriteToLog('Running: %s\n' % cmd)
                rc = os.system(cmd)
                WriteToLog('Skipped Comm Search, attempting to just add m4v Markers.')
            # Add the Comskip information as chapters to all m4v files
            # Notify the user
            sendGrowlNotification(commercialStop, commercialStop + " " + showName, commercialStopDescription + " on " + showName)
            WriteToLog('Return code is: %d, 0x%x\n' % (rc, rc))
            errorCode = (rc >> 8) & 0xff
            WriteToLog('Error code is: %d, 0x%x\n' % (errorCode, errorCode))
            # Error code:
            #   3 = no Video pid found
            #   2 = Can't open the mpeg2 file TBD
            #   1 = Commercials found
            #   0 = Commercials not found
            if errorCode  == 2:
                msg = 'Unable to open mpeg2 file return from "comskip": %d\n' % errorCode
                return None
            elif errorCode == 3:
                msg = 'No video pid found return from "comskip": %d\n' % errorCode
                return None
            elif errorCode == 1:
                WriteToLog('Commercials found by comskip\n')
                return PlistFile
            elif errorCode == 0:
                WriteToLog('No commercials found by comskip\n')
                return None
                msg = 'Error: unknown error code from comskip: %d, assuming it worked.\n' % errorCode
                return PlistFile
    # return start and ending times for the given line
    def TimeChop(line):
        fields = line.split("\t")
        start = (float(fields[0]))
        end = (float(fields[1]))
        return (start,end)
    # given a plist file, return a markers array suitable for adding to a recording
    def GetMarkersArray(PlistFile):
            file = open(PlistFile)
            lines = file.readlines()
        except Exception, e:
            msg = 'Error: accessing %s\n%s\n' % (PlistFile, e)
        WriteToLog('Plist file contents: %s\n' % lines)
        for line in lines:
            WriteToLog("Processing plist file line: '%s'\n" % line)
            start,end = TimeChop(line)
            WriteToLog('Adding marker, start: %d, end: %d\n' % (start, end))
            marker = {}
            marker['position'] = start
            marker[aem.AEType('leng')] = end - start
        return markers
    def ProcessRecording(recording, run_comskip):
        global recordingCount
        channel = recording.channel_number()
        title = recording.title()
        stationName = recording.station_name()
        recordingCount += 1
        msg = '%2d. Processing "%s" on [%s] channel [%s]...' % (recordingCount, title, stationName, channel)
        WriteToLog('%s\n' % msg)
        print msg.encode("utf-8")
        # Should excludes be allowed?
        if not options.noexclude:
            # Yes, Is this one allowed?
            #  User can exclude titles, channels and station names
            # Channel
            msg='  Channel: %s' % channel
            print msg.encode("utf-8"),
            if str(channel) in excludedChannels:
                WriteToLog('Skipped due to channel match\n')
                print ' skipped'
            print ', not skipped'
            # Title
            msg='  Title: %s' % title
            print msg.encode("utf-8"),
            if title in excludedTitles:
                WriteToLog('Skipped due to title match\n')
                print ' skipped'
            print ', not skipped'
            # Station name
            msg='  Station name: %s' % stationName
            print msg.encode("utf-8"),
            if stationName in excludedStationNames:
                WriteToLog('Skipped due to station name match\n')
                print ' skipped'
            print ', not skipped'
        # Get its path
        etvr_path = recording.location.get().path
        WriteToLog('Path to recording is %s\n' % etvr_path)
        # get the plist file for this recording, and make a markers array for it
        Plist = GetPlistFile(etvr_path, run_comskip)
        # Did we get a plist file?
        if Plist is not None:
            # Yes, convert it.
            markers = GetMarkersArray(Plist)
            # and finally, set them
            WriteToLog('Setting markers on recording\n')
            markers_string = str(markers)
            WriteToLog('Adding marker: %s\n' % (markers_string))
    mp4chaps = '/opt/local/bin/mp4chaps'
    def sec2hhmmss(secs):
        """Convert seconds to string HH:MM:SS.SSS format."""
        rem = secs/3600
        hh = int(rem)
        rem = (rem-hh)*60
        mm = int(rem)
        rem = (rem-mm)*60
        ss = int(rem)
        rem = (rem-ss)
        rem = '%.3f' % rem  # millisecond precision
        rem = rem.replace('0.','.')
        return '%02d:%02d:%02d%s' % (hh,mm,ss,rem)
    def split_whitespace_nolibs(str):
        """Split a string by whitespace without using re or shlex libraries.."""
        strs = filter(None,str.split('\t'))
        strs = map(lambda s: s.split(' '),strs)
        strs = filter(None,[item for sublist in strs for item in sublist])
        return strs
    def edl2mp4chaps(edl_file,file):
        """Convert an edl file into an mp4chaps file."""
        txt_file = file.replace('.m4v','.chapters.txt')
        ftxt = open(txt_file,'w')
        comskipno = 0
        comskipchapno = 0
        lines = [line.strip() for line in open(edl_file)]
        for line in lines:
            times = map(float,split_whitespace_nolibs(line))
            if (comskipno == 0 and times[0] != 0.0):
                comskipno += 1
            if (len(times) < 2 or times[2] == 0.0):
                if (times[0] != 0.0):
                    ftxt.write('%s Chapter %d End\n' % (sec2hhmmss(times[0]),comskipno))
                    ftxt.write('00:00:00.000 Beginning\n')
                comskipno += 1
                ftxt.write('%s Chapter %d Start\n' % (sec2hhmmss(times[1]),comskipno))
                # never seen this case, but here for logical consistency
                comskipchapno += 1
                if (times[0] != 0.0):
                    ftxt.write('%s Chapter %d Start\n' % (sec2hhmmss(times[0]),comskipchapno))
                    ftxt.write('00:00:00.000 Beginning\n')
                ftxt.write('%s Chapter %d End\n' % (sec2hhmmss(times[1]),comskipchapno))
    def mp4chaps_all_m4v(dir):
        """Apply an edl file's entries to all m4v files in a directory."""
        edl_file = ""
        files = [ file for file in listdir(dir) if isfile(join(dir,file)) ]
        for file in files:
            if file.find('.edl') != -1:
                edl_file = file
        if edl_file != "" and os.path.isfile(mp4chaps):
            for file in files:
                if file.find('.m4v') != -1:
                    # remove all chapters
                    cmd = mp4chaps + ' -r ' + file + ' > /dev/null 2>&1'
                    rc = os.system(cmd)
                    # create chapter file
                    # import chapters
                    cmd = mp4chaps + ' -i ' + file + ' > /dev/null 2>&1'
                    rc = os.system(cmd)
    def main():
        global options
        global args
        global excludedTitles
        global excludedChannels
        global excludedStationNames
        global log
        global EyeTV
        # Do the options
        usage = "usage: %prog [options] [RECORDING-ID | 'all' | 'forceall']"
        parser = OptionParser(usage=usage, version=version)
                          action="store_true", dest="noexclude", default=False,
                          help="Do NOT exclude recordings specified in cfg file, default=%default")
                          action="store_true", dest="force", default=False,
                          help="Force commercial marking on specified RECORDING-ID. Allows marking when markers already exist, default=%default")
                          action="store_true", dest="growl", default=False,
                          help="Enable growl notification, default=%default")
                          action="store_true", dest="log", default=False,
                          help="Enable logging, default=%default")
                          dest="app", default='EyeTV',
                          help="Specify EyeTV application name, default=%default")
                          dest="verbose", default=0,
                          help="Verbosity level, 0-10, default=%default")
                          dest="pid", default='',
                          help="Specify the Video PID, default=%default")
                          dest="m4vonly", default=False,
                          help="Only chapter m4v files that have already had commercials marked, don't try to mark commercials. default=%default")
        (options, args) = parser.parse_args()
        if len(args):
            name = args[0]
            name = None
        WriteToLog('%s, %s starting\n' % (sys.argv[0], version))
        WriteToLog('Command line: %s\n' % sys.argv)
        WriteToLog('Application name: %s\n' %
        print '\t\t%s\t%s\n' % (os.path.splitext(os.path.basename(sys.argv[0]))[0], version)
        # Get the app
        EyeTV = app(
        # Use growl notifications
        # Get our configuration file & data
        configInput = SafeConfigParser()
            cfgFilesRead =[os.path.join(os.path.dirname(sys.argv[0]), 'MarkCommercials.cfg'),
                                             os.path.expanduser('~/Library/Application Support/ETVComskip/MarkCommercials.cfg')])
        except Exception, e:
            msg = 'Error: reading configuration file\n%s\n' % e
        WriteToLog('Cfg files read: %s\n' % cfgFilesRead)
        if cfgFilesRead:
            # Process the user's section
            if configInput.has_section(userSectionName):
                # Process the user's section
                #   Get the delimiter
                listDelimiter = configInput.get(userSectionName, listDelimiterName)
                if configInput.has_option(userSectionName, excludedChannelsName):
                    for channel in configInput.get(userSectionName, excludedChannelsName).split(listDelimiter):
                if configInput.has_option(userSectionName, excludedTitlesName):
                    for title in configInput.get(userSectionName, excludedTitlesName).split(listDelimiter):
                if configInput.has_option(userSectionName, excludedStationNamesName):
                    for title in configInput.get(userSectionName, excludedStationNamesName).split(listDelimiter):
            WriteToLog('List Delimiter: %s\n' % listDelimiter)
            WriteToLog('Excluded Channels: %s\n' % excludedChannels)
            WriteToLog('Excluded Titles: %s\n' % excludedTitles)
            WriteToLog('Excluded Station names: %s\n' % excludedStationNames)
        # Test communications with application
        # Get the location of the commercial skipper
        # Show the IDs and program names when there are no arguments
        #    replace any non ascii characters with ?
        if len(args) == 0:
            for rec in GetRecordings():
                programName = os.path.split(os.path.splitext(os.path.dirname(rec.location.get().path))[0])[1]
                outputName = ''
                for char in programName:
                    # Insure the character is ascii
                    if ord(char) <= 127:
                        outputName += char
                        outputName += '?'
                msg = '  %d = [%s], [%s], [%s]' % (rec.unique_ID.get(), outputName, rec.channel_number(), rec.station_name())
                WriteToLog('%s\n' % msg)
                print msg.encode("utf-8")
            return successExitCode
        if args[0] == "all" or args[0] == "forceall":
            # batch mode, process all recordings without markers
            recs = GetRecordings()
            for rec in recs:
                markerCount = len(rec.markers.get())
                WriteToLog('Marker count: %d\n' % markerCount)
                if markerCount == 0 or args[0] == "forceall":
                    ProcessRecording(rec, RUN_COMSKIP)
            # triggered mode, process just the listed recording
            recs = GetRecordings()
            recordingRequested = int(args[0])
            for rec in recs:
                if rec.unique_ID() == recordingRequested:
                    WriteToLog('Found recording %d\n' % recordingRequested)
                msg = 'Error: unable to find recording %d\n' % recordingRequested
            markerCount = len(rec.markers.get())
            WriteToLog('Marker count: %d\n' % markerCount)
            # Recording already have markers?
            if markerCount == 0 or options.m4vonly:
                # No
                    ProcessRecording(rec, RUN_COMSKIP)
                except Exception,e:
                    exc_type, exc_value, exc_traceback = sys.exc_info()
                    WriteToLog(repr(traceback.format_exception(exc_type, exc_value,exc_traceback)))
            # Is the recording already marked but the user wants it done again?
            elif markerCount != 0 and options.force:
                # Yes
                WriteToLog('Recording already marked - use forcing with --force option\n')
                ProcessRecording(rec, RUN_COMSKIP)
                # Recording already maked and user doesn't want it done again
                msg = 'Recording previously marked'
                WriteToLog('%s\n' % msg)
                print '  ',
                print msg.encode("utf-8")
        return successExitCode
    if __name__ == '__main__':
            Call main
            exitStatus = main()
        except KeyboardInterrupt, e:
            msg = 'Error: %s\n' % e
  • sheldoa Level 1 Level 1 (0 points)

    I have been super excited to attempt to get this working on my install of EyeTV.  The issue that I'm running into is that when I attempt to either run "MarkCommercials all" from the command line the result I get is this:

    1. Processing "Castle" on [KSTP HDTV] channel [431]...

      Channel: 431 , not skipped

      Title: Castle , not skipped

      Station name: KSTP HDTV , not skipped

    Error: accessing 00000000162cf6e4.edl

    (2, 'No such file or directory')


    However when I run comskip directly from the command line using the following command (running from inside the recording's package):

    /opt/local/bin/wine /Library/Application\ Support/ETVComskip/comskip/comskip.exe --ini=/Library/Application\ Support/ETVComskip/comskip/comskip.ini 00000000162cda88.mpg

    There is a text file that is output named "00000000162cda88.txt".  I've also been having issues with the program crashing but I'm betting that was one of my recordings being funky.  Any thoughts on how to go about getting this to work?  I'm getting the same result off of the RecordingDone.scpt as well.  Any help would be appreciated.  Thanks!

  • essandess Level 1 Level 1 (15 points)



    Looks like a mismatch between the .mpg file name and the .edl file name, whose root name should be identical. Troubleshooting:


    1. Cut-and-paste--posting-error on your part, or is this real?
    2. Is this a problem for all recordings or a single recording? To test (using @gardnern's above):
      • $ /Library/Application\ Support/ETVComskip/  # prints out all your EyeTV recordID's
      • $ /Library/Application\ Support/ETVComskip/ <recordID>  # rerun comskip
      • $ /Library/Application\ Support/ETVComskip/ --m4vonly=true <recordID>
      • $ /Library/Application\ Support/ETVComskip/ all  # mark 'em all!
    3. Use the command
      • $ find /The/Full/POSIX/Path/to/Your/EyeTV\ Archive -name '00000000162cda88.mpg'
    4. Then look at this .eyetv's directories contents. It should look like this:


    $ ls -1















    @gardnern Thanks for the updated script! Works great on my system, and it's been nice to have the m4vonly option to test to improved functionality I've added.


    @all interested: I've written script that allows automatic, asynchronous iTunes exports and commercial marking. I'm almost done testing them and will post soon to ETVComskip's googlecode page. These scripts transparently and automatically address all the issues discussed above: you can do exports or commercial marking in any order you want, or simultaneously, and both scripts take care of adding commercial skipping information to the iTunes files independently. There is an EyeTV ExportDone.scpt that saves all exported files' inode numbers to a new file called root-name.exported_inodes.txt (see above), then imports the .edl markings as h.264 chapters if there's a corresponding .edl file. And there's an updated script that also imports  .edl markings as h.264 chapters into all existing exports if there's a corresponding .exported_inodes.txt file. iTunes can and will rename and reorganize the exported files, and the python script finds whatever iTunes filenames that have the inode number saved by ExportDone.scpt. It all works asynchronously, and though I've only tested it on my iPad, I expect it to work with Apple TV and many individual iTunes and HTPC setups. So watch this space.

  • essandess Level 1 Level 1 (15 points)

    P.S. Did you update your script?


    $ cd /Library/Application\ Support/ETVComskip/

    $ sudo cp ./ ./

    $ sudo cp ~/Desktop/ ./


    (We probably need to post a recap of which files must be updated.)

  • gardnern Level 1 Level 1 (0 points)

    I'm not much for exporting to iTunes however pushing an appropriately named file to Plex would be pretty spiffy.


    Does your export process do any renameing and/or file copying to specific locations?

  • essandess Level 1 Level 1 (15 points)

    No. EyeTV takes care of all exporting. The (modified) ETVComskip setup simply does two things: (1) adds a .edl Edit Decision List file to the .eyetv directory for commercial skipping during EyeTV/Turbo.264/etc. playback; (2) adds the .edl cutpoints as chapters within the h.264 files created by EyeTV, whether in the EyeTV Archive or exported to iTunes.


    It would be quite simple to modify EyeTV's RecordingDone script to export files to whichever location you would like to see them in. It would also be simple to copy part of the to-be-posted ExportDone AppleScript  that remembers the inode information of each exported file, allowing to asynchronously add .edl information to these exported files.


    BTW, I tried Plex once but gave up on it because Plex kept using my 720p files (created for fast streaming) rather than the full 1080p HD recordings -- there didn't seem to be a way to tell it to use the best video available. Do you have Plex working well with EyeTV? How does this work? Is it really necessary to export files from EyeTV to Plex to avoid this problem?

  • gardnern Level 1 Level 1 (0 points)

    Actually after I asked I went on a bit of a research trip.  Right now I'm using a Scanner written to add links to the playable file in an EyeTV package into Plex.  This works very well and even grabs weird stuff I record that dosn't have Episode numbers on them, like PBS cooking episodes.


    Primarily I'm interested in the commercial skipping working within Plex itself.  Whil this doesn't appear to be an option playing the recorded .mpg file directly, perhaps it would work if I exported into the eyeTV recording package and then had Plex play the .m4v file instead.


    The question is if Plex will respect the chapter marks.  If it won't then I'd have to run Comskip first, then have EyeTV run an export which would leave out the flagged commercial sections.


    I have glanced over a lot of discussions on the Plex forums concerning its use of the improper (lower quality) stream.  I believe a lot of work has been done in this respect.  I know there is now a place to define your preferred stream quality at least for web based myPlex videos.  In their new web client you can also define the quality of stream you wish to receive.


    If you haven't looked into Plex within the last 6 months I would encourage you to give the latest release a try.  If I could get football to live broadcast through Plex with Pause/FF/Rewind working I probably wouldn't watch anything at all through EyeTV directly..

  • essandess Level 1 Level 1 (15 points)

    I've posted new and improved ETVComskip modifications at the ETVComskip google code page.


    These modifications allow the use of comskip for H.264 HD video and adds comskip information to EyeTV iTunes exports.


    Comskip .edl information is added as chapters within the H.264 .m4v files exported by EyeTV and Turbo.264, providing a mobile Comskip solution: when the ads appear, skip to the next chapter. It avoids the problem of false detections by not deleting any program material.


    The scripts above perform simple load balancing by making sure that transcoding and/or exports are done first, then Comskip is run, as well as setting a high UNIX nice value for the comskip process. A Turbo.264 HD dongle used to offload transcoding from the CPU may be used but is not necessary, as the RecordingDone script is written so that commercial marking waits for the process "Elgato H.264 Decoder" to complete before running Comskip. The steps look like:


    Record -> Transcode [ -> Export to iTunes] -> Mark Commercials


    Automatic, asynchronous, and transparent  commercial marking of iTunes exports is performed. There is an EyeTV ExportDone.scpt that saves all exported files' inode numbers to a new file called root-name.exported_inodes.txt, then imports the .edl markings as h.264 chapters if there's a corresponding .edl file. And there's an updated script that also imports  .edl markings as h.264 chapters into all existing exports if there's a corresponding .exported_inodes.txt file. iTunes can and will rename and reorganize the exported files, and the python script finds whatever iTunes filenames that have the inode number saved by ExportDone.scpt. It all works asynchronously, and though I've tested it using an iPad, I expect it to work with Apple TV and many individual iTunes and HTPC setups.


    Zip file contents:







    Help string in




    1. Go donate to Eric Kaashoek's website

       and download his HD-capable Comskip software.


    2. Install Xcode from the App Store and Macports from

       For Mountain Lion, install Xquartz.


    3. Install ETVComskip from


    4. Install Kaashoek's comskip files within the directory

          /Library/Application\ Support/ETVComskip/comskip

       Make sure that the ownership/group/permissions are set exactly the same

       as the original ./comskip directory and files.


    5. Download this zip file and check that your iTunes TV Shows directory

       matches this setting at the beginning of ./

          iTunes_TV_Shows = '~/Music/iTunes Media/TV Shows'

       Edit ./ to match this, then run these commands as a sudoer:


    # Necessary Macports

    sudo port selfupdate

    sudo port install wine-devel mp4v2


    # Move these five files into their correct locations

    sudo install -B .orig -b -m 0644 ./comskip.ini /Library/Application\ Support/ETVComskip/comskip/comskip.ini

    sudo install -B .orig -b -m 0644 ./ /Library/Application\ Support/ETVComskip/

    sudo install -B .orig -b -m 0644 ./RecordingStarted.scpt /Library/Application\ Support/EyeTV/Scripts/TriggeredScripts/RecordingStarted.scpt

    sudo install -B .orig -b -m 0644 ./RecordingDone.scpt /Library/Application\ Support/EyeTV/Scripts/TriggeredScripts/RecordingDone.scpt

    sudo install -B .orig -b -m 0644 ./ExportDone.scpt /Library/Application\ Support/EyeTV/Scripts/TriggeredScripts/ExportDone.scpt


    The AppleScript ExportDone.scpt is new; here is the new code to accomplish asynchronous comskip marking of EyeTV iTunes exports:


    -- EyeTV ExportDone script to save exported file inode numbers as the text file filename.exported_inodes.txt and import .edl files as mp4 chapters


    -- Copyright © 2012 Steven T. Smith <steve dot t dot smith at gmail dot com>, GPL


    --    This program is free software: you can redistribute it and/or modify

    --    it under the terms of the GNU General Public License as published by

    --    the Free Software Foundation, either version 3 of the License, or

    --    (at your option) any later version.


    --    This program is distributed in the hope that it will be useful,

    --    but WITHOUT ANY WARRANTY; without even the implied warranty of


    --    GNU General Public License for more details.


    --    You should have received a copy of the GNU General Public License

    --    along with this program.  If not, see <>.



    on ExportDone(recordingID)


        set myid to recordingID as integer

        set mp4chaps to "/opt/local/bin/mp4chaps"

        set mp4chaps_suffix to ".chapters.txt"

        set export_suffix to ".exported_inodes.txt"

        set edl_suffix to ".edl"

        set perl_suffix to ".pl"


        tell application "EyeTV"

            set myshortname to get the title of recording id myid

            set eyetvr_file to get the location of recording id myid as alias

        end tell


        tell application "Finder"

            -- Get EyeTV's root file names and paths for the recording

            set eyetv_path to container of eyetvr_file as string

            -- fix AppleScript's strange trailing colon issue for paths

            if character -1 of eyetv_path is not ":" then set eyetv_path to eyetv_path & ":"

            set eyetv_file to name of eyetvr_file

            set eyetv_root to (RootName(eyetv_file) of me)

            set edl_file to eyetv_path & eyetv_root & edl_suffix

            set edl_file_posix to POSIX path of edl_file

            set exported_inodes_file to eyetv_path & eyetv_root & export_suffix

        end tell


        -- give iTunes a chance to sync

        delay 30 --if the script does not seem to work, try increasing this delay slightly.


        tell application "iTunes"

            set mytv to get the location of (the tracks of playlist "TV Shows" whose name is myshortname or artist is myshortname)

        end tell


        -- return if no exports match; this shouldn't happen!

        if the (count of mytv) is less than 1 then return


        tell application "Finder"

            -- find the most recent export that isn't an open file

            set mymp4 to item 1 of mytv

            set mydate to modification date of mymp4

            repeat with kk from 2 to count of mytv

                if mydate is less than (modification date of item kk of mytv) and not my IsFileOpen(POSIX path of item kk of mytv) then

                    set mymp4 to item kk of mytv

                    set mydate to modification date of mymp4

                end if

            end repeat

            set mymp4_posix to POSIX path of mymp4

            -- safely quote any single quote characters for system calls: ' --> '"'"'

            set mymp4_posix_safequotes to my replace_chars(mymp4_posix, "'", "'\"'\"'")

            set itunes_path to container of mymp4 as string

            -- fix AppleScript's strange trailing colon issue for paths

            if character -1 of itunes_path is not ":" then set itunes_path to itunes_path & ":"

            set itunes_file to name of mymp4

            set itunes_root to (RootName(itunes_file) of me)


            -- save the iTunes file inode to the exported files file "*.exported_inodes.txt"

            -- find the exported file with the command: find . -type f -inum <inum>

            set ef_handle to open for access exported_inodes_file with write permission

            write (my FileInode(mymp4_posix) as string) & return to ef_handle starting at eof

            close access ef_handle


            -- return if no .edl file

            if not (exists file edl_file) then return


            -- add the mp4 chapters if the .edl file exists


            -- define the mp4chaps chapter file

            set itunes_chapter_file to (POSIX path of itunes_path) & itunes_root & mp4chaps_suffix


            -- translate the .edl file into a mp4chaps chapter file using perl


            set perlCode to "







    # Copyright © 2012 Steven T. Smith <steve dot t dot smith at gmail dot com>, GPL


    #    This program is free software: you can redistribute it and/or modify

    #    it under the terms of the GNU General Public License as published by

    #    the Free Software Foundation, either version 3 of the License, or

    #    (at your option) any later version.


    #    This program is distributed in the hope that it will be useful,

    #    but WITHOUT ANY WARRANTY; without even the implied warranty of


    #    GNU General Public License for more details.


    #    You should have received a copy of the GNU General Public License

    #    along with this program.  If not, see <>.


    use strict;


    my $edl_file = q{" & edl_file_posix & "};

    my $txt_file = q{" & itunes_chapter_file & "};


    sub sec2hhmmss {

    my $rem = $_[0]/3600;

    my $hh = int($rem);

    $rem = ($rem - $hh)*60;

    my $mm = int($rem);

    $rem = ($rem - $mm)*60;

        my $ss = int($rem);

        $rem = ($rem - $ss);

        $rem = sprintf(\"%.3f\",$rem);  # millisecond precision

        $rem =~ s/^0//;

        return sprintf(\"%02d:%02d:%02d%s\",$hh,$mm,$ss,$rem);



    open (EDL,$edl_file) || die(\"Cannot open edl file \" . $edl_file);

    open (TXT,\">\",$txt_file) || die(\"Cannot open txt file \" . $txt_file);

    my $line;

    my @times;

    my $comskipno = 0;

    my $comskipchapno = 0;

    print TXT \"00:00:00.000 Beginning\\n\";

    while ($line = <EDL>) {

        chomp $line;

        # parse the space-delimited edl ascii times into an array of numbers

        @times = split ' ', $line;

        @times = map {$_+0} @times; # (unnecessarily) convert strings to numbers

        $comskipno += 1 if ($comskipno == 0 && $times[0] != 0.0);

        if ($#times < 2 || $times[2] == 0.0) {

            if ($times[0] != 0.0) {

                print TXT sprintf(\"%s Chapter %d End\\n\",sec2hhmmss($times[0]),$comskipno);



            print TXT sprintf(\"%s Chapter %d Start\\n\",sec2hhmmss($times[1]),$comskipno);

        } else {

            # never seen this case, but here for logical consistency


            if ($times[0] != 0.0) {

                print TXT sprintf(\"%s Chapter %d Start\\n\",sec2hhmmss($times[0]),$comskipchapno);


            print TXT sprintf(\"%s Chapter %d End\\n\",sec2hhmmss($times[1]),$comskipchapno);



    close (EDL) ;

    close (TXT) ;



            -- define the perl  script and run it and delete it

            set perl_file to eyetv_path & eyetv_root & perl_suffix

            -- safely quote any single quote characters for system calls: ' --> '"'"'

            set perl_file_safequotes to my replace_chars(POSIX path of perl_file, "'", "'\"'\"'")


            set pl_handle to open for access perl_file with write permission

            set eof of pl_handle to 0

            write perlCode & return to pl_handle

            close access pl_handle

            set perlRes to do shell script "perl '" & perl_file_safequotes & "' || true"

            delete file perl_file


            -- execute mp4chaps and delete the chapter file

            -- remove any existing chapters

            set mp4chapsRes to do shell script mp4chaps & " -r '" & mymp4_posix_safequotes & "' > /dev/null 2>&1 || true"

            -- import the comskip chapters

            set mp4chapsRes to do shell script mp4chaps & " -i '" & mymp4_posix_safequotes & "' > /dev/null 2>&1 || true"

            -- delete the chapter file

            delete file (itunes_path & itunes_root & mp4chaps_suffix)


        end tell

    end ExportDone


    -- extract the root name of a file

    on RootName(fname)

        -- escript-paths

        set root to fname as string

        set delims to AppleScript's text item delimiters

        set AppleScript's text item delimiters to "."

        if root contains "." then set root to (text items 1 thru -2 of root) as text

        set AppleScript's text item delimiters to delims

        return root

    end RootName


    -- get the inode of a file

    on FileInode(posix_filename)

        -- safely quote any single quote characters for system calls: ' --> '"'"'

        set posix_filename_safequotes to my replace_chars(posix_filename, "'", "'\"'\"'")

        set fi to do shell script ("ls -i '" & posix_filename_safequotes & "' || true")

        if fi is not equal to "" then

            set fi to word 1 of fi

            return fi as number


            return ""

        end if

    end FileInode


    -- test if a file is open

    on IsFileOpen(posix_filename)

        -- safely quote any single quote characters for system calls: ' --> '"'"'

        set posix_filename_safequotes to my replace_chars(posix_filename, "'", "'\"'\"'")

        set res to do shell script ("lsof '" & posix_filename_safequotes & "' > /dev/null 2>&1 && echo 'true' || echo 'false' || true")

        if res is equal to "true" then

            set res to true


            set res to false

        end if

        return res

    end IsFileOpen


    -- string replacement

    on replace_chars(this_text, search_string, replacement_string)

        set delims to AppleScript's text item delimiters

        set AppleScript's text item delimiters to the search_string

        set the item_list to every text item of this_text

        set AppleScript's text item delimiters to the replacement_string

        set this_text to the item_list as string

        set AppleScript's text item delimiters to delims

        return this_text

    end replace_chars



    -- testing code: this will not be called when triggered from EyeTV, but only when the script is run as a stand-alone script

    on run

        tell application "EyeTV"

            --set rec to unique ID of item 1 of recordings

            -- for all your id's, run /Library/Application\ Support/ETVComskip/

            set rec to 372308280

            my ExportDone(rec)

        end tell

    end run

  • sheldoa Level 1 Level 1 (0 points)

    I installed your latest files from the site and works like a charm now.  Thanks for all your help and hard work!

  • essandess Level 1 Level 1 (15 points)

    Thanks. It even turns steering wheel controls into commercial skipping buttons when my kid is watching tv over Bluetooth in the car. This alone makes the coding worthwhile.

  • fracnl14 Level 1 Level 1 (0 points)


    thank you. your instructions were very helpful for me but I live in Europe and I don't have neither HD or turbo264. Are your changes to the standard version (edit, the new scripts) useful for me? my etv comskip (standard version) marks well short program (below 1 and half hour) but not the longer ones (like movies).

    if your changes are useful, could you explain better how to "edit"? do I have to substitute the similar parts in the original file?

    thank you

  • essandess Level 1 Level 1 (15 points)

    The ETVComskip modifications allow commercial marking on HD content, so if you don't have an HD PVR, the original ETVComskip is what you want. However, the EyeTV script ExportDone.scpt above will mark commercials in iTunes exports whether they're HD or not, so that may be useful.


    Also, if you're having issues with Comskip, try downloading the latest free version and swapping it in as described for the contributor version above—that has a good chance of resolving little problems.


    Finally, google "os x text editor" for text editing questions.

  • Ste88 Level 1 Level 1 (0 points)



    Perhaps you can help me - I'm having a problem that initially was similar to sheldoa, i.e., that I was getting "No such file or directory" when running MarkCommercials from the command line. It doesn't appear to be the same problem, however, as when I look in my .eyetv file, I see no .edl file whatsoever. I came to the conclusion (correct me if I'm wrong here) that comskip didn't do it's job. So, I also tried running comskip from the command line:


    /opt/local/bin/wine /Library/Application\ Support/ETVComskip/comskip/comskip.exe --ini=/Library/Application\ Support/ETVComskip/comskip/comskip.ini 0000000016536aa4.mpg


    This fails with the error:


    err:module:attach_process_dlls "winspool.drv" failed to initialize, aborting

    err:module:LdrInitializeThunk Main exe initialization for L"Z:\\Library\\Application Support\\ETVComskip\\comskip\\comskip.exe" failed, status c0000005


    Which leads me to believe I was right. It also seems like comskip is not functioning because wine itself is not starting. Does comskip itself create the .edl file within each .eyetv package?


    I have carefully gone through the installation steps detailed in the readme, and have made sure I have the latest macports updates, the donor version of comskip, etc. XQuartz is installed and running, and wine-devel and mp4v2 and their dependencies install without error from macports. I also have copied your files from the zip as instructed. Is there something I'm missing?


    Any help would be very much appreciated!





    PS. This is Mountain Lion on a Mac Mini with my EyeTV Archive in a non-default location (on my external firewire Media volume)

Previous 1 2 3 4 Next