Difference between revisions of "MythCompress"

From MythTV Official Wiki
Jump to: navigation, search
m (wiki formatting)
m (additional wiki formatting)
 
(3 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 
Due to disc restrictions in my mythbox i decided to write a script to compress all my videos.  
 
Due to disc restrictions in my mythbox i decided to write a script to compress all my videos.  
 
Mythtranscode does this but the videos have been recorded quite some time ago and are spread over multiple discs. Compressing those videos to a single destination is no option because they probably won't fit onto one partition. Therefore i wrote a script that dynamically picks a show that is at least two weeks old, compresses it and saves it to a partition with enough disc space.  
 
Mythtranscode does this but the videos have been recorded quite some time ago and are spread over multiple discs. Compressing those videos to a single destination is no option because they probably won't fit onto one partition. Therefore i wrote a script that dynamically picks a show that is at least two weeks old, compresses it and saves it to a partition with enough disc space.  
So, there are multiple folders where videos are waiting for compression and multiple folders where compressed videos are stored. Actually, these can be the same, but they don't have to be as long they are visible to mythtv (see mythtv-setup and storing directories). For compressed videos i created another storing group called 'compressed' and added all destination folders. Additionally i wanted this script to run as a system service and between 1 am and 6 pm.
+
So, there are multiple folders where videos are waiting for compression and multiple folders where compressed videos are stored. Actually, these folders can be the same, but they don't have to be as long they are visible to mythtv (see mythtv-setup and storing directories). For compressed videos i created another storing group called 'compressed' and added all destination folders. Videos are recorded in two different formats by my tv-card (PAL and 720p). This script decides upon video resolution which parameters to pick. Additionally i wanted this script to run as a system service and between 1 am and 6 pm.  
  
 
Processing:
 
Processing:
Line 12: Line 12:
 
# Back to 1.
 
# Back to 1.
  
 +
Following programs are required:
 +
python 2.7, ffmpeg (ffmpeg version git-2013-10-01-2e2a2d8), ffprobe (comes with ffmpeg)
  
This script consists of three program files: mythCompress.py, mythLogging.py, mythFunctions.py
+
This script consists of three python scripts: mythCompress.py, mythLogging.py, mythFunctions.py
and one system script: compressVideos
+
and one system (init.d) script: compressVideos
  
#!/usr/bin/env python
+
'''mythCompress.py'''
+
{{python|mythCompress.py|
import MySQLdb
+
<pre>
import os, sys, glob
+
#!/usr/bin/env python
import shlex, subprocess
+
 
import tempfile
+
import MySQLdb
import time
+
import os, sys, glob
from datetime import datetime, timedelta
+
import shlex, subprocess
from os.path import basename
+
import tempfile
import re
+
import time
import shutil
+
from datetime import datetime, timedelta
import math
+
from os.path import basename
import atexit
+
import re
+
import shutil
from mythFunctions import *
+
import math
from mythLogging import error, debug, clean
+
import atexit
+
 
import psutil
+
from mythFunctions import *
os.nice(19)
+
from mythLogging import error, debug, clean
program = psutil.Process(os.getpid())
+
 
program.set_ionice(psutil.IOPRIO_CLASS_IDLE)
+
import psutil
+
os.nice(19)
######################################################################
+
program = psutil.Process(os.getpid())
# SETTINGS
+
program.set_ionice(psutil.IOPRIO_CLASS_IDLE)
#
+
 
# folders to save compressed videos
+
######################################################################
DESTINATION=[ "/media/myth/compressed/" , "/media/data/mythtv/compressed/" ]
+
# SETTINGS
# folders to read uncompressed videos
+
#
SOURCES=[ "/media/data/mythtv/default/" , "/media/myth/default/" ]  
+
# folders to save compressed videos
TEMPPARENTDIR=os.path.expanduser("~/mythtv-tmp/")
+
DESTINATION=[ "/media/myth/compressed/" , "/media/data/mythtv/compressed/" ]
PRINTDEBUG=True
+
# folders to read uncompressed videos
mysql_opts = {
+
SOURCES=[ "/media/data/mythtv/default/" , "/media/myth/default/" ]  
    'host': "localhost",
+
TEMPPARENTDIR=os.path.expanduser("~/mythtv-tmp/")
    'user': "mythtv",
+
PRINTDEBUG=True
    'pass': "CHANGEME",
+
mysql_opts = {
    'db':  "mythconverg"
+
    'host': "localhost",
    }  
+
    'user': "mythtv",
minimumAge = timedelta(days=14) # for conversion
+
    'pass': "CHANGEME",
ffmpegBinary="/home/rfs/bin/ffmpeg"
+
    'db':  "mythconverg"
ffprobeBinary="/home/rfs/bin/ffprobe"
+
    }  
+
minimumAge = timedelta(days=14) # for conversion
freeSpaceRequired=20000000000
+
ffmpegBinary="/home/rfs/bin/ffmpeg"
freeSpaceInTempRequired=10000000000
+
ffprobeBinary="/home/rfs/bin/ffprobe"
######################################################################
+
 
# constants
+
freeSpaceRequired=20000000000
PID=os.getpgid(0)
+
freeSpaceInTempRequired=10000000000
now=datetime.now()
+
######################################################################
+
# constants
#################################################################
+
PID=os.getpgid(0)
# functions
+
now=datetime.now()
def createTemp(status, parentDirectory):
+
 
    tempDir=None
+
#################################################################
    if os.path.exists(parentDirectory):
+
# functions
        tempDir = tempfile.mkdtemp(prefix=parentDirectory + str(PID) + "-")
+
def createTemp(status, parentDirectory):
        debug(status, "Temporary directory: " + tempDir)
+
    tempDir=None
    else:
+
    if os.path.exists(parentDirectory):
        status.tempDir=None
+
        tempDir = tempfile.mkdtemp(prefix=parentDirectory + str(PID) + "-")
        error(status, "Parent directory for temp does not exist.")
+
        debug(status, "Temporary directory: " + tempDir)
    # change directory to working directory
+
    else:
    try:
+
        status.tempDir=None
        os.chdir(tempDir)
+
        error(status, "Parent directory for temp does not exist.")
    except OSError:
+
    # change directory to working directory
        status.tempDir=None
+
    try:
        error(status, "Could not find temporary directory (OSError).")
+
        os.chdir(tempDir)
    # return result
+
    except OSError:
    return tempDir
+
        status.tempDir=None
+
        error(status, "Could not find temporary directory (OSError).")
class status(object):
+
    # return result
    prefix=None
+
    return tempDir
    tempDir=None
+
 
+
class status(object):
class videoFile(object):
+
    prefix=None
    channelId=None
+
    tempDir=None
    channelStart=None
+
 
    channelDate=None
+
class videoFile(object):
    title=None
+
    channelId=None
+
    channelStart=None
    # quellvideo
+
    channelDate=None
    filename=None
+
    title=None
    basename=None
+
 
    videoInfo=None
+
    # quellvideo
    duration=None
+
    filename=None
    numAudioStreams=None
+
    basename=None
+
    videoInfo=None
    # temporaere ausgabe
+
    duration=None
    tmpFilename=None
+
    numAudioStreams=None
    tmpBasename=None
+
 
+
    # temporaere ausgabe
    # ziel
+
    tmpFilename=None
    newFilename=None
+
    tmpBasename=None
    newBasename=None
+
 
    newVideoInfo=None
+
    # ziel
    newDuration=None
+
    newFilename=None
    newNumAudioStreams=None
+
    newBasename=None
   
+
    newVideoInfo=None
+
    newDuration=None
def compressVideo(s):
+
    newNumAudioStreams=None
    #################################################################
+
   
    # choose partition to save compressed video
+
 
    # a partition with enough free space is required
+
def compressVideo(s):
    chosenDestination=None
+
    #################################################################
    for d in DESTINATION:
+
    # choose partition to save compressed video
        if enoughSpaceAvailable(d, space=freeSpaceRequired):
+
    # a partition with enough free space is required
            chosenDestination=d
+
    chosenDestination=None
            break
+
    for d in DESTINATION:
    if chosenDestination is None:
+
        if enoughSpaceAvailable(d, space=freeSpaceRequired):
        error(s, "Could not select a partition with enough free space")
+
            chosenDestination=d
    else:
+
            break
        debug(s, "Chose following partition as destination for compressed video:\n\t"  
+
    if chosenDestination is None:
              + chosenDestination)
+
        error(s, "Could not select a partition with enough free space")
+
    else:
    TEMPDIR=s.tempDir
+
        debug(s, "Chose following partition as destination for compressed video:\n\t"  
    #################################################################
+
              + chosenDestination)
    # choose video file for compression
+
 
    v=videoFile()
+
    TEMPDIR=s.tempDir
    v.filename=selectVideoFile(SOURCES, minimumAge)
+
    #################################################################
    if v.filename is None:
+
    # choose video file for compression
        error(s, "No videofile could be selected.")
+
    v=videoFile()
    v.basename=basename(v.filename)
+
    v.filename=selectVideoFile(SOURCES, minimumAge)
    debug(s, "Chose videofile: " + v.filename)
+
    if v.filename is None:
+
        error(s, "No videofile could be selected.")
    try:
+
    v.basename=basename(v.filename)
        v.filesize=os.path.getsize(v.filename)
+
    debug(s, "Chose videofile: " + v.filename)
    except OSError, e:
+
 
        error(s, "Could not get mpg filesize.")
+
    try:
   
+
        v.filesize=os.path.getsize(v.filename)
    # prefix for LOGGING
+
    except OSError, e:
    s.prefix=v.basename
+
        error(s, "Could not get mpg filesize.")
    tokens=v.basename.replace(".mpg","").split("_")
+
   
+
    # prefix for LOGGING
    if len(tokens) != 2:
+
    s.prefix=v.basename
        error(s, "Could not parse filename for chanid and starttime.")
+
    tokens=v.basename.replace(".mpg","").split("_")
+
 
    try:
+
    if len(tokens) != 2:
        v.channelId=int(tokens[0])
+
        error(s, "Could not parse filename for chanid and starttime.")
        v.channelStart=tokens[1]
+
 
        v.channelDate=datetime.strptime(v.channelStart, "%Y%m%d%H%M%S")
+
    try:
    except ValueError:
+
        v.channelId=int(tokens[0])
        error(s, "Could not parse channel id and date: " + "_".join(tokens))
+
        v.channelStart=tokens[1]
    # get video information
+
        v.channelDate=datetime.strptime(v.channelStart, "%Y%m%d%H%M%S")
    v.videoInfo=getVideoStreamInformation(s, v.filename)
+
    except ValueError:
    v.resolution=getVideoResolution(v.videoInfo)
+
        error(s, "Could not parse channel id and date: " + "_".join(tokens))
    v.duration=getVideoDuration(v.videoInfo)
+
    # get video information
    v.numAudioStreams=getNumberOfAudioStreams(v.videoInfo)
+
    v.videoInfo=getVideoStreamInformation(s, v.filename)
   
+
    v.resolution=getVideoResolution(v.videoInfo)
    v.creation=getFileCreationDate(v.filename)
+
    v.duration=getVideoDuration(v.videoInfo)
+
    v.numAudioStreams=getNumberOfAudioStreams(v.videoInfo)
    v.newBasename=str(v.channelId) + "_" + str(v.channelStart) + ".avi"
+
   
    v.newFilename=chosenDestination + v.newBasename
+
    v.creation=getFileCreationDate(v.filename)
+
 
    v.tmpBasename=str(v.channelId) + "_" + str(v.channelStart) + ".tmp"
+
    v.newBasename=str(v.channelId) + "_" + str(v.channelStart) + ".avi"
    v.tmpFilename=TEMPDIR + "/" + v.newBasename
+
    v.newFilename=chosenDestination + v.newBasename
+
 
+
    v.tmpBasename=str(v.channelId) + "_" + str(v.channelStart) + ".tmp"
    debug(s, str(v.channelId)  + "," + str(v.channelStart) + "," + str(v.channelDate) + ":\n"
+
    v.tmpFilename=TEMPDIR + "/" + v.newBasename
          + "filename:\t" + v.filename + "\n"
+
 
          + "resolution:\t" + 'x'.join(map(str,v.resolution)) + "\n"
+
 
          )
+
    debug(s, str(v.channelId)  + "," + str(v.channelStart) + "," + str(v.channelDate) + ":\n"
    debug(s, "Temporary Directory: " + TEMPDIR)
+
          + "filename:\t" + v.filename + "\n"
    debug(s, "Temporary filename : " + v.tmpFilename)
+
          + "resolution:\t" + 'x'.join(map(str,v.resolution)) + "\n"
    debug(s, "Temporary basename : " + v.tmpBasename )
+
          )
    debug(s, "New basename      : " + v.newBasename )
+
    debug(s, "Temporary Directory: " + TEMPDIR)
    debug(s, "New File will be  : " + v.newFilename)
+
    debug(s, "Temporary filename : " + v.tmpFilename)
+
    debug(s, "Temporary basename : " + v.tmpBasename )
+
    debug(s, "New basename      : " + v.newBasename )
    #################################################################
+
    debug(s, "New File will be  : " + v.newFilename)
    # update mysql db
+
 
    mysql = MySQLdb.connect(mysql_opts['host'], mysql_opts['user'], mysql_opts['pass'], mysql_opts['db'])  
+
 
    cursor = mysql.cursor()
+
    #################################################################
    sql="SELECT title FROM recorded WHERE chanid='%s' AND starttime='%s'"
+
    # update mysql db
    sql=sql % (v.channelId , v.channelDate.strftime('%Y-%m-%d %H:%M:%S'))
+
    mysql = MySQLdb.connect(mysql_opts['host'], mysql_opts['user'], mysql_opts['pass'], mysql_opts['db'])  
    debug(s, "Mysql database query for title")
+
    cursor = mysql.cursor()
    debug(s, sql)
+
    sql="SELECT title FROM recorded WHERE chanid='%s' AND starttime='%s'"
    cursor.execute(sql)
+
    sql=sql % (v.channelId , v.channelDate.strftime('%Y-%m-%d %H:%M:%S'))
    #cursor.execute(sql, (v.channelId,  v.channelDate.strftime('%Y-%m-%d %H:%M:%S')))
+
    debug(s, "Mysql database query for title")
    videoTable=cursor.fetchall()
+
    debug(s, sql)
    try:
+
    cursor.execute(sql)
        v.title=videoTable[0][0]
+
    #cursor.execute(sql, (v.channelId,  v.channelDate.strftime('%Y-%m-%d %H:%M:%S')))
        if len(v.title) == 0:
+
    videoTable=cursor.fetchall()
            error(s, "Title has zero length")
+
    try:
    except IndexError, e:
+
        v.title=videoTable[0][0]
        error(s, "No mysql database entry found for this video.")
+
        if len(v.title) == 0:
+
            error(s, "Title has zero length")
    debug(s, "Title of video is  : " + v.title)
+
    except IndexError, e:
+
        error(s, "No mysql database entry found for this video.")
    # fix seeking and bookmarking
+
 
    debug(s, "fix Seeking and bookmarking")
+
    debug(s, "Title of video is  : " + v.title)
    delete1="DELETE FROM `recordedseek` WHERE chanid='%s' AND starttime='%s'"
+
 
    delete1=delete1 % (v.channelId, v.channelStart)
+
    # fix seeking and bookmarking
    debug(s, delete1)
+
    debug(s, "fix Seeking and bookmarking")
    cursor.execute(delete1)#, (v.channelId, v.channelStart))
+
    delete1="DELETE FROM `recordedseek` WHERE chanid='%s' AND starttime='%s'"
+
    delete1=delete1 % (v.channelId, v.channelStart)
    delete2="DELETE FROM `recordedmarkup` WHERE chanid='%s' AND starttime='%s'"
+
    debug(s, delete1)
    delete2=delete2 % (v.channelId, v.channelStart)
+
    cursor.execute(delete1)#, (v.channelId, v.channelStart))
    debug(s, delete2)
+
 
    cursor.execute(delete2)#, (v.channelId, v.channelStart))
+
    delete2="DELETE FROM `recordedmarkup` WHERE chanid='%s' AND starttime='%s'"
+
    delete2=delete2 % (v.channelId, v.channelStart)
    updateBase1="UPDATE `recorded` SET basename='%s' WHERE chanid='%s' AND starttime='%s';"
+
    debug(s, delete2)
    debug(s, "\nrenaming file in database")
+
    cursor.execute(delete2)#, (v.channelId, v.channelStart))
    updateBase1=updateBase1 % (v.tmpBasename, v.channelId, v.channelStart)
+
 
    debug(s, updateBase1)
+
    updateBase1="UPDATE `recorded` SET basename='%s' WHERE chanid='%s' AND starttime='%s';"
    cursor.execute(updateBase1)#, (v.tmpBasename, v.channelId, v.channelStart))
+
    debug(s, "\nrenaming file in database")
+
    updateBase1=updateBase1 % (v.tmpBasename, v.channelId, v.channelStart)
    #################################################################
+
    debug(s, updateBase1)
    # set conversion parameter
+
    cursor.execute(updateBase1)#, (v.tmpBasename, v.channelId, v.channelStart))
    ffCommand=""
+
 
    ffOutput=" " + v.tmpFilename
+
    #################################################################
    # TODO remove ffClip
+
    # set conversion parameter
    if v.resolution == (1280,720):
+
    ffCommand=""
        debug(s, "Compression profile for 1280")
+
    ffOutput=" " + v.tmpFilename
        ffDefault=" -c copy"
+
    # TODO remove ffClip
        ffVideoCodec=" -c:v libx264 -crf:v 23 -preset:v slow -tune:v film"
+
    if v.resolution == (1280,720):
        ffAudioCodec=" -c:a libvorbis -q:a 5"
+
        debug(s, "Compression profile for 1280")
        ffSize=" -s 960x540"
+
        ffDefault=" -c copy"
        ffRate=" -r 25"
+
        ffVideoCodec=" -c:v libx264 -crf:v 23 -preset:v slow -tune:v film"
        ffMaps=" -map 0:v -map 0:a"# -map 0:s"
+
        ffAudioCodec=" -c:a libvorbis -q:a 5"
        ffClip=" -ss 16 -t 100"
+
        ffSize=" -s 960x540"
        ffClip=""
+
        ffRate=" -r 25"
        ffCommand=ffmpegBinary + " -i " + v.filename
+
        ffMaps=" -map 0:v -map 0:a"# -map 0:s"
        ffCommand+=ffDefault + ffVideoCodec + ffAudioCodec + ffRate + ffSize + ffMaps + ffClip + ffOutput
+
        ffClip=" -ss 16 -t 100"
    elif v.resolution == (720,576):
+
        ffClip=""
        debug(s, "Compression profile for720")
+
        ffCommand=ffmpegBinary + " -i " + v.filename
        ffDefault=" -c copy"
+
        ffCommand+=ffDefault + ffVideoCodec + ffAudioCodec + ffRate + ffSize + ffMaps + ffClip + ffOutput
        ffVideoCodec=" -c:v libx264 -crf:v 23 -preset:v slow -tune:v film"
+
    elif v.resolution == (720,576):
        ffAudioCodec=" -c:a libvorbis -q:a 5"
+
        debug(s, "Compression profile for720")
        #ffSize=" -s 960x540"
+
        ffDefault=" -c copy"
        ffSize=""
+
        ffVideoCodec=" -c:v libx264 -crf:v 23 -preset:v slow -tune:v film"
        ffMaps=" -map 0:v -map 0:a"# -map 0:s"
+
        ffAudioCodec=" -c:a libvorbis -q:a 5"
        ffClip=" -ss 16 -t 100"
+
        #ffSize=" -s 960x540"
        ffClip=""
+
        ffSize=""
        ffCommand=ffmpegBinary + " -i " + v.filename
+
        ffMaps=" -map 0:v -map 0:a"# -map 0:s"
        ffCommand+=ffDefault + ffVideoCodec + ffAudioCodec + ffSize + ffMaps + ffClip + ffOutput
+
        ffClip=" -ss 16 -t 100"
    else:
+
        ffClip=""
        error(s, "unsupported format")
+
        ffCommand=ffmpegBinary + " -i " + v.filename
+
        ffCommand+=ffDefault + ffVideoCodec + ffAudioCodec + ffSize + ffMaps + ffClip + ffOutput
+
    else:
    #################################################################
+
        error(s, "unsupported format")
    # compress video file
+
 
    cmd=shlex.split(ffCommand)
+
 
    debug(s, "compressing video file...")
+
    #################################################################
    debug(s, "FFMPEG command:\n" + ffCommand)
+
    # compress video file
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
    cmd=shlex.split(ffCommand)
    stdout, stderr = p.communicate()
+
    debug(s, "compressing video file...")
+
    debug(s, "FFMPEG command:\n" + ffCommand)
    print "Returncode:",p.returncode
+
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    if os.path.isfile(v.tmpFilename):
+
    stdout, stderr = p.communicate()
        debug(s, "Video created.")
+
 
    else:
+
    print "Returncode:",p.returncode
        print stdout
+
    if os.path.isfile(v.tmpFilename):
        print stderr
+
        debug(s, "Video created.")
        error(s, "No video created.")
+
    else:
+
        print stdout
    #################################################################
+
        print stderr
    # check new filesize
+
        error(s, "No video created.")
    try:
+
 
        v.newFilesize=os.path.getsize(v.tmpFilename)
+
    #################################################################
    except OSError, e:
+
    # check new filesize
        error(s, "Could not get new filesize.")
+
    try:
+
        v.newFilesize=os.path.getsize(v.tmpFilename)
    #################################################################
+
    except OSError, e:
    # compare video duration of result video
+
        error(s, "Could not get new filesize.")
    v.newVideoInfo=getVideoStreamInformation(s, v.tmpFilename)
+
 
    v.newDuration=getVideoDuration(v.newVideoInfo)
+
    #################################################################
    v.newNumAudioStreams=getNumberOfAudioStreams(v.newVideoInfo)
+
    # compare video duration of result video
    difference=v.duration - v.newDuration
+
    v.newVideoInfo=getVideoStreamInformation(s, v.tmpFilename)
    debug(s, "Duration difference                : " + str(difference))
+
    v.newDuration=getVideoDuration(v.newVideoInfo)
    if math.fabs(difference) > 2:
+
    v.newNumAudioStreams=getNumberOfAudioStreams(v.newVideoInfo)
        debug(s, "Duration of original video: " + str(v.duration))
+
    difference=v.duration - v.newDuration
        debug(s, "Duration of new video    : " + str(v.newDuration))
+
    debug(s, "Duration difference                : " + str(difference))
        error(s, "Duration of result video is not equal.")
+
    if math.fabs(difference) > 2:
+
        debug(s, "Duration of original video: " + str(v.duration))
    #################################################################
+
        debug(s, "Duration of new video    : " + str(v.newDuration))
    # all good ...
+
        error(s, "Duration of result video is not equal.")
    # ... move compressed video file to destination
+
 
    debug(s, "moving " + v.tmpFilename + " to " + v.newFilename)
+
    #################################################################
    try:  
+
    # all good ...
        shutil.move(v.tmpFilename,v.newFilename)
+
    # ... move compressed video file to destination
    except shutil.Error, e:
+
    debug(s, "moving " + v.tmpFilename + " to " + v.newFilename)
        print "Could not move compressed video file"
+
    try:  
        error(s, e)
+
        shutil.move(v.tmpFilename,v.newFilename)
+
    except shutil.Error, e:
    if os.path.isfile(v.newFilename):
+
        print "Could not move compressed video file"
        debug(s, "Video successfully moved.")
+
        error(s, e)
    else:
+
 
        error(s, "Moved videofile does not exist.")
+
    if os.path.isfile(v.newFilename):
    #################################################################
+
        debug(s, "Video successfully moved.")
    # rename file in database
+
    else:
    updateBase2="UPDATE recorded SET basename='%s', filesize='%s' WHERE chanid='%s' AND starttime='%s';"
+
        error(s, "Moved videofile does not exist.")
    updateBase2=updateBase2 % (v.newBasename, v.newFilesize, v.channelId, v.channelStart)
+
    #################################################################
    # TODO
+
    # rename file in database
    # write transcoded="1"
+
    updateBase2="UPDATE recorded SET basename='%s', filesize='%s' WHERE chanid='%s' AND starttime='%s';"
    #  
+
    updateBase2=updateBase2 % (v.newBasename, v.newFilesize, v.channelId, v.channelStart)
    debug(s, updateBase2)
+
    # TODO
    cursor.execute(updateBase2)
+
    # write transcoded="1"
+
    #  
    #################################################################
+
    debug(s, updateBase2)
    # delete pngs  
+
    cursor.execute(updateBase2)
    #  
+
 
    debug(s, "deleting... " + v.filename + "*png")
+
    #################################################################
    for filePath in glob.glob(v.filename + "*png"):
+
    # delete pngs  
        if os.path.isfile(filePath):
+
    #  
            debug(s, "delete " + filePath)
+
    debug(s, "deleting... " + v.filename + "*png")
            os.remove(filePath)
+
    for filePath in glob.glob(v.filename + "*png"):
+
        if os.path.isfile(filePath):
    if os.path.isfile(v.filename):
+
            debug(s, "delete " + filePath)
        debug(s, "deleting original videofile: " + v.filename)
+
            os.remove(filePath)
        os.remove(v.filename)
+
 
    else:
+
    if os.path.isfile(v.filename):
        error(s, "Could not delete original video")
+
        debug(s, "deleting original videofile: " + v.filename)
+
        os.remove(v.filename)
    #################################################################
+
    else:
    clean(s)
+
        error(s, "Could not delete original video")
    #sys.exit(0)
+
 
def allowedTimes():
+
    #################################################################
    now = datetime.now()
+
    clean(s)
    hour=int(datetime.strftime(now, "%H"))
+
    #sys.exit(0)
    if hour > 0 and not (hour > 17):
+
def allowedTimes():
        return True
+
    now = datetime.now()
    else:
+
    hour=int(datetime.strftime(now, "%H"))
        return False
+
    if hour > 0 and not (hour > 17):
+
        return True
+
    else:
s=status()
+
        return False
s.prefix="system: "
+
 
# cleaning up temporary directory
+
 
for filePath in glob.glob(TEMPPARENTDIR + "/*"):
+
s=status()
    if os.path.isdir(filePath):
+
s.prefix="system: "
        debug(s, "delete old temporary directory " + filePath)
+
# cleaning up temporary directory
        shutil.rmtree(filePath)
+
for filePath in glob.glob(TEMPPARENTDIR + "/*"):
+
    if os.path.isdir(filePath):
count=0
+
        debug(s, "delete old temporary directory " + filePath)
#while count < 1:
+
        shutil.rmtree(filePath)
while True:
+
 
    s=status()
+
count=0
    s.prefix="system: "
+
#while count < 1:
    TEMPDIR=createTemp(s, TEMPPARENTDIR)
+
while True:
    s.tempDir=TEMPDIR
+
    s=status()
+
    s.prefix="system: "
    if not enoughSpaceAvailable(TEMPDIR, space=freeSpaceInTempRequired):
+
    TEMPDIR=createTemp(s, TEMPPARENTDIR)
        error(s, "Not enough space free in temporary directory!!")
+
    s.tempDir=TEMPDIR
    atexit.register(clean, status=s)
+
 
+
    if not enoughSpaceAvailable(TEMPDIR, space=freeSpaceInTempRequired):
    ############################
+
        error(s, "Not enough space free in temporary directory!!")
    # video compression logic  
+
    atexit.register(clean, status=s)
    if allowedTimes():
+
 
        count+=1
+
    ############################
        debug(s, "Video number: " + str(count))
+
    # video compression logic  
        compressVideo(s)
+
    if allowedTimes():
+
        count+=1
    time.sleep(600)
+
        debug(s, "Video number: " + str(count))
 +
        compressVideo(s)
 +
 
 +
    time.sleep(600)
 +
</pre>
 +
}}
 +
 
 +
 
 +
'''mythFunctions.py'''
 +
{{python|mythFunctions.py|
 +
<pre>
 +
#!/usr/bin/env python
 +
 
 +
import json
 +
import shlex, subprocess
 +
import MySQLdb
 +
import os, sys, glob
 +
import tempfile
 +
import time
 +
from datetime import datetime, timedelta
 +
from os.path import basename
 +
import re
 +
import shutil
 +
 
 +
 
 +
PID=os.getpgid(0)
 +
ffprobeBinary="/home/rfs/bin/ffprobe"
 +
 
 +
from mythLogging import error
 +
 
 +
#######################################################################################
 +
def getDisksize(filename):
 +
    df = subprocess.Popen(["/bin/df", filename], stdout=subprocess.PIPE)
 +
    output = df.communicate()[0]
 +
    print output
 +
    device, size, used, available, percent, mountpoint = output.split("\n")[1].split()
 +
   
 +
def get_fs_freespace(pathname):
 +
    "Get the free space of the filesystem containing pathname"
 +
    stat= os.statvfs(pathname)
 +
    # use f_bfree for superuser, or f_bavail if filesystem
 +
    # has reserved space for superuser
 +
    return int(stat.f_bfree*stat.f_bsize)
 +
 
 +
def enoughSpaceAvailable(path, space=20000000000):
 +
    return get_fs_freespace(path) > space
 +
 
 +
def getFileCreationDate(filename):
 +
    t = os.path.getmtime(filename)
 +
    then=datetime.fromtimestamp(t)
 +
    return then
 +
 
 +
def isMPGvideoPath(path):
 +
    return os.path.isfile(path) and path.endswith(".mpg")
 +
 
 +
def isMPGvideoFile(f, directory):
 +
    filename=directory + "/" + f
 +
    return os.path.isfile(filename) and f.endswith(".mpg")
 +
 
 +
# video suchen, das aelter als minimumAge ist
 +
def selectVideoFile(SOURCES, minimumAge):
 +
    result=None
 +
    now = datetime.now()
 +
    for directory in SOURCES:
 +
        files = [f for f in os.listdir( directory ) if isMPGvideoFile(f, directory)]
 +
        for f in files:
 +
            filename=directory + "/" + f
 +
            then=getFileCreationDate(filename)
 +
            if (now - then) > minimumAge:
 +
                return filename
 +
    return result
 +
 
 +
def getVideoStreamInformation(status, pathtovideo):
 +
    stdout = ""
 +
    stderr = ""
 +
    try:
 +
        cmd=ffprobeBinary + " -print_format json -show_streams -loglevel quiet "
 +
        cmd+=pathtovideo
 +
        p = subprocess.Popen(shlex.split(cmd),
 +
                            stdout=subprocess.PIPE,
 +
                            stderr=subprocess.PIPE)
 +
        stdout, stderr = p.communicate()
 +
    except OSError:
 +
        error(status, "Could not get video duration with ffprobe.")
 +
    if len(stdout) == 0:
 +
        error(status, "No data read for file: " + pathtovideo)
 +
 
 +
    data = json.loads(stdout)
 +
    streams=None
 +
    try:
 +
        streams=data['streams']
 +
    except KeyError, e:
 +
        error(status, "Could not load stream information.")
 +
    return streams
 +
 
 +
def getVideoDuration(videoInfo):
 +
    duration=None
 +
    for s in videoInfo:
 +
        try:
 +
            codec_type=s['codec_type']
 +
            if codec_type=="video":
 +
                d=s['duration']
 +
                if len(d) > 0:
 +
                    duration=float(d)
 +
                    break
 +
        except KeyError, e:
 +
            pass
 +
    return duration
 +
 
 +
def getVideoResolution(videoInfo):
 +
    w=0; h=0
 +
    for s in videoInfo:
 +
        try:
 +
            codec_type=s['codec_type']
 +
            if codec_type=="video":
 +
                w=int(s['width'])
 +
                h=int(s['height'])
 +
                break
 +
        except KeyError, e:
 +
            pass
 +
    return (w,h)
 +
 
 +
def getNumberOfAudioStreams(videoInfo):
 +
    count=0
 +
    for s in videoInfo:
 +
        try:
 +
            codec_type=s['codec_type']
 +
            if codec_type=="audio":
 +
                count=count+1
 +
        except KeyError, e:
 +
            pass
 +
    return count
 +
 
 +
</pre>
 +
}}
 +
 
 +
'''mythLogging.py'''
 +
{{python|mythLogging.py|
 +
<pre>
 +
#!/usr/bin/env python
 +
import sys,os, shutil
 +
 
 +
PRINTDEBUG=True
 +
 
 +
def debug(status, message):
 +
    if PRINTDEBUG:
 +
        for line in message.split("\n"):
 +
            print "STATUS:" + status.prefix + " " + line
 +
 
 +
def error(status, message):
 +
    for line in message.split("\n"):
 +
        sys.stderr.write("ERROR: " + status.prefix + " " + str(line) + "\n")
 +
        print "ERROR: " + status.prefix + " " + str(line)
 +
    clean(status)
 +
    sys.exit(1)
 +
 
 +
def clean(status):
 +
    os.chdir(os.path.expanduser("~"))
 +
    tempDir=status.tempDir
 +
    if not tempDir is None:
 +
        # remove temporary directory
 +
        if os.path.exists(tempDir):
 +
            debug(status, "Removing temporary directory " + tempDir + ".")
 +
            shutil.rmtree(tempDir)
 +
 
 +
</pre>
 +
}}
 +
 
 +
{{Code_box|compressVideos|
 +
<pre>
 +
#! /bin/sh
 +
#
 +
# /etc/init.d/vncserver        this Script
 +
# /usr/bin/vncserver            Program
 +
#
 +
 
 +
# Check for missing binaries
 +
AUTOSSH_BIN=/home/rfs/bin/mythCompress.py
 +
test -x $AUTOSSH_BIN || exit 5
 +
 
 +
LOG="/home/rfs/logs/mythCompress.syslog"
 +
 
 +
case "$1" in
 +
    start)
 +
        echo "Starting automatic video compression"
 +
        sudo -u rfs /home/rfs/bin/mythCompress.py >> $LOG 2>> $LOG &
 +
        echo "Started"
 +
        ;;
 +
#
 +
    stop)
 +
        echo "Stopping automatic video compression not implemented"
 +
        echo 'ps axu | grep "mythCompress" | grep -v "init.d" | grep -v grep | awk '{print $2}' | xargs kill'
 +
        ;;
 +
#
 +
#
 +
    *)
 +
        echo "Usage: $0 {start|stop}"
 +
        exit 1
 +
        ;;
 +
