Difference between revisions of "Myth-Rec-to-Vid.py"

From MythTV Official Wiki
Jump to: navigation, search
(Created page with "{{Script info |author=Scott Morton |short=A userjob for migrating recordings to Myth Video |long=This python script migrates videos from Myth Recordings to Myth Video. |catego...")
 
m
 
(21 intermediate revisions by 4 users not shown)
Line 5: Line 5:
 
|category=User Job Scripts
 
|category=User Job Scripts
 
|file=Myth-Rec-to-Vid.py
 
|file=Myth-Rec-to-Vid.py
|S26=yes}}
+
|S26=yes | S27=yes | S28=yes }}
  
 +
Features (among others)
 +
    - Adjustable destination formats
 +
    - Duplication detection at destination
 +
    - National Character support
  
This is a rewrite of a script from Raymond Wagner call mythvidexport.py
+
This python script is intended to function as a [[User_Jobs|user job]], capable of copying recordings into [[MythVideo]].
The script was rewritten to comply with version .26 as well as cleanup some
 
of the code no longer needed for running under .26 like grabbing meta data.
 
  
Keep up to date at [https://github.com/spmorton/Myth-Scripts link https://github.com/spmorton/Myth-Scripts]  
+
Keep up to date at [https://github.com/spmorton/Myth-Scripts https://github.com/spmorton/Myth-Scripts]  
  
 +
Run this script from a terminal/CL with no options for usage information.
  
Run this script form the command line with no options for usage information.
+
ex. python Myth-Rec-to-Vid.py
 +
 
 +
Instructions
 +
    - Copy this script to a folder of your choosing that is accessable to the BE
 +
    - Be sure to chmod +x Myth-Rec-to-Vid.py to make the script executable(Linux)
 +
    - Configure a user job to run this script with your options plus %JOBID% to capture the job number
 +
    - Add %VERBOSEMODE% to the command line to have script logging in a separate file for each job (otherwise it is discarded)
  
  
 
{{Python|Myth-Rec-to-Vid.py|
 
{{Python|Myth-Rec-to-Vid.py|
 
<pre>
 
<pre>
#!/usr/bin/env python
+
#!/usr/bin/env python2
# -*- coding: UTF-8 -*-
+
# -*- coding: utf-8 -*-
 
#---------------------------
 
#---------------------------
 
#  Name: Myth-Rec-to-Vid.py
 
#  Name: Myth-Rec-to-Vid.py
Line 38: Line 47:
 
__title__  = "Myth-Rec-to-Vid"
 
__title__  = "Myth-Rec-to-Vid"
 
__author__ = "Scott Morton"
 
__author__ = "Scott Morton"
__version__= "v0.9.1"
+
__version__= "v1.2.1"
  
from MythTV import MythDB, Job, Recorded, Video, MythLog, MythError, static, MythBE
+
from MythTV import MythDB, Job, Recorded, Video, VideoGrabber,\
 +
                  MythLog, static, MythBE  
 
from optparse import OptionParser, OptionGroup
 
from optparse import OptionParser, OptionGroup
  
import sys, time, hashlib
+
import sys, time
  
 
# Global Constants
 
# Global Constants
Line 65: Line 75:
 
#    %STORAGEGROUP%:  storage group containing recorded show
 
#    %STORAGEGROUP%:  storage group containing recorded show
 
#    %GENRE%:        first genre listed for recording
 
#    %GENRE%:        first genre listed for recording
 
def hashfile(fd):
 
    hasher = hashlib.sha1()
 
    while True:
 
        buff = fd.read(2**16)
 
        if len(buff) == 0:
 
            break
 
        hasher.update(buff)
 
    return hasher.hexdigest()
 
  
 
class VIDEO:
 
class VIDEO:
     def __init__(self, opts, jobid=None):
+
     def __init__(self, opts, jobid=None):                          
 
          
 
          
 
         # Setup for the job to run
 
         # Setup for the job to run
Line 84: Line 85:
 
             self.startTime = self.thisJob.starttime
 
             self.startTime = self.thisJob.starttime
 
             self.thisJob.update(status=Job.STARTING)
 
             self.thisJob.update(status=Job.STARTING)
           
+
       
 
         # If no job ID given, must be a command line run
 
         # If no job ID given, must be a command line run
 
         else:
 
         else:
Line 90: Line 91:
 
             self.chanID = opts.chanid
 
             self.chanID = opts.chanid
 
             self.startTime = opts.startdate + " " + opts.starttime + opts.offset
 
             self.startTime = opts.startdate + " " + opts.starttime + opts.offset
 
 
         self.opts = opts
 
         self.opts = opts
 
+
        self.type = "none"
 
         self.db = MythDB()
 
         self.db = MythDB()
 
         self.log = MythLog(module='Myth-Rec-to-Vid.py', db=self.db)
 
         self.log = MythLog(module='Myth-Rec-to-Vid.py', db=self.db)
 +
       
  
 
         # Capture the backend host name
 
         # Capture the backend host name
Line 104: Line 105:
 
                         '%s - %s' % (self.rec.title.encode('utf-8'),  
 
                         '%s - %s' % (self.rec.title.encode('utf-8'),  
 
                                     self.rec.subtitle.encode('utf-8')))
 
                                     self.rec.subtitle.encode('utf-8')))
 +
 
         self.vid = Video(db=self.db).create({'title':'', 'filename':'',
 
         self.vid = Video(db=self.db).create({'title':'', 'filename':'',
 
                                             'host':self.host})
 
                                             'host':self.host})
  
 +
        self.bend = MythBE(db=self.db)
 +
       
 +
       
 +
    def check_hash(self):
 +
        self.log(self.log.GENERAL, self.log.INFO,
 +
                'Performing copy validation.')
 +
        srchash = self.bend.getHash(self.rec.basename, self.rec.storagegroup)
 +
        dsthash = self.bend.getHash(self.vid.filename, 'Videos')
 +
        if srchash != dsthash:
 +
            return False
 +
        else:
 +
            return True
 +
             
 +
    def copy(self):
 +
        stime = time.time()
 +
        srcsize = self.rec.filesize
 +
        htime = [stime,stime,stime,stime]
 +
 +
        self.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "Copying myth://%s@%s/%s"\
 +
              % (self.rec.storagegroup, self.rec.hostname, self.rec.basename)\
 +
                                                    +" to myth://Videos@%s/%s"\
 +
                                          % (self.host, self.vid.filename))
 +
       
 +
 +
        srcfp = self.rec.open('r')
 +
        dstfp = self.vid.open('w')
 +
 +
        if self.thisJob:
 +
            self.set_job_status(Job.RUNNING)
 +
        tsize = 2**24
 +
        while tsize == 2**24:
 +
            tsize = min(tsize, srcsize - dstfp.tell())
 +
            dstfp.write(srcfp.read(tsize))
 +
            htime.append(time.time())
 +
            rate = float(tsize*4)/(time.time()-htime.pop(0))
 +
            remt = (srcsize-dstfp.tell())/rate
 +
            if self.thisJob:
 +
                self.thisJob.setComment("%02d%% complete - %d seconds remaining" %\
 +
                                      (dstfp.tell()*100/srcsize, remt))
 +
        srcfp.close()
 +
        dstfp.close()
 +
       
 +
        self.vid.hash = self.vid.getHash()
 +
       
 +
        self.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "Transfer Complete",
 +
              "%d seconds elapsed" % int(time.time()-stime))
 +
 +
        if self.thisJob:
 +
            self.thisJob.setComment("Complete - %d seconds elapsed" % \
 +
              (int(time.time()-stime)))
  
 
     def copy_markup(self, start, stop):
 
     def copy_markup(self, start, stop):
Line 117: Line 169:
 
             self.vid.markup.add(seek.mark, seek.offset, seek.type)
 
             self.vid.markup.add(seek.mark, seek.offset, seek.type)
 
                  
 
                  
 +
    def delete_vid(self):
 +
        self.vid.delete()
 +
       
 +
    def delete_rec(self):
 +
        self.rec.delete()
 +
       
 
     def dup_check(self):
 
     def dup_check(self):
        bend = MythBE(db=self.vid._db)
 
 
         self.log(MythLog.GENERAL, MythLog.INFO, 'Processing new file name ',
 
         self.log(MythLog.GENERAL, MythLog.INFO, 'Processing new file name ',
 
                     '%s' % (self.vid.filename))
 
                     '%s' % (self.vid.filename))
Line 124: Line 181:
 
                     '%s - %s' % (self.rec.title.encode('utf-8'),  
 
                     '%s - %s' % (self.rec.title.encode('utf-8'),  
 
                                 self.rec.subtitle.encode('utf-8')))
 
                                 self.rec.subtitle.encode('utf-8')))
         if bend.fileExists(self.vid.filename, 'Videos'):
