--- /dev/null
+ReconstructApSc-1.0
+-------------------
+2009-12-14
+Anders Holst (aho@sics.se)
+
+
+This plugin makes it possible to reconstruct missing or corrupt .ap
+and .sc files of recorded movies. These files are used for precise
+seeking and winding in the movie, and also for finding the cut
+positions when using the plugin MovieCut to cut movies.
+
+Situations when this plugin may be useful include:
+ * The .sc files are relatively new, so movies recorded with Enigma2
+ from before the spring of 2009 doesn't have them.
+ * The previous version of MovieCut did not know about .sc, and
+ therefore produced no such file for the resulting cut movie.
+ * If you have cut your movies on a PC, you may not get any .ap or .sc
+ files.
+ * If a specific movie is impossible to seek correctly in, or just
+ gets black or a frozen picture when trying to fast forward or
+ rewind, then one may suspect that the coresponding .ap and .sc
+ have got corrupt for some reason.
+ * When downloading ts-files from internet, there may not be any
+ provided .ap and .sc.
+ * After a disk crash it may be possible to rescue the .ts files
+ (because their specific structure) but perhaps not the others.
+
+The plugin uses the C++ program "reconstruct_apsc" to scan through the
+.ts file, and store the structural information into the .ap and .sc
+files. You can either tell the program to reconstruct the files for a
+specific movie, in which case any existing .ap and .sc for that movie
+will be removed first. Or, you can tell it to reconstruct all files in
+a specific directory, in which case it only reconstructs missing .ap
+and .sc files. The typical situation is that you have a directory with
+many older movies that don't have any .sc files. Note that
+reconstructing .sc files for all movies in a directory may take
+considerable time and disk activity. If the process should be
+interupted for some reason, it should however be able to continue from
+there next time you start it.
+
+Disclaimer
+
+I have tried to be careful, and to make it reasonably safe to use: It
+should never change or overwrite the actual .ts file, but only the .ap
+and .sc files; It checks for errors during the process and will abort
+in a controlled way if it happens; It remembers which file it was
+processing if it gets interupted so it can start from there next time,
+not leaving any half-finished .ap or .sc.
+
+However, all use will be on your own risk. I will not take
+responsibility for any damage or loss of movies or other files due to
+either bugs in the program or uncareful use.
+
--- /dev/null
+ /* Copyright (C) 2009 Anders Holst
+ *
+ * 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, 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.
+ */
+
+#define _LARGEFILE64_SOURCE
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <byteswap.h>
+#include <errno.h>
+
+#define LEN 24064
+
+
+char* makefilename(const char* dir, const char* base, const char* ext, const char* post)
+{
+ static char buf[256];
+ int len1, len2, len3;
+ len1 = (dir ? strlen(dir) : 0);
+ len2 = (base ? strlen(base) : 0);
+ len3 = (ext ? strlen(ext) : 0);
+ if (dir) {
+ strcpy(buf, dir);
+ if (buf[len1-1] != '/') {
+ buf[len1++] = '/';
+ buf[len1] = 0;
+ }
+ }
+ if (base)
+ strcpy(buf+len1, base);
+ if (ext && len2>=len3 && !strcmp(base+len2-len3,ext))
+ len2 -= len3;
+ if (ext)
+ strcpy(buf+len1+len2, ext);
+ if (post)
+ strcpy(buf+len1+len2+len3, post);
+ return buf;
+}
+
+int writebufinternal(int f, off64_t sz, off64_t tm)
+{
+ off64_t buf[2];
+ buf[0] = (off64_t)bswap_64((unsigned long long int)sz);
+ buf[1] = (off64_t)bswap_64((unsigned long long int)tm);
+ if (write(f, buf, 16) != 16)
+ return 1;
+ else
+ return 0;
+}
+
+int framepid(unsigned char* buf, int pos)
+{
+ return ((buf[pos+1] & 0x1f) << 8) + buf[pos+2];
+}
+
+off64_t framepts(unsigned char* buf, int pos)
+{
+ int tmp = (buf[pos+3] & 0x20 ? pos+buf[pos+4]+5 : pos+4);
+ off64_t pts;
+ if (buf[pos+1] & 0x40 &&
+ buf[pos+3] & 0x10 &&
+ buf[tmp]==0 && buf[tmp+1]==0 && buf[tmp+2]==1 &&
+ buf[tmp+7] & 0x80) {
+ pts = ((unsigned long long)(buf[tmp+9]&0xE)) << 29;
+ pts |= ((unsigned long long)(buf[tmp+10]&0xFF)) << 22;
+ pts |= ((unsigned long long)(buf[tmp+11]&0xFE)) << 14;
+ pts |= ((unsigned long long)(buf[tmp+12]&0xFF)) << 7;
+ pts |= ((unsigned long long)(buf[tmp+13]&0xFE)) >> 1;
+ } else
+ pts = 0;
+ return pts;
+}
+
+int framesearch(int fts, int first, off64_t& retpos, off64_t& retpts, off64_t& retpos2, off64_t& retdat)
+{
+ static unsigned char buf[LEN];
+ static int ind;
+ static off64_t pos = -1;
+ static off64_t num;
+ static int pid = -1;
+ static int st = 0;
+ static int sdflag = 0;
+ unsigned char* p;
+ if (pos == -1 || first) {
+ num = read(fts, buf, LEN);
+ ind = 0;
+ pos = 0;
+ st = 0;
+ }
+ while (1) {
+ p = buf+ind+st;
+ ind = -1;
+ for (; p < buf+num-6; p++)
+ if (p[0]==0 && p[1]==0 && p[2]==1) {
+ ind = ((p - buf)/188)*188;
+ if ((p[3] & 0xf0) == 0xe0 && (buf[ind+1] & 0x40) &&
+ (p-buf)-ind == (buf[ind+3] & 0x20 ? buf[ind+4] + 5 : 4)) {
+ pid = framepid(buf, ind);
+ } else if (pid != -1 && pid != framepid(buf, ind)) {
+ ind = -1;
+ continue;
+ }
+ if (p[3]==0 || p[3]==0xb3 || p[3]==0xb8) { // MPEG2
+ if (p[3]==0 && ((p[5] >> 3 & 7) == 1)) {
+ retpts = framepts(buf, ind);
+ retpos = pos + ind;
+ } else {
+ retpts = 0;
+ retpos = -1;
+ }
+ retdat = (unsigned int) p[3] | (p[4]<<8) | (p[5]<<16) | (p[6]<<24);
+ retpos2 = pos + (p - buf);
+ st = (p - buf) - ind + 1;
+ sdflag = 1;
+ return 1;
+ } else if (!sdflag && p[3]==0x09 && (buf[ind+1] & 0x40)) { // H264
+ if ((p[4] >> 5)==0) {
+ retpts = framepts(buf, ind);
+ retpos = pos + ind;
+ } else {
+ retpts = 0;
+ retpos = -1;
+ }
+ retdat = p[3] | (p[4]<<8);
+ retpos2 = pos + (p - buf);
+ st = (p - buf) - ind + 1;
+ return 1;
+ } else {
+ ind = -1;
+ continue;
+ }
+ }
+ st = 0;
+ sdflag = 0; // reset to get some fault tolerance
+ if (num == LEN) {
+ pos += num;
+ num = read(fts, buf, LEN);
+ ind = 0;
+ } else if (num) {
+ ind = num;
+ retpts = 0;
+ retdat = 0;
+ retpos = pos + num;
+ num = 0;
+ return -1;
+ } else {
+ retpts = 0;
+ retdat = 0;
+ retpos = 0;
+ return -1;
+ }
+ }
+}
+
+int do_one(int fts, int fap, int fsc)
+{
+ off64_t pos;
+ off64_t pos2;
+ off64_t pts;
+ off64_t dat;
+ int first = 1;
+ while (framesearch(fts, first, pos, pts, pos2, dat) >= 0) {
+ first = 0;
+ if (pos >= 0)
+ if (fap >= 0 && writebufinternal(fap, pos, pts))
+ return 1;
+ if (fsc >= 0 && writebufinternal(fsc, pos2, dat))
+ return 1;
+ }
+ return 0;
+}
+
+int do_movie(char* inname)
+{
+ int f_ts=-1, f_sc=-1, f_ap=-1, f_tmp=-1;
+ char* tmpname;
+ tmpname = makefilename(0, inname, ".ts", 0);
+ f_ts = open(tmpname, O_RDONLY | O_LARGEFILE);
+ if (f_ts == -1) {
+ printf("Failed to open input stream file \"%s\"\n", tmpname);
+ return 1;
+ }
+ tmpname = makefilename(0, inname, ".ts", ".reconstruct_apsc");
+ f_tmp = open(tmpname, O_WRONLY | O_CREAT | O_TRUNC, 0x1a4);
+ if (f_tmp == -1) {
+ printf("Failed to open sentry file \"%s\"\n", tmpname);
+ goto failure;
+ }
+ close(f_tmp);
+ tmpname = makefilename(0, inname, ".ts", ".ap");
+ f_ap = open(tmpname, O_WRONLY | O_CREAT | O_TRUNC, 0x1a4);
+ if (f_ap == -1) {
+ printf("Failed to open output .ap file \"%s\"\n", tmpname);
+ goto failure;
+ }
+ tmpname = makefilename(0, inname, ".ts", ".sc");
+ f_sc = open(tmpname, O_WRONLY | O_CREAT | O_TRUNC, 0x1a4);
+ if (f_sc == -1) {
+ printf("Failed to open output .sc file \"%s\"\n", tmpname);
+ goto failure;
+ }
+
+ printf(" Processing .ap and .sc of \"%s\" ... ", inname);
+ fflush(stdout);
+ if (do_one(f_ts, f_ap, f_sc)) {
+ printf("\nFailed to reconstruct files for \"%s\"\n", inname);
+ goto failure;
+ }
+ printf("done\n");
+
+ close(f_ts);
+ close(f_ap);
+ close(f_sc);
+ unlink(makefilename(0, inname, ".ts", ".reconstruct_apsc"));
+ return 0;
+ failure:
+ if (f_ts != -1)
+ close(f_ts);
+ if (f_ap != -1) {
+ close(f_ap);
+ unlink(makefilename(0, inname, ".ts", ".ap"));
+ }
+ if (f_sc != -1) {
+ close(f_sc);
+ unlink(makefilename(0, inname, ".ts", ".sc"));
+ }
+ unlink(makefilename(0, inname, ".ts", ".reconstruct_apsc"));
+ return 1;
+}
+
+int do_directory(char* dirname)
+{
+ int f_ts, f_sc, f_ap, f_tmp;
+ int do_ap, do_sc;
+ char *inname, *tmpname;
+ DIR* dir = opendir(dirname);
+ dirent* entry;
+ struct stat statbuf;
+ if (dir) {
+ while ((entry = readdir(dir))) {
+ inname = entry->d_name;
+ if (strlen(inname) > 3 && !strcmp(inname + strlen(inname) - 3, ".ts")) {
+ tmpname = makefilename(dirname, inname, ".ts", ".reconstruct_apsc");
+ errno = 0;
+ if (stat(tmpname, &statbuf) != -1)
+ do_ap = do_sc = 1;
+ else {
+ tmpname = makefilename(dirname, inname, ".ts", ".ap");
+ errno = 0;
+ do_ap = (stat(tmpname, &statbuf) == -1 && errno == ENOENT);
+ tmpname = makefilename(dirname, inname, ".ts", ".sc");
+ errno = 0;
+ do_sc = (stat(tmpname, &statbuf) == -1 && errno == ENOENT);
+ }
+ if (do_ap || do_sc) {
+ f_ts=-1, f_sc=-1, f_ap=-1, f_tmp=-1;
+ tmpname = makefilename(dirname, inname, ".ts", 0);
+ f_ts = open(tmpname, O_RDONLY | O_LARGEFILE);
+ if (f_ts == -1) {
+ printf("Failed to open input stream file \"%s\"\n", tmpname);
+ continue;
+ }
+ tmpname = makefilename(dirname, inname, ".ts", ".reconstruct_apsc");
+ f_tmp = open(tmpname, O_WRONLY | O_CREAT | O_TRUNC, 0x1a4);
+ if (f_tmp == -1) {
+ printf("Failed to open sentry file \"%s\"\n", tmpname);
+ goto failure;
+ }
+ close(f_tmp);
+ if (do_ap) {
+ tmpname = makefilename(dirname, inname, ".ts", ".ap");
+ f_ap = open(tmpname, O_WRONLY | O_CREAT | O_TRUNC, 0x1a4);
+ if (f_ap == -1) {
+ printf("Failed to open output .ap file \"%s\"\n", tmpname);
+ goto failure;
+ }
+ } else
+ f_ap = -1;
+ if (do_sc) {
+ tmpname = makefilename(dirname, inname, ".ts", ".sc");
+ f_sc = open(tmpname, O_WRONLY | O_CREAT | O_TRUNC, 0x1a4);
+ if (f_sc == -1) {
+ printf("Failed to open output .sc file \"%s\"\n", tmpname);
+ goto failure;
+ }
+ } else
+ f_sc = -1;
+
+ printf(" Processing %s of \"%s\" ... ", (do_ap ? (do_sc ? ".ap and .sc" : ".ap") : ".sc"), inname);
+ fflush(stdout);
+ if (do_one(f_ts, f_ap, f_sc)) {
+ printf("\nFailed to reconstruct files for \"%s\"\n", inname);
+ close(f_ts);
+ if (f_ap != -1) {
+ close(f_ap);
+ unlink(makefilename(dirname, inname, ".ts", ".ap"));
+ }
+ if (f_sc != -1) {
+ close(f_sc);
+ unlink(makefilename(dirname, inname, ".ts", ".sc"));
+ }
+ unlink(makefilename(dirname, inname, ".ts", ".reconstruct_apsc"));
+ continue;
+ }
+ printf("done\n");
+
+ close(f_ts);
+ close(f_ap);
+ close(f_sc);
+ unlink(makefilename(dirname, inname, ".ts", ".reconstruct_apsc"));
+ }
+ }
+ }
+ closedir(dir);
+ } else {
+ printf("Failed to open directory \"%s\"\n", dirname);
+ return 1;
+ }
+ return 0;
+ failure:
+ closedir(dir);
+ if (f_ts != -1)
+ close(f_ts);
+ if (f_ap != -1) {
+ close(f_ap);
+ unlink(makefilename(dirname, inname, ".ts", ".ap"));
+ }
+ if (f_sc != -1) {
+ close(f_sc);
+ unlink(makefilename(dirname, inname, ".ts", ".sc"));
+ }
+ unlink(makefilename(dirname, inname, ".ts", ".reconstruct_apsc"));
+ return 1;
+}
+
+int main(int argc, char* argv[])
+{
+ if (argc == 2 && *argv[1] != '-') {
+ if (do_movie(argv[1]))
+ exit(1);
+ } else if (argc == 3 && !strcmp(argv[1], "-d")) {
+ if (do_directory(argv[2]))
+ exit(1);
+ } else {
+ printf("Usage: reconstruct_apsc movie_file\n");
+ printf(" reconstruct_apsc -d movie_directory\n");
+ exit(1);
+ }
+}
+
--- /dev/null
+from Plugins.Plugin import PluginDescriptor
+from Screens.Screen import Screen
+from Screens.MessageBox import MessageBox
+from Screens.ChoiceBox import ChoiceBox
+import Screens.Standby
+from Components.ActionMap import ActionMap
+from enigma import eTimer, eServiceCenter, iServiceInformation, eConsoleAppContainer
+from os import access, chmod, X_OK
+
+recons_path = "/usr/lib/enigma2/python/Plugins/Extensions/ReconstructApSc/bin/reconstruct_apsc"
+# Hack to make sure it is executable
+if not access(recons_path, X_OK):
+ chmod(recons_path, 493)
+
+def main(session, service, **kwargs):
+ session.open(ReconstructApSc, service, **kwargs)
+
+def Plugins(**kwargs):
+ return PluginDescriptor(name="ReconstructApSc", description=_("Reconstruct AP/SC ..."), where = PluginDescriptor.WHERE_MOVIELIST, fnc=main)
+
+
+class ReconstructApSc(ChoiceBox):
+ def __init__(self, session, service):
+ self.service = service
+ serviceHandler = eServiceCenter.getInstance()
+ path = self.service.getPath()
+ info = serviceHandler.info(self.service)
+ if not info:
+ self.name = path
+ else:
+ self.name = info.getName(self.service)
+ tlist = [
+ (_("Don't reconstruct"), "CALLFUNC", self.confirmed0),
+ (_("Reconstruct the .ap and .sc files of the selected movie"), "CALLFUNC", self.confirmed1),
+ (_("Reconstruct all missing .ap and .sc files in this directory"), "CALLFUNC", self.confirmed2),
+ (_("Check any running reconstruct process"), "CALLFUNC", self.confirmed3),
+ ]
+ ChoiceBox.__init__(self, session, _("What would you like to reconstruct? (\"%s\"") % (self.name), list = tlist, selection = 0)
+ self.skinName = "ChoiceBox"
+
+ def confirmed0(self, arg):
+ self.close()
+
+ def confirmed1(self, arg):
+ ReconstructApScSpawn(self.session, self, [recons_path, self.service.getPath()], self.name, _("movie"))
+
+ def confirmed2(self, arg):
+ dir = self.dirName(self.service.getPath())
+ ReconstructApScSpawn(self.session, self, [recons_path, "-d", dir], dir, _("directory"))
+
+ def confirmed3(self, arg):
+ output = global_recons_queue.checkOutput()
+ if output == False:
+ mess = "There is no running reconstruction process"
+ else:
+ mess = "Current reconstruction process output:\n%s" % output
+ self.session.openWithCallback(self.close, MessageBox, mess, MessageBox.TYPE_INFO)
+
+ def dirName(self, str):
+ return '/'.join(str.split('/')[:-1]) + '/'
+
+
+class ReconstructApScQueue:
+ def __init__(self):
+ self.container = eConsoleAppContainer()
+ self.container.appClosed.append(self.runDone)
+ self.container.dataAvail.append(self.collOutput)
+ self.queue = []
+ self.output = ""
+ self.running = False
+
+ def enqueue(self, cb, cmd):
+ self.queue.append((cb, cmd))
+ if not self.running:
+ self.runNext()
+ return True
+ else:
+ return False
+
+ def collOutput(self, data):
+ self.output += data
+
+ def checkOutput(self):
+ if not self.running:
+ return False
+ else:
+ return self.output
+
+ def runNext(self):
+ self.output = ""
+ if not self.queue:
+ self.running = False
+ else:
+ self.running = True
+ self.container.execute(*self.queue[0][1])
+
+ def runDone(self, retval):
+ cb = self.queue[0][0]
+ self.queue = self.queue[1:]
+ cb(retval, self.output)
+ self.runNext()
+
+global_recons_errors = [_("The %s \"%s\" is successfully processed:\n%s"),
+ _("Processing failed for the %s \"%s\":\n%s")]
+
+global_recons_queue = ReconstructApScQueue()
+
+global_recons_block = False
+
+class ReconstructApScSpawn:
+ def __init__(self, session, parent, clist, name, typename):
+ global global_recons_queue
+ global global_recons_block
+ self.session = session
+ self.parent = parent
+ self.name = name
+ self.typename = typename
+ self.clist = [clist[0]] + clist
+ self.mess = ""
+ self.dialog = False
+ self.waitTimer = eTimer()
+ self.waitTimer.callback.append(self.doWaitAck)
+ if global_recons_queue.enqueue(self.doAck, self.clist):
+ mess = _("The %s \"%s\" is processed in the background.") % (self.typename, self.name)
+ else:
+ mess = _("Another movie or directory is currently processed.\nThe %s \"%s\" will be processed in the background after it.") % (self.typename, self.name)
+ global_recons_block = True
+ self.dialog = self.session.openWithCallback(self.endc, MessageBox, mess, MessageBox.TYPE_INFO)
+
+ def doAck(self, retval, output):
+ global global_recons_errors
+ self.mess = global_recons_errors[retval] % (self.typename, self.name, output)
+ self.doWaitAck()
+
+ def doWaitAck(self):
+ global global_recons_block
+ if Screens.Standby.inStandby or not self.session.in_exec or (global_recons_block and not self.dialog):
+ self.waitTimer.start(2000, True)
+ else:
+ global_recons_block = True
+ self.session.openWithCallback(self.endw, MessageBox, self.mess, MessageBox.TYPE_INFO)
+
+ def endw(self, arg = 0):
+ global global_recons_block
+ global_recons_block = False
+ if self.session.current_dialog == self.dialog:
+ self.session.current_dialog.close(True)
+ self.endc(arg)
+
+ def endc(self, arg = 0):
+ global global_recons_block
+ global_recons_block = False
+ self.dialog = False
+ self.parent.close()