esac
 +
 
 +
</pre>
 +
}}

Latest revision as of 19:23, 21 October 2013

Due to disc restrictions in my mythbox i decided to write a script to compress all my videos. Mythtranscode does this but the videos have been recorded quite some time ago and are spread over multiple discs. Compressing those videos to a single destination is no option because they probably won't fit onto one partition. Therefore i wrote a script that dynamically picks a show that is at least two weeks old, compresses it and saves it to a partition with enough disc space. So, there are multiple folders where videos are waiting for compression and multiple folders where compressed videos are stored. Actually, these folders can be the same, but they don't have to be as long they are visible to mythtv (see mythtv-setup and storing directories). For compressed videos i created another storing group called 'compressed' and added all destination folders. Videos are recorded in two different formats by my tv-card (PAL and 720p). This script decides upon video resolution which parameters to pick. Additionally i wanted this script to run as a system service and between 1 am and 6 pm.

Processing:

  1. Is it in the right time slot? (1am until 6pm)
  2. Pick a video for compression
  3. Temporarily rename video in mysql database
  4. Compress video in temporary folder
  5. Check if compressed video is okay (duration check)
  6. Update mysql database
  7. Back to 1.

Following programs are required: python 2.7, ffmpeg (ffmpeg version git-2013-10-01-2e2a2d8), ffprobe (comes with ffmpeg)

