From 33beb10602013aedb91eae77ec505f0f40c1885d Mon Sep 17 00:00:00 2001 From: "T. Lohmar" Date: Sun, 22 Feb 2015 21:10:17 +0100 Subject: Tools to add MP4 Metadata --- tools/append_metadata.py | 479 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 479 insertions(+) create mode 100755 tools/append_metadata.py (limited to 'tools/append_metadata.py') diff --git a/tools/append_metadata.py b/tools/append_metadata.py new file mode 100755 index 0000000..73d83f6 --- /dev/null +++ b/tools/append_metadata.py @@ -0,0 +1,479 @@ +#!/usr/bin/python + +# +# append_metadata.py: VDR on Smart TV plugin +# +# Copyright (C) 2015 T. Lohmar +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# + + +import sys, os + +class AssetMetadata: + def __init__(self, vdr_dir): + self.mTitle = "" + self.mImage = "" + self.mDesc = "" + self.mChannel = "" + self.mShort = "" + self.mImagePath = None + + self.mRecTime =-1 + + print vdr_dir + + if (os.path.exists(vdr_dir+"info.vdr")): + self._parseInfo(vdr_dir+"info.vdr") + elif (os.path.exists(vdr_dir+"info")): + self._parseInfo(vdr_dir+"info") + + if (os.path.isfile(vdr_dir+"preview_vdr.png")): + self.mImagePath= vdr_dir+"preview_vdr.png" + pass + pass + + def _parseInfo(self, p): + infd= open(p, "r") + for line in infd: + if line[0] == 'T': + self.mTitle = line[2:-1] + elif line[0] == 'C': + comp = line.split() + self.mChannel = "".join(comp[2:]) + elif line[0] == 'S': + # Short Text + self.mShort = line[2:-1] + elif line[0] == 'D': + # Description + self.mDesc = line[2:-1] + elif line[0] == 'E': + # Event Info + elm = line.split() + try: + self.mRecTime = int(elm[2]) + except: + print "WARNING: Not a number",elm[2] + + def Summary(self): + print "Title: ", self.mTitle + print "Channel: ", self.mChannel + print "Short: ", self.mShort + print "Desc: ", self.mDesc + print "RecTime: ", self.mRecTime + + +class MP4FileReader: + def __init__(self, fn): + self.mFilename = fn + self.mParentBox= "root" + self.mMoovPos = -1 + self.mMoovSize = -1 + self.mMoov64 = False + self.mIsMoovLast = False + self.mHaveUdta = False + + self.mUdtaPos = -1 + self.mUdtaSize = -1 + self.mUdta64 = False + self.mMetaPos = -1 + self.mMetaSize = -1 + self.mMeta64 = False + + self.mIlstPos = -1 + self.mIlstSize = -1 + self.mIlst64 = False + + self.mIlstKeys = [] + + self.mMvhdPos = -1 + self.mMvhdVer = -1 + + self._findRelevantBoxes() + + def readInt(self, n, ifd): + "Read n-byte uint from file (MSB first). Return -1 if no more bytes" + bStr = ifd.read(n) + if len(bStr) n_max: + tgt = n_max + for i in range (0, tgt): + if ((i % 16) == 0) and (i != 0): + print "%s %s" % (hex_str, asc_str) + asc_str = "" + hex_str = "" + + if (i % 16) == 8: + asc_str += " " + hex_str += " " + + hex_str += "%02X " % ord(in_str[i]) + if (ord(in_str[i]) >= 32 ) and (ord(in_str[i]) < 128 ): + asc_str += in_str[i] + else: + asc_str += "." + + + if len(hex_str) > 0: + for i in range (0, (16*3) - len(hex_str) +1): + hex_str += " " + print "%s %s\n" % (hex_str, asc_str) + + +class Mp4FileModifyer (MP4FileReader): + def __init__(self, fn): + MP4FileReader.__init__(self, fn) + self.mMetadata = [] + + def writeIntAtPos(self, val, n, ofd, pos): + ofd.seek(pos) + for i in range(n): + ofd.write(chr((val >> 8*(n-i-1))&255)) + + def writeInt(self, val, n, b): + for i in range(n): + b.append((val >> 8*(n-i-1))&255) + return b + + def writeString(self, val, b): + for i in range(len(val)): + b.append(ord(val[i])) + return b + + def createBoxHdr(self, size, name, b): + b= self.writeInt(size, 4, b) + b= self.writeString(name, b) + return b + + def createDataBoxHdr(self, size, t, l, b): + b= self.createBoxHdr(size + 16, "data", b) + b= self.writeInt(t, 4, b) + b= self.writeInt(l, 4, b) + return b + + def appendBlob(self, b): + for i in range(len(b)): + self.mMetadata.append(b[i]) + + def addTitle(self, title): + # add title box into the temp container + print "addTitle (size= %d): %s" %(len(title), title) + b = [] + b= self.createBoxHdr(len(title) + 8 + 16, "\xA9\x6E\x61\x6D", b) # nam + b= self.createDataBoxHdr(len(title), 1, 0, b) + b= self.writeString(title, b) + + self.appendBlob(b) + pass + + def addLongDesc(self,desc): + print "addLongDesc (size= %d)" %(len(desc)) + b = [] + b= self.createBoxHdr(len(desc) + 8 + 16, "ldes", b) + b= self.createDataBoxHdr(len(desc), 1, 0, b) + b= self.writeString(desc.replace('|', "\n"), b) + + self.appendBlob(b) + + def addShortDesc(self, desc): + print "addShortDesc (size= %d): %s" %(len(desc), desc) + size = len(desc) + if size > 255: + size = 255 + b = [] + b= self.createBoxHdr(size + 8 + 16, "desc", b) + b= self.createDataBoxHdr(size, 1, 0, b) + b= self.writeString(desc[0:size].replace('|', "\n"), b) + + self.appendBlob(b) + + def addTvNetwork(self, desc): + print "addTvNetwork (size= %d): %s" %(len(desc), desc) + size = len(desc) + if size > 255: + size = 255 + b = [] + b= self.createBoxHdr(size + 8 + 16, "tvnn", b) + b= self.createDataBoxHdr(size, 1, 0, b) + b= self.writeString(desc[0:size].replace('|', "\n"), b) + + self.appendBlob(b) + + + def addImage(self, path): + # add title box into the temp container + b = [] + f_size = os.path.getsize(path) + print "addImage size= ", f_size + + b= self.createBoxHdr(f_size + 8 + 16, "\x63\x6F\x76\x72", b) # nam + b= self.createDataBoxHdr(f_size, 0x0e, 0, b) + + idx = 0 + ifd = open(path, "r") + for i in range (f_size): + c = ifd.read(1) + b.append(ord(c)) +# if (idx % 100) == 0: +# print idx + idx += 1 + self.appendBlob(b) + ifd.close() + + def appendMetadataToFile(self): + # TODO: check the case of moov-size goes beyong 32bit space + if self.mMoov64 == False: + if (self.mMoovSize + len(self.mMetadata) ) > 0xffffffff: + print "ERROR: moov box sizes becomes larger that 32bit" + sys.exit(0) + ofd = open (self.mFilename, "r+b") + if not self.mMoov64: + self.writeIntAtPos(self.mMoovSize+ len(self.mMetadata), 4, ofd, self.mMoovPos) + else: + print "Moov box header is 64 bit" + self.writeIntAtPos(self.mMoovSize+ len(self.mMetadata), 8, ofd, self.mMoovPos +8) + + if not self.mUdta64: + self.writeIntAtPos(self.mUdtaSize+ len(self.mMetadata), 4, ofd, self.mUdtaPos) + else: + print "Udta box header is 64 bit" + self.writeIntAtPos(self.mUdtaSize+ len(self.mMetadata), 8, ofd, self.mUdtaPos +8) + + if not self.mMeta64: + self.writeIntAtPos(self.mMetaSize+ len(self.mMetadata), 4, ofd, self.mMetaPos) + else: + print "Meta box header is 64 bit" + self.writeIntAtPos(self.mMetaSize+ len(self.mMetadata), 8, ofd, self.mMetaPos +8) + + if not self.mIlst64: + self.writeIntAtPos(self.mIlstSize+ len(self.mMetadata), 4, ofd, self.mIlstPos) + else: + print "Ilst box header is 64 bit" + self.writeIntAtPos(self.mIlstSize+ len(self.mMetadata), 4, ofd, self.mIlstPos) + + ofd.seek(0, 2) + for i in range (len(self.mMetadata)): + ofd.write(chr( self.mMetadata[i])) + + ofd.close() + +if __name__ == "__main__": + meta = AssetMetadata(sys.argv[1]) + + mp4 = Mp4FileModifyer (sys.argv[2]) + mp4.addTitle(meta.mTitle) + mp4.addTvNetwork(meta.mChannel) + mp4.addShortDesc(meta.mShort) + mp4.addLongDesc(meta.mDesc) + + if meta.mImagePath != None: + mp4.addImage(meta.mImagePath) + + mp4.appendMetadataToFile() -- cgit v1.2.3