Initial revision
authorAnders Holst <aholst@users.schwerkraft.elitedvb.net>
Fri, 18 Jul 2008 21:29:56 +0000 (21:29 +0000)
committerAnders Holst <aholst@users.schwerkraft.elitedvb.net>
Fri, 18 Jul 2008 21:29:56 +0000 (21:29 +0000)
14 files changed:
moviecut/CONTROL/control [new file with mode: 0644]
moviecut/Makefile.am [new file with mode: 0644]
moviecut/Readme-MovieCut-1.2.txt [new file with mode: 0644]
moviecut/src_cc/Makefile.am [new file with mode: 0644]
moviecut/src_cc/mcut.cc [new file with mode: 0644]
moviecut/src_py/Makefile.am [new file with mode: 0644]
moviecut/src_py/__init__.py [new file with mode: 0644]
moviecut/src_py/plugin.py [new file with mode: 0644]
movieretitle/CONTROL/control [new file with mode: 0644]
movieretitle/Makefile.am [new file with mode: 0644]
movieretitle/Readme-MovieRetitle-1.2.txt [new file with mode: 0644]
movieretitle/src/Makefile.am [new file with mode: 0755]
movieretitle/src/__init__.py [new file with mode: 0644]
movieretitle/src/plugin.py [new file with mode: 0644]