This script consists of three python scripts: mythCompress.py, mythLogging.py, mythFunctions.py and one system (init.d) script: compressVideos

mythCompress.py

PythonIcon.png mythCompress.py

#!/usr/bin/env python

import MySQLdb
import os, sys, glob
import shlex, subprocess
import tempfile
import time
from datetime import datetime, timedelta
from os.path import basename
import re
import shutil
import math
import atexit

from mythFunctions import *
from mythLogging import error, debug, clean

import psutil
os.nice(19)
program = psutil.Process(os.getpid())
program.set_ionice(psutil.IOPRIO_CLASS_IDLE)

######################################################################
# SETTINGS
#
# folders to save compressed videos
DESTINATION=[ "/media/myth/compressed/" , "/media/data/mythtv/compressed/" ]
# folders to read uncompressed videos
SOURCES=[ "/media/data/mythtv/default/" , "/media/myth/default/" ] 
TEMPPARENTDIR=os.path.expanduser("~/mythtv-tmp/")
PRINTDEBUG=True
mysql_opts = {
    'host': "localhost",
    'user': "mythtv",
    'pass': "CHANGEME",
    'db':   "mythconverg"
    } 
minimumAge = timedelta(days=14) # for conversion
ffmpegBinary="/home/rfs/bin/ffmpeg"
ffprobeBinary="/home/rfs/bin/ffprobe"