+
         if self.bend.fileExists(self.vid.filename, 'Videos'):
 
             self.log(MythLog.GENERAL, MythLog.INFO, 'Recording already exists in Myth Videos')
 
             self.log(MythLog.GENERAL, MythLog.INFO, 'Recording already exists in Myth Videos')
 
             if self.thisJob:
 
             if self.thisJob:
                 self.thisJob.setComment("Action would result in duplicate entry, job ended" )
+
                 self.thisJob.setComment("Action would result in duplicate entry" )
                self.thisJob.setStatus(Job.FINISHED)
 
            self.vid.delete()
 
            self.log(MythLog.GENERAL, MythLog.INFO, 'Exiting program')
 
 
             return True
 
             return True
 
            
 
            
Line 149: Line 203:
 
     def get_meta(self):
 
     def get_meta(self):
 
         metadata = self.rec.exportMetadata()
 
         metadata = self.rec.exportMetadata()
 +
        yrInfo = self.rec.getProgram()
 +
        metadata['year'] = yrInfo.get('year')
 
         self.vid.importMetadata(metadata)
 
         self.vid.importMetadata(metadata)
         self.log(self.log.GENERAL, self.log.INFO, 'MetaData Import complete')
+
        if self.type == 'MOVIE':
 +
            grab = VideoGrabber('Movie')
 +
            results = grab.sortedSearch(self.rec.title)
 +
            if len(results) > 0:
 +
                for i in results:
 +
                    if i.year == yrInfo.get('year') and i.title == self.rec.get('title'):
 +
                        self.vid.importMetadata(i)
 +
                        match = grab.grabInetref(i.get('inetref'))
 +
                        length = len(match.people)
 +
                        for p in range(length-2):
 +
                            self.vid.cast.add(match.people[p].get('name'))
 +
                        self.vid.director = match.people[length - 1].get('name')
 +
                        import_info = 'Full MetaData Import complete'
 +
            elif len(results) == 0:
 +
                import_info = 'Listing only MetaData import complete'
 +
        else:
 +
            grab = VideoGrabber('TV')
 +
            results = grab.sortedSearch(self.rec.title, self.rec.subtitle)
 +
            if len(results) > 0:
 +
                for i in results:
 +
                    if  i.title == self.rec.get('title') and i.subtitle == self.rec.get('subtitle'):
 +
                        self.vid.importMetadata(i)
 +
                        match = grab.grabInetref(grab.grabInetref(i.get('inetref'), \
 +
                                season=i.get('season'),episode=i.get('episode')))
 +
                        length = len(match.people)
 +
                        for p in range(length-2):
 +
                            self.vid.cast.add(match.people[p].get('name'))
 +
                        self.vid.director = match.people[length - 1].get('name')
 +
                        import_info = 'Full MetaData Import complete'
 +
            elif len(results) == 0:
 +
                import_info = 'Listing only MetaData import complete'
 +
           
 +
       
 +
        self.vid.category = self.rec.get('category')
 +
 
 +
         self.log(self.log.GENERAL, self.log.INFO, import_info)
  
 
     def get_type(self):
 
     def get_type(self):
            if self.rec.season is not 0 or self.rec.subtitle:
