Commit 59e8d35a authored by Jan Gerber's avatar Jan Gerber
Browse files

add subtitles to python frontend

parent 16e02e1c
......@@ -119,6 +119,16 @@ class SimpleTheoraEncoder(wx.Frame):
if key in options and options[key]:
settings.append('--%s' % key)
settings.append("%s" % float(options[key]))
if 'subtitles' in options and options['subtitles']:
for s in options['subtitles']:
settings.append('--subtitles')
settings.append('%s' % s['file'])
settings.append('--subtitles-language')
settings.append('%s' % s['language'])
settings.append('--subtitles-category')
settings.append('%s' % s['category'])
settings.append('--subtitles-encoding')
settings.append('%s' % s['encoding'])
return settings
def addItemThread(self, item):
......@@ -167,7 +177,7 @@ class SimpleTheoraEncoder(wx.Frame):
self.removeItem.Enable()
def OnClickAdd(self, event):
result = addVideoDialog(self)
result = addVideoDialog(self, theoraenc.hasKate)
time.sleep(0.5)
if result['ok']:
self.addItemToQueue(result['videoFile'], result)
......
# -*- coding: utf-8 -*-
# vi:si:et:sw=2:sts=2:ts=2
import os
from os.path import basename
import time
import wx
#import wx.lib.langlistctrl
#from wx.lib.langlistctrl import GetWxIdentifierForLanguage
from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin
# on my box, I only get two versions of en, and two languages I'd never heard of, and keyboard input is unavailable
# to override for a language not in the list, so we don't use it, though it'd be nice as languages names would be
# translated, etc.
# After installing all locales, I don't even get some non obscure locales.
# And it doesn't seem to use ISO 639-1 tags anyway, but wxWidgets specific enums.
# Therefore, we use a "ComboBox" widget instead, and build a list of languages from a known set plus parsing
# the output of 'locale -a' to ensure we get the user's own language, if set (and also plenty of others if using
# a distro that spams the locale database with lots of unused ones); keyboard input overrides is available.
#use_langlistctrl=False
class SubtitlesProperties(wx.Dialog):
def __init__(
self, parent, ID, title,
language, category, encoding, file,
size=wx.DefaultSize, pos=wx.DefaultPosition,
style=wx.DEFAULT_DIALOG_STYLE,
):
pre = wx.PreDialog()
pre.Create(parent, ID, title, pos, size, style)
self.PostCreate(pre)
# defaults
if language == '':
language = 'en'
if category == '':
category = 'SUB'
if encoding == '':
encoding = 'UTF-8'
padding = 4
mainBox = wx.BoxSizer(wx.VERTICAL)
mainBox.AddSpacer((8, 16))
# file
self.btnSubtitlesFile = wx.Button(self, size=(380, -1))
self.btnSubtitlesFile.SetLabel('Select...')
self.Bind(wx.EVT_BUTTON, self.OnClickSubtitlesFile, self.btnSubtitlesFile)
self.addProperty(mainBox, 'File', self.btnSubtitlesFile)
# language
# if use_langlistctrl:
# self.languageWidget = wx.lib.langlistctrl.LanguageListCtrl(self, -1, style=wx.LC_REPORT, size=(380,140))
# else:
# self.languageWidget = wx.ComboBox(self, -1, language, (380,-1), wx.DefaultSize, self.BuildLanguagesList(), wx.CB_SIMPLE)
self.languageWidget = wx.ComboBox(self, -1, language, (380,-1), wx.DefaultSize, self.BuildLanguagesList(), wx.CB_SIMPLE)
self.addProperty(mainBox, 'Language', self.languageWidget, self.OnLanguageHelp)
# category
categories = ['SUB', 'CC', 'TRX', 'LRC'] # TODO: change when Silvia's list is final
self.categoryWidget = wx.ComboBox(self, -1, category, (80,-1), wx.DefaultSize, categories, wx.CB_SIMPLE)
self.addProperty(mainBox, 'Category', self.categoryWidget, self.OnCategoryHelp)
# encoding
encodings = ['UTF-8', 'ISO-8859-1']
self.encodingWidget = wx.Choice(self, -1, (80,-1), choices=encodings, name=encoding)
self.addProperty(mainBox, 'Encoding', self.encodingWidget, self.OnEncodingHelp)
#Buttons
hbox = wx.BoxSizer(wx.HORIZONTAL)
mainBox.Add(hbox)
hbox.AddSpacer((8, 16))
hbox = wx.BoxSizer(wx.HORIZONTAL)
mainBox.Add(hbox)
hbox.AddSpacer((280, 10))
self.btnCancel = wx.Button(self, wx.ID_CANCEL)
self.btnCancel.SetLabel('Cancel')
hbox.Add(self.btnCancel, 0, wx.EXPAND|wx.ALL, padding)
self.btnOK = wx.Button(self, wx.ID_OK)
self.btnOK.SetDefault()
self.btnOK.Disable()
self.btnOK.SetLabel('OK')
hbox.Add(self.btnOK, 0, wx.EXPAND|wx.ALL, padding)
hbox = wx.BoxSizer(wx.HORIZONTAL)
mainBox.Add(hbox)
hbox.AddSpacer((8, 8))
self.SetSizerAndFit(mainBox)
# preselect file, if any
if file and file != '' and os.path.exists(file):
self.selectSubtitlesFile(file)
def addProperty(self, mainBox, name, widget, help=None):
padding = 4
vspacer = 40
hspacer = 80
hbox = wx.BoxSizer(wx.HORIZONTAL)
mainBox.Add(hbox, 0, padding)
hbox.AddSpacer((8, 8))
label = wx.StaticText(self, -1, name)
label.SetMinSize((hspacer,vspacer))
hbox.Add(label, 0, wx.EXPAND|wx.ALL, padding)
hbox.Add(widget, 0, padding)
if help:
hbox.AddSpacer((16, 0))
btnHelp = wx.Button(self, size=(80, -1))
btnHelp.SetLabel('More info...')
self.Bind(wx.EVT_BUTTON, help, btnHelp)
hbox.Add(btnHelp, 0, padding)
hbox.AddSpacer((8, 8))
def OnCategoryHelp(self, event):
self.DisplayHelp(
'The category is a string representing the semantics of the text in a Kate stream.\n'+
'These codes include:\n'+
' SUB: text subtitles\n'+
' CC: closed captions\n'+
' TRX: transcript of a speech\n'+
' LRC: lyrics\n'+
'If the category needed is not available in the list, a custom one may be entered.\n')
def OnLanguageHelp(self, event):
self.DisplayHelp(
'Language is an ISO 639-1 or RFC 3066 language tag.\n'+
'Usually, these are two letter language tags (eg, "en", or "de"), '+
'optionally followed by a hypen (or underscore) and a country code (eg, "en_GB", "de_DE")\n'+
'If the language tag needed is not available in the list, a custom one may be entered.\n')
def OnEncodingHelp(self, event):
self.DisplayHelp(
'Kate streams are encoded in UTF-8 (a Unicode character encoding that allows to represent '+
'pretty much any existing script.\n'+
'If the input file is not already encoded in UTF-8, it will need converting to UTF-8 first.\n'+
'ffmpeg2theora can convert ISO-8859-1 (also known as latin1) encoding directly.\n'+
'Files in other encodings will have to be converted manually in order to be used. See the '+
'subtitles.txt documentation for more information on how to manually convert files.\n')
def DisplayHelp(self, msg):
wx.MessageBox(msg, 'More info...', style=wx.OK|wx.CENTRE)
def OnClickSubtitlesFile(self, event):
wildcard = "SubRip files|*.SRT;*.srt|All Files (*.*)|*.*"
dialogOptions = dict()
dialogOptions['message'] = 'Add subtitles..'
dialogOptions['wildcard'] = wildcard
dialog = wx.FileDialog(self, **dialogOptions)
if dialog.ShowModal() == wx.ID_OK:
filename = dialog.GetFilename()
dirname = dialog.GetDirectory()
self.selectSubtitlesFile(os.path.join(dirname, filename))
else:
filename=None
dialog.Destroy()
return filename
def selectSubtitlesFile(self, subtitlesFile):
self.subtitlesFile = subtitlesFile
lValue = subtitlesFile
lLength = 45
if len(lValue) > lLength:
lValue = "..." + lValue[-lLength:]
self.btnSubtitlesFile.SetLabel(lValue)
self.btnOK.Enable()
def BuildLanguagesList(self):
# start with a known basic set
languages = ['en', 'ja', 'de', 'fr', 'it', 'es', 'cy', 'ar', 'cn', 'pt', 'ru']
# add in whatever's known from 'locale -a' - this works fine if locale isn't found,
# but i'm not sure what that'll do if we get another program named locale that spews
# random stuff to stdout :)
f = os.popen('locale -a')
line = f.readline()
while line:
line = self.ExtractLanguage(line)
if line != '' and line != 'C' and line != 'POSIX':
languages.append(line)
line = f.readline()
f.close()
#oneliner from german python forum => unique list
languages = [languages[i] for i in xrange(len(languages)) if languages[i] not in languages[:i]]
languages.sort()
return languages
def ExtractLanguage(self, line):
line = line.split('.')[0] # stop at a dot
line = line.split(' ')[0] # stop at a space
line = line.split('@')[0] # stop at a @
line = line.split('\t')[0] # stop at a tab
line = line.split('\n')[0] # stop at a newline
line = line.split('\r')[0] # Mac or Windows
return line
def addSubtitlesPropertiesDialog(parent, language, category, encoding, file):
dlg = SubtitlesProperties(parent, -1, "Add subtitles", language, category, encoding, file, size=(490, 560), style=wx.DEFAULT_DIALOG_STYLE)
dlg.CenterOnScreen()
val = dlg.ShowModal()
result = dict()
if val == wx.ID_OK:
result['ok'] = True
result['subtitlesFile'] = dlg.subtitlesFile
# if use_langlistctrl:
# result['subtitlesLanguage'] = GetWxIdentifierForLanguage(dlg.languageWidget.GetLanguage())
# else:
# result['subtitlesLanguage'] = dlg.languageWidget.GetValue()
result['subtitlesLanguage'] = dlg.languageWidget.GetValue()
result['subtitlesCategory'] = dlg.categoryWidget.GetValue()
result['subtitlesEncoding'] = dlg.encodingWidget.GetStringSelection()
print result
else:
result['ok'] = False
dlg.Destroy()
return result
class SubtitlesList(wx.ListCtrl, ListCtrlAutoWidthMixin):
def __init__(self, parent):
wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT)
ListCtrlAutoWidthMixin.__init__(self)
self.ClearAll()
self.InsertColumn(0, "Language")
self.InsertColumn(1, "Category")
self.InsertColumn(2, "Encoding")
self.InsertColumn(3, "Name")
self.SetColumnWidth(0, 80)
self.SetColumnWidth(1, 80)
self.SetColumnWidth(2, 80)
self.SetColumnWidth(3, 80)
def ResizeFilenameColumn(self):
if self.GetItemCount() > 0:
self.resizeLastColumn(1024)
else:
self.resizeLastColumn(0)
# -*- coding: utf-8 -*-
# vi:si:et:sw=2:sts=2:ts=2
# vi:si:et:sw=2:sts=2:ts=2
import os
from os.path import basename
import time
from addSubtitlesDialog import addSubtitlesPropertiesDialog, SubtitlesList
import wx
class AddVideoDialog(wx.Dialog):
def __init__(
self, parent, ID, title, size=wx.DefaultSize, pos=wx.DefaultPosition,
self, parent, ID, title, hasKate, size=wx.DefaultSize, pos=wx.DefaultPosition,
style=wx.DEFAULT_DIALOG_STYLE,
):
......@@ -145,6 +146,54 @@ class AddVideoDialog(wx.Dialog):
hbox = wx.BoxSizer(wx.HORIZONTAL)
mainBox.Add(hbox)
# subtitles ('add' button and list)
hbox = wx.BoxSizer(wx.HORIZONTAL)
mainBox.Add(hbox)
label = wx.StaticText(self, -1, "Subtitles")
hbox.AddSpacer((12, 10))
hbox.Add(label, 0, wx.EXPAND|wx.ALL, padding)
hbox = wx.BoxSizer(wx.HORIZONTAL)
mainBox.Add(hbox)
hbox.AddSpacer((section_padding, 10))
if hasKate:
vbox = wx.BoxSizer(wx.VERTICAL)
hbox.Add(vbox)
subtitlesButtons_hbox = wx.BoxSizer(wx.HORIZONTAL)
vbox.Add(subtitlesButtons_hbox)
self.btnSubtitlesAdd = wx.Button(self, size=(120, -1))
self.btnSubtitlesAdd.SetLabel('Add...')
self.Bind(wx.EVT_BUTTON, self.OnClickSubtitlesAdd, self.btnSubtitlesAdd)
subtitlesButtons_hbox.Add(self.btnSubtitlesAdd, 0, wx.EXPAND|wx.ALL, padding)
self.btnSubtitlesRemove = wx.Button(self, size=(120, -1))
self.btnSubtitlesRemove.SetLabel('Remove')
self.Bind(wx.EVT_BUTTON, self.OnClickSubtitlesRemove, self.btnSubtitlesRemove)
self.btnSubtitlesRemove.Disable()
subtitlesButtons_hbox.Add(self.btnSubtitlesRemove, 0, wx.EXPAND|wx.ALL, padding)
self.btnSubtitlesProperties = wx.Button(self, size=(120, -1))
self.btnSubtitlesProperties.SetLabel('Properties')
self.Bind(wx.EVT_BUTTON, self.OnClickSubtitlesProperties, self.btnSubtitlesProperties)
self.btnSubtitlesProperties.Disable()
subtitlesButtons_hbox.Add(self.btnSubtitlesProperties, 0, wx.EXPAND|wx.ALL, padding)
#self.subtitles = wx.ListCtrl(self, -1, style=wx.LC_REPORT)
self.subtitles = SubtitlesList(self)
self.subtitles.Bind(wx.EVT_LIST_ITEM_SELECTED, self.CheckSubtitlesSelection)
self.subtitles.Bind(wx.EVT_LEFT_DCLICK, self.OnClickSubtitlesProperties)
self.subtitles.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.CheckSubtitlesSelection)
self.subtitles.Bind(wx.EVT_KILL_FOCUS, self.CheckSubtitlesSelection)
vbox.Add(self.subtitles, 0, wx.EXPAND|wx.ALL, padding)
else:
self.subtitles = None
hbox.Add(wx.StaticText(self, -1, "ffmpeg2theora doesn't seem to be built with subtitles support.\nSee documentation for how to enable subtitles.\n"))
'''
#Metadata
label = wx.StaticText(self, -1, "Metadata")
......@@ -257,14 +306,61 @@ class AddVideoDialog(wx.Dialog):
def selectVideoFile(self, videoFile):
self.videoFile = videoFile
lValue = videoFile
lLenght = 45
if len(lValue) > lLenght:
lValue = "..." + lValue[-lLenght:]
lLength = 45
if len(lValue) > lLength:
lValue = "..." + lValue[-lLength:]
self.btnVideoFile.SetLabel(lValue)
self.btnOK.Enable()
def addVideoDialog(parent):
dlg = AddVideoDialog(parent, -1, "Add Video", size=(490, 560), style=wx.DEFAULT_DIALOG_STYLE)
def CheckSubtitlesSelection(self, event):
idx=self.subtitles.GetFirstSelected()
if idx<0:
self.btnSubtitlesRemove.Disable()
self.btnSubtitlesProperties.Disable()
else:
self.btnSubtitlesRemove.Enable()
self.btnSubtitlesProperties.Enable()
self.subtitles.ResizeFilenameColumn()
def OnClickSubtitlesAdd(self, event):
self.subtitles.Append(['', '', '', ''])
if not self.ChangeSubtitlesProperties(self.subtitles.GetItemCount()-1):
self.subtitles.DeleteItem(self.subtitles.GetItemCount()-1)
self.subtitles.ResizeFilenameColumn()
def OnClickSubtitlesRemove(self, event):
while 1:
idx=self.subtitles.GetFirstSelected()
if idx<0:
break
self.subtitles.DeleteItem(idx)
self.CheckSubtitlesSelection(event)
def OnClickSubtitlesProperties(self, event):
idx=self.subtitles.GetFirstSelected()
if idx<0:
return
self.ChangeSubtitlesProperties(idx)
def ChangeSubtitlesProperties(self, idx):
language = self.subtitles.GetItem(idx, 0).GetText()
category = self.subtitles.GetItem(idx, 1).GetText()
encoding = self.subtitles.GetItem(idx, 2).GetText()
file = self.subtitles.GetItem(idx, 3).GetText()
result = addSubtitlesPropertiesDialog(self, language, category, encoding, file)
time.sleep(0.5) # why ? race condition ?
if result['ok']:
self.subtitles.SetStringItem(idx, 0, result['subtitlesLanguage'])
self.subtitles.SetStringItem(idx, 1, result['subtitlesCategory'])
self.subtitles.SetStringItem(idx, 2, result['subtitlesEncoding'])
self.subtitles.SetStringItem(idx, 3, result['subtitlesFile'])
return True
else:
return False
def addVideoDialog(parent, hasKate):
dlg = AddVideoDialog(parent, -1, "Add Video", hasKate, size=(490, 560), style=wx.DEFAULT_DIALOG_STYLE)
dlg.CenterOnScreen()
val = dlg.ShowModal()
result = dict()
......@@ -274,6 +370,16 @@ def addVideoDialog(parent):
for key in ('width', 'height', 'videoquality', 'videobitrate', 'framerate',
'audioquality', 'audiobitrate', 'samplerate'):
result[key] = getattr(dlg, key).GetValue()
# subtitles
if dlg.subtitles:
for idx in range(dlg.subtitles.GetItemCount()):
if not 'subtitles' in result:
result['subtitles'] = []
language = dlg.subtitles.GetItem(idx, 0).GetText()
category = dlg.subtitles.GetItem(idx, 1).GetText()
encoding = dlg.subtitles.GetItem(idx, 2).GetText()
file = dlg.subtitles.GetItem(idx, 3).GetText()
result['subtitles'].append({'encoding':encoding, 'language':language, 'category':category, 'file':file})
print result
else:
result['ok'] = False
......
......@@ -13,6 +13,32 @@ import simplejson
resourcePath = abspath(dirname(__file__))
def probe_ffmpeg2theora():
appname = 'ffmpeg2theora'
if os.name == 'nt':
appname = appname + '.exe'
ffmpeg2theora = join(resourcePath, appname)
if not exists(ffmpeg2theora):
# ffmpeg2theora is likely in $resourcePath/../.. since we're in frontend
ffmpeg2theora = join(resourcePath, join('../../', appname))
if not exists(ffmpeg2theora):
ffmpeg2theora = join('./', appname)
if not exists(ffmpeg2theora):
ffmpeg2theora = appname
return ffmpeg2theora
def probe_kate(ffmpeg2theora):
hasKate = False
cmd = ffmpeg2theora + ' --help'
f = os.popen(cmd)
line = f.readline()
while line:
if line.find('Subtitles options:') >= 0:
hasKate = True
line = f.readline()
f.close()
return hasKate
def timestr(seconds):
hours = int(seconds/3600)
minutes = int((seconds-( hours*3600 ))/60)
......@@ -22,20 +48,15 @@ def timestr(seconds):
class TheoraEnc:
settings = []
p = None
def __init__(self, inputFile, outputFile, updateGUI):
self.inputFile = inputFile
self.outputFile = outputFile
self.updateGUI = updateGUI
appname = 'ffmpeg2theora'
if os.name == 'nt':
appname = appname + '.exe'
self.ffmpeg2theora = join(resourcePath, appname)
if not exists(self.ffmpeg2theora):
self.ffmpeg2theora = appname
def commandline(self):
cmd = []
cmd.append(self.ffmpeg2theora)
cmd.append(ffmpeg2theora)
cmd.append('--frontend')
for e in self.settings:
cmd.append(e)
......@@ -74,23 +95,33 @@ class TheoraEnc:
line = f.readline()
info = dict()
status = ''
self.warning_timeout = 0
while line:
now = time.time()
try:
data = simplejson.loads(line)
for key in data:
info[key] = data[key]
if 'position' in info:
if 'duration' in info and float(info['duration']):
encoded = "encoding % 3d %% done " % ((float(info['position']) / float(info['duration'])) * 100)
else:
encoded = "encoded %s/" % timestr(float(info['position']))
if float(info['remaining'])>0:
status = encoded + '/ '+ timestr(float(info['remaining']))
else:
status = encoded
if 'WARNING' in info:
status = info['WARNING']
self.warning_timeout = now + 3
del info['WARNING']
else:
status = "encoding.."
self.updateGUI(status)
status=None
if now >= self.warning_timeout:
if 'position' in info:
if 'duration' in info and float(info['duration']):
encoded = "encoding % 3d %% done " % ((float(info['position']) / float(info['duration'])) * 100)
else:
encoded = "encoded %s/" % timestr(float(info['position']))
if float(info['remaining'])>0:
status = encoded + '/ '+ timestr(float(info['remaining']))
else:
status = encoded
else:
status = "encoding.."
if status != None:
self.updateGUI(status)
except:
pass
line = f.readline()
......@@ -102,3 +133,6 @@ class TheoraEnc:
self.updateGUI(info.get('result', 'Encoding failed.'))
return False
ffmpeg2theora = probe_ffmpeg2theora()
hasKate = probe_kate(ffmpeg2theora)
......@@ -857,7 +857,7 @@ void ff2theora_output(ff2theora this) {
info.with_kate=1;
}
}
else if (load_subtitles(ks,this->ignore_non_utf8)>0) {
else if (load_subtitles(ks,this->ignore_non_utf8,info.frontend)>0) {
#ifdef DEBUG
printf("Muxing Kate stream %d from %s as %s %s\n",
i,ks->filename,
......@@ -1350,7 +1350,7 @@ void ff2theora_output(ff2theora this) {
t = 0;
}
if (utf8 && t >= 0)
add_subtitle_for_stream(this->kate_streams, this->n_kate_streams, pkt.stream_index, t, duration, utf8, utf8len);
add_subtitle_for_stream(this->kate_streams, this->n_kate_streams, pkt.stream_index, t, duration, utf8, utf8len, info.frontend);
if (allocated_utf8) free(allocated_utf8);
}
else {
......@@ -1640,7 +1640,7 @@ void print_usage() {
" supported are " SUPPORTED_ENCODINGS "\n"
" --subtitles-language language set subtitles language (de, en_GB, etc)\n"
" --subtitles-category category set subtitles category (default \"subtitles\")\n"
" --subtitles-ignore-non-utf8 ignores any non utf-8 sequence in utf-8 text\n"
" --subtitles-ignore-non-utf8 ignores any non UTF-8 sequence in UTF-8 text\n"
" --nosubtitles disables subtitles from input\n"
"\n"
#endif
......@@ -1900,11 +1900,11 @@ int main(int argc, char **argv) {
info.with_kate=1;
break;
case SUBTITLES_ENCODING_FLAG:
if (!strcmp(optarg,"utf-8")) set_subtitles_encoding(convert,ENC_UTF8);
if (!strcmp(optarg,"utf8")) set_subtitles_encoding(convert,ENC_UTF8);
else if (!strcmp(optarg,"iso-8859-1")) set_subtitles_encoding(convert,ENC_ISO_8859_1);
else if (!strcmp(optarg,"latin1")) set_subtitles_encoding(convert,ENC_ISO_8859_1);
else report_unknown_subtitle_encoding(optarg);
if (!strcasecmp(optarg,"utf-8")) set_subtitles_encoding(convert,ENC_UTF8);
else if (!strcasecmp(optarg,"utf8")) set_subtitles_encoding(convert,ENC_UTF8);
else if (!strcasecmp(optarg,"iso-8859-1")) set_subtitles_encoding(convert,ENC_ISO_8859_1);
else if (!strcasecmp(optarg,"latin1")) set_subtitles_encoding(convert,ENC_ISO_8859_1);
else report_unknown_subtitle_encoding(optarg, info.frontend);
flag = -1;
break;
case SUBTITLES_IGNORE_NON_UTF8_FLAG:
......
......@@ -26,6 +26,7 @@
#include <getopt.h>
#include <math.h>
#include <errno.h>
#include <stdarg.h>
#include "libavformat/avformat.h"
......@@ -37,6 +38,26 @@
#include "subtitles.h"