예제 #1
0
 def GetNewCoronationStreetFilename(self, baseFile, _queuedFile):
     fileExtension = baseFile[baseFile.rfind('.') + 1:]
     baseFile = baseFile[0:baseFile.rfind('.')]
     if "Coronation Street" in baseFile:
         baseParts = baseFile.split(' - ')
         dtFormat = "%Y-%m-%d %H %M %S"
         locCreateDt = self.__ltz.localize(
             datetime.datetime.strptime(baseParts[1],
                                        dtFormat)).astimezone(self.__london)
         metaInfo = self.GetCorrieIndex(locCreateDt)
         baseParts[1] = locCreateDt.strftime(dtFormat)
         if "19 30" in baseParts[1]:
             baseParts[2] += " - pt1"
         if "20 30" in baseParts[1]:
             baseParts[2] += " - pt2"
         if metaInfo is not None:
             epInfo = 's' + str(metaInfo[2]) + 'e' + str(metaInfo[3])
             baseParts.insert(1, epInfo)
         baseFile = ' - '.join(baseParts)
     if Settings.GetConfig("Applications", "handbrake", "false").lower in [
             'true', '1', 't', 'y', 'yes', 'yeah', 'yup', 'certainly',
             'uh-huh', 'on'
     ]:
         return baseFile + ".mp4"
     else:
         return baseFile + ".done." + fileExtension
예제 #2
0
 def ProcessQueuedFile(self, i, queuedFile):
   print(' Processing ' + str(i) + queuedFile.GetFilename() + ' in state ' + queuedFile.GetState().name)
   if queuedFile.GetState() == PlexPostProcessState.INITIAL:
     if queuedFile.GetFiletype() == 'm4v':
       if Settings.GetConfig("Applications", "handbrake", "false").lower in ['true', '1', 't', 'y', 'yes', 'yeah', 'yup', 'certainly', 'uh-huh', 'on' ]:
         queuedFile.SetState(PlexPostProcessState.COMMSKIP)
       else:
         queuedFile.SetState(PlexPostProcessState.TRANSCODING) # Comskip no longer needed
       self.GetDatabaseInteraction().UpdateQFState(queuedFile, "Startup", "Started commskip")
     else:
       queuedFile.SetState(PlexPostProcessState.TRANSCODING)
       self.GetDatabaseInteraction().UpdateQFState(queuedFile, "Comskip", "Started processing")
   elif queuedFile.GetState() == PlexPostProcessState.COMMSKIP:
     Commskip(self).Commskip(i, queuedFile)
   elif queuedFile.GetState() == PlexPostProcessState.TRANSCODING:
     Transcode(self).Transcode(i, queuedFile)
   elif queuedFile.GetState() == PlexPostProcessState.ADD_META:
     AddMeta(self).AddMeta(i, queuedFile)
   elif queuedFile.GetState() == PlexPostProcessState.MOVING_FILES:
     self.MoveFiles(i, queuedFile)
   elif queuedFile.GetState() == PlexPostProcessState.DELETING_ORIGINAL_FILE:
     self.DeleteOriginalFile(i, queuedFile)
   elif queuedFile.GetState() == PlexPostProcessState.PENDING_DELETE_DUPLICATE:
     self.DeleteDuplicateFile(i, queuedFile)
   else:
     raise Exception("Damn, invalid state " + queuedFile.GetState().name)