freeSpaceRequired=20000000000
freeSpaceInTempRequired=10000000000
######################################################################
# constants
PID=os.getpgid(0)
now=datetime.now()

#################################################################
# functions
def createTemp(status, parentDirectory):
    tempDir=None
    if os.path.exists(parentDirectory):
        tempDir = tempfile.mkdtemp(prefix=parentDirectory + str(PID) + "-")
        debug(status, "Temporary directory: " + tempDir)
    else:
        status.tempDir=None
        error(status, "Parent directory for temp does not exist.")
    # change directory to working directory
    try:
        os.chdir(tempDir)
    except OSError:
        status.tempDir=None
        error(status, "Could not find temporary directory (OSError).")
    # return result
    return tempDir

class status(object):
    prefix=None
    tempDir=None

class videoFile(object):
    channelId=None
    channelStart=None
    channelDate=None
    title=None

    # quellvideo
    filename=None
    basename=None
    videoInfo=None
    duration=None
    numAudioStreams=None

    # temporaere ausgabe
    tmpFilename=None
    tmpBasename=None

    # ziel
    newFilename=None
    newBasename=None
    newVideoInfo=None
    newDuration=None
    newNumAudioStreams=None
    

def compressVideo(s):
    #################################################################
    # choose partition to save compressed video
    # a partition with enough free space is required
    chosenDestination=None
    for d in DESTINATION:
        if enoughSpaceAvailable(d, space=freeSpaceRequired):
            chosenDestination=d
            break
    if chosenDestination is None:
        error(s, "Could not select a partition with enough free space")
    else:
        debug(s, "Chose following partition as destination for compressed video:\n\t" 
              + chosenDestination)

    TEMPDIR=s.tempDir
    #################################################################
    # choose video file for compression
    v=videoFile()
    v.filename=selectVideoFile(SOURCES, minimumAge)
    if v.filename is None:
        error(s, "No videofile could be selected.")
    v.basename=basename(v.filename)
    debug(s, "Chose videofile: " + v.filename)

    try:
        v.filesize=os.path.getsize(v.filename)
    except OSError, e:
        error(s, "Could not get mpg filesize.")
    
    # prefix for LOGGING
    s.prefix=v.basename
    tokens=v.basename.replace(".mpg","").split("_")

    if len(tokens) != 2:
        error(s, "Could not parse filename for chanid and starttime.")

    try:
        v.channelId=int(tokens[0])
        v.channelStart=tokens[1]
        v.channelDate=datetime.strptime(v.channelStart, "%Y%m%d%H%M%S")
    except ValueError:
        error(s, "Could not parse channel id and date: " + "_".join(tokens))
    # get video information
    v.videoInfo=getVideoStreamInformation(s, v.filename)
    v.resolution=getVideoResolution(v.videoInfo)
    v.duration=getVideoDuration(v.videoInfo)
    v.numAudioStreams=getNumberOfAudioStreams(v.videoInfo)
    
    v.creation=getFileCreationDate(v.filename)

    v.newBasename=str(v.channelId) + "_" + str(v.channelStart) + ".avi"
    v.newFilename=chosenDestination + v.newBasename

    v.tmpBasename=str(v.channelId) + "_" + str(v.channelStart) + ".tmp"
    v.tmpFilename=TEMPDIR + "/" + v.newBasename


    debug(s, str(v.channelId)  + "," + str(v.channelStart) + "," + str(v.channelDate) + ":\n"
          + "filename:\t" + v.filename + "\n"
          + "resolution:\t" + 'x'.join(map(str,v.resolution)) + "\n"
          )
    debug(s, "Temporary Directory: " + TEMPDIR)
    debug(s, "Temporary filename : " + v.tmpFilename)
    debug(s, "Temporary basename : " + v.tmpBasename )
    debug(s, "New basename       : " + v.newBasename )
    debug(s, "New File will be   : " + v.newFilename)


    #################################################################
    # update mysql db
    mysql = MySQLdb.connect(mysql_opts['host'], mysql_opts['user'], mysql_opts['pass'], mysql_opts['db']) 
    cursor = mysql.cursor()
    sql="SELECT title FROM recorded WHERE chanid='%s' AND starttime='%s'"
    sql=sql % (v.channelId , v.channelDate.strftime('%Y-%m-%d %H:%M:%S'))
    debug(s, "Mysql database query for title")
    debug(s, sql)
    cursor.execute(sql)
    #cursor.execute(sql, (v.channelId,  v.channelDate.strftime('%Y-%m-%d %H:%M:%S')))
    videoTable=cursor.fetchall()
    try:
        v.title=videoTable[0][0]
        if len(v.title) == 0:
            error(s, "Title has zero length")
    except IndexError, e:
        error(s, "No mysql database entry found for this video.")

    debug(s, "Title of video is  : " + v.title)

    # fix seeking and bookmarking
    debug(s, "fix Seeking and bookmarking")
    delete1="DELETE FROM `recordedseek` WHERE chanid='%s' AND starttime='%s'"
    delete1=delete1 % (v.channelId, v.channelStart)
    debug(s, delete1)
    cursor.execute(delete1)#, (v.channelId, v.channelStart))

    delete2="DELETE FROM `recordedmarkup` WHERE chanid='%s' AND starttime='%s'"
    delete2=delete2 % (v.channelId, v.channelStart)
    debug(s, delete2)
    cursor.execute(delete2)#, (v.channelId, v.channelStart))

    updateBase1="UPDATE `recorded` SET basename='%s' WHERE chanid='%s' AND starttime='%s';"
    debug(s, "\nrenaming file in database")
    updateBase1=updateBase1 % (v.tmpBasename, v.channelId, v.channelStart)
    debug(s, updateBase1)
    cursor.execute(updateBase1)#, (v.tmpBasename, v.channelId, v.channelStart))

    #################################################################
    # set conversion parameter
    ffCommand=""
    ffOutput=" " + v.tmpFilename
    # TODO remove ffClip
    if v.resolution == (1280,720):
        debug(s, "Compression profile for 1280")
        ffDefault=" -c copy"
        ffVideoCodec=" -c:v libx264 -crf:v 23 -preset:v slow -tune:v film"
        ffAudioCodec=" -c:a libvorbis -q:a 5"
        ffSize=" -s 960x540"
        ffRate=" -r 25"
        ffMaps=" -map 0:v -map 0:a"# -map 0:s"
        ffClip=" -ss 16 -t 100"
        ffClip=""
        ffCommand=ffmpegBinary + " -i " + v.filename
        ffCommand+=ffDefault + ffVideoCodec + ffAudioCodec + ffRate + ffSize + ffMaps + ffClip + ffOutput
    elif v.resolution == (720,576):
        debug(s, "Compression profile for720")
        ffDefault=" -c copy"
        ffVideoCodec=" -c:v libx264 -crf:v 23 -preset:v slow -tune:v film"
        ffAudioCodec=" -c:a libvorbis -q:a 5"
        #ffSize=" -s 960x540"
        ffSize=""
        ffMaps=" -map 0:v -map 0:a"# -map 0:s"
        ffClip=" -ss 16 -t 100"
        ffClip=""
        ffCommand=ffmpegBinary + " -i " + v.filename
        ffCommand+=ffDefault + ffVideoCodec + ffAudioCodec + ffSize + ffMaps + ffClip + ffOutput
    else:
        error(s, "unsupported format")


    #################################################################
    # compress video file
    cmd=shlex.split(ffCommand)
    debug(s, "compressing video file...")
    debug(s, "FFMPEG command:\n" + ffCommand)
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    stdout, stderr = p.communicate()

    print "Returncode:",p.returncode
    if os.path.isfile(v.tmpFilename):
        debug(s, "Video created.")
    else:
        print stdout
        print stderr
        error(s, "No video created.")

    #################################################################
    # check new filesize
    try:
        v.newFilesize=os.path.getsize(v.tmpFilename)
    except OSError, e:
        error(s, "Could not get new filesize.")

    #################################################################
    # compare video duration of result video
    v.newVideoInfo=getVideoStreamInformation(s, v.tmpFilename)
    v.newDuration=getVideoDuration(v.newVideoInfo)
    v.newNumAudioStreams=getNumberOfAudioStreams(v.newVideoInfo)
    difference=v.duration - v.newDuration
    debug(s, "Duration difference                : " + str(difference))
    if math.fabs(difference) > 2:
        debug(s, "Duration of original video: " + str(v.duration))
        debug(s, "Duration of new video     : " + str(v.newDuration))
        error(s, "Duration of result video is not equal.")

    #################################################################
    # all good ...
    # ... move compressed video file to destination
    debug(s, "moving " + v.tmpFilename + " to " + v.newFilename)
    try: 
        shutil.move(v.tmpFilename,v.newFilename)
    except shutil.Error, e:
        print "Could not move compressed video file"
        error(s, e)

    if os.path.isfile(v.newFilename):
        debug(s, "Video successfully moved.")
    else:
        error(s, "Moved videofile does not exist.")
    #################################################################
    # rename file in database
    updateBase2="UPDATE recorded SET basename='%s', filesize='%s' WHERE chanid='%s' AND starttime='%s';"
    updateBase2=updateBase2 % (v.newBasename, v.newFilesize, v.channelId, v.channelStart)
    # TODO
    # write transcoded="1"
    # 
    debug(s, updateBase2)
    cursor.execute(updateBase2)

    #################################################################
    # delete pngs 
    # 
    debug(s, "deleting... " + v.filename + "*png")
    for filePath in glob.glob(v.filename + "*png"):
        if os.path.isfile(filePath):
            debug(s, "delete " + filePath)
            os.remove(filePath)

    if os.path.isfile(v.filename):
        debug(s, "deleting original videofile: " + v.filename)
        os.remove(v.filename)
    else:
        error(s, "Could not delete original video")

    #################################################################
    clean(s)
    #sys.exit(0)