+
        if self.rec.seriesid != None and self.rec.programid[:2] != 'MV':
                self.type = 'TV'
+
            self.type = 'TV'
                self.log(self.log.GENERAL, self.log.INFO,
+
            self.log(self.log.GENERAL, self.log.INFO,
                        'Performing TV type migration.')
+
                    'Performing TV type migration.')
            else:
+
        else:
                self.type = 'MOVIE'
+
            self.type = 'MOVIE'
                self.log(self.log.GENERAL, self.log.INFO,
+
            self.log(self.log.GENERAL, self.log.INFO,
                        'Performing Movie type migration.')
+
                    'Performing Movie type migration.')
  
 
     def process_fmt(self, fmt):
 
     def process_fmt(self, fmt):
Line 190: Line 281:
 
         return fmt+ext
 
         return fmt+ext
  
     def copy(self):
+
     def set_job_status(self, status):
        stime = time.time()
+
         self.thisJob.setStatus(status)
        srcsize = self.rec.filesize
 
        htime = [stime,stime,stime,stime]
 
 
 
        self.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "Copying myth://%s@%s/%s"\
 
              % (self.rec.storagegroup, self.rec.hostname, self.rec.basename)\
 
                                                    +" to myth://Videos@%s/%s"\
 
                                          % (self.host, self.vid.filename))
 
          
 
 
        srcfp = self.rec.open('r')
 
        dstfp = self.vid.open('w')
 
 
 
        if self.thisJob:
 
            self.thisJob.setStatus(Job.RUNNING)
 
        tsize = 2**24
 
        while tsize == 2**24:
 
            tsize = min(tsize, srcsize - dstfp.tell())
 
            dstfp.write(srcfp.read(tsize))
 
            htime.append(time.time())
 
            rate = float(tsize*4)/(time.time()-htime.pop(0))
 
            remt = (srcsize-dstfp.tell())/rate
 
            if self.thisJob:
 
                self.thisJob.setComment("%02d%% complete - %d seconds remaining" %\
 
                                      (dstfp.tell()*100/srcsize, remt))
 
        srcfp.close()
 
        dstfp.close()
 
 
          
 
          
 +
    def set_vid_hash(self):
 
         self.vid.hash = self.vid.getHash()
 
         self.vid.hash = self.vid.getHash()
 +
   
 +
    def update_vid(self):
 +
        self.vid.update()
 
          
 
          
        self.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "Transfer Complete",
