_FFPREVIEW_VERSION = '0.2+'
+_FFPREVIEW_IDX = 'ffpreview.idx'
+
import sys
_PYTHON_VERSION = float("%d.%d" % (sys.version_info.major, sys.version_info.minor))
cfg = {
'conffile': 'ffpreview.conf',
'vid': '',
- 'path': '',
'outdir': '',
- 'thdir': '',
- 'idxfile': '',
'grid': '5x5',
'grid_columns': 5,
'grid_rows': 5,
' Shift+Enter play video starting at selected position\n'
' Alt+Enter open the context menu\n'
)
- parser.add_argument('filename', nargs='*', default=os.getcwd(), help='input video file')
+ parser.add_argument('filename', nargs='*', default=[os.getcwd()], help='input video file')
parser.add_argument('-b', '--batch', action='count', help='batch mode, do not draw window')
parser.add_argument('-m', '--manage', action='count', help='start with thumbnail manager')
parser.add_argument('-c', '--config', metavar='F', help='read configuration from file F')
def mouseReleaseEvent(self, event):
self.window().set_cursorw(self)
if QApplication.keyboardModifiers() == Qt.ShiftModifier:
- play_video(cfg['vid'], self.info[2])
+ play_video(self.window().fname, self.info[2])
def mouseDoubleClickEvent(self, event):
- play_video(os.path.join(cfg['path'], cfg['vid']), self.info[2], True)
+ play_video(self.window().fname, self.info[2], True)
def contextMenuEvent(self, event):
self.window().set_cursorw(self)
def contextMenu_show(self, pos):
menu = QMenu(self)
menu.addAction('Play From Here',
- lambda: play_video(os.path.join(cfg['path'], cfg['vid']), self.info[2]))
+ lambda: play_video(self.window().fname, self.info[2]))
menu.addAction('Play From Start',
- lambda: play_video(os.path.join(cfg['path'], cfg['vid'])))
+ lambda: play_video(self.window().fname))
menu.addSeparator()
menu.addAction('Copy Timestamp [H:M:S.ms]',
lambda: self.window().clipboard.setText(s2hms(self.info[2])))
lambda: self.window().clipboard.setText(self.info[2]))
menu.addSeparator()
menu.addAction('Copy Original Filename',
- lambda: self.window().clipboard.setText(os.path.join(cfg['path'], cfg['vid'])))
+ lambda: self.window().clipboard.setText(self.window().fname))
menu.addAction('Copy Thumb Filename',
- lambda: self.window().clipboard.setText(os.path.join(cfg['thdir'], self.info[1])))
+ lambda: self.window().clipboard.setText(os.path.join(self.window().thdir, self.info[1])))
menu.addAction('Copy Thumbnail Image',
lambda: self.window().clipboard.setPixmap(self.layout().itemAt(0).widget().pixmap()))
menu.addSeparator()
menu.addAction('Optimize Window Extent', lambda: self.window().optimize_extent())
menu.addSeparator()
- menu.addAction('Open Video File...', lambda: self.window().load_view(cfg['path']))
+ menu.addAction('Open Video File...', lambda: self.window().load_view(self.window().vpath))
menu.addAction('Open Thumbs Manager', lambda: self.window().manage_thumbs(cfg['outdir']))
menu.addSeparator()
menu.addAction('Quit', lambda: die(0))
def contextMenuEvent(self, event):
menu = QMenu(self)
menu.addAction('Play From Start',
- lambda: play_video(cfg['vid']))
+ lambda: play_video(self.window().fname))
menu.addSeparator()
menu.addAction('Copy Original Filename',
- lambda: self.window().clipboard.setText(os.path.join(cfg['path'], cfg['vid'])))
+ lambda: self.window().clipboard.setText(self.window().fname))
menu.addSeparator()
menu.addAction('Optimize Window Extent', lambda: self.window().optimize_extent())
menu.addSeparator()
- menu.addAction('Open Video File...', lambda: self.window().load_view(cfg['path']))
+ menu.addAction('Open Video File...', lambda: self.window().load_view(self.window().vpath))
menu.addAction('Open Thumbs Manager', lambda: self.window().manage_thumbs(cfg['outdir']))
menu.addSeparator()
menu.addAction('Quit', lambda: die(0))
tlwidth = 0
tlheight = 0
tlabels = []
+ thinfo = None
+ fname = None
+ vfile = None
+ vpath = None
+ thdir = None
cur = 0
def __new__(cls, *args, title='', **kwargs):
QShortcut('Ctrl+Q', self).activated.connect(lambda: die(0))
QShortcut('Ctrl+W', self).activated.connect(lambda: die(0))
QShortcut('Ctrl+G', self).activated.connect(self.optimize_extent)
- QShortcut('Ctrl+O', self).activated.connect(lambda: self.load_view(cfg['path']))
+ QShortcut('Ctrl+O', self).activated.connect(lambda: self.load_view(self.thinfo['path']))
QShortcut('Ctrl+M', self).activated.connect(lambda: self.manage_thumbs(cfg['outdir']))
QShortcut('Tab', self).activated.connect(lambda: self.advance_cursor(1))
QShortcut('Shift+Tab', self).activated.connect(lambda: self.advance_cursor(-1))
QShortcut('PgDown', self).activated.connect(lambda: self.advance_cursor(cfg['grid_rows'] * cfg['grid_columns']))
QShortcut('Home', self).activated.connect(lambda: self.set_cursor(0))
QShortcut('End', self).activated.connect(lambda: self.set_cursor(len(self.tlabels)-1))
- QShortcut('Return', self).activated.connect(lambda: play_video(cfg['vid'], self.tlabels[self.cur].info[2], True))
- QShortcut('Shift+Return', self).activated.connect(lambda: play_video(cfg['vid'], self.tlabels[self.cur].info[2]))
+ QShortcut('Return', self).activated.connect(lambda: play_video(self.fname, self.tlabels[self.cur].info[2], True))
+ QShortcut('Shift+Return', self).activated.connect(lambda: play_video(self.fname, self.tlabels[self.cur].info[2]))
QShortcut('Alt+Return', self).activated.connect(
lambda: self.tlabels[self.cur].contextMenu_show(QPoint(self.tlwidth/2, self.tlheight/2)))
self.set_cursor(0)
'Video Files (*.avi *.mkv *.mp4);;All Files (*)', options=options)
if not os.path.exists(fname) or not os.access(fname, os.R_OK):
return
- cfg['vid'] = os.path.basename(fname)
- cfg['path'] = os.path.dirname(os.path.abspath(fname))
- cfg['thdir'] = os.path.join(cfg['outdir'], os.path.basename(cfg['vid']))
- cfg['idxfile'] = os.path.join(cfg['thdir'], 'ffpreview.idx')
- self.setWindowTitle('ffpreview - ' + cfg['vid'])
+ self.fname = os.path.abspath(fname)
+ self.vfile = os.path.basename(self.fname)
+ self.vpath = os.path.dirname(self.fname)
+ self.thdir = os.path.join(cfg['outdir'], self.vfile)
+ self.setWindowTitle('ffpreview - ' + self.vfile)
# clear previous view
self.statdsp[0].setText('Clearing view')
QApplication.processEvents()
# analyze video
self.statdsp[0].setText('Analyzing')
QApplication.processEvents()
- thinfo, ok = get_thinfo(fname)
- if thinfo is None:
+ self.thinfo, ok = get_thinfo(fname, self.thdir)
+ if self.thinfo is None:
self.statdsp[0].setText('Unrecognized file format')
return
if not ok:
- # prepare thumbnail directory
- try:
- os.makedirs(cfg['thdir'], exist_ok=True)
- except Exception as e:
- eprint(0, str(e))
- return False
# (re)generate thumbnails and index file
- self.statdsp[0].setText('Processing video:')
- clear_thumbdir()
+ self.statdsp[0].setText('Processing')
+ clear_thumbdir(self.thdir)
self.progbar.show()
- thinfo, ok = make_thumbs(fname, thinfo, self.statdsp[1], self.progbar)
+ self.thinfo, ok = make_thumbs(fname, self.thinfo, self.thdir, self.statdsp[1], self.progbar)
# load thumbnails and make labels
self.statdsp[0].setText('Loading ')
self.progbar.show()
- tlabels = make_tlabels(self.tlabels, self.statdsp[1], self.progbar, self.broken_img)
+ self.tlabels = make_tlabels(self.tlabels, self.thdir, self.statdsp[1], self.progbar, self.broken_img)
# roughly fix window geometry
- self.tlwidth = tlabels[0].width()
- self.tlheight = tlabels[0].height()
+ self.tlwidth = self.tlabels[0].width()
+ self.tlheight = self.tlabels[0].height()
w = self.tlwidth * cfg['grid_columns'] + self.px
h = self.tlheight * cfg['grid_rows'] + self.py
self.resize(w, h)
return meta, False
# extract thumbnails from video and collect timestamps
-def make_thumbs(vidfile, thinfo, ilabel=None, pbar=None):
+def make_thumbs(vidfile, thinfo, thdir, ilabel=None, pbar=None):
global proc
rc = False
pictemplate = '%08d.png'
else: # iframe
cmd += ' -vf "select=eq(pict_type\,I)'
cmd += ',showinfo,scale=' + str(cfg['thumb_width']) + ':-1"'
- cmd += ' -vsync vfr "' + os.path.join(cfg['thdir'], pictemplate) + '"'
+ cmd += ' -vsync vfr "' + os.path.join(thdir, pictemplate) + '"'
eprint(2, cmd)
ebuf = ''
cnt = 0
eprint(0, cmd + '\n returned %d' % retval)
eprint(1, ebuf)
thinfo['count'] = cnt
- with open(cfg['idxfile'], 'w') as idxfile:
+ with open(os.path.join(thdir, _FFPREVIEW_IDX), 'w') as idxfile:
json.dump(thinfo, idxfile, indent=2)
rc = True
except Exception as e:
Popen('exec ' + cmd, shell=True, stdout=DEVNULL, stderr=DEVNULL, start_new_session=True)
# check validity of existing index file
-def chk_idxfile(thinfo):
+def chk_idxfile(thinfo, thdir):
try:
- with open(cfg['idxfile'], 'r') as idxfile:
+ with open(os.path.join(thdir, _FFPREVIEW_IDX), 'r') as idxfile:
idx = json.load(idxfile)
if idx['name'] != thinfo['name']:
return False
return False
# initialize thumbnail info structure
-def get_thinfo(vidfile):
- vidfile = os.path.abspath(vidfile)
+def get_thinfo(vfile, thdir):
thinfo = {
- 'name': os.path.basename(vidfile),
- 'path': os.path.dirname(vidfile),
+ 'name': os.path.basename(vfile),
+ 'path': os.path.dirname(vfile),
'frames': -1,
'duration': -1,
'fps': -1,
'date': 0,
'th': []
}
- meta, ok = get_meta(vidfile)
+ meta, ok = get_meta(vfile)
if not ok:
return None, False
thinfo.update(meta)
thinfo['date'] = int(time.time())
if not cfg['force']:
- chk = chk_idxfile(thinfo)
+ chk = chk_idxfile(thinfo, thdir)
if chk:
return chk, True
return thinfo, False
# clear out thumbnail directory
-def clear_thumbdir():
+def clear_thumbdir(thdir):
+ # prepare thumbnail directory
try:
- os.unlink(cfg['idxfile'])
+ os.makedirs(thdir, exist_ok=True)
+ except Exception as e:
+ eprint(0, str(e))
+ return False
+ try:
+ os.unlink(thinfo['idxfile'])
except Exception as e:
pass
- for f in os.listdir(cfg['thdir']):
+ for f in os.listdir(thdir):
if re.match('^\d{8}\.png$', f):
try:
- os.unlink(os.path.join(cfg['thdir'], f))
+ os.unlink(os.path.join(thdir, f))
except Exception as e:
pass
# generate clickable thumbnail labels
-def make_tlabels(tlabels, ilabel, pbar, dummy_img):
+def make_tlabels(tlabels, thdir, ilabel, pbar, dummy_img):
try:
- with open(cfg['idxfile'], 'r') as idxfile:
+ with open(os.path.join(thdir, _FFPREVIEW_IDX), 'r') as idxfile:
idx = json.load(idxfile)
if cfg['verbosity'] > 3:
eprint(3, 'idx = ' + json.dumps(idx, indent=2))
ilabel.setText('%d / %d' % (th[0], idx['count']))
pbar.setValue(th[0] * 100 / idx['count'])
QApplication.processEvents()
- thumb = QPixmap(os.path.join(cfg['thdir'], th[1]))
+ thumb = QPixmap(os.path.join(thdir, th[1]))
if thumb.isNull():
thumb = dummy_img.scaledToWidth(cfg['thumb_width'])
tlabel = tLabel(pixmap=thumb, text=s2hms(th[2]), info=th)
if os.path.isdir(fname):
eprint(0, '%s is a directory!' % fname)
return False
- cfg['vid'] = os.path.basename(fname)
- cfg['path'] = os.path.dirname(os.path.abspath(fname))
- cfg['thdir'] = os.path.join(cfg['outdir'], cfg['vid'])
- cfg['idxfile'] = os.path.join(cfg['thdir'], 'ffpreview.idx')
+ fname = os.path.abspath(fname)
+ vfile = os.path.basename(fname)
+ thdir = os.path.join(cfg['outdir'], vfile)
# analyze video
- print('Analyzing %s ...\r' % cfg['vid'], end='', file=sys.stderr)
- thinfo, ok = get_thinfo(fname)
+ print('Analyzing %s ...\r' % vfile, end='', file=sys.stderr)
+ thinfo, ok = get_thinfo(fname, thdir)
if thinfo is None:
print('\nFailed.', file=sys.stderr)
return False
# prepare info and thumbnail files
if not ok:
- # prepare thumbnail directory
- try:
- os.makedirs(cfg['thdir'], exist_ok=True)
- except Exception as e:
- eprint(0, str(e))
- return False
# (re)generate thumbnails and index file
print('Processing', file=sys.stderr)
- clear_thumbdir()
- thinfo, ok = make_thumbs(fname, thinfo)
+ clear_thumbdir(thdir)
+ thinfo, ok = make_thumbs(fname, thinfo, thdir)
else:
print('', file=sys.stderr)
if ok:
continue
entry = { 'tdir': None, 'idx': False, 'vfile': None }
entry['tdir'] = sd
- fidx = os.path.join(d, 'ffpreview.idx')
+ fidx = os.path.join(d, _FFPREVIEW_IDX)
if os.path.isfile(fidx):
entry['idx'] = True
with open(fidx, 'r') as idxfile:
if cfg['batch']:
errcnt = 0
- if isinstance(cfg['vid'], list):
- for fn in cfg['vid']:
- if not batch_process(fn):
- errcnt += 1
- elif not batch_process(cfg['vid']):
- errcnt += 1
+ for fn in cfg['vid']:
+ if not batch_process(fn):
+ errcnt += 1
exit(errcnt)
os.environ['QT_LOGGING_RULES'] = 'qt5ct.debug=false'
root.show()
if cfg['manage']:
- outdir = os.path.join(cfg['outdir'])
- root.manage_thumbs(outdir)
+ root.manage_thumbs(cfg['outdir'])
else:
- if isinstance(cfg['vid'], list):
- eprint(2, 'Only using first file in interactive mode.')
- cfg['vid'] = cfg['vid'][0]
- root.load_view(cfg['vid'])
+ root.load_view(cfg['vid'][0])
# start main loop
exit(app.exec_())