예제 #3
0
    def Transcode(self, _i, queuedFile):
        filenameHandler = DetermineFilename(self.GetPlexPostProcess())
        print("  PlexPostProcess to " +
              filenameHandler.GetTempFilename(queuedFile))
        self.GetPlexPostProcess().GetDatabaseInteraction().AddQFHistory(
            queuedFile, "Transcode", "  PlexPostProcess to " +
            filenameHandler.GetTempFilename(queuedFile))

        command = []

        if queuedFile.GetFiletype() == 'm4v':
            command = [
                Settings.GetConfig('Applications', 'handbrake',
                                   '/usr/local/bin/HandBrakeCLI'),
                '--preset-import-file',
                os.path.join(Settings.GetRootPath(),
                             'handbrake_preset.json'), '-i',
                queuedFile.GetFilename(), '-o',
                filenameHandler.GetTempFilename(queuedFile), '--preset',
                'Super HQ 1080p30 Surround MP3', '--decomb', 'bob'
            ]
            if Settings.GetConfig("Applications", "handbrake",
                                  "false").lower not in [
                                      'true', '1', 't', 'y', 'yes', 'yeah',
                                      'yup', 'certainly', 'uh-huh', 'on'
                                  ]:
                command = None

        else:
            command = [
                Settings.GetConfig('Applications', 'ffmpeg',
                                   '/usr/local/bin/ffmpeg'), '-i',
                queuedFile.GetFilename(), '-vn', '-acodec', 'copy',
                filenameHandler.GetTempFilename(queuedFile)
            ]

        if command is not None:
            self.RunCommand(queuedFile, command)
        else:
            queuedFile.SetState(PlexPostProcessState.MOVING_FILES)
            self.GetPlexPostProcess().GetDatabaseInteraction().UpdateQFState(
                queuedFile, "Transcode", "Transcode skipped")
예제 #4
0
 def __init__(self, plexPostProcess):
     self.__plexPostProcess = plexPostProcess
     self.__london = pytz.timezone(
         'Europe/London')  #This is where corrie is aired
     self.__ltz = get_localzone()  #The servers timezone
     self.__ltz = pytz.timezone(
         'Europe/London'
     )  #For some reason plex writes it in pacific time on freenas
     self.__tmpDir = Settings.GetConfig('Paths', 'backup',
                                        '/mnt/PlexRecordings/BackupMP2')
     self.__plexLibraryPath = "/usr/local/plexdata-plexpass/Plex Media Server/Plug-in Support/Databases/com.plexapp.plugins.library.db"
예제 #5
0
 def GetIDCommand(self, tempFilename):
     return [
         Settings.GetConfig("Applications", "id3v2",
                            '/usr/local/bin/id3v2'), '--TYER',
         self.GetYear(), '--TALB',
         self.GetShow(), '--TIT2',
         self.GetFilename(), '--TRCK',
         self.GetEpisode(), '--TPOS',
         self.GetSeason(), '--TPE1',
         self.GetShow(), '--TPE2',
         self.GetShow(), '--TALB',
         self.GetSeasonStr(), tempFilename
     ]
예제 #6
0
 def Reconnect(self):
     self.__connection = pymysql.connect(
         host=Settings.GetConfig('Database', 'host', 'localhost'),
         user=Settings.GetConfig('Database', 'user', 'root'),
         password=Settings.GetConfig('Database', 'password', 'password'),
         db=Settings.GetConfig('Database', 'db', 'plex_post_process'),
         unix_socket=Settings.GetConfig('Database', 'unix_socket',
                                        '/tmp/mysql.sock'),
         charset=Settings.GetConfig('Database', 'charset', 'utf8mb4'),
         cursorclass=pymysql.cursors.DictCursor)
예제 #7
0
 def GetTempFilename(self, queuedFile):
     if queuedFile.GetFiletype() == 'm4v':
         if Settings.GetConfig("Applications", "handbrake",
                               "false").lower not in [
                                   'true', '1', 't', 'y', 'yes', 'yeah',
                                   'yup', 'certainly', 'uh-huh', 'on'
                               ]:
             return queuedFile.GetFilename()
         return os.path.join(
             self.__tmpDir,
             ntpath.basename(queuedFile.GetFilename()) + "." +
             str(queuedFile.GetId()) + ".mp4")
     else:
         return os.path.join(
             self.__tmpDir,
             ntpath.basename(queuedFile.GetFilename()) + "." +
             str(queuedFile.GetId()) + ".mp3")