+
#---END CLASS--------------------------------------------------
              "%d seconds elapsed" % int(time.time()-stime))
 
       
 
        if self.opts.reallysafe:
 
            if self.thisJob:
 
                self.thisJob.setComment("Checking file hashes")
 
            self.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "Checking file hashes.")
 
            srchash = hashfile(self.rec.open('r'))
 
            dsthash = hashfile(self.vid.open('r'))
 
            if srchash != dsthash:
 
                raise MythError('Source hash (%s) does not match destination hash (%s)' \
 
                  % (srchash, dsthash))
 
        elif self.opts.safe:
 
            self.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "Checking file sizes.")
 
            be = MythBE(db=self.vid._db)
 
            try:
 
                srcsize = be.getSGFile(self.rec.hostname, self.rec.storagegroup, \
 
            self.rec.basename)[1]
 
                dstsize = be.getSGFile(self.host, 'Videos', self.vid.filename)[1]
 
            except:
 
                raise MythError('Could not query file size from backend')
 
            if srcsize != dstsize:
 
                raise MythError('Source size (%d) does not match destination size (%d)' \
 
                              % (srcsize, dstsize))
 
       
 
        if self.thisJob:
 
            self.thisJob.setComment("Complete - %d seconds elapsed" % \
 
              (int(time.time()-stime)))
 
            self.thisJob.setStatus(Job.FINISHED)
 
 
 
 
 
 
 
  
 
def main():
 
def main():
Line 271: Line 309:
  
 
     actiongroup = OptionGroup(parser, "Additional Actions",
 
     actiongroup = OptionGroup(parser, "Additional Actions",
                     "These options perform additional actions after the recording has been exported.")
+
                     "These options perform additional actions after the recording has been migrated. "+\
 +
                    "A safe copy is always performed in that the file is checked to match the "+\
 +
                    "MythBE hash. The safe copy option will abort the entire process if selected "+\
 +
                    "along with Other Data and an exception occurs in the process")
 
     actiongroup.add_option('--safe', action='store_true', default=False, dest='safe',
 
     actiongroup.add_option('--safe', action='store_true', default=False, dest='safe',
             help='Perform quick sanity check of exported file using file size.')
+
             help='If other data is copied and a failure occurs this will abort the whole process.')
    actiongroup.add_option('--really-safe', action='store_true', default=False, dest='reallysafe',
 
            help='Perform slow sanity check of exported file using SHA1 hash.')
 
 
     actiongroup.add_option("--delete", action="store_true", default=False,
 
     actiongroup.add_option("--delete", action="store_true", default=False,
 
             help="Delete source recording after successful export. Enforces use of --safe.")
 
             help="Delete source recording after successful export. Enforces use of --safe.")
Line 294: Line 333:
 
     opts, args = parser.parse_args()
 
     opts, args = parser.parse_args()
  
 +
    def error_out():
 +
        export.delete_vid()
 +
        if export.thisJob:       
 +
            export.set_job_status(Job.ERRORED)
 +
        sys.exit(1)
 +
       
 
     if opts.verbose:
 
     if opts.verbose:
 
         if opts.verbose == 'help':
 
         if opts.verbose == 'help':
             print MythLog.helptext
+
             print(MythLog.helptext)
 
             sys.exit(0)
 
             sys.exit(0)
 
         MythLog._setlevel(opts.verbose)
 
         MythLog._setlevel(opts.verbose)
Line 305: Line 350:
 
     # if a manual channel and time entry then setup the export with opts
 
     # if a manual channel and time entry then setup the export with opts
 
     if opts.chanid and opts.startdate and opts.starttime and opts.offset:
 
     if opts.chanid and opts.startdate and opts.starttime and opts.offset:
         export = VIDEO(opts)
+
         try:
 +
            export = VIDEO(opts)
 +
 
 +
        except Exception as e:
 +
            sys.exit(1)
  
 
     # If an auto or manual job entry then setup the export with the jobID
 
     # If an auto or manual job entry then setup the export with the jobID
Line 311: Line 360:
 
         try:
 
         try:
 
             export = VIDEO(opts,int(args[0]))
 
             export = VIDEO(opts,int(args[0]))
             
+
 
         except Exception, e:
+
         except Exception as e:
 
             Job(int(args[0])).update({'status':Job.ERRORED,
 
             Job(int(args[0])).update({'status':Job.ERRORED,
 
                                       'comment':'ERROR: '+e.args[0]})
 
                                       'comment':'ERROR: '+e.args[0]})
             MythLog(module='mythvidexport.py').logTB(MythLog.GENERAL)
+
             MythLog(module='Myth-Rec-to-Vid.py').logTB(MythLog.GENERAL)
 
             sys.exit(1)
 
             sys.exit(1)
  