def allowedTimes():
    now = datetime.now()
    hour=int(datetime.strftime(now, "%H"))
    if hour > 0 and not (hour > 17):
        return True
    else:
        return False


s=status()
s.prefix="system: "
# cleaning up temporary directory
for filePath in glob.glob(TEMPPARENTDIR + "/*"):
    if os.path.isdir(filePath):
        debug(s, "delete old temporary directory " + filePath)
        shutil.rmtree(filePath)

count=0
#while count < 1:
while True:
    s=status()
    s.prefix="system: "
    TEMPDIR=createTemp(s, TEMPPARENTDIR)
    s.tempDir=TEMPDIR

    if not enoughSpaceAvailable(TEMPDIR, space=freeSpaceInTempRequired):
        error(s, "Not enough space free in temporary directory!!")
    atexit.register(clean, status=s)

    ############################
    # video compression logic 
    if allowedTimes():
        count+=1
        debug(s, "Video number: " + str(count))
        compressVideo(s)

    time.sleep(600)


mythFunctions.py

PythonIcon.png mythFunctions.py

#!/usr/bin/env python

import json
import shlex, subprocess
import MySQLdb
import os, sys, glob
import tempfile
import time
from datetime import datetime, timedelta
from os.path import basename
import re
import shutil


PID=os.getpgid(0)
ffprobeBinary="/home/rfs/bin/ffprobe"