예제 #8
0
 def MoveFiles(self, _i, queuedFile):
   filenameHandler = DetermineFilename(self)
   self.GetDatabaseInteraction().AddQFHistory(queuedFile, "Move Files", "Moving from '" + filenameHandler.GetTempFilename(queuedFile) + "' to '" + filenameHandler.GetDestFilename(queuedFile) + "'")
   try:
     shutil.move(filenameHandler.GetTempFilename(queuedFile), filenameHandler.GetDestFilename(queuedFile))
   except Exception as e:
     queuedFile.SetState(PlexPostProcessState.ERROR)
     print(e.__doc__)
     print(e.message)
     sys.exit(2)
     self.GetDatabaseInteraction().UpdateQFState(queuedFile, "Move Files", "Error " + str(sys.exc_info()[0]))
     return;
   if Settings.GetConfig("Applications", "handbrake", "false").lower in ['true', '1', 't', 'y', 'yes', 'yeah', 'yup', 'certainly', 'uh-huh', 'on' ]:
     queuedFile.SetState(PlexPostProcessState.DELETING_ORIGINAL_FILE)
   else:
     queuedFile.SetState(PlexPostProcessState.SUCCESS)
   self.GetDatabaseInteraction().UpdateQFState(queuedFile, "Move Files", "Finished moving files with success!")
예제 #9
0
    def GetCorrieIndex(self, locCreateDt):
        locale.setlocale(locale.LC_TIME, "en_GB.UTF-8")
        conn = sqlite3.connect(
            Settings.GetConfig("Paths", "plexLibrary",
                               self.GetPlexLibraryPath()))
        c = conn.cursor()
        formattedDate = locCreateDt.strftime("%A,%%" +
                                             ordinal(locCreateDt.day) +
                                             " %B %Y")
        sql = "select \"index\",title,guid from metadata_items where library_section_id=17 and guid like '%/71565/%' and metadata_type = 4 and title like '%" + formattedDate + "%';"

        possibilities = []
        for row in c.execute(sql):
            possibilities.append(self.GetCorriePosibility(row))

        conn.close()
        if len(possibilities) == 0:
            return None

        hasPart = False
        for posibility in possibilities:
            hasPart = hasPart or posibility[4] != None

        if hasPart:
            desired = possibilities[0][4]
            if locCreateDt.hour == 19:
                desired = 1
            elif locCreateDt.hour == 20:
                desired = 2

            if possibilities[0][4] != desired:
                possibilities[0][
                    3] = possibilities[0][3] - possibilities[0][4] + desired
                possibilities[0][4] = desired

        return possibilities[0]
예제 #10
0
 
class PlexPostProcessDaemon(Daemon):
  def __init__(self, pidfile):
    Daemon.__init__(self, pidfile)
    self.__config = []
    if len(sys.argv) >= 3:
      configFile = sys.argv[2]
      with open(configFile, 'r+') as configFileStream:
        self.__config = configFileStream.read().splitlines()
    self.__config.append('--daemon')
            
  def run(self):
    PlexPostProcessShared().mainWithArgs(self.__config)
 