Line 324: Line 373:
  
 
     # Export object created so process the job
 
     # Export object created so process the job
     export.get_meta()
+
     try:
    export.get_type()
+
        export.get_type()
     export.get_dest()
+
        export.get_meta()
 +
        export.get_dest()
 +
 
 +
     except Exception as e:
 +
        export.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "ERROR in Processing ",
 +
          "Message was: %s" % e.message)
 +
        error_out()
 +
 
 
     if (export.dup_check()):
 
     if (export.dup_check()):
 +
        export.delete_vid()
 +
        if export.thisJob:
 +
            export.set_job_status(Job.FINISHED)
 
         sys.exit(0)
 
         sys.exit(0)
 +
 
     else:
 
     else:
         export.copy()
+
         try:
 +
            export.copy()
 +
            export.vid.update()
 +
            export.set_vid_hash()
 +
 
 +
        except Exception as e:
 +
            export.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "ERROR in Processing ",
 +
              "Message was: %s" % e.message)
 +
            error_out()
 +
 
 +
        try:
 +
            if not export.check_hash():
 +
                error_out()
 +
               
 +
        except Exception as e:
 +
            export.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "ERROR in Hash Check",
 +
              "Message was: %s" % e.message)
 +
            error_out()
 +
 
 
     if opts.seekdata:
 
     if opts.seekdata:
         export.copy_seek()
+
         try:
 +
            export.copy_seek()
 +
 
 +
        except Exception as e:
 +
            export.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "ERROR in Seek Data",
 +
              "Message was: %s" % e.message)
 +
            if opts.safe:
 +
                error_out()
 +
 
 
     if opts.skiplist:
 
     if opts.skiplist:
         export.copy_markup(static.MARKUP.MARK_COMM_START,
+
         try:
 +
            export.copy_markup(static.MARKUP.MARK_COMM_START,
 
                         static.MARKUP.MARK_COMM_END)
 
                         static.MARKUP.MARK_COMM_END)
 +
 +
        except Exception as e:
 +
            export.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "ERROR in Skip List",
 +
              "Message was: %s" % e.message)
 +
            if opts.safe:
 +
                error_out()
 +
 
     if opts.cutlist:
 
     if opts.cutlist:
         export.copy_markup(static.MARKUP.MARK_CUT_START,
+
         try:
 +
            export.copy_markup(static.MARKUP.MARK_CUT_START,
 
                         static.MARKUP.MARK_CUT_END)
 
                         static.MARKUP.MARK_CUT_END)
    export.vid.update()
+
        except Exception as e:
 +
            export.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "ERROR in Cut List",
 +
              "Message was: %s" % e.message)
 +
            if opts.safe:
 +
                error_out()
  
 
     # delete old file
 
     # delete old file
 
     if opts.delete:
 
     if opts.delete:
         export.rec.delete()
+
         try:
 +
            export.delete_rec()
  
 +
        except Exception as e:
 +
            export.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "ERROR in Delete Orig",
 +
              "Message was: %s" % e.message)
 +
            if opts.safe:
 +
                error_out()
 +
   
 +
    export.set_job_status(Job.FINISHED)
 +
   
  
 
if __name__ == "__main__":
 
if __name__ == "__main__":
 
     main()
 
     main()
  
</pre>
 
}}
 
 
[[Category:Python_Scripts]]
 
[[Category:User_Job_Scripts]]
 
  
  

Latest revision as of 23:30, 4 January 2019


Author Scott Morton
Description This python script migrates videos from Myth Recordings to Myth Video.
Supports Version26.png Version27.png Version28.png 


Features (among others)

   - Adjustable destination formats
   - Duplication detection at destination
   - National Character support

This python script is intended to function as a user job, capable of copying recordings into MythVideo.

Keep up to date at https://github.com/spmorton/Myth-Scripts

Run this script from a terminal/CL with no options for usage information.

ex. python Myth-Rec-to-Vid.py

Instructions

   - Copy this script to a folder of your choosing that is accessable to the BE
   - Be sure to chmod +x Myth-Rec-to-Vid.py to make the script executable(Linux)
   - Configure a user job to run this script with your options plus %JOBID% to capture the job number
   - Add %VERBOSEMODE% to the command line to have script logging in a separate file for each job (otherwise it is discarded)


PythonIcon.png Myth-Rec-to-Vid.py

#!/usr/bin/env python2
# -*- coding: utf-8 -*-
#---------------------------
#   Name: Myth-Rec-to-Vid.py
#   Python Script
#   Author: Scott Morton
# 
#   This is a rewrite of a script by Raymond Wagner
#   The objective is to clean it up and streamline 
#   the code for use with Myth 26


#   Migrates MythTV Recordings to MythVideo in Version .26.
#---------------------------