diff --git a/moviecut/CONTROL/control b/moviecut/CONTROL/control
new file mode 100644 (file)
index 0000000..c51a294
--- /dev/null
@@ -0,0 +1,8 @@
+Package: enigma2-plugin-extensions-moviecut
+Version: 1.2-20080717
+Description: Perform the cuts specified with the Cutlist editor of DM7025
+Section: extra
+Priority: optional
+Architecture: mipsel
+Maintainer: Anders Holst <aho@sics.se>
+Depends: enigma2(>2.5cvs20080628)
diff --git a/moviecut/Makefile.am b/moviecut/Makefile.am
new file mode 100644 (file)
index 0000000..9c865cb
--- /dev/null
@@ -0,0 +1,3 @@
+SUBDIRS = src_cc src_py
+
+EXTRA_DIST = Readme-MovieCut-1.2.txt
diff --git a/moviecut/Readme-MovieCut-1.2.txt b/moviecut/Readme-MovieCut-1.2.txt
new file mode 100644 (file)
index 0000000..0c1baf6
--- /dev/null
@@ -0,0 +1,96 @@
+MovieCut-1.2
+------------
+2008-07-17
+Anders Holst (aho@sics.se)
+
+
+This module makes it possible to execute the cuts specified by the
+Cutlist editor on DM7025, i.e. the specified sections are actually
+removed from the file, saving disk space and simplifying if the movie
+is to be e.g. burned to a DVD. When installed it will be accessible
+from the file list menu (i.e. selecting a file in the file list and
+pressing the menu button) under the name "Execute cuts...". First use
+the Cutlist editor to set the appropriate cut marks. Then select 
+"Execute cuts..." and it will give some options for usage.
+
+The real work is done by the program "mcut". It was inspired and
+guided by the similar package "moviecutter" bu Georges. However, you
+need not wait over the night but it can run in the background as you
+keep watching on your dreambox, and a typical movie takes about 15-20
+minutes to process. The program "mcut" can also be called directly
+from a shell. With no arguments it will give a brief description of
+the options.
+
+
+News since version 1.1
+
+* Nothing really is supposed to have changed functionally. However,
+  the plugin is updated to the skin changes of 2008-04-14, and the
+  location changes of 2008-06-28, and it uses eConsoleAppContainer
+  instead of spawnv/waitpid to launch "mcut".
+
+
+News since version 1.0
+
+* Cutting is still in the background, but now a notification will pop
+  up either when cutting is successfully finished, or with a suitable
+  error message if cutting fails.
+
+* The cutting can now be done quite exact: If all cut marks are placed
+  at GOP boundaries (singlestep GOPs by pausing and pressing either
+  "rewind" or with the appropriate settings "pause"), then the
+  retained part will start with the same frame you were at when you
+  placed the IN cut, and it will stop just a few frames before
+  (typically 3 frames before - I can't easily get rid of this) the one
+  where you placed the OUT cut. Note however that due to a bug in
+  enigma2 (still there 2008-02-28) the CutListEditor will set the mark
+  at the right place, but when revisiting the mark you will end up one
+  GOP later. Trust the original mark position, not where it seems to
+  be when jumping to it.
+
+* There are no flickering between cuts any more! There is a small (3
+  frames long) pause before the next cut starts, but all flickering
+  and squares seem to be gone. I believe this is as good as it can get
+  without remuxing.
+
+
+Caveats
+
+* Since some time in the spring 2008 there seems to be a change/bug in
+  enigma2 that makes the elapsed time calculation somewhat offset
+  compared to earlier. This appears such that cut marks placed with
+  earlier versions appears to have drifted when watched in the new
+  version of enigma2. MovieCut is still callibrated to the earlier
+  calculation, and may thus also place the cuts somewhat offset from
+  the intended places. Always cut to a new file and check the result
+  to be on the safe side!
+
+* The manual specification of cuts in the "advanced cut parameter"
+  dialogue is as awkward as before to specify with the remote control:
+  "0" is mapped to ".","0", and ":" since this was most
+  straightforward to implement using existing python code. Also note
+  that the cuts are given in pairs of *included* sections, not
+  *excluded* as in the cutlist editor (i.e. there is first an IN time,
+  followed by OUT, and so on).
+
+* Although the flickering between cuts may be gone when looking at the
+  movie on the dreambox, there is no guarantee that they are gone when
+  trying to convert it to mpeg for burning to DVD or similar. It is
+  not even guaranteed that they can be easily converted at all - at
+  least one program is reported to fail to continue after the first
+  cut.
+
+
+Disclaimer
+
+I have really tried to be careful and make it reasonably "safe": It
+checks for errors during the process and will abort in a controlled
+way if it happens; If "-r"="replace" is specified, it will not remove
+any files until the whole cutting is successfully done; if "-r" is not
+given it will not overwrite any existing file with the same name as the
+destination; if no cuts are specified nothing will be done; etc.
+
+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. 
+
diff --git a/moviecut/src_cc/Makefile.am b/moviecut/src_cc/Makefile.am
new file mode 100644 (file)
index 0000000..3807b6f
--- /dev/null
@@ -0,0 +1,6 @@
+installdir = /usr/bin/
+
+bin_PROGRAMS = mcut
+
+mcut_SOURCES = mcut.cc
+
diff --git a/moviecut/src_cc/mcut.cc b/moviecut/src_cc/mcut.cc
new file mode 100644 (file)
index 0000000..1d0a367
--- /dev/null
@@ -0,0 +1,792 @@
+ /* Copyright (C) 2007, 2008 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.
+  * 
+  * You should have received a copy of the GNU General Public License
+  * along with this software; see the file COPYING.  If not, write to
+  * the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+  * Boston, MA 02111-1307 USA
+  */
+
+#define _LARGEFILE64_SOURCE
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define LEN 24064
+
+static off64_t* buf0 = 0;
+static off64_t* buf1 = 0;
+static off64_t time_offset;
+static off64_t size_offset;
+
+int use_leadin = 1;
+int use_leadout = 1;
+
+inline int absless(long long int x, int lim)
+{
+  return (x<lim && x>-lim);
+}
+
+double strtotime(char* str)
+{
+  int i=0, t1, tt;
+  char *p1, *p2;
+  double tmp;
+  p1 = str;
+  tt = strtol(p1, &p2, 10);
+  if (p1==p2) return -1.0;
+  while (*p2 == ':' && i<2) {
+    i++;
+    if (p2-p1>2) return -1.0;
+    p1 = p2+1;
+    t1 = strtol(p1, &p2, 10);
+    if (p1==p2) return -1.0;
+    tt = 60*tt + t1;
+  }
+  if (i>0 && p2-p1>2) return -1.0;
+  if (*p2 == 0) return (double)tt;
+  if (*p2 != '.') return -1.0;
+  p1 = p2+1;
+  t1 = strtol(p1, &p2, 10);
+  for (i=0, tmp=1.0; i<(p2-p1); tmp*=0.1, i++);
+  return (double)tt + tmp*t1;
+}
+
+char* timetostr(double tm)
+{
+  static char buf[15];
+  int r = (int)(tm/60);
+  sprintf(buf, "%d:%d:%.3f", r/60, r%60, tm-60*r);
+  return buf;
+}
+
+inline unsigned int byteswop(unsigned int n)
+{
+  return ((n&0xff000000)>>24) | ((n&0xff0000)>>8) | ((n&0xff00)<<8) | ((n&0xff)<<24);
+}
+
+inline unsigned long long int byteswopl(unsigned long long int n)
+{
+  return (n>>56) | ((n>>40)&0xff00) | ((n>>24)&0xff0000) | ((n>>8)&0xff000000) | ((n&0xff000000)<<8) | ((n&0xff0000)<<24) | ((n&0xff00)<<40) | ((n&0xff)<<56);
+}
+
+double inttotime(unsigned int t1, unsigned int t2)
+{
+  return (byteswop(t2)*1.1111111111111112e-05 + byteswop(t1)*47721.858844444447);
+}
+
+double lltotime(long long int t)
+{
+  return ((unsigned int)(t&0xffffffff)*1.1111111111111112e-05) + ((unsigned int)(t>>32)*47721.858844444447);
+}
+
+void timetoint(double tm, unsigned int& t1, unsigned int& t2)
+{
+  double tmp=tm/47721.858844444447;
+  t1 = byteswop((unsigned int)tmp);
+  t2 = byteswop((unsigned int)((tm - t1*47721.858844444447)*90000));
+}
+
+int readbufinternal(int f)
+{
+  off64_t* buf;
+  buf = buf0;
+  buf0 = buf1;
+  buf1 = buf;
+  if (read(f, buf, 16) != 16)
+    return 0;
+  buf[0] = (off64_t)byteswopl((unsigned long long int)buf[0]);
+  buf[1] = (off64_t)byteswopl((unsigned long long int)buf[1]);
+  return 1;
+}
+
+void writebufinternal(int f)
+{
+  off64_t buf2[2];
+  buf2[0] = (off64_t)byteswopl((unsigned long long int)buf0[0] - size_offset);
+  buf2[1] = (off64_t)byteswopl((unsigned long long int)buf0[1]);
+  write(f, buf2, 16);
+}
+
+off64_t readoff(int f, int fo, double t, int beg, double& tr)
+{
+  static off64_t lastreturn;
+  static double last;
+  static int endp;
+  off64_t sizetmp;
+  double tt, lt;
+  if (!buf0) {
+    buf0 = new off64_t[2];
+    buf1 = new off64_t[2];
+    if (!(readbufinternal(f) && readbufinternal(f))) {
+      printf("The corresponding \".ap\"-file is empty.\n");
+      exit(8);
+    }
+    time_offset = buf0[1];
+    if (buf1[1] > buf0[1] && buf1[1] - buf0[1] < 900000)
+      time_offset -= (buf1[1]-buf0[1])*buf0[0]/(buf1[0]-buf0[0]);
+    size_offset = buf0[0];
+    lastreturn = 0;
+    last = 0.0;
+    endp = 0;
+  }
+  if (t < last && t != -1.0) {
+    sizetmp = buf0[0];
+    lseek(f, 0, SEEK_SET);
+    readbufinternal(f);
+    readbufinternal(f);
+    time_offset = buf0[1];
+    if (buf1[1]>buf0[1] && buf1[1]-buf0[1]<900000)
+      time_offset -= (buf1[1]-buf0[1])*buf0[0]/(buf1[0]-buf0[0]);
+    size_offset += buf0[0] - sizetmp;
+    lastreturn = 0;
+    last = 0.0;
+    endp = 0;
+  }
+  if (t == last || endp == 1) {
+    return lastreturn;
+  }
+  if (!beg)
+    writebufinternal(fo);
+  last = t;
+  lt = lltotime(buf0[1] - time_offset);
+  tt = lltotime(buf1[1] - time_offset);
+  sizetmp = buf0[0];
+  while (tt < t || t == -1.0) {
+    if (!readbufinternal(f))
+      endp = 1;
+    if (!beg)
+      writebufinternal(fo);
+    if (endp)
+      break;
+    if (buf1[1] < buf0[1] || buf1[1] - buf0[1] > 900000) {
+      if (absless(buf1[1] + ((long long int)1)<<33 - buf0[1], 900000))
+       time_offset -= ((long long int)1)<<33;
+      else
+       time_offset += buf1[1] - buf0[1];
+    }
+    lt = tt;
+    tt = lltotime(buf1[1] - time_offset);
+  }
+  if (endp) {
+    tr = tt;
+  } else if (beg ? (lt == tt || (t-lt > tt-t && tt-t<0.18)) : (t-lt >= tt-t || t-lt>0.18)) {
+    if (!readbufinternal(f))
+      endp = 1;
+    if (!beg)
+      writebufinternal(fo);
+    tr = tt;
+  } else {
+    tr = lt;
+  }
+  if (beg)
+    size_offset += buf0[0] - sizetmp;
+  lastreturn = buf0[0];
+  return lastreturn;
+}
+
+int framepid(char* buf, int pos)
+{
+  return ((buf[pos+1] & 0x1f) << 8) + buf[pos+2];
+}
+
+int framesearch_f(char* buf, int start, int stop, int pid)
+{
+  char* p;
+  int pos = -1;
+  for (p = buf+start; p < buf+stop-3; p++)
+    if (p[0]==0 && p[1]==0 && p[2]==1 && p[3]==0) {
+        pos = ((p - buf)/188)*188;
+        if (pid == -1 || framepid(buf, pos) == pid)
+          return pos;
+    }
+  return -1;
+}
+
+int framesearch_b(char* buf, int start, int stop, int pid)
+{
+  char* p;
+  int pos = -1;
+  for (p = buf+stop-1; p >= buf+start+3; p--)
+    if (p[0]==0 && p[-1]==1 && p[-2]==0 && p[-3]==0) {
+        pos = ((p - buf)/188)*188;
+        if (pid == -1 || framepid(buf, pos) == pid)
+          return pos;
+    }
+  return -1;
+}
+
+int transfer_start(int f_ts, int f_out, off64_t n1, off64_t& n1ret)
+{
+  off64_t num;
+  int pos, tmp;
+  char buf[LEN];
+  if (use_leadin) {
+    num = 0;
+    tmp = 0;
+    do {
+      num += LEN;
+      if (num > n1)
+        tmp = LEN - (int)(num - n1), num = n1;
+      else
+        tmp = LEN;
+      lseek64(f_ts, n1 - num, SEEK_SET);
+      if (read(f_ts, buf, tmp) != tmp) return 1;
+    } while ((pos = framesearch_b(buf, 0, tmp, -1)) == -1 && num < n1);
+    if (pos != -1) {
+      if (write(f_out, buf+pos, tmp-pos) != tmp-pos) return 1;
+      n1ret = n1 - (num - pos);
+      size_offset -= (num - pos);
+      num -= tmp;
+      while (num > 0) {
+        if (read(f_ts, buf, LEN) != LEN) return 1;
+        if (write(f_out, buf, LEN) != LEN) return 1;
+        num -= LEN;
+      }
+    } else {
+      n1ret = n1;
+    }
+    return 0;
+  }
+  else {
+    lseek64(f_ts, n1, SEEK_SET);
+    n1ret = n1;
+    return 0;
+  }
+}
+
+int transfer_rest(int f_ts, int f_out, off64_t n1, off64_t n2, off64_t& n2ret)
+{
+  off64_t i;
+  int num, pos, st, pid, tmp;
+  char buf[LEN];
+  static off64_t lastn2 = -1, lastn2ret;
+  if (n1 == lastn2) {
+    i = lastn2ret;
+    lseek64(f_ts, i, SEEK_SET);
+  } else
+    i = n1;
+  for (; i+LEN<=n2; i+=LEN) {
+    if (read(f_ts, buf, LEN) != LEN) return 1;
+    if (write(f_out, buf, LEN) != LEN) return 1;
+  }
+  if (use_leadout) {
+    num = read(f_ts, buf, LEN);
+    pid = framepid(buf, n2-i);
+    st = (i < n2 ? n2-i : 0);
+    tmp = -st;
+    st += 188;
+    while ((pos = framesearch_f(buf, st, num, pid)) == -1 && num == LEN) {
+      if (write(f_out, buf, LEN) != LEN) return 1;
+      num = read(f_ts, buf, LEN);
+      st = 0;
+      tmp += LEN;
+    }
+    if (st && num < st)
+      return 1;
+    else if (pos == -1) {
+      if (write(f_out, buf, num) != num) return 1;
+      tmp += num;
+      size_offset -= tmp;
+    } else {
+      if (write(f_out, buf, pos) != pos) return 1;
+      tmp += pos;
+      size_offset -= tmp;
+    }
+    lastn2 = n2;
+    lastn2ret = n2ret = n2 + tmp;
+    return 0;
+  } else {
+    if (i < n2) {
+      if (read(f_ts, buf, n2-i) != n2-i) return 1;
+      if (write(f_out, buf, n2-i) != n2-i) return 1;
+    }
+    lastn2 = lastn2ret = n2ret = n2;
+    return 0;
+  }
+}
+
+int donextinterval1(int fc, int fco, int fa, int fao, int fts, int ftso)
+{
+  static int n = -1;
+  static double tlast, toff = 0.0;
+  static off64_t c2;
+  off64_t c1, c1ret, c2ret;
+  double ttmp;
+  unsigned int buf[3];
+  unsigned int tmp, lcheck=0;
+  if (n==0)
+    return 0;
+  else if (n==-1) {
+    n = lseek(fc, 0, SEEK_END) / 12;
+    lseek(fc, 0, SEEK_SET);
+    while (1) {
+      if (n == 0)
+       return 0;
+      read(fc, buf, 12);
+      n--;
+      tmp = byteswop(buf[2]);
+      if (tmp == 1) {
+        c1 = readoff(fa, fao, 0.0, 1, toff);
+        if (transfer_start(fts, ftso, c1, c1ret)) return -1;
+        c2 = readoff(fa, fao, inttotime(buf[0], buf[1]), 0, tlast);
+        if (transfer_rest(fts, ftso, c1, c2, c2ret)) return -1;
+        printf("Interval: %lld - %lld\n", c1ret, c2ret);
+       // move all passed marks
+       lseek(fc, 0, SEEK_SET);
+       read(fc, buf, 12);
+       while (byteswop(buf[2]) != 1) {
+         write(fco, buf, 12);
+         read(fc, buf, 12);
+       }
+       return 1;
+      } else if (tmp == 0) {
+        c1 = readoff(fa, fao, inttotime(buf[0], buf[1]), 1, toff);
+        if (transfer_start(fts, ftso, c1, c1ret)) return -1;
+       if (lcheck) {
+         buf[0] = buf[1] = 0;
+         write(fco, buf, 12);
+       }
+       break;
+      } else if (tmp == 3)
+       lcheck = 1;
+    }
+  } else {
+    while (1) {
+      read(fc, buf, 12);
+      n--;
+      tmp = byteswop(buf[2]);
+      if (tmp == 0) {
+        c1 = readoff(fa, fao, inttotime(buf[0], buf[1]), 1, ttmp);
+        if (c1 != c2)
+          if (transfer_start(fts, ftso, c1, c1ret)) return -1;
+        toff += ttmp - tlast;
+       break;
+      } else if (tmp == 3) {
+       timetoint(tlast-toff, buf[0], buf[1]);
+       write(fco, buf, 12);
+      }
+      if (n == 0)
+       return 0;
+    }
+  }
+  while (1) {
+    if (n == 0) {
+      c2 = readoff(fa, fao, -1.0, 0, tlast);
+      if (transfer_rest(fts, ftso, c1, c2, c2ret)) return -1;
+      printf("Interval: %lld - %lld\n", c1ret, c2ret);
+      return 1;
+    }
+    read(fc, buf, 12);
+    n--;
+    tmp = byteswop(buf[2]);
+    if (tmp == 1) {
+      c2 = readoff(fa, fao, inttotime(buf[0], buf[1]), 0, tlast);
+      if (transfer_rest(fts, ftso, c1, c2, c2ret)) return -1;
+      printf("Interval: %lld - %lld\n", c1ret, c2ret);
+      return 1;
+    } else if (tmp != 0) {
+      timetoint(inttotime(buf[0], buf[1])-toff, buf[0], buf[1]);
+      write(fco, buf, 12);
+    }
+  }
+  return 0;
+}
+
+int donextinterval2(int barg, int earg, char* argv[], int fc, int fco, int fa, int fao, int fts, int ftso)
+{
+  static int n = -1, i, lio = -1, lcheck=0;
+  static double tlast = 0.0, toff = 0.0;
+  static off64_t c2 = -1;
+  off64_t c1, c1ret, c2ret;
+  double ttmp, ttmp2;
+  int j, io = -1;
+  unsigned int buf[3];
+  unsigned int buff[3];
+  unsigned int tmp;
+  if (i>=earg) {
+    if (!lcheck && n!=-1) {
+      lseek(fc, 0, SEEK_SET);
+      for (j=0; j<n; j++) {
+        read(fc, buf, 12);
+        tmp = byteswop(buf[2]);
+        if (tmp == 3) {
+          timetoint(tlast-toff, buf[0], buf[1]);
+          write(fco, buf, 12);
+          break;
+        }
+      }
+    }
+    if (lio != -1) {  // Add an extra "out" at the end to avoid bug in playback
+      buff[2] = byteswop(1);
+      timetoint(tlast-toff, buff[0], buff[1]);
+      write(fco, buff, 12);
+    }
+    return 0;
+  }
+  if (n==-1) {
+    i = barg;
+    n = lseek(fc, 0, SEEK_END) / 12;
+  }
+  c1 = readoff(fa, fao, strtotime(argv[i]), 1, ttmp);
+  if (c1 != c2)
+    if (transfer_start(fts, ftso, c1, c1ret)) return -1;
+  toff += ttmp - tlast;
+  c2 = readoff(fa, fao, strtotime(argv[i+1]), 0, tlast);
+  if (transfer_rest(fts, ftso, c1, c2, c2ret)) return -1;
+  printf("Interval: %lld - %lld\n", c1ret, c2ret);
+  lseek(fc, 0, SEEK_SET);
+  for (j=0; j<n; j++) {
+    read(fc, buf, 12);
+    tmp = byteswop(buf[2]);
+    ttmp2=inttotime(buf[0], buf[1]);
+    if (tmp == 3) {
+      if (!lcheck) {
+        if (ttmp2 <= ttmp) {
+          timetoint(ttmp-toff, buf[0], buf[1]);
+          write(fco, buf, 12);
+          lcheck = 1;
+        } else if (ttmp2 <= tlast) {
+          timetoint(ttmp2-toff, buf[0], buf[1]);
+          write(fco, buf, 12);
+          lcheck = 1;
+        }
+      }
+    } else if (ttmp2 >= ttmp && ttmp2 <= tlast) {
+      if (tmp < 2) {
+        if (lio != io && lio != -1) {
+          buff[2] = byteswop(io);
+          timetoint(ttmp-toff, buff[0], buff[1]);
+          write(fco, buff, 12);
+        }
+        lio = io = tmp;
+      }
+      timetoint(ttmp2-toff, buf[0], buf[1]);
+      write(fco, buf, 12);
+    } else if (tmp < 2) {
+      io = tmp;
+    }
+  }
+  i+=2;
+  return 1;
+}
+
+char* makefilename(const char* base, const char* pre, const char* ext, const char* post)
+{
+  static char buf[256];
+  int len1, len2, len3;
+  len1 = strlen(base);
+  len2 = (pre ? strlen(pre) : 0);
+  len3 = (ext ? strlen(ext) : 0);
+  strcpy(buf, base);
+  if (ext && len1>=len3 && !strcmp(base+len1-len3,ext))
+    len1 -= len3;
+  if (pre)
+    strcpy(buf+len1, pre);
+  if (ext)
+    strcpy(buf+len1+len2, ext);
+  if (post)
+    strcpy(buf+len1+len2+len3, post);
+  return buf;
+}
+
+void copymeta(int n, int f1, int f2, const char* title, const char* suff, const char* descr)
+{ 
+  int i, j, k;
+  char* buf = new char[n];
+  read(f1, buf, n);
+  for (i=0; i<n; i++)
+    if (buf[i] == 10)
+      break;
+  write(f2, buf, i);
+  if (i == n) return;
+  for (j=i+1; j<n; j++)
+    if (buf[j] == 10)
+      break;
+  if (title) {
+    write(f2, buf+i, 1);
+    for (k=0; title[k] && title[k] != 10; k++);
+    write(f2, title, k);
+  } else {
+    write(f2, buf+i, j-i);
+    if (suff && j-i>1)
+      write(f2, suff, strlen(suff));
+  }
+  if (j == n) return;
+  i = j;
+  for (j=i+1; j<n; j++)
+    if (buf[j] == 10)
+      break;
+  if (descr) {
+    write(f2, buf+i, 1);
+    for (k=0; descr[k] && descr[k] != 10; k++);
+    write(f2, descr, k);
+  } else {
+    write(f2, buf+i, j-i);
+  }
+  if (j < n)
+    write(f2, buf+j, n-j);
+  delete [] buf;
+}
+
+int main(int argc, char* argv[])
+{
+  int f_ts, f_out, f_cuts, f_cutsout, f_ap, f_apout, f_meta, f_metaout;
+  char* tmpname;
+  const char* suff = 0;
+  char* inname = 0;
+  char* outname = 0;
+  char* title = 0;
+  char* descr = 0;
+  int cutarg = 0, cutargend = 0;
+  int replace = 0;
+  int i, j, ok, bad = 0;
+  double t1, t2;
+  struct stat statbuf;
+  struct stat64 statbuf64;
+
+  for (i=1; i<argc; i++) {
+    if (!strcmp(argv[i], "-r"))
+      replace = 1;
+    else if (!strcmp(argv[i], "-o")) {
+      if (i == argc-1) {
+        bad = 1;
+        break;
+      }
+      outname = argv[++i];
+    } else if (!strcmp(argv[i], "-n")) {
+      if (i == argc-1) {
+        bad = 1;
+        break;
+      }
+      title = argv[++i];
+    } else if (!strcmp(argv[i], "-d")) {
+      if (i == argc-1) {
+        bad = 1;
+        break;
+      }
+      descr = argv[++i];
+    } else if (!strcmp(argv[i], "-c")) {
+      cutarg = ++i;
+      for (j=i; j<argc; j+=2) {
+        t1 = strtotime(argv[j]);
+        t2 = (j+1<argc ? strtotime(argv[j+1]) : -1.0);
+        if (t1 < 0 || t2 < 0)
+          break;
+        else if (t1 > t2) {
+          printf("Bad time interval: %s - %s\n", argv[j], argv[j+1]);
+          bad = 1;
+          break;
+        }
+      }
+      cutargend = i = j;
+      if (bad)
+        break;
+    } else if (*argv[i] == '-' && (*(argv[i]+1) == 0 ||*(argv[i]+2) == 0)) {
+      bad = 1;
+      break;
+    } else if (!inname)
+      inname = argv[i];
+    else {
+      bad = 1;
+      break;
+    }
+  }
+  if (argc == 1 || bad) {
+    printf("Usage: mcut [-r] [-o output_ts_file] [-n title] [-d description] ts_file [-c start1 end1 [start2 end2] ... ]\n");
+    printf("   -r : Replace (= remove) the original movie.\n");
+    printf("   -o : Filename of resulting movie (defaults to the original name appended by \" cut\", unless -r is given).\n");
+    printf("   -n : Title of resulting movie.\n");
+    printf("   -d : Description of resulting movie.\n");
+    printf("   -c : A sequence of starttime and endtime pairs. Each time is given as hour:min:sec. The portion between start and end is retained (i.e. not cut away).\n");
+    exit(1);
+  }
+  if (outname) {
+    suff = 0;
+  } else {
+    outname = inname;
+    suff = (replace ? "_" : " cut");
+  }
+  tmpname = makefilename(inname, 0, ".ts", 0);
+  f_ts = open(tmpname, O_RDONLY | O_LARGEFILE);
+  if (f_ts == -1) {
+    printf("Failed to open input stream file \"%s\"\n", tmpname);
+    exit(2);
+  }
+  tmpname = makefilename(inname, 0, ".ts", ".cuts");
+  f_cuts = open(tmpname, O_RDONLY);
+  if (f_cuts == -1) {
+    printf("Failed to open input cuts file \"%s\"\n", tmpname);
+    close(f_ts);
+    exit(3);
+  }
+  tmpname = makefilename(inname, 0, ".ts", ".ap");
+  f_ap = open(tmpname, O_RDONLY);
+  if (f_ap == -1) {
+    printf("Failed to open input ap file \"%s\"\n", tmpname);
+    close(f_ts);
+    close(f_cuts);
+    exit(4);
+  }
+  if (fstat64(f_ts, &statbuf64)) {
+    printf("Failed to stat input stream file.\n");
+    close(f_ts);
+    close(f_cuts);
+    close(f_ap);
+    exit(2);
+  }
+  tmpname = makefilename(outname, suff, ".ts", 0);
+  f_out = open(tmpname, O_WRONLY | O_CREAT | O_EXCL | O_LARGEFILE, statbuf64.st_mode & 0xfff);
+  if (f_out == -1) {
+    printf("Failed to open output stream file \"%s\"\n", tmpname);
+    close(f_ts);
+    close(f_cuts);
+    close(f_ap);
+    exit(5);
+  }
+  if (fstat(f_cuts, &statbuf)) {
+    printf("Failed to stat input cuts file.\n");
+    close(f_ts);
+    close(f_cuts);
+    close(f_ap);
+    close(f_out);
+    unlink(makefilename(outname, suff, ".ts", 0));
+    exit(3);
+  }
+  tmpname = makefilename(outname, suff, ".ts", ".cuts");
+  f_cutsout = open(tmpname, O_WRONLY | O_CREAT | O_TRUNC, statbuf.st_mode & 0xfff);
+  if (f_cutsout == -1) {
+    printf("Failed to open output cuts file \"%s\"\n", tmpname);
+    close(f_ts);
+    close(f_cuts);
+    close(f_ap);
+    close(f_out);
+    unlink(makefilename(outname, suff, ".ts", 0));
+    exit(6);
+  }
+  if (fstat(f_ap, &statbuf)) {
+    printf("Failed to stat input ap file.\n");
+    close(f_ts);
+    close(f_cuts);
+    close(f_ap);
+    close(f_out);
+    close(f_cutsout);
+    unlink(makefilename(outname, suff, ".ts", 0));
+    unlink(makefilename(outname, suff, ".ts", ".cuts"));
+    exit(4);
+  }
+  tmpname = makefilename(outname, suff, ".ts", ".ap");
+  f_apout = open(tmpname, O_WRONLY | O_CREAT | O_TRUNC, statbuf.st_mode & 0xfff);
+  if (f_apout == -1) {
+    printf("Failed to open output ap file \"%s\"\n", tmpname);
+    close(f_ts);
+    close(f_cuts);
+    close(f_ap);
+    close(f_out);
+    close(f_cutsout);
+    unlink(makefilename(outname, suff, ".ts", 0));
+    unlink(makefilename(outname, suff, ".ts", ".cuts"));
+    exit(7);
+  }
+
+  if (cutarg)
+    ok = donextinterval2(cutarg, cutargend, argv, f_cuts, f_cutsout, f_ap, f_apout, f_ts, f_out);
+  else
+    ok = donextinterval1(f_cuts, f_cutsout, f_ap, f_apout, f_ts, f_out);
+  if (!ok) {
+    printf("There are no cuts specified. Leaving the movie as it is.\n");
+    close(f_ts);
+    close(f_cuts);
+    close(f_ap);
+    close(f_out);
+    close(f_cutsout);
+    close(f_apout);
+    unlink(makefilename(outname, suff, ".ts", 0));
+    unlink(makefilename(outname, suff, ".ts", ".cuts"));
+    unlink(makefilename(outname, suff, ".ts", ".ap"));
+    exit(9);
+  }
+
+  while (ok > 0) {
+    if (cutarg)
+      ok = donextinterval2(cutarg, cutargend, argv, f_cuts, f_cutsout, f_ap, f_apout, f_ts, f_out);
+    else
+      ok = donextinterval1(f_cuts, f_cutsout, f_ap, f_apout, f_ts, f_out);
+  }
+
+  close(f_ts);
+  close(f_cuts);
+  close(f_ap);
+  close(f_out);
+  close(f_cutsout);
+  close(f_apout);
+  if (ok < 0) {
+    printf("Copying %s failed, read/write error.\n", makefilename(inname, 0, ".ts", 0));
+    unlink(makefilename(outname, suff, ".ts", 0));
+    unlink(makefilename(outname, suff, ".ts", ".cuts"));
+    unlink(makefilename(outname, suff, ".ts", ".ap"));
+    exit(10);
+  }
+
+  tmpname = makefilename(inname, 0, ".ts", ".meta");
+  f_meta = open(tmpname, O_RDONLY);
+  if (f_meta == -1) {
+    printf("Failed to open input meta file \"%s\"\n", tmpname);
+    exit(0);
+  }
+  if (fstat(f_meta, &statbuf)) {
+    printf("Failed to stat input meta file.\n");
+    close(f_meta);
+    exit(0);
+  }
+  tmpname = makefilename(outname, suff, ".ts", ".meta");
+  f_metaout = open(tmpname, O_WRONLY | O_CREAT | O_TRUNC, statbuf.st_mode & 0xfff);
+  if (f_metaout == -1) {
+    printf("Failed to open output meta file \"%s\"\n", tmpname);
+    close(f_meta);
+    exit(0);
+  }
+  copymeta((int)statbuf.st_size, f_meta, f_metaout, title, (replace ? 0 : suff), descr);
+  close(f_meta);
+  close(f_metaout);
+
+  if (replace) {
+    if (suff) {
+      tmpname = makefilename(inname, 0, ".ts", 0);
+      tmpname = strcpy(new char[strlen(tmpname)+1], tmpname);
+      unlink(tmpname);
+      rename(makefilename(outname, suff, ".ts", 0), tmpname);
+      tmpname = makefilename(inname, 0, ".ts", ".cuts");
+      tmpname = strcpy(new char[strlen(tmpname)+1], tmpname);
+      unlink(tmpname);
+      rename(makefilename(outname, suff, ".ts", ".cuts"), tmpname);
+      tmpname = makefilename(inname, 0, ".ts", ".ap");
+      tmpname = strcpy(new char[strlen(tmpname)+1], tmpname);
+      unlink(tmpname);
+      rename(makefilename(outname, suff, ".ts", ".ap"), tmpname);
+      tmpname = makefilename(inname, 0, ".ts", ".meta");
+      tmpname = strcpy(new char[strlen(tmpname)+1], tmpname);
+      unlink(tmpname);
+      rename(makefilename(outname, suff, ".ts", ".meta"), tmpname);
+    } else {
+      unlink(makefilename(inname, 0, ".ts", 0));
+      unlink(makefilename(inname, 0, ".ts", ".cuts"));
+      unlink(makefilename(inname, 0, ".ts", ".ap"));
+      unlink(makefilename(inname, 0, ".ts", ".meta"));
+    }
+  }
+}
+
diff --git a/moviecut/src_py/Makefile.am b/moviecut/src_py/Makefile.am
new file mode 100644 (file)
index 0000000..56f5b79
--- /dev/null
@@ -0,0 +1,3 @@
+installdir = /usr/lib/enigma2/python/Plugins/Extensions/MovieCut
+
+install_PYTHON = __init__.py plugin.py
diff --git a/moviecut/src_py/__init__.py b/moviecut/src_py/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/moviecut/src_py/plugin.py b/moviecut/src_py/plugin.py
new file mode 100644 (file)
index 0000000..edf4e24
--- /dev/null
@@ -0,0 +1,304 @@
+from Plugins.Plugin import PluginDescriptor
+from Screens.Screen import Screen
+from Screens.MessageBox import MessageBox
+from Screens.ChoiceBox import ChoiceBox
+from Screens.LocationBox import MovieLocationBox
+import Screens.Standby
+from Components.config import *
+from Components.ActionMap import ActionMap, NumberActionMap
+from Components.ConfigList import ConfigList, ConfigListScreen
+from Components.Button import Button
+from Components.Label import Label
+from Components.Pixmap import Pixmap
+from enigma import eTimer, eServiceReference, eServiceCenter, iServiceInformation, eConsoleAppContainer
+from os import WIFEXITED, WEXITSTATUS
+
+def main(session, service, **kwargs):
+       session.open(MovieCut, service, **kwargs)
+
+def Plugins(**kwargs):
+       return PluginDescriptor(name="MovieCut", description="Execute cuts...", where = PluginDescriptor.WHERE_MOVIELIST, fnc=main)
+
+
+class MovieCut(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 = []
+               tlist.append(("Don't cut", "CALLFUNC", self.confirmed0))
+               tlist.append(("Replace the original movie with the cut movie", "CALLFUNC", self.confirmed1))
+               tlist.append(("Place the cut movie in a new file ending with \" cut\"", "CALLFUNC", self.confirmed2))
+               tlist.append(("Advanced cut parameter settings...", "CALLFUNC", self.confirmed3))
+               ChoiceBox.__init__(self, session, _("How would you like to cut \"%s\"?") % (self.name), list = tlist, selection = 0)
+               self.skinName = "ChoiceBox"
+
+       def confirmed0(self, arg):
+               self.close()
+
+       def confirmed1(self, arg):
+               MovieCutSpawn(self.session, self, ["/usr/bin/mcut", "-r", self.service.getPath()], self.name)
+
+       def confirmed2(self, arg):
+               MovieCutSpawn(self.session, self, ["/usr/bin/mcut", self.service.getPath()], self.name)
+
+       def confirmed3(self, arg):
+               serviceHandler = eServiceCenter.getInstance()
+               info = serviceHandler.info(self.service)
+               self.path = self.service.getPath()
+               self.name = info.getName(self.service)
+               self.descr = info.getInfoString(self.service, iServiceInformation.sDescription)
+               self.session.openWithCallback(self.advcutConfirmed, AdvancedCutInput, self.name, self.path, self.descr)
+
+       def advcutConfirmed(self, ret):
+               if len(ret) <= 1 or not ret[0]:
+                       self.close()
+                       return
+               clist = ["/usr/bin/mcut"]
+               if ret[1] == True:
+                       clist.append("-r")
+               clist.append(self.service.getPath())
+               if ret[2] != False:
+                       clist += ["-o", ret[2]]
+               if ret[3] != False:
+                       clist += ["-n", ret[3]]
+               if ret[4] != False:
+                       clist += ["-d", ret[4]]
+               if ret[5] != False:
+                       clist.append("-c")
+                       clist += ret[5]
+               MovieCutSpawn(self.session, self, clist, self.name)
+               
+class AdvancedCutInput(Screen, ConfigListScreen):
+       skin = """
+       <screen name="AdvancedCutInput" position="80,100" size="550,320" title="Cut Parameter Input">
+               <widget name="config" position="5,10" size="530,250" />
+               <widget name="ok" position="90,265" size="140,40" pixmap="skin_default/buttons/green.png" alphatest="on" />
+               <widget name="oktext" position="90,265" size="140,40" valign="center" halign="center" zPosition="2" font="Regular;20" transparent="1" />
+               <widget name="cancel" position="320,265" size="140,40" pixmap="skin_default/buttons/red.png" alphatest="on" />
+               <widget name="canceltext" position="320,265" size="140,40" valign="center" halign="center" zPosition="2" font="Regular;20" transparent="1" />
+       </screen>"""
+
+       def __init__(self, session, name, path, descr):
+               self.skin = AdvancedCutInput.skin
+               Screen.__init__(self, session)
+
+               self["oktext"] = Label(_("OK"))
+               self["canceltext"] = Label(_("Cancel"))
+               self["ok"] = Pixmap()
+               self["cancel"] = Pixmap()
+
+               if self.baseName(path) == self.baseName(name):
+                       self.title = ""
+               else:
+                       self.title = name
+               self.dir = self.dirName(path)
+               self.file = self.baseName(path) + " cut"
+               self.descr = descr
+               self.input_replace = ConfigSelection(choices = [("no", _("No")), ("yes", _("Yes"))], default = "no")
+               self.input_file = ConfigText(default = self.file, fixed_size = False, visible_width = 45)
+               self.input_title = ConfigText(default = self.title, fixed_size = False, visible_width = 45)
+               self.input_descr = ConfigText(default = self.descr, fixed_size = False, visible_width = 45)
+               tmp = config.movielist.videodirs.value
+               if not self.dir in tmp:
+                       tmp.append(self.dir)
+               self.input_dir = ConfigSelection(choices = tmp, default = self.dir)
+               self.input_manual = ConfigSelection(choices = [("no", _("Cutlist")), ("yes", _("Manual specification"))], default = "no")
+               self.input_space = ConfigNothing()
+               self.input_manualcuts = ConfigText(default = "", fixed_size = False)
+               self.input_manualcuts.setUseableChars(" 0123456789:.")
+               self["actions"] = NumberActionMap(["SetupActions"],
+               {
+                       "ok": self.keySelectOrGo,
+                       "save": self.keyGo,
+                       "cancel": self.keyCancel,
+               }, -2)
+
+               self.list = []
+               ConfigListScreen.__init__(self, self.list)
+               self.entry_replace = getConfigListEntry(_("Replace original:"), self.input_replace)
+               self.entry_file = getConfigListEntry(_("New filename:"), self.input_file)
+               self.entry_title = getConfigListEntry(_("New title:"), self.input_title)
+               self.entry_descr = getConfigListEntry(_("New description:"), self.input_descr)
+               self.entry_dir = getConfigListEntry(_("New location:"), self.input_dir)
+               self.entry_manual = getConfigListEntry(_("Cut source:"), self.input_manual)
+               self.entry_space = getConfigListEntry(_("Cuts (an IN OUT IN OUT ... sequence of hour:min:sec)"), self.input_space)
+               self.entry_manualcuts = getConfigListEntry(_(":"), self.input_manualcuts)
+               self.createSetup(self["config"])
+
+       def createSetup(self, configlist):
+               self.list = []
+               self.list.append(self.entry_replace)
+               if self.input_replace.value == "no":
+                       self.list.append(self.entry_file)
+                       self.list.append(self.entry_dir)
+               self.list.append(self.entry_title)
+               self.list.append(self.entry_descr)
+               self.list.append(self.entry_manual)
+               if self.input_manual.value == "yes":
+                       self.list.append(self.entry_space)
+                       self.list.append(self.entry_manualcuts)
+               configlist.list = self.list
+               configlist.l.setList(self.list)
+
+       def keyLeft(self):
+               ConfigListScreen.keyLeft(self)
+               cc = self["config"].getCurrent()
+               if cc is self.entry_replace or cc is self.entry_manual:
+                       self.createSetup(self["config"])
+
+       def keyRight(self):
+               ConfigListScreen.keyRight(self)
+               cc = self["config"].getCurrent()
+               if cc is self.entry_replace or cc is self.entry_manual:
+                       self.createSetup(self["config"])
+
+       def pathSelected(self, res):
+               if res is not None:
+                       if config.movielist.videodirs.value != self.input_dir.choices:
+                               self.input_dir.setChoices(config.movielist.videodirs.value, default=res)
+                       self.input_dir.value = res
+
+       def keySelectOrGo(self):
+               if self["config"].getCurrent() == self.entry_dir:
+                       self.session.openWithCallback(
+                               self.pathSelected,
+                               MovieLocationBox,
+                               _("Choose target folder"),
+                               self.input_dir.value,
+                       )
+               else:
+                       self.keyGo()
+
+       def keyGo(self):
+               if self.input_replace.value == "yes":
+                       path = False
+               else:
+                       path = self.rejoinName(self.input_dir.value, self.input_file.value)
+               if self.input_manual.value == "no":
+                       cuts = False
+               else:
+                       cuts = self.input_manualcuts.value.split(' ')
+                       while "" in cuts:
+                               cuts.remove("")
+               self.close((True, self.input_replace.value, path, self.input_title.value, self.input_descr.value, cuts))
+
+       def keyCancel(self):
+               self.close((False,))
+
+       def baseName(self, str):
+               name = str.split('/')[-1]
+               if name.endswith(".ts") is True:
+                       return name[:-3]
+               else:
+                       return name
+
+       def dirName(self, str):
+               return '/'.join(str.split('/')[:-1]) + '/'
+
+       def rejoinName(self, dir, name):
+               name = name.strip()
+               if name.endswith(".ts") is True:
+                       return dir + name[:-3]
+               else:
+                       return dir + name
+
+class MovieCutQueue:
+       def __init__(self):
+               self.container = eConsoleAppContainer()
+               self.container.appClosed.get().append(self.runDone)
+               self.queue = []
+               self.running = False
+
+       def enqueue(self, cb, cmd):
+               self.queue.append((cb, cmd))
+               if not self.running:
+                       self.running = True
+                       self.runNext()
+                       return True
+               else:
+                       return False
+
+       def runNext(self):
+               if not self.queue:
+                       self.running = False
+               else:
+                       self.container.execute(self.queue[0][1][0], self.queue[0][1])
+
+       def runDone(self, retval):
+               cb = self.queue[0][0]
+               self.queue = self.queue[1:]
+               cb(retval)
+               self.runNext()
+
+global_mcut_errors = ["The movie \"%s\" is successfully cut",
+                     "Cutting failed for movie \"%s\":\nBad arguments",
+                     "Cutting failed for movie \"%s\":\nCouldn't open input .ts file",
+                     "Cutting failed for movie \"%s\":\nCouldn't open input .cuts file",
+                     "Cutting failed for movie \"%s\":\nCouldn't open input .ap file",
+                     "Cutting failed for movie \"%s\":\nCouldn't open output .ts file",
+                     "Cutting failed for movie \"%s\":\nCouldn't open output .cuts file",
+                     "Cutting failed for movie \"%s\":\nCouldn't open output .ap file",
+                     "Cutting failed for movie \"%s\":\nEmpty .ap file",
+                     "Cutting failed for movie \"%s\":\nNo cuts specified",
+                     "Cutting failed for movie \"%s\":\nRead/write error (disk full?)",
+                     "Cutting was aborted for movie \"%s\""]
+
+global_mcut_queue = MovieCutQueue()
+
+global_mcut_block = False
+
+class MovieCutSpawn:
+       def __init__(self, session, parent, clist, name):
+               global global_mcut_queue
+               global global_mcut_block
+               self.session = session
+               self.parent = parent
+               self.name = name
+               self.clist = clist
+               self.mess = ""
+               self.dialog = False
+               self.waitTimer = eTimer()
+               self.waitTimer.callback.append(self.doWaitAck)
+               if global_mcut_queue.enqueue(self.doAck, clist):
+                       mess = _("The movie \"%s\" is cut in the background.") % (self.name)
+               else:
+                       mess = _("Another movie is currently cut.\nThe movie \"%s\" will be cut in the background after it.") % (self.name)
+               global_mcut_block = True
+               self.dialog = self.session.openWithCallback(self.endc, MessageBox, mess, MessageBox.TYPE_INFO)
+
+       def doAck(self, retval):
+               global global_mcut_errors
+#              if WIFEXITED(retval):
+#                      self.mess = global_mcut_errors[WEXITSTATUS(retval)] % (self.name)
+#              else:
+#                      self.mess = global_mcut_errors[-1] % (self.name)
+               self.mess = global_mcut_errors[retval] % (self.name)
+               self.doWaitAck()
+
+       def doWaitAck(self):
+               global global_mcut_block
+               if Screens.Standby.inStandby or not self.session.in_exec or (global_mcut_block and not self.dialog):
+                       self.waitTimer.start(2000, True)
+               else:
+                       global_mcut_block = True
+                       self.session.openWithCallback(self.endw, MessageBox, self.mess, MessageBox.TYPE_INFO)
+
+       def endw(self, arg = 0):
+               global global_mcut_block
+               global_mcut_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_mcut_block
+               global_mcut_block = False
+               self.dialog = False
+               self.parent.close()
+#              self.session.current_dialog.close()
diff --git a/movieretitle/CONTROL/control b/movieretitle/CONTROL/control
new file mode 100644 (file)
index 0000000..992a470
--- /dev/null
@@ -0,0 +1,8 @@
+Package: enigma2-plugin-extensions-movieretitle
+Version: 1.2-20080717
+Description: Change the filename, title, description and location of a movie
+Section: extra
+Priority: optional
+Architecture: mipsel
+Maintainer: Anders Holst <aho@sics.se>
+Depends: enigma2(>2.5cvs20080628)
diff --git a/movieretitle/Makefile.am b/movieretitle/Makefile.am
new file mode 100644 (file)
index 0000000..ef67721
--- /dev/null
@@ -0,0 +1,3 @@
+SUBDIRS = src
+
+EXTRA_DIST = Readme-MovieRetitle-1.2.txt
diff --git a/movieretitle/Readme-MovieRetitle-1.2.txt b/movieretitle/Readme-MovieRetitle-1.2.txt
new file mode 100644 (file)
index 0000000..50feb14
--- /dev/null
@@ -0,0 +1,26 @@
+MovieRetitle-1.2
+----------------
+2008-07-17
+Anders Holst (aho@sics.se)
+
+
+With this module it is possible to change the filename, title,
+description, and location of a movie. When installed it will show up
+in the movie list menu (i.e. find a movie in the movie selection list
+and press the menu button) under the name "Change name...".
+
+This version requires an Enigma2 version from later than 2008-06-28,
+but should work on any Dreambox model running Enigma2.
+
+News since version 1.1:
+
+* It is possible to move to another location. If the other location is
+  on another device, moving will be performed in the background and a
+  notification given when moving is finished. The old movie will not
+  disappear from the movie list until then. It is possible to request
+  moving more movies before the first one has finished, but they will
+  be queued up and moved after each other not the stress the external
+  media too much.
+
+* Adapted to the skin changes from 2008-04-14.
+
diff --git a/movieretitle/src/Makefile.am b/movieretitle/src/Makefile.am
new file mode 100755 (executable)
index 0000000..d5a66dd
--- /dev/null
@@ -0,0 +1,3 @@
+installdir = /usr/lib/enigma2/python/Plugins/Extensions/MovieRetitle
+
+install_PYTHON = __init__.py plugin.py
diff --git a/movieretitle/src/__init__.py b/movieretitle/src/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/movieretitle/src/plugin.py b/movieretitle/src/plugin.py
new file mode 100644 (file)
index 0000000..d6b2767
--- /dev/null
@@ -0,0 +1,302 @@
+from Plugins.Plugin import PluginDescriptor
+from Screens.Screen import Screen
+from Screens.MessageBox import MessageBox
+from Screens.LocationBox import MovieLocationBox
+import Screens.Standby
+from Components.config import *
+from Components.ActionMap import ActionMap, NumberActionMap
+from Components.ConfigList import ConfigList, ConfigListScreen
+from Components.Button import Button
+from Components.Label import Label
+from Components.Pixmap import Pixmap
+from enigma import eTimer, eServiceReference, eServiceCenter, iServiceInformation, eConsoleAppContainer
+import os
+
+def main(session, service, **kwargs):
+       session.open(MovieRetitle, service, session.current_dialog, **kwargs)
+
+def Plugins(**kwargs):
+       return PluginDescriptor(name="MovieRetitle", description=_("Change name..."), where = PluginDescriptor.WHERE_MOVIELIST, fnc=main)
+
+
+class MovieRetitle(Screen, ConfigListScreen):
+       skin = """
+       <screen name="TitleDescrInput" position="100,150" size="500,200" title="Name and Description Input">
+               <widget name="config" position="10,10" size="480,120" />
+               <widget name="ok" position="70,140" size="140,40" pixmap="skin_default/buttons/green.png" alphatest="on" />
+               <widget name="oktext" position="70,140" size="140,40" valign="center" halign="center" zPosition="2" font="Regular;20" transparent="1" />
+               <widget name="cancel" position="290,140" size="140,40" pixmap="skin_default/buttons/red.png" alphatest="on" />
+               <widget name="canceltext" position="290,140" size="140,40" valign="center" halign="center" zPosition="2" font="Regular;20" transparent="1" />
+       </screen>"""
+
+       def __init__(self, session, service, parent, args = 0):
+               Screen.__init__(self, session)
+               self.session = session
+               self.service = service
+               self.parentscreen = parent
+               Screen.__init__(self, session)
+               serviceHandler = eServiceCenter.getInstance()
+               info = serviceHandler.info(self.service)
+               self.path = self.service.getPath()
+               if self.path.endswith(".ts") is True:
+                       self.path = self.path[:-3]
+               self.dir = self.dirName(self.path)
+               self.file = self.baseName(self.path)
+               self.name = info.getName(self.service)
+               if self.file == self.baseName(self.name):
+                       self.title = ""
+               else:
+                       self.title = self.name
+               self.descr = info.getInfoString(self.service, iServiceInformation.sDescription)
+
+               self["oktext"] = Label(_("OK"))
+               self["canceltext"] = Label(_("Cancel"))
+               self["ok"] = Pixmap()
+               self["cancel"] = Pixmap()
+
+               self.input_file = ConfigText(default = self.file, fixed_size = False, visible_width = 42)
+               self.input_title = ConfigText(default = self.title, fixed_size = False, visible_width = 42)
+               self.input_descr = ConfigText(default = self.descr, fixed_size = False, visible_width = 42)
+               tmp = config.movielist.videodirs.value
+               if not self.dir in tmp:
+                       tmp.append(self.dir)
+               self.input_dir = ConfigSelection(choices = tmp, default = self.dir)
+
+               self["actions"] = NumberActionMap(["SetupActions"],
+               {
+                       "ok": self.keySelectOrGo,
+                       "save": self.keyGo,
+                       "cancel": self.keyCancel,
+               }, -2)
+
+               self.list = []
+               ConfigListScreen.__init__(self, self.list)
+               self.createSetup(self["config"])
+
+       def createSetup(self, configlist):
+               self.list = []
+               self.list.append(getConfigListEntry(_("Filename"), self.input_file))
+               self.list.append(getConfigListEntry(_("Title"), self.input_title))
+               self.list.append(getConfigListEntry(_("Description"), self.input_descr))
+               self.list.append(getConfigListEntry(_("Location"), self.input_dir))
+               configlist.list = self.list
+               configlist.l.setList(self.list)
+
+       def pathSelected(self, res):
+               if res is not None:
+                       if config.movielist.videodirs.value != self.input_dir.choices:
+                               self.input_dir.setChoices(config.movielist.videodirs.value, default=res)
+                       self.input_dir.value = res
+
+       def keySelectOrGo(self):
+               if self["config"].getCurrent() == self.list[3]:
+                       self.session.openWithCallback(
+                               self.pathSelected,
+                               MovieLocationBox,
+                               _("Choose target folder"),
+                               self.input_dir.value,
+                       )
+               else:
+                       self.keyGo()
+
+       def keyGo(self):
+               if self.input_title.value != self.title or self.input_descr.value != self.descr:
+                       self.setTitleDescr(self.path, self.input_title.value, self.input_descr.value)
+               if self.input_file.value != self.file or self.input_dir.value != self.dir:
+                       self.maybeMoveMovieFiles(self.path, self.rejoinName(self.input_dir.value, self.input_file.value))
+               else:
+                       self.exitDialog()
+
+       def keyCancel(self):
+               self.close()
+
+       def setTitleDescr(self, file, title, descr):
+               if os.path.exists(file + ".ts.meta"):
+                       metafile = open(file + ".ts.meta", "r")
+                       sid = metafile.readline()
+                       oldtitle = metafile.readline().rstrip()
+                       olddescr = metafile.readline().rstrip()
+                       rest = metafile.read()
+                       metafile.close()
+                       if not title and title != "":
+                               title = oldtitle
+                       if not descr and descr != "":
+                               descr = olddescr
+                       metafile = open(file + ".ts.meta", "w")
+                       metafile.write("%s%s\n%s\n%s" %(sid, title, descr, rest))
+                       metafile.close()
+
+       def maybeMoveMovieFiles(self, fr, to):
+               if os.path.exists(to+".ts"):
+                       self.inter_fr = fr
+                       self.inter_to = to
+                       self.session.openWithCallback(self.confirmedReplace, MessageBox, _("Target file %s.ts already exist.\nDo you want to replace it?") % (to), MessageBox.TYPE_YESNO)
+               elif os.path.isdir(os.path.dirname(to)):
+                       self.moveMovieFiles(fr, to)
+               else:
+                       self.session.openWithCallback(self.exitDialog, MessageBox, _("The target directory is not found. The file is not renamed."), MessageBox.TYPE_ERROR) 
+
+       def confirmedReplace(self, answer):
+               if answer == True:
+                       self.moveMovieFiles(self.inter_fr, self.inter_to)
+
+       def moveMovieFiles(self, fr, to):
+               try:
+                       os.rename(fr + ".ts", to + ".ts")
+               except OSError:
+                       print "Moving in background"
+                       global_background_mover.enqueue(self.exitDialog, self.session, fr, to)
+               else:
+                       print "Moving in foreground"
+                       for suff in [".ts.meta", ".ts.cuts", ".ts.ap", ".eit"]:
+                               if os.path.exists(fr + suff):
+                                       os.rename(fr + suff, to + suff)
+                       self.exitDialog()
+
+       def exitDialog(self, dummy=None):
+               self.close()
+               # This will try to get back to an updated movie list.
+               # A proper way to do this should be provided in enigma2.
+               try:
+                       self.parentscreen.csel.reloadList()
+                       self.parentscreen.close()
+               except AttributeError:
+                       pass
+
+       def baseName(self, str):
+               name = str.split('/')[-1]
+               if name.endswith(".ts") is True:
+                       return name[:-3]
+               else:
+                       return name
+
+       def dirName(self, str):
+               return '/'.join(str.split('/')[:-1]) + '/'
+
+       def rejoinName(self, dir, name):
+               name = name.strip()
+               if name.endswith(".ts") is True:
+                       return dir + name[:-3]
+               else:
+                       return dir + name
+
+class MovieRetitleBackgroundMover:
+       def __init__(self):
+               self.container = eConsoleAppContainer()
+               self.container.appClosed.get().append(self.moveNextSuffBG)
+               self.currid = 0;
+               self.queue = []
+               self.running = False
+               self.messageQueue = []
+               self.messageTimer = eTimer()
+               self.messageTimer.callback.append(self.tryLaunchMessage)
+
+       def message(self, session, id, cb, txt):
+               global global_message_block
+               done = False
+               if global_message_block and global_message_block == id:
+                       self.messageQueue = [(session, id, txt)] + self.messageQueue
+               else:
+                       i = 0
+                       for ele in self.messageQueue:
+                               if ele[1] == id:
+                                       self.messageQueue[i] = (session, id, txt)
+                                       done = True
+                                       break
+                               i += 1
+                       if not done:
+                               self.messageQueue.append((session, id, txt))
+               self.tryLaunchMessage(callback = cb)
+
+       def tryLaunchMessage(self, dummy=0, callback = None):
+               global global_message_block
+               self.messageTimer.stop()
+               if not self.messageQueue:
+                       if callback:
+                               callback()
+               elif not Screens.Standby.inStandby and self.messageQueue[0][0].in_exec and (not global_message_block or global_message_block == self.messageQueue[0][1]):
+                       self.messageTimer.stop()
+                       session = self.messageQueue[0][0]
+                       id = self.messageQueue[0][1]
+                       mess = self.messageQueue[0][2]
+                       self.messageQueue = self.messageQueue[1:]
+                       if global_message_block == id:
+                               closeprev = session.current_dialog
+                       else:
+                               closeprev = None
+                       global_message_block = id
+                       try:
+                               session.openWithCallback(lambda x: self.tryLaunchMessageCallback(callback, closeprev), MessageBox, mess, MessageBox.TYPE_INFO)
+                       except:
+                               global_message_block = False
+                               self.tryLaunchMessage()
+               else:
+                       self.messageTimer.start(1500, True)
+                       if callback:
+                               callback()
+
+       def tryLaunchMessageCallback(self, callback, closeprev):
+               global global_message_block
+               global_message_block = False
+               if closeprev:
+                       closeprev.close(True)
+               self.tryLaunchMessage(callback = callback)
+
+       def enqueue(self, cb, session, fr, to):
+               self.currid += 1
+               mess = _("The movie is moved in the background from %s to %s.") % (os.path.dirname(fr), os.path.dirname(to))
+               self.message(session, self.currid, cb, mess)
+               self.queue.append((session, self.currid, fr, to))
+               if not self.running:
+                       self.running = True
+                       self.runNext()
+                       return True
+               else:
+                       return False
+
+       def runNext(self):
+               if not self.queue:
+                       self.running = False
+               else:
+                       self.moveMovieFilesBackground(self.queue[0])
+
+       def runDone(self, retval):
+               ele = self.queue[0]
+               self.queue = self.queue[1:]
+               self.runNext()
+
+       def moveMovieFilesBackground(self, ele):
+               self.ele = ele
+               self.sufflst = [".ts.meta", ".ts.cuts", ".ts.ap", ".eit", ".ts"]
+               self.sufflst2 = self.sufflst
+               self.moveNextSuffBG(0)
+
+       def moveNextSuffBG(self, retval):
+               if self.sufflst and not retval:
+                       fr = self.ele[2] + self.sufflst[0]
+                       to = self.ele[3] + self.sufflst[0]
+                       self.sufflst = self.sufflst[1:]
+                       print "Moving %s to %s" % (fr, to)
+                       if os.path.exists(fr):
+                               self.container.execute("/bin/cp", ["/bin/cp", fr, to])
+                       else:
+                               self.moveNextSuffBG(0)
+               elif retval:
+                       for suff in self.sufflst2:
+                               if os.path.exists(self.ele[3] + suff) and os.path.exists(self.ele[2] + suff):
+                                       os.unlink(self.ele[3] + suff)
+                       mess = "Failed to move the movie %s to %s in the background" % (self.ele[2], self.ele[3])
+                       self.message(self.ele[0], self.ele[1], None, mess)
+                       self.runDone(1)
+               else:
+                       for suff in self.sufflst2:
+                               if os.path.exists(self.ele[2] + suff) and os.path.exists(self.ele[3] + suff):
+                                       os.unlink(self.ele[2] + suff)
+                       mess = "Successfully moved the movie %s" % (self.ele[2])
+                       self.message(self.ele[0], self.ele[1], None, mess)
+                       self.runDone(0)
+
+global_background_mover = MovieRetitleBackgroundMover()
+
+global_message_block = False
+