if __name__ == "__main__":
  daemon = PlexPostProcessDaemon(Settings.GetConfig('Paths','daemonLinePidFile','/tmp/daemon_plex_post_process.pid'))
  if len(sys.argv) > 1:
    if 'start' == sys.argv[1]:
      daemon.start()
    elif 'stop' == sys.argv[1]:
      daemon.stop()
    elif 'restart' == sys.argv[1]:
      daemon.restart()
    elif 'status' == sys.argv[1]:
      print('Current state: ' + daemon.status().name)
    else:
      print("Unknown command '" + sys.argv[1] + "'")
      sys.exit(2)
    sys.exit(0)
  else:
    print("usage: %s start|stop|restart|status" % sys.argv[0])
    def mainWithArgs(self, argv):
        global DEBUG
        global TESTRUN
        global PROFILE

        global __all__
        global __version__
        global __date__
        global __updated__

        global wakeUp

        program_name = os.path.basename(sys.argv[0])
        program_version = "v%s" % __version__
        program_build_date = str(__updated__)
        program_version_message = '%%(prog)s %s (%s)' % (program_version,
                                                         program_build_date)
        program_shortdesc = 'Scan and PlexPostProcess'
        program_license = '''%s %s

   MIT License

Copyright (c) 2018 Christopher Dawes

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
  ''' % (program_shortdesc, str(__date__))

        try:
            # Setup argument parser
            parser = ArgumentParser(
                description=program_license,
                formatter_class=RawDescriptionHelpFormatter)
            parser.add_argument(
                "-r",
                "--recursive",
                dest="recurse",
                action="store_true",
                help="recurse into subfolders [default: %(default)s]",
                default=True)
            parser.add_argument(
                "-v",
                "--verbose",
                dest="verbose",
                action="count",
                help="set verbosity level [default: %(default)s]",
                default=0)
            parser.add_argument(
                "-i",
                "--include",
                dest="include",
                help=
                "only include paths matching this regex pattern. Note: exclude is given preference over include. [default: %(default)s]",
                metavar="RE",
                default="*.ts")
            parser.add_argument(
                "-e",
                "--exclude",
                dest="exclude",
                help=
                "exclude paths matching this regex pattern. [default: %(default)s]",
                metavar="RE",
                default="*.done.ts")
            parser.add_argument('-V',
                                '--version',
                                action='version',
                                version=program_version_message)
            parser.add_argument(
                '-D',
                '--daemon',
                dest='daemon',
                action="store_true",
                help="Run in daemon mode [default: %(default)s]",
                default=False)
            parser.add_argument(
                '-C',
                '--debug-corrie',
                dest='debugCorrie',
                action="store_true",
                help="Debug coronation street [default: %(default)s]",
                default=False)
            parser.add_argument(
                dest="paths",
                help=
                "paths to folder(s) with source file(s) [default: %(default)s]",
                metavar="path",
                nargs='*')

            # Process arguments
            args = parser.parse_args(argv)
            print(args)
            paths = args.paths + list(
                Settings.GetConfig('FileScanner').values())
            verbose = args.verbose > 0
            recurse = args.recurse
            inpat = args.include
            expat = args.exclude
            daemon = args.daemon

            if daemon:
                signal.signal(signal.SIGUSR1, WakeUpNow)

            if verbose:
                print("Verbose mode on")
                if recurse:
                    print("Recursive mode on")
                else:
                    print("Recursive mode off")

            if inpat and expat and inpat == expat:
                raise Exception(
                    "include and exclude pattern are equal! Nothing will be processed."
                )
            fileScanner = FileScanner(recurse=recurse,
                                      paths=paths,
                                      inpat=inpat,
                                      expat=expat,
                                      verbose=verbose)
            with DatabaseInteraction() as databaseInteraction:
                if args.debugCorrie:
                    x = DetermineFilename(
                        PlexPostProcessStateMachine(databaseInteraction))
                    print(
                        x.GetNewCoronationStreetFilename(
                            "Coronation Street (1960) - 2018-11-30 20 30 00 - Episode 11-30.ts",
                            None))
                    return 0

                running = True
                first = True
                updateDb = True
                while running:
                    nrNewFiles = 0
                    if not first:
                        updateDb = fileScanner.Rescan()
                    if updateDb:
                        print("Starting to update database...")
                        nrNewFiles = databaseInteraction.UpdateWithFiles(
                            fileScanner)
                        print("Database update complete with " +
                              str(nrNewFiles) + " new files.")
                    if nrNewFiles > 0 or first:
                        plexPostProcessr = PlexPostProcessStateMachine(
                            databaseInteraction)
                        DetermineFiletype(
                            plexPostProcessr).DetermineFiletypes()
                        plexPostProcessr.PlexPostProcess()
                    if nrNewFiles == 0:
                        wakeUp.clear()
                        if wakeUp.wait(3600):  #sleep for one hour
                            time.sleep(
                                5
                            )  #sleep for 5 seconds to prevent race condition on plex
                    running = daemon
                    first = False

            return 0
        except KeyboardInterrupt:
            ### handle keyboard interrupt ###
            return 0
        except Exception as e:
            if DEBUG or TESTRUN:
                raise (e)
            indent = len(program_name) * " "
            sys.stderr.write(program_name + ": " + repr(e) + "\n")
            sys.stderr.write(indent + "  for help use --help")
            return 2