__title__  = "Myth-Rec-to-Vid"
__author__ = "Scott Morton"
__version__= "v1.2.1"

from MythTV import MythDB, Job, Recorded, Video, VideoGrabber,\
                   MythLog, static, MythBE    
from optparse import OptionParser, OptionGroup

import sys, time

# Global Constants

# Modify these setting to your prefered defaults
TVFMT = 'Television/%TITLE%/Season %SEASON%/'+\
                    '%TITLE% - S%SEASON%E%EPISODEPAD% - %SUBTITLE%'

MVFMT = 'Movies/%TITLE%'

# Available strings:
#    %TITLE%:         series title
#    %SUBTITLE%:      episode title
#    %SEASON%:        season number
#    %SEASONPAD%:     season number, padded to 2 digits
#    %EPISODE%:       episode number
#    %EPISODEPAD%:    episode number, padded to 2 digits
#    %YEAR%:          year
#    %DIRECTOR%:      director
#    %HOSTNAME%:      backend used to record show
#    %STORAGEGROUP%:  storage group containing recorded show
#    %GENRE%:         first genre listed for recording

class VIDEO:
    def __init__(self, opts, jobid=None):                           
        
        # Setup for the job to run
        if jobid:
            self.thisJob = Job(jobid)
            self.chanID = self.thisJob.chanid
            self.startTime = self.thisJob.starttime
            self.thisJob.update(status=Job.STARTING)
        
        # If no job ID given, must be a command line run
        else:
            self.thisJob = jobid
            self.chanID = opts.chanid
            self.startTime = opts.startdate + " " + opts.starttime + opts.offset
        self.opts = opts
        self.type = "none"
        self.db = MythDB()
        self.log = MythLog(module='Myth-Rec-to-Vid.py', db=self.db)
        

        # Capture the backend host name
        self.host = self.db.gethostname()

        # prep objects
        self.rec = Recorded((self.chanID,self.startTime), db=self.db)
        self.log(MythLog.GENERAL, MythLog.INFO, 'Using recording',
                        '%s - %s' % (self.rec.title.encode('utf-8'), 
                                     self.rec.subtitle.encode('utf-8')))

        self.vid = Video(db=self.db).create({'title':'', 'filename':'',
                                             'host':self.host})

        self.bend = MythBE(db=self.db)
        
        
    def check_hash(self):
        self.log(self.log.GENERAL, self.log.INFO,
                 'Performing copy validation.')
        srchash = self.bend.getHash(self.rec.basename, self.rec.storagegroup)
        dsthash = self.bend.getHash(self.vid.filename, 'Videos')
        if srchash != dsthash:
            return False
        else:
            return True
               
    def copy(self):
        stime = time.time()
        srcsize = self.rec.filesize
        htime = [stime,stime,stime,stime]

        self.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "Copying myth://%s@%s/%s"\
               % (self.rec.storagegroup, self.rec.hostname, self.rec.basename)\
                                                    +" to myth://Videos@%s/%s"\
                                          % (self.host, self.vid.filename))
        
 
        srcfp = self.rec.open('r')
        dstfp = self.vid.open('w')

        if self.thisJob:
            self.set_job_status(Job.RUNNING)
        tsize = 2**24
        while tsize == 2**24:
            tsize = min(tsize, srcsize - dstfp.tell())
            dstfp.write(srcfp.read(tsize))
            htime.append(time.time())
            rate = float(tsize*4)/(time.time()-htime.pop(0))
            remt = (srcsize-dstfp.tell())/rate
            if self.thisJob:
                self.thisJob.setComment("%02d%% complete - %d seconds remaining" %\
                                      (dstfp.tell()*100/srcsize, remt))
        srcfp.close()
        dstfp.close()
        
        self.vid.hash = self.vid.getHash()
        
        self.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "Transfer Complete",
        			      "%d seconds elapsed" % int(time.time()-stime))

        if self.thisJob:
            self.thisJob.setComment("Complete - %d seconds elapsed" % \
        	      (int(time.time()-stime)))

    def copy_markup(self, start, stop):
        for mark in self.rec.markup:
            if mark.type in (start, stop):
                self.vid.markup.add(mark.mark, 0, mark.type)

    def copy_seek(self):
        for seek in self.rec.seek:
            self.vid.markup.add(seek.mark, seek.offset, seek.type)
                
    def delete_vid(self):
        self.vid.delete()
        
    def delete_rec(self):
        self.rec.delete()
        
    def dup_check(self):
        self.log(MythLog.GENERAL, MythLog.INFO, 'Processing new file name ',
                    '%s' % (self.vid.filename))
        self.log(MythLog.GENERAL, MythLog.INFO, 'Checking for duplication of ',
                    '%s - %s' % (self.rec.title.encode('utf-8'), 
                                 self.rec.subtitle.encode('utf-8')))
        if self.bend.fileExists(self.vid.filename, 'Videos'):
            self.log(MythLog.GENERAL, MythLog.INFO, 'Recording already exists in Myth Videos')
            if self.thisJob:
                self.thisJob.setComment("Action would result in duplicate entry" )
            return True
          
        else:
            self.log(MythLog.GENERAL, MythLog.INFO, 'No duplication found for ',
                    '%s - %s' % (self.rec.title.encode('utf-8'), 
                                 self.rec.subtitle.encode('utf-8')))
            return False 

    def get_dest(self):
        if self.type == 'TV':
            self.vid.filename = self.process_fmt(TVFMT)
        elif self.type == 'MOVIE':
            self.vid.filename = self.process_fmt(MVFMT)
        
        self.vid.markup._refdat = (self.vid.filename,)

    def get_meta(self):
        metadata = self.rec.exportMetadata()
        yrInfo = self.rec.getProgram()
        metadata['year'] = yrInfo.get('year')
        self.vid.importMetadata(metadata)
        if self.type == 'MOVIE':
            grab = VideoGrabber('Movie')
            results = grab.sortedSearch(self.rec.title)
            if len(results) > 0:
                for i in results:
                    if i.year == yrInfo.get('year') and i.title == self.rec.get('title'):
                        self.vid.importMetadata(i)
                        match = grab.grabInetref(i.get('inetref'))
                        length = len(match.people)
                        for p in range(length-2):
                            self.vid.cast.add(match.people[p].get('name'))
                        self.vid.director = match.people[length - 1].get('name')
                        import_info = 'Full MetaData Import complete'
            elif len(results) == 0:
                import_info = 'Listing only MetaData import complete'
        else:
            grab = VideoGrabber('TV')
            results = grab.sortedSearch(self.rec.title, self.rec.subtitle)
            if len(results) > 0:
                for i in results:
                    if  i.title == self.rec.get('title') and i.subtitle == self.rec.get('subtitle'):
                        self.vid.importMetadata(i)
                        match = grab.grabInetref(grab.grabInetref(i.get('inetref'), \
                                season=i.get('season'),episode=i.get('episode')))
                        length = len(match.people)
                        for p in range(length-2):
                            self.vid.cast.add(match.people[p].get('name'))
                        self.vid.director = match.people[length - 1].get('name')
                        import_info = 'Full MetaData Import complete'
            elif len(results) == 0:
                import_info = 'Listing only MetaData import complete'
            
        
        self.vid.category = self.rec.get('category')

        self.log(self.log.GENERAL, self.log.INFO, import_info)

    def get_type(self):
        if self.rec.seriesid != None and self.rec.programid[:2] != 'MV':
            self.type = 'TV'
            self.log(self.log.GENERAL, self.log.INFO,
                    'Performing TV type migration.')
        else:
            self.type = 'MOVIE'
            self.log(self.log.GENERAL, self.log.INFO,
                    'Performing Movie type migration.')

    def process_fmt(self, fmt):
        # replace fields from viddata

        ext = '.'+self.rec.basename.rsplit('.',1)[1]
        rep = ( ('%TITLE%','title','%s'),   ('%SUBTITLE%','subtitle','%s'),
            ('%SEASON%','season','%d'),     ('%SEASONPAD%','season','%02d'),
            ('%EPISODE%','episode','%d'),   ('%EPISODEPAD%','episode','%02d'),
            ('%YEAR%','year','%s'),         ('%DIRECTOR%','director','%s'))
        for tag, data, format in rep:
            if self.vid[data]:
                fmt = fmt.replace(tag,format % self.vid[data])
            else:
                fmt = fmt.replace(tag,'')

        # replace fields from program data
        rep = ( ('%HOSTNAME%',    'hostname',    '%s'),
                ('%STORAGEGROUP%','storagegroup','%s'))
        for tag, data, format in rep:
            data = getattr(self.rec, data)
            fmt = fmt.replace(tag,format % data)


        if len(self.vid.genre):
            fmt = fmt.replace('%GENRE%',self.vid.genre[0].genre)
        else:
            fmt = fmt.replace('%GENRE%','')
        return fmt+ext

    def set_job_status(self, status):
        self.thisJob.setStatus(status)
        
    def set_vid_hash(self):
        self.vid.hash = self.vid.getHash()
    
    def update_vid(self):
        self.vid.update()
        