from mythLogging import error

#######################################################################################
def getDisksize(filename):
    df = subprocess.Popen(["/bin/df", filename], stdout=subprocess.PIPE)
    output = df.communicate()[0]
    print output
    device, size, used, available, percent, mountpoint = output.split("\n")[1].split()
    
def get_fs_freespace(pathname):
    "Get the free space of the filesystem containing pathname"
    stat= os.statvfs(pathname)
    # use f_bfree for superuser, or f_bavail if filesystem
    # has reserved space for superuser
    return int(stat.f_bfree*stat.f_bsize)

def enoughSpaceAvailable(path, space=20000000000):
    return get_fs_freespace(path) > space

def getFileCreationDate(filename):
    t = os.path.getmtime(filename)
    then=datetime.fromtimestamp(t)
    return then

def isMPGvideoPath(path):
    return os.path.isfile(path) and path.endswith(".mpg")

def isMPGvideoFile(f, directory):
    filename=directory + "/" + f
    return os.path.isfile(filename) and f.endswith(".mpg")

# video suchen, das aelter als minimumAge ist
def selectVideoFile(SOURCES, minimumAge):
    result=None
    now = datetime.now()
    for directory in SOURCES:
        files = [f for f in os.listdir( directory ) if isMPGvideoFile(f, directory)]
        for f in files:
            filename=directory + "/" + f
            then=getFileCreationDate(filename)
            if (now - then) > minimumAge:
                return filename
    return result