예제 #12
0
    PlexPostProcessShared().mainWithArgs(argv)


if __name__ == "__main__":
    if DEBUG:
        sys.argv.append("-v")
        sys.argv.append("-r")
    if TESTRUN:
        import doctest
        doctest.testmod()
    if PROFILE:
        import cProfile
        import pstats
        profile_filename = 'com.camding.plexpostprocess.PlexPostProcess_profile.txt'
        cProfile.run('main()', profile_filename)
        statsfile = open("profile_stats.txt", "wb")
        p = pstats.Stats(profile_filename, stream=statsfile)
        stats = p.strip_dirs().sort_stats('cumulative')
        stats.print_stats()
        statsfile.close()
        sys.exit(0)
    sys.exit(main())

pid_file = Settings.GetConfig('Paths', 'cmdLinePidFile',
                              '/tmp/cmd_plex_post_process.pid')
fp = open(pid_file, 'w')
try:
    fcntl.lockf(fp, fcntl.LOCK_EX | fcntl.LOCK_NB)
except IOError:
    # another instance is running
    sys.exit(0)
예제 #13
0
  def Commskip(self, _i, queuedFile):
    filenameHandler = DetermineFilename(self.GetPlexPostProcess())
    print("  Commskip to " + filenameHandler.GetTempFilename(queuedFile));
    self.GetPlexPostProcess().GetDatabaseInteraction().AddQFHistory(queuedFile, "Commskip", "  Commskip to " + filenameHandler.GetTempFilename(queuedFile));

    if "The X Factor (2004)" in queuedFile.GetFilename():
      queuedFile.SetState(PlexPostProcessState.TRANSCODING)
      self.GetPlexPostProcess().GetDatabaseInteraction().UpdateQFState(queuedFile, "Comskip", "XFactor cannot be comskipped; it's too much like an advert ;)")
      return
      
    command = [
        Settings.GetConfig('Applications', 'bash', '/usr/local/bin/bash'), 
        os.path.join(Settings.GetRootPath(), 'plex_commskip.sh'),
        queuedFile.GetFilename()
        ]

    logfile_queue = Queue()
    logfile_queue.put(" ".join(command).encode('utf-8'))
    sys.stdout.write(" ".join(command))
    sys.stdout.flush()
  
    # Launch the process with PIPE stdout and stderr
    process = self.GetPlexPostProcess().RunProcess(command, None, sys.stdin, PIPE, PIPE)
  
    # Function for reader threads, echo lines from in_fh to out_fh and out_queue
    def read_handler(in_fh, out_fh, out_queue):
      while True:
        line = in_fh.readline()
        if not line: return
        out_fh.buffer.write(line)
        out_fh.flush()
        out_queue.put(line)
  
    # Function for writer thread, echo lines from in_queue to out_fh
    def write_handler(in_queue, queuedFile, databaseInteraction):
      while True:
        line = in_queue.get()
        if line is None: return
        databaseInteraction.AddQFHistory(queuedFile, "Commskip", line)
  
    # Launch a thread for reading stdout, reading stderr, and writing the logfile
    stdout_thread = Thread(target=read_handler, args=(process.stdout, sys.stdout, logfile_queue))
    stderr_thread = Thread(target=read_handler, args=(process.stderr, sys.stderr, logfile_queue))
    logfile_thread = Thread(target=write_handler, args=(logfile_queue, queuedFile, self.GetPlexPostProcess().GetDatabaseInteraction()))
  
    for thread in stdout_thread, stderr_thread, logfile_thread:
      thread.daemon = True
      thread.start()
  
    # Wait for the process to complete
    process.wait()
  
    # Wait for stdout and stderr threads to complete
    for thread in stdout_thread, stderr_thread:
      thread.join()
  
    # Signal and wait for the logfile thread to complete
    logfile_queue.put(None)
    logfile_thread.join()
  
    if process.returncode == 0:
      queuedFile.SetState(PlexPostProcessState.TRANSCODING)
      self.GetPlexPostProcess().GetDatabaseInteraction().UpdateQFState(queuedFile, "Comskip", "Started processing")
    else:
      queuedFile.SetState(PlexPostProcessState.TRANSCODING)
      self.GetPlexPostProcess().GetDatabaseInteraction().UpdateQFState(queuedFile, "Comskip", "Error " + str(process.returncode))