#---END CLASS--------------------------------------------------

def main():
    parser = OptionParser(usage="usage: %prog [options] [jobid]")

    sourcegroup = OptionGroup(parser, "Source Definition",
                    "These options can be used to manually specify a recording to operate on "+\
                    "in place of the job id.")
    sourcegroup.add_option("--chanid", action="store", type="int", dest="chanid",
            help="Use chanid for manual operation, format interger")
    sourcegroup.add_option("--startdate", action="store", type="string", dest="startdate",
            help="Use startdate for manual operation, format is year-mm-dd")
    sourcegroup.add_option("--starttime", action="store", type="string", dest="starttime",
            help="Use starttime for manual operation, format is hh:mm:ss in UTC")
    sourcegroup.add_option("--offset", action="store", type="string", dest="offset",
            help="Use offset(timezone) for manual operation, format is [+/-]hh:mm. Do not adjust for DST")
    parser.add_option_group(sourcegroup)

    actiongroup = OptionGroup(parser, "Additional Actions",
                    "These options perform additional actions after the recording has been migrated. "+\
                    "A safe copy is always performed in that the file is checked to match the "+\
                    "MythBE hash. The safe copy option will abort the entire process if selected "+\
                    "along with Other Data and an exception occurs in the process")
    actiongroup.add_option('--safe', action='store_true', default=False, dest='safe',
            help='If other data is copied and a failure occurs this will abort the whole process.')
    actiongroup.add_option("--delete", action="store_true", default=False,
            help="Delete source recording after successful export. Enforces use of --safe.")
    parser.add_option_group(actiongroup)

    othergroup = OptionGroup(parser, "Other Data",
                    "These options copy additional information from the source recording.")
    othergroup.add_option("--seekdata", action="store_true", default=False, dest="seekdata",
            help="Copy seekdata from source recording.")
    othergroup.add_option("--skiplist", action="store_true", default=False, dest="skiplist",
            help="Copy commercial detection from source recording.")
    othergroup.add_option("--cutlist", action="store_true", default=False, dest="cutlist",
            help="Copy manual commercial cuts from source recording.")
    parser.add_option_group(othergroup)

    MythLog.loadOptParse(parser)

    opts, args = parser.parse_args()

    def error_out():
        export.delete_vid()
        if export.thisJob:        
            export.set_job_status(Job.ERRORED)
        sys.exit(1)
        
    if opts.verbose:
        if opts.verbose == 'help':
            print(MythLog.helptext)
            sys.exit(0)
        MythLog._setlevel(opts.verbose)

    if opts.delete:
        opts.safe = True

    # if a manual channel and time entry then setup the export with opts
    if opts.chanid and opts.startdate and opts.starttime and opts.offset:
        try:
            export = VIDEO(opts)

        except Exception as e:
            sys.exit(1)

    # If an auto or manual job entry then setup the export with the jobID
    elif len(args) == 1:
        try:
            export = VIDEO(opts,int(args[0]))

        except Exception as e:
            Job(int(args[0])).update({'status':Job.ERRORED,
                                      'comment':'ERROR: '+e.args[0]})
            MythLog(module='Myth-Rec-to-Vid.py').logTB(MythLog.GENERAL)
            sys.exit(1)

    # else bomb the job and return an error code
    else:
        parser.print_help()
        sys.exit(2)

    # Export object created so process the job
    try:
        export.get_type()
        export.get_meta()
        export.get_dest()

    except Exception as e:
        export.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "ERROR in Processing ",
    			      "Message was: %s" % e.message)
        error_out()

    if (export.dup_check()):
        export.delete_vid()
        if export.thisJob:
            export.set_job_status(Job.FINISHED)
        sys.exit(0)

    else:
        try:
            export.copy()
            export.vid.update()
            export.set_vid_hash()

        except Exception as e:
            export.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "ERROR in Processing ",
        			      "Message was: %s" % e.message)
            error_out()

        try:
            if not export.check_hash():
                error_out()
                
        except Exception as e:
            export.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "ERROR in Hash Check",
        			      "Message was: %s" % e.message)
            error_out()

    if opts.seekdata:
        try:
            export.copy_seek()

        except Exception as e:
            export.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "ERROR in Seek Data",
        			      "Message was: %s" % e.message)
            if opts.safe:
                error_out()

    if opts.skiplist:
        try:
            export.copy_markup(static.MARKUP.MARK_COMM_START,
                         static.MARKUP.MARK_COMM_END)

        except Exception as e:
            export.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "ERROR in Skip List",
        			      "Message was: %s" % e.message)
            if opts.safe:
                error_out()

    if opts.cutlist:
        try:
            export.copy_markup(static.MARKUP.MARK_CUT_START,
                         static.MARKUP.MARK_CUT_END)
        except Exception as e:
            export.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "ERROR in Cut List",
        			      "Message was: %s" % e.message)
            if opts.safe:
                error_out()

    # delete old file
    if opts.delete:
        try:
            export.delete_rec()

        except Exception as e:
            export.log(MythLog.GENERAL|MythLog.FILE, MythLog.INFO, "ERROR in Delete Orig",
        			      "Message was: %s" % e.message)
            if opts.safe:
                error_out()
    
    export.set_job_status(Job.FINISHED)
    

if __name__ == "__main__":
    main()