def getVideoStreamInformation(status, pathtovideo):
    stdout = ""
    stderr = ""
    try:
        cmd=ffprobeBinary + " -print_format json -show_streams -loglevel quiet "
        cmd+=pathtovideo
        p = subprocess.Popen(shlex.split(cmd),
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
        stdout, stderr = p.communicate()
    except OSError:
        error(status, "Could not get video duration with ffprobe.")
    if len(stdout) == 0:
        error(status, "No data read for file: " + pathtovideo)

    data = json.loads(stdout)
    streams=None
    try:
        streams=data['streams']
    except KeyError, e:
        error(status, "Could not load stream information.")
    return streams

def getVideoDuration(videoInfo):
    duration=None
    for s in videoInfo:
        try:
            codec_type=s['codec_type']
            if codec_type=="video":
                d=s['duration']
                if len(d) > 0:
                    duration=float(d)
                    break
        except KeyError, e:
            pass
    return duration

def getVideoResolution(videoInfo):
    w=0; h=0
    for s in videoInfo:
        try:
            codec_type=s['codec_type']
            if codec_type=="video":
                w=int(s['width'])
                h=int(s['height'])
                break
        except KeyError, e:
            pass
    return (w,h)

def getNumberOfAudioStreams(videoInfo):
    count=0
    for s in videoInfo:
        try:
            codec_type=s['codec_type']
            if codec_type=="audio":
                count=count+1
        except KeyError, e:
            pass
    return count

mythLogging.py

PythonIcon.png mythLogging.py

#!/usr/bin/env python
import sys,os, shutil

PRINTDEBUG=True

def debug(status, message):
    if PRINTDEBUG:
        for line in message.split("\n"):
            print "STATUS:" + status.prefix + " " + line

def error(status, message):
    for line in message.split("\n"):
        sys.stderr.write("ERROR: " + status.prefix + " " + str(line) + "\n")
        print "ERROR: " + status.prefix + " " + str(line)
    clean(status)
    sys.exit(1)

def clean(status):
    os.chdir(os.path.expanduser("~"))
    tempDir=status.tempDir
    if not tempDir is None:
        # remove temporary directory
        if os.path.exists(tempDir):
            debug(status, "Removing temporary directory " + tempDir + ".")
            shutil.rmtree(tempDir)


Script.png compressVideos

#! /bin/sh
#
# /etc/init.d/vncserver         this Script
# /usr/bin/vncserver            Program
#

# Check for missing binaries
AUTOSSH_BIN=/home/rfs/bin/mythCompress.py
test -x $AUTOSSH_BIN || exit 5

LOG="/home/rfs/logs/mythCompress.syslog"

case "$1" in
    start)
        echo "Starting automatic video compression"
        sudo -u rfs /home/rfs/bin/mythCompress.py >> $LOG 2>> $LOG &
        echo "Started"
        ;;
#
     stop)
        echo "Stopping automatic video compression not implemented"
        echo 'ps axu | grep "mythCompress" | grep -v "init.d" | grep -v grep | awk '{print $2}' | xargs kill'
        ;;
#
#
    *)
        echo "Usage: $0 {start|stop}"
        exit 1
        ;;
esac