예제 #14
0
 def DetermineFiletypeForFile(self, i, queuedFile):
   if queuedFile.GetState() == PlexPostProcessState.PENDING_DELETE_DUPLICATE:
     return
   
   print(' Determining filetype for ' + str(i) + queuedFile.GetFilename() + ' in state ' + queuedFile.GetState().name)
   self.GetPlexPostProcess().GetDatabaseInteraction().AddQFHistory(queuedFile, "DetermineFiletype", "  Determine file type for " + queuedFile.GetFilename());
   
   command = [
     Settings.GetConfig('Applications', 'ffprobe', '/usr/local/bin/ffprobe'), 
     '-v',
     'quiet',
     '-show_streams',
     '-hide_banner',
     queuedFile.GetFilename()]
 
   logfile_queue = Queue()
   logfile_queue.put(" ".join(command).encode('utf-8'))
   sys.stdout.write(" ".join(command))
   sys.stdout.flush()
 
   # Launch the process with PIPE stdout and stderr
   process = self.GetPlexPostProcess().RunProcess(command, None, sys.stdin, PIPE, PIPE)
 
   # Function for reader threads, echo lines from in_fh to out_fh and out_queue
   def read_handler(in_fh, out_fh, out_queue):
     while True:
       line = in_fh.readline()
       if not line: return
       out_fh.buffer.write(line)
       out_fh.flush()
       out_queue.put(line)
 
   # Function for writer thread, echo lines from in_queue to out_fh
   def write_handler(in_queue, queuedFile, databaseInteraction, texts):
     while True:
       line = in_queue.get()
       if line is None: return
       databaseInteraction.AddQFHistory(queuedFile, "DetermineFiletype", line)
       if "codec_type=video" in line.decode("utf-8"):
         texts.append(line.decode("utf-8"))
 
   texts = []
   # Launch a thread for reading stdout, reading stderr, and writing the logfile
   stdout_thread = Thread(target=read_handler, args=(process.stdout, sys.stdout, logfile_queue))
   stderr_thread = Thread(target=read_handler, args=(process.stderr, sys.stderr, logfile_queue))
   logfile_thread = Thread(target=write_handler, args=(logfile_queue, queuedFile, self.GetPlexPostProcess().GetDatabaseInteraction(), texts))
 
   for thread in stdout_thread, stderr_thread, logfile_thread:
     thread.daemon = True
     thread.start()
 
   # Wait for the process to complete
   process.wait()
 
   # Wait for stdout and stderr threads to complete
   for thread in stdout_thread, stderr_thread:
     thread.join()
 
   # Signal and wait for the logfile thread to complete
   logfile_queue.put(None)
   logfile_thread.join()
 
   if process.returncode == 0:
     queuedFile.SetFiletype('m4v' if len(texts) > 0 else 'mp3')
     self.GetPlexPostProcess().GetDatabaseInteraction().UpdateQFFiletype(queuedFile, "DetermineFiletype", "Determined output format as " + queuedFile.GetFiletype())
   else:
     queuedFile.SetState(PlexPostProcessState.ERROR)
     self.GetPlexPostProcess().GetDatabaseInteraction().UpdateQFState(queuedFile, "DetermineFiletype", "ffprobe error " + str(process.returncode))