[open-allure-ds] 2 new revisions pushed by jg07...@gmail.com on 2011-12-06 01:25 GMT

8 views
Skip to first unread message

open-al...@googlecode.com

unread,
Dec 5, 2011, 8:26:07 PM12/5/11
to open-al...@googlegroups.com
2 new revisions:

Revision: 9fb3ef4fdc96
Author: John Graves
Date: Mon Dec 5 15:09:35 2011
Log: SlideSpeech installer script NullSoft
http://code.google.com/p/open-allure-ds/source/detail?r=9fb3ef4fdc96

Revision: 03a34494d834
Author: John Graves
Date: Mon Dec 5 17:25:29 2011
Log: Renaming to SlideSpeechConverter. Windows installer for both SS
and SS...
http://code.google.com/p/open-allure-ds/source/detail?r=03a34494d834

==============================================================================
Revision: 9fb3ef4fdc96
Author: John Graves
Date: Mon Dec 5 15:09:35 2011
Log: SlideSpeech installer script NullSoft
http://code.google.com/p/open-allure-ds/source/detail?r=9fb3ef4fdc96

Added:
/odp2wts/SlideSpeech-from-ODP.nsi

=======================================
--- /dev/null
+++ /odp2wts/SlideSpeech-from-ODP.nsi Mon Dec 5 15:09:35 2011
@@ -0,0 +1,132 @@
+; check for latest version of UltraModernUI at
https://sourceforge.net/projects/ultramodernui/files/UltraModernUI/
+!include "D:\open-allure-ds\odp2wts\UMUI.nsh"
+!include Library.nsh
+
+Var ALREADY_INSTALLED ; variable - no {} needed, just $
+
+!define SLIDESPEECH_VERSION "0.1.28" ; define - ${} needed
+
+!define MUI_ICON "D:\open-allure-ds\odp2wts\slidespeech.ico"
+!define UMUI_LEFTIMAGE_BMP "D:\open-allure-ds\odp2wts\openallure_left.bmp"
+
+; Welcome
+!define MUI_PAGE_HEADER_TEXT "SlideSpeech from ODP"
+!define MUI_WELCOMEPAGE_TITLE "SlideSpeech from ODP
v.${SLIDESPEECH_VERSION}"
+!define MUI_WELCOMEPAGE_TEXT "SlideSpeech from ODP turns your presentation
\
+into a slide show with voice over and a video. \
+\r\n\r\nClick Next to start installation."
+!insertmacro MUI_PAGE_WELCOME
+
+; Licence
+; !insertmacro MUI_PAGE_LICENSE "D:\open-allure-ds\odp2wts\LICENSE.txt"
+
+; Location folder
+!insertmacro MUI_PAGE_DIRECTORY
+
+; Install files
+!insertmacro MUI_PAGE_INSTFILES
+
+; Finish
+!define MUI_FINISHPAGE_TITLE "SlideSpeech from ODP should now be ready to
use"
+!define MUI_FINISHPAGE_TEXT "See on-line documentation for details."
+!define MUI_FINISHPAGE_LINK "SlideSpeech Documentation"
+!define
MUI_FINISHPAGE_LINK_LOCATION "http://code.google.com/p/slidespeech/wiki/Resources"
+!insertmacro MUI_PAGE_FINISH
+
+; Uninstall
+!insertmacro MUI_UNPAGE_CONFIRM
+!insertmacro MUI_UNPAGE_INSTFILES
+
+;
http://forums.winamp.com/printthread.php?s=4d90165acf6fa187290bbbe00bb790b7&threadid=202244
+;after all the other insertmacros
+!insertmacro MUI_LANGUAGE "English"
+
+;--------------------------------
+
+; The name of the installer
+Name "SlideSpeech from ODP"
+
+; The file to write
+OutFile "SlideSpeech-from-ODP-win-${SLIDESPEECH_VERSION}.exe"
+
+; Default installation directory
+InstallDir "$PROGRAMFILES\SlideSpeech-from-ODP"
+; The text to prompt the user to enter a directory
+DirText "Please choose the directory where you would like SlideSpeech from
ODP installed."
+;ShowInstDetails show
+;ShowUnInstDetails show
+
+; Request application privileges for Windows Vista
+RequestExecutionLevel admin #
http://forums.gforums.winamp.com/showthread.php?s=8a0906800e16ffd51be2d2fda1a23c4c&threadid=306563
+; user is the other option I used to use
+
+; http://nsis.sourceforge.net/Docs/Chapter4.html#4.8.1
+;4.8.1.32 RequestExecutionLevel
+;none|user|highest|admin
+;Specifies the requested execution level for Windows Vista and Windows 7.
The value is embedded in the installer and
+; uninstaller's XML manifest and tells Vista/7, and probably future
versions of Windows, what privileges level the
+; installer requires. user requests the a normal user's level with no
administrative privileges. highest will request
+; the highest execution level available for the current user and will
cause Windows to prompt the user to verify
+; privilege escalation. The prompt might request for the user's password.
admin requests administrator level and
+; will cause Windows to prompt the user as well. Specifying none, which is
also the default, will keep the manifest
+; empty and let Windows decide which execution level is required. Windows
Vista/7 automatically identifies NSIS
+; installers and decides administrator privileges are required. Because of
this, none and admin have virtually the same effect.
+;It's recommended, at least by Microsoft, that every application will be
marked with the required execution level.
+; Unmarked installers are subject to compatibility mode. Workarounds of
this mode include automatically moving any
+; shortcuts created in the user's start menu to all users' start menu.
Installers that need not install anything into
+; system folders or write to the local machine registry (HKLM) should
specify user execution level.
+;More information about this topic can be found at MSDN. Keywords
include "UAC", "requested execution level",
+; "vista manifest" and "vista security".
+
+;--------------------------------
+
+Section ""
+
+ CreateDirectory $INSTDIR
+ SetOutPath "$INSTDIR"
+
+ File "D:\open-allure-ds\odp2wts\slidespeech.ico"
+
+ File /r "D:\open-allure-ds\odp2wts\dist\*.*"
+
+ ; http://osdir.com/ml/python.db.pysqlite.user/2005-05/msg00040.html (can
be missing)
+ ; http://nsis.sourceforge.net/Docs/AppendixB.html
+ IfFileExists "$INSTDIR\odp2ss.exe" 0 new_installation ; continue if
true, else jump to new_installation label
+ StrCpy $ALREADY_INSTALLED 1 ;set to non-zero value if already installed
+ new_installation: ; already installed stays as initialised
+
+ ; WAIT FOR JOHN TO SUPPLY
+ ;!insertmacro InstallLib REGDLL $ALREADY_INSTALLED
REBOOT_NOTPROTECTED "G:\openallure\msvcr71.dll" $SYSDIR\msvcr100.dll $SYSDIR
+
+ SetOutPath $INSTDIR
+ ;Create shortcuts etc
+ ;create desktop shortcut
+ CreateShortCut "$DESKTOP\SlideSpeech from ODP.lnk" \
+ "$INSTDIR\odp2ss.exe" "" \
+ "$INSTDIR\slidespeech.ico"
+
+ CreateDirectory "$SMPROGRAMS\SlideSpeech"
+ CreateShortCut "$SMPROGRAMS\SlideSpeech\SlideSpeech from ODP.lnk" \
+ "$INSTDIR\odp2ss.exe" "" \
+ "$INSTDIR\slidespeech.ico"
+ CreateShortCut "$SMPROGRAMS\SlideSpeech\Uninstall.lnk" \
+ "$INSTDIR\Uninstall.exe" "" \
+ "$INSTDIR\uninstall.exe" 0
+ ;Create uninstaller
+ WriteUninstaller "$INSTDIR\Uninstall.exe"
+
+SectionEnd ; end the section
+
+;--------------------------------
+
+;Uninstaller Section
+
+Section "Uninstall"
+
+ RMDir /r /REBOOTOK $INSTDIR
+ RMDir /r /REBOOTOK "$PROFILE\SlideSpeech"
+ RMDir /r "$SMPROGRAMS\SlideSpeech"
+ Delete "$DESKTOP\SlideSpeech from ODP.lnk"
+
+SectionEnd
+

==============================================================================
Revision: 03a34494d834
Author: John Graves
Date: Mon Dec 5 17:25:29 2011
Log: Renaming to SlideSpeechConverter. Windows installer for both SS
and SSConverter.
http://code.google.com/p/open-allure-ds/source/detail?r=03a34494d834

Added:
/SlideSpeechInstaller.nsi
/blurb.txt
/make_exes_and_installer.cmd
/odp2wts/SlideSpeechConverter.py
/odp2wts/exeMaker.py
/odp2wts/icon.svg
/odp2wts/slideSpeechLogo32.png
/odp2wts/slidespeech.ico
/wikitospeech/exeMaker.py
Deleted:
/MANIFEST.in
/odp2wts/SlideSpeech-from-ODP.nsi
/odp2wts/Wiki-to-Speech-from-ODP.nsi
/odp2wts/make_exe.cmd
/odp2wts/odp2exe.py
/odp2wts/odp2ss.py
/odp2wts/odp2wts.py
/odp2wts/openallure_32x32.ico
/odp2wts/script.txt
/odp2wts/setup.nsi
/wikitospeech/Wiki-to-Speech.py
/wikitospeech/make_exe.cmd
/wikitospeech/ss2exe.py
/wikitospeech/wts2app.py
/wikitospeech/wts2exe.py
Modified:
/README.txt

=======================================
--- /dev/null
+++ /SlideSpeechInstaller.nsi Mon Dec 5 17:25:29 2011
@@ -0,0 +1,140 @@
+; check for latest version of UltraModernUI at
https://sourceforge.net/projects/ultramodernui/files/UltraModernUI/
+!include "D:\open-allure-ds\odp2wts\UMUI.nsh"
+!include Library.nsh
+
+Var ALREADY_INSTALLED ; variable - no {} needed, just $
+
+!define SLIDESPEECH_VERSION "0.1.30" ; define - ${} needed
+
+!define MUI_ICON "D:\open-allure-ds\odp2wts\slidespeech.ico"
+!define UMUI_LEFTIMAGE_BMP "D:\open-allure-ds\odp2wts\openallure_left.bmp"
+
+; Welcome
+!define MUI_PAGE_HEADER_TEXT "SlideSpeech"
+!define MUI_WELCOMEPAGE_TITLE "SlideSpeech v.${SLIDESPEECH_VERSION}"
+!define MUI_WELCOMEPAGE_TEXT "SlideSpeech turns your presentation \
+into a slide show with voice over and a video. \
+\r\n\r\nClick Next to start installation."
+!insertmacro MUI_PAGE_WELCOME
+
+; Licence
+; !insertmacro MUI_PAGE_LICENSE "D:\open-allure-ds\odp2wts\LICENSE.txt"
+
+; Location folder
+!insertmacro MUI_PAGE_DIRECTORY
+
+; Install files
+!insertmacro MUI_PAGE_INSTFILES
+
+; Finish
+!define MUI_FINISHPAGE_TITLE "SlideSpeech should now be ready to use"
+!define MUI_FINISHPAGE_TEXT "See on-line documentation for details."
+!define MUI_FINISHPAGE_LINK "SlideSpeech Documentation"
+!define
MUI_FINISHPAGE_LINK_LOCATION "http://code.google.com/p/slidespeech/wiki/Resources"
+!insertmacro MUI_PAGE_FINISH
+
+; Uninstall
+!insertmacro MUI_UNPAGE_CONFIRM
+!insertmacro MUI_UNPAGE_INSTFILES
+
+;
http://forums.winamp.com/printthread.php?s=4d90165acf6fa187290bbbe00bb790b7&threadid=202244
+;after all the other insertmacros
+!insertmacro MUI_LANGUAGE "English"
+
+;--------------------------------
+
+; The name of the installer
+Name "SlideSpeech Installer"
+
+; The file to write
+OutFile "SlideSpeech-win-${SLIDESPEECH_VERSION}.exe"
+
+; Default installation directory
+InstallDir "$PROGRAMFILES\SlideSpeech"
+; The text to prompt the user to enter a directory
+DirText "Please choose the directory where you would like SlideSpeech
installed."
+;ShowInstDetails show
+;ShowUnInstDetails show
+
+; Request application privileges for Windows Vista
+RequestExecutionLevel admin #
http://forums.gforums.winamp.com/showthread.php?s=8a0906800e16ffd51be2d2fda1a23c4c&threadid=306563
+; user is the other option I used to use
+
+; http://nsis.sourceforge.net/Docs/Chapter4.html#4.8.1
+;4.8.1.32 RequestExecutionLevel
+;none|user|highest|admin
+;Specifies the requested execution level for Windows Vista and Windows 7.
The value is embedded in the installer and
+; uninstaller's XML manifest and tells Vista/7, and probably future
versions of Windows, what privileges level the
+; installer requires. user requests the a normal user's level with no
administrative privileges. highest will request
+; the highest execution level available for the current user and will
cause Windows to prompt the user to verify
+; privilege escalation. The prompt might request for the user's password.
admin requests administrator level and
+; will cause Windows to prompt the user as well. Specifying none, which is
also the default, will keep the manifest
+; empty and let Windows decide which execution level is required. Windows
Vista/7 automatically identifies NSIS
+; installers and decides administrator privileges are required. Because of
this, none and admin have virtually the same effect.
+;It's recommended, at least by Microsoft, that every application will be
marked with the required execution level.
+; Unmarked installers are subject to compatibility mode. Workarounds of
this mode include automatically moving any
+; shortcuts created in the user's start menu to all users' start menu.
Installers that need not install anything into
+; system folders or write to the local machine registry (HKLM) should
specify user execution level.
+;More information about this topic can be found at MSDN. Keywords
include "UAC", "requested execution level",
+; "vista manifest" and "vista security".
+
+;--------------------------------
+
+Section ""
+
+ CreateDirectory $INSTDIR
+ SetOutPath "$INSTDIR"
+
+ File "D:\open-allure-ds\odp2wts\slidespeech.ico"
+
+ File /r "D:\open-allure-ds\odp2wts\dist\*.*"
+ File /r "D:\open-allure-ds\wikitospeech\dist\*.*"
+
+ ; http://osdir.com/ml/python.db.pysqlite.user/2005-05/msg00040.html (can
be missing)
+ ; http://nsis.sourceforge.net/Docs/AppendixB.html
+ IfFileExists "$INSTDIR\odp2ss.exe" 0 new_installation ; continue if
true, else jump to new_installation label
+ StrCpy $ALREADY_INSTALLED 1 ;set to non-zero value if already installed
+ new_installation: ; already installed stays as initialised
+
+ ; WAIT FOR JOHN TO SUPPLY
+ ;!insertmacro InstallLib REGDLL $ALREADY_INSTALLED
REBOOT_NOTPROTECTED "G:\openallure\msvcr71.dll" $SYSDIR\msvcr100.dll $SYSDIR
+
+ SetOutPath $INSTDIR
+ ;Create shortcuts etc
+ ;create desktop shortcut
+ CreateShortCut "$DESKTOP\SlideSpeech Converter.lnk" \
+ "$INSTDIR\SlideSpeechConverter.exe" "" \
+ "$INSTDIR\slidespeech.ico"
+ CreateShortCut "$DESKTOP\SlideSpeech.lnk" \
+ "$INSTDIR\SlideSpeech.exe" "" \
+ "$INSTDIR\slidespeech.ico"
+
+ CreateDirectory "$SMPROGRAMS\SlideSpeech"
+ CreateShortCut "$SMPROGRAMS\SlideSpeech\SlideSpeech Converter.lnk" \
+ "$INSTDIR\SlideSpeechConverter.exe" "" \
+ "$INSTDIR\slidespeech.ico"
+ CreateShortCut "$SMPROGRAMS\SlideSpeech\SlideSpeech.lnk" \
+ "$INSTDIR\SlideSpeech.exe" "" \
+ "$INSTDIR\slidespeech.ico"
+ CreateShortCut "$SMPROGRAMS\SlideSpeech\Uninstall.lnk" \
+ "$INSTDIR\Uninstall.exe" "" \
+ "$INSTDIR\uninstall.exe" 0
+ ;Create uninstaller
+ WriteUninstaller "$INSTDIR\Uninstall.exe"
+
+SectionEnd ; end the section
+
+;--------------------------------
+
+;Uninstaller Section
+
+Section "Uninstall"
+
+ RMDir /r /REBOOTOK $INSTDIR
+ RMDir /r /REBOOTOK "$PROFILE\SlideSpeech"
+ RMDir /r "$SMPROGRAMS\SlideSpeech"
+ Delete "$DESKTOP\SlideSpeech Converter.lnk"
+ Delete "$DESKTOP\SlideSpeech.lnk"
+
+SectionEnd
+
=======================================
--- /dev/null
+++ /blurb.txt Mon Dec 5 17:25:29 2011
@@ -0,0 +1,9 @@
+SlideSpeech and SlideSpeech Converter for Windows
+
+Download the .exe and run it to install two programs:
+
+SlideSpeech - play wiki-based scripts using the voice selected in Windows
Control Panel. Copy/paste the address of a script into the input area. For
example: http://code.google.com/p/wiki-to-speech/wiki/HiKim
+
+SlideSpeech Converter - utility to convert an open document presentation
(ODP) or PowerPoint (PPT) file into a script, a zip file, a slideshow with
voice over and a video using the voice selected in Windows Control Panel.
+
+http://slidespeech.googlecode.com/files/SlideSpeech-win-0.1.30.exe
=======================================
--- /dev/null
+++ /make_exes_and_installer.cmd Mon Dec 5 17:25:29 2011
@@ -0,0 +1,17 @@
+echo off
+rem 20111206 JG Modified to run for SlideSpeech and SlideSpeech Converter
+echo ========================
+echo Building SlideSpeech.exe
+echo ========================
+cd wikitospeech
+if exist dist (rmdir dist /S)
+if exist build (rmdir build /S)
+python exeMaker.py py2exe
+cd ..\odp2wts
+echo =================================
+echo Building SlideSpeechConverter.exe
+echo =================================
+if exist dist (rmdir dist /S)
+python exeMaker.py py2exe
+cd ..
+D:\NSIS\makensisw.exe SlideSpeechInstaller.nsi
=======================================
--- /dev/null
+++ /odp2wts/SlideSpeechConverter.py Mon Dec 5 17:25:29 2011
@@ -0,0 +1,1073 @@
+# -*- coding: utf-8 -*-
+"""
+SlideSpeechConverter.py
+a component of SlideSpeech.py
+
+Extract speaker notes from .odp or .ppt file
+Prepare script.txt for SlideSpeech
+Prepare convert.bat to generate audio via text-to-speech
+Output HTML wrappers for slide images and audio
+Prepare makeVid.bat to generate video
+
+Copyright (c) 2011 John Graves
+
+MIT License: see LICENSE.txt
+
+20110825 Add version to title bar
+20110901 Add direct to video output
+20110901 Switch to jpg, mklink with /h
+20110902 Switch to jpg for Windows and png for Mac
+20110907 More tweaks to joinContents
+20110909 Allow over 20 slides in MP4Box to cat for Mac
+20110910 Coping with unavailable mklink in Windows and path names
containing spaces
+20110913 Remove [] from script output and wrap ctypes import with win32
test
+20110915 Added boilerplate script comments including version number
+20110916 Read Unicode
+20110917 Write out bits of Question/Answer/Response
+20111118 Show image along with question. Requires slide with comment first.
+ Example:
+ Comment on Slide4
+ [questions=on]
+ Question for slide 4:
+ Answer 1 ; Response 1
+ [questions=off]
+
+ NOTE: last slide must not have questions
+
+img1.png > img1.htm > img1.mp3
+[questions=on]
+How many slides have we seen? > q/img1q1.htm > q/img1q1.mp3
+One ;; > q/img1q1a1.mp3
+Two ;; > q/img1q1a1.mp3
+
+What slide is next? > q/img1q2.htm > q/img1q2.mp3
+Third ;; > q/img1q2a1.mp3
+Fourth; No, just the third. > q/img1q2a2.mp3, > q/img1q2r2.mp3
+
+[questions=off]
+
+20111112 If script has [Source=http:// ...] add this link to the question
page
+20111121 Turn off debug2.txt and put quotes around calls in makeVid.bat
+20111128 Working Linux version once dependencies are installed
+ Linux dependencies include:
+ sox
+ ffmpeg
+ mencode
+ espeak
+20111205 Allow for direct script and image creation from PowerPoint files
+20111206 Renamed SlideSpeech Converter
+"""
+__version__ = "0.1.30"
+
+import BeautifulSoup
+from BeautifulSoup import BeautifulStoneSoup
+from ConfigParser import ConfigParser
+import codecs
+import easygui
+import math
+import os
+import os.path
+import shutil
+import scriptParser
+import stat
+import subprocess
+import sys
+import time
+if sys.platform.startswith("win"):
+ import win32com.client
+import webbrowser
+from zipfile import ZipFile
+
+def ensure_dir(d):
+ """Make a directory if it does not exist"""
+ if not os.path.exists(d):
+ os.makedirs(d)
+
+# Find location of Windows common application data files for odp2ss.ini
+iniDirectory = None
+if sys.platform.startswith("win"):
+ import ctypes
+ from ctypes import wintypes, windll
+ CSIDL_COMMON_APPDATA = 35
+
+ _SHGetFolderPath = windll.shell32.SHGetFolderPathW
+ _SHGetFolderPath.argtypes = [wintypes.HWND,
+ ctypes.c_int,
+ wintypes.HANDLE,
+ wintypes.DWORD, wintypes.LPCWSTR]
+
+
+ path_buf = wintypes.create_unicode_buffer(wintypes.MAX_PATH)
+ result = _SHGetFolderPath(0, CSIDL_COMMON_APPDATA, 0, 0, path_buf)
+ iniDirectory = path_buf.value+os.sep+"SlideSpeech"
+else:
+ iniDirectory = os.path.expanduser('~')+os.sep+".SlideSpeech"
+
+ensure_dir(iniDirectory)
+
+if sys.platform.startswith("win"):
+ imageFileSuffix = ".jpg"
+else:
+ imageFileSuffix = ".png"
+
+## Obtain odpFile name and directory
+
+# Check for last .odp file in config file
+if sys.platform.startswith("win"):
+ lastOdpFile = '~/*.ppt*'
+else:
+ lastOdpFile = '~/*.odp'
+config = ConfigParser()
+try:
+ config.read(iniDirectory+os.sep+'odp2ss.ini')
+ lastOdpFile = config.get("Files","lastOdpFile")
+except:
+ config.add_section("Files")
+ config.set("Files","lastOdpFile","")
+ with open(iniDirectory+os.sep+'odp2ss.ini', 'wb') as configfile:
+ config.write(configfile)
+
+if not os.path.isfile(lastOdpFile):
+ lastOdpFile = None
+
+if sys.platform.startswith("win"):
+ odpFilePath = easygui.fileopenbox(title="SlideSpeech from PPT
Converter "+__version__, msg="Select a .ppt file",
+ default=lastOdpFile, filetypes=None)
+else:
+ odpFilePath = easygui.fileopenbox(title="SlideSpeech from ODP
Converter "+__version__, msg="Select an .odp file",
+ default=lastOdpFile, filetypes=None)
+
+if odpFilePath == None:
+ sys.exit()
+
+(odpFileDirectory, odpFile) = os.path.split(odpFilePath)
+(odpName, odpSuffix) = odpFile.split(".")
+
+## Find or create list of .png or .jpg files
+
+odpFileSubdirectory = odpFileDirectory+os.sep+odpName
+# Create a subdirectory for generated files (if needed)
+ensure_dir(odpFileSubdirectory)
+
+scriptAndImagesCreated = False
+if sys.platform.startswith("win") and odpSuffix.startswith("ppt"):
+ # create .jpg files
+ slideNotes = []
+ try:
+ Application = win32com.client.Dispatch("PowerPoint.Application")
+ except:
+ easygui.msgbox("PowerPoint not available.")
+ sys.exit()
+ Application.Visible = True
+ Presentation = Application.Presentations.Open(odpFilePath)
+ onSlide = 0
+ for Slide in Presentation.Slides:
+ imageName = "Slide" + str(onSlide) + ".jpg"
+ onSlide += 1
+ Slide.Export(odpFileSubdirectory+os.sep+imageName,"JPG",800,600)
+
+ for Shape in Slide.NotesPage.Shapes:
+ if Shape.HasTextFrame:
+ if Shape.TextFrame.HasText:
+ text = Shape.TextFrame.TextRange.Text
+ if not text.isdigit():
+ slideNotes.append(text)
+ Application.Quit()
+
+ # Look for .jpg files (slide images) in the odpName subdirectory
+ dir = os.listdir(odpFileSubdirectory)
+ imageFileList = [file for file in dir if
file.lower().endswith(imageFileSuffix)]
+
+ outFile = open(odpFileSubdirectory+os.sep+"script.txt","w")
+ onSlide = 0
+ for item in slideNotes:
+ imageName = "Slide" + str(onSlide) + ".jpg\n"
+ onSlide += 1
+ outFile.write(imageName)
+ outFile.write(item + "\n\n")
+ outFile.close()
+
+ if ((0 != len(odpFile)) and (os.path.exists(odpFilePath))):
+ # Save file name to config file
+ config.set("Files","lastOdpFile",odpFilePath)
+ with open(iniDirectory+os.sep+'odp2ss.ini', 'wb') as configfile:
+ config.write(configfile)
+
+ scriptAndImagesCreated = True
+else:
+ # Look for .jpg files (slide images) in the odpName subdirectory
+ dir = os.listdir(odpFileSubdirectory)
+ imageFileList = [file for file in dir if
file.lower().endswith(imageFileSuffix)]
+
+ # If no image files found there ...
+ if len(imageFileList)==0:
+ # ... look for image files in odpFileDirectory and copy to odpName
subdirectory
+ dir = os.listdir(odpFileDirectory)
+ imageFileList = [file for file in dir if
file.lower().endswith(imageFileSuffix)]
+ # If still no image files, request some.
+ if len(imageFileList)==0:
+ easygui.msgbox("Need some slide image files for this
presentation.\n.jpg for Windows or .png for Mac OSX.")
+ sys.exit()
+ else:
+ for file in imageFileList:
+ shutil.copy(odpFileDirectory+os.sep+file,
odpFileSubdirectory)
+
+# Find minimum value for slide number for linking to First Slide
+# Find maximum value for slide number for linking to Last Slide
+# Find imageFilePrefix, imageFileSuffix
+# Default values
+minNum = 0
+maxNum = 0
+wrongStem = False
+
+for file in imageFileList:
+ # Parse out file name stem (which includes number) and imageFileSuffix
+ (stem, imageFileSuffix) = file.split(".")
+
+ # Parse out just number (num) and imageFilePrefix
+ if stem.startswith("Slide"):
+ # PowerPoint Slide images are output to jpg with starting index of
0
+ imageFilePrefix = "Slide"
+ minNum=0
+ num = int(stem[5:])
+ elif stem.startswith("img"):
+ # ODP slide images are output to img with starting index of 0
+ imageFilePrefix = "img"
+ num = int(stem[3:])
+ else:
+ wrongStem = True
+ if num>maxNum:
+ maxNum=num
+
+if wrongStem:
+ easygui.msgbox("Need slide image files for this presentation\n"+
+ "with consistent stem: Slide* or img*\n\nCheck
in "+odpFileSubdirectory)
+ sys.exit()
+
+if not scriptAndImagesCreated:
+ ## Step 1 - parse the .odp file, prepare script.txt and .zip file
+
+ def joinContents(textPList):
+ """Combine tagged XML into single string
+
+ Needs to handle this from PowerPoint:
+ <text:p text:style-name="a785" text:class-names=""
text:cond-style-name="">
+ <text:span text:style-name="a783" text:class-names="">Voice over
1</text:span>
+ <text:span text:style-name="a784" text:class-names=""/>
+ </text:p>
+
+ or worse, this:
+ <text:p text:style-name="a786" text:class-names=""
text:cond-style-name="">
+ <text:span text:style-name="a783" text:class-names="">
+ Voice
+ <text:s text:c="1"/>
+ </text:span>
+ <text:span text:style-name="a784" text:class-names="">
+ over 1
+ <text:s text:c="1"/>
+ asdf
+ </text:span>
+ <text:span text:style-name="a785" text:class-names=""/>
+ </text:p>
+
+ """
+ # item is list of all the XML for a single slide
+ joinedItems = ""
+ if len(textPList)>0:
+ textItems = []
+ i = 0
+ for textP in textPList:
+ textSpans = []
+ # break the XML into a list of tagged pieces (text:span)
+ for item in textP:
+ if type(item)==BeautifulSoup.Tag:
+ tagContents = item.contents
+ if type(tagContents)==type([]):
+ for item2 in tagContents:
+ if type(item2)==BeautifulSoup.Tag:
+ textSpans.append([item2.contents])
+ else:
+ textSpans.append([item2])
+ else:
+ textSpans.append([tagContents])
+ else:
+ textSpans.append([item])
+
+ # flatten list
+ textSpans1 = [item for sublist in textSpans for item in
sublist]
+ # clean up
+ textSpans1b = []
+ for item in textSpans1:
+ if type(item)==BeautifulSoup.NavigableString:
+ textSpans1b.append(item)
+ elif type(item)==type([]):
+ if len(item)==0:
+ pass
+ elif len(item)==1:
+ textSpans1b.append(item[0])
+ else:
+ for itemInList in item:
+ textSpans1b.append(itemInList)
+ # find the contents of these pieces if they are still
tagged (text:s)
+ textSpans2 = []
+ for textSpan in textSpans1b:
+ if type(textSpan)==BeautifulSoup.Tag:
+ textSpans2.append(textSpan.text)
+ else:
+ if (type(textSpan)==type([]) and len(textSpan)>0):
+ textSpans2.append(unicode(textSpan[0]))
+ else:
+ textSpans2.append(unicode(textSpan))
+
+ justText = u""
+ for item in textSpans2:
+ # deal with single quote and double quotes and dashes
+ # \u2018 LEFT SINGLE QUOTATION MARK
+ justText = justText + item + u" "
+ textItems.append(justText)
+ joinedItems = "\n".join(textItems)
+ return joinedItems
+
+ if ((0 != len(odpFile)) and (os.path.exists(odpFilePath))):
+ # Save file name to config file
+ config.set("Files","lastOdpFile",odpFilePath)
+ with open(iniDirectory+os.sep+'odp2ss.ini', 'wb') as configfile:
+ config.write(configfile)
+
+ odpName = odpFile.replace(".odp","")
+ odp = ZipFile(odpFilePath,'r')
+ f = odp.read(u'content.xml')
+ soup = BeautifulStoneSoup(f)
+ notes = soup.findAll(attrs={"presentation:class":"notes"})
+ noteTextPLists = [item.findAll("text:p") for item in notes]
+ noteText = [joinContents(noteTextPList) for noteTextPList in
noteTextPLists]
+ else:
+ sys.exit()
+
+ # Create script.txt file
+ scriptFile = codecs.open(odpFileSubdirectory+os.sep+'script.txt',
encoding='utf-8', mode='w+')
+ scriptFile.write("""#[path=]
+ #
+ # Script created with SlideSpeech from ODP version """+__version__+
+ "\n# http://slidespeech.org\n"+
+ "# Date: "+time.strftime("%a, %d %b %Y %H:%M:%S",
time.localtime())+"""
+ #
+ # Title:
+ # Author:
+ #
+ # SlideSpeech Slide show version
+ # http://
+ #
+ # SlideSpeech Video version
+ # http://
+ #
+ # SlideSpeech script:
+ """)
+ onImg = minNum
+ for item in noteText:
+ if onImg-minNum == 0: # first slide
+ # insert line with link to first slide image after parameter
lines
+ # For example, noteText could start with [path=...]
+ lines = item.split("\n")
+ slideOnLine = -1
+ for linenum, line in enumerate(lines):
+ if len(line.strip())>0:
+ if line.startswith("["):
+ scriptFile.write(line+"\n")
+ elif slideOnLine == -1:
+
scriptFile.write(imageFilePrefix+str(onImg)+"."+imageFileSuffix+"\n")
+ slideOnLine = linenum
+ scriptFile.write(line+"\n")
+ else:
+ scriptFile.write(line+"\n")
+ else:
+ scriptFile.write("\n")
+ else:
+ # Add a line with a link to each slide
+
scriptFile.write(imageFilePrefix+str(onImg)+"."+imageFileSuffix+"\n")
+ # followed by the voice over text for the slide
+ scriptFile.write(item+"\n")
+ scriptFile.write("\n")
+ onImg += 1
+ scriptFile.close()
+
+# Collect script and image files into ZIP file
+outputFile = ZipFile(odpFileDirectory+os.sep+odpName+".zip",'w')
+savePath = os.getcwd()
+os.chdir(odpFileSubdirectory)
+outputFile.write("script.txt")
+for file in imageFileList:
+ outputFile.write(file)
+os.chdir(savePath)
+easygui.msgbox("Zipped script.txt and image files
to "+odpFileDirectory+os.sep+odpName+".zip")
+
+## Step 2 - Sequence script and make and run convert.bat
+def convertItem(f,item,onImgStr):
+ if sys.platform.startswith("win"):
+ # For Windows
+
f.write('"'+savePath+os.sep+'sapi2wav.exe" '+imageFilePrefix+onImgStr+'.wav
1 -t "')
+ lines = item.split("\n")
+ for linenum, line in enumerate(lines):
+ if not line.startswith("["):
+ line.replace('"',' ').replace('`',' ').replace(';',' ')
+ if not line.startswith("#"):
+ f.write(line+" ")
+ elif linenum>0:
+ break
+ f.write('"\n')
+ f.write('"'+savePath+os.sep+'lame.exe"
-h '+imageFilePrefix+onImgStr+'.wav '+ '"' + \
+
odpFileSubdirectory+os.sep+imageFilePrefix+onImgStr+'.mp3"\n')
+
f.write('"'+savePath+os.sep+'sox.exe" '+imageFilePrefix+onImgStr+'.wav '+ '"'
+ \
+
odpFileSubdirectory+os.sep+imageFilePrefix+onImgStr+'.ogg"\n')
+ f.write('del '+imageFilePrefix+onImgStr+'.wav\n')
+ elif sys.platform.startswith("darwin"):
+ # For Mac OSX
+ f.write("/usr/bin/say -o "+imageFilePrefix+onImgStr+'.aiff "')
+ lines = item.split("\n")
+ for linenum, line in enumerate(lines):
+ line.replace('"',' ').replace('`',' ').replace(';',' ')
+ if not line.startswith("["):
+ f.write(line+" ")
+ elif linenum>0:
+ break
+ # f.write(item)
+ f.write('"\n')
+ f.write("~/bin/sox "+imageFilePrefix+onImgStr+'.aiff "'+
+ odpFileSubdirectory+os.sep+imageFilePrefix+onImgStr+'.ogg"\n')
+ f.write("~/bin/sox "+imageFilePrefix+onImgStr+'.aiff "'+
+ odpFileSubdirectory+os.sep+imageFilePrefix+onImgStr+'.mp3"\n')
+ f.write("rm "+imageFilePrefix+onImgStr+'.aiff\n')
+
+ else:
+ # For Linux
+ f.write("/usr/bin/espeak -w "+imageFilePrefix+onImgStr+'.wav "')
+ lines = item.split("\n")
+ for linenum, line in enumerate(lines):
+ line.replace('"',' ').replace('`',' ').replace(';',' ')
+ if not line.startswith("["):
+ f.write(line+" ")
+ elif linenum>0:
+ break
+ # f.write(item)
+ f.write('"\n')
+ f.write("/usr/bin/sox "+imageFilePrefix+onImgStr+'.wav "'+
+ odpFileSubdirectory+os.sep+imageFilePrefix+onImgStr+'.ogg"\n')
+ f.write("/usr/bin/sox "+imageFilePrefix+onImgStr+'.wav "'+
+ odpFileSubdirectory+os.sep+imageFilePrefix+onImgStr+'.mp3"\n')
+ # f.write("rm "+imageFilePrefix+onImgStr+'.wav\n')
+
+def writeHtmlHeader(htmlFile):
+ htmlFile.write('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01
Transitional//EN"' + "\n")
+ htmlFile.write('"http://www.w3.org/TR/html4/transitional.dtd">' + "\n")
+ htmlFile.write("<html>\n<head>\n")
+ htmlFile.write('<meta HTTP-EQUIV=CONTENT-TYPE CONTENT="text/html;
charset=utf-8">' + "\n")
+ htmlFile.write('<title>SlideSpeech</title>\n')
+
+def writeHtmlHeader2(htmlFile):
+ htmlFile.write('</head>\n')
+ htmlFile.write('<body text="#000000" bgcolor="#FFFFFF" link="#000080"
vlink="#0000CC" alink="#000080">' + "\n")
+ htmlFile.write('<center>' + "\n")
+
+def writeHtmlFileNavigation(htmlFile, questionFileNames, maxNum,
position):
+ # First page and Back navigation
+ # First page
+ if position==0:
+ htmlFile.write("""First page Back """)
+ # Second page
+ elif position==1:
+ htmlFile.write("""<a href="../""" + odpName +""".htm">First
page</a> <a href="../""" +
+ odpName +""".htm">Back</a> """)
+ # Rest of pages
+ else:
+ htmlFile.write("""<a href="../""" + odpName +""".htm">First
page</a> <a href=""" + '"' +
+
questionFileNames[position-1]+""".htm">Back</a> """)
+
+ # Continue and Last Page navigation
+ # Last page
+ if position==maxNum:
+ htmlFile.write('Continue Last page<br>\n')
+ # First page
+ elif position==0:
+ htmlFile.write( \
+ '<a href="'+
+ odpName+"/"+questionFileNames[position+1]+
+ '.htm">Continue</a> ')
+ htmlFile.write( \
+ '<a href="'+
+ odpName+"/"+questionFileNames[-1]+
+ '.htm">Last page</a><br>\n')
+ # Rest of pages
+ else:
+ htmlFile.write( \
+ '<a href="'+
+ questionFileNames[position+1]+
+ '.htm">Continue</a> ')
+ htmlFile.write( \
+ '<a href="' +
+ questionFileNames[-1] +
+ '.htm">Last page</a><br>\n')
+
+def writeHtmlJavascript(htmlFile,
+ questionFileNames,
+ question,
+ position,
+ audioFileTimes):
+ """
+ <script language="javascript" type="text/javascript">
+ var t;
+ function respond0()
+ {
+ clearTimeout(t)
+ document.getElementById("a0").innerHTML = "Separators of plus
and quotes";
+ document.getElementById("a0").style.color = "grey";
+ document.getElementById('playaudio').innerHTML='<audio
controls autoplay><source src="img8q1r0.mp3" /><source src="img8q1r0.ogg"
/>Your browser does not support the <code>audio</code>
element.</audio><!--[if lte IE 8]><embed src="img8q1r0.mp3"
autostart="true"><![endif]-->';
+ }
+ function respond1()
+ {
+ clearTimeout(t)
+ document.getElementById('playaudio').innerHTML='<audio
controls autoplay><source src="img8q1r1.mp3" /><source src="img8q1r1.ogg"
/>Your browser does not support the <code>audio</code>
element.</audio><!--[if lte IE 8]><embed src="img8q1r1.mp3"
autostart="true"><![endif]-->';
+ t=setTimeout("advance1()",12762);
+ }
+ function advance1()
+ {
+ location.href="img8q2.htm";
+ }
+
+ </script>
+ """
+ htmlFile.write('<script language="javascript"
type="text/javascript">\nvar t;\n')
+ for answerNum, answer in enumerate(question.answers):
+ if len(answer.answerText)>0:
+ htmlFile.write('function respond'+
+ str(answerNum)+
+ '()\n{\n clearTimeout(t);\n')
+ if not answer.action > 0:
+ htmlFile.write(' document.getElementById("a'+
+ str(answerNum)+'").innerHTML = "'+
+ answer.answerText+
+ '";\n')
+ htmlFile.write(' document.getElementById("a'+
+ str(answerNum)+
+ '").style.color = "grey";\n')
+
+ if len(answer.responseText)>0:
+ if position==0:
+ pathToAudio =
odpName+'/'+questionFileNames[position]+'r'+str(answerNum)
+ else:
+ pathToAudio =
questionFileNames[position]+'r'+str(answerNum)
+
+ htmlFile.write( \
+ " document.getElementById('playaudio').innerHTML=" +
+ "'<audio controls autoplay><source src=" +
+ '"' + pathToAudio +
+ '.mp3" />')
+ htmlFile.write('<source src="' +
+ pathToAudio +
+ '.ogg" />')
+ htmlFile.write( \
+ "<embed src=" +
+ '"' + pathToAudio +
+ '.mp3' +
+ '" autostart="true"></audio>' + "';\n")
+ if answer.action > 0:
+ htmlFile.write(' t=setTimeout("advance'+
+ str(answerNum)+
+ '()",'+
+ str(audioFileTimes[pathToAudio]+1000)+
+ ');\n')
+ elif answer.action > 0:
+ htmlFile.write(" advance"+
+ str(answerNum)+
+ '();\n')
+ htmlFile.write('}\n')
+
+ if (answer.action > 0 and position+answer.action <
len(questionFileNames)):
+ htmlFile.write('function advance'+
+ str(answerNum)+
+ '()\n{\n')
+ htmlFile.write(' location.href="'+
+ questionFileNames[position+answer.action]+
+ '.htm";\n}\n')
+ htmlFile.write('</script>\n')
+
+
+def makeConvert(sequence):
+ # Make list of question file names for navigation
+ questionFileNames = []
+ onImg = minNum
+ onImgStr = str(onImg)
+ onQ = 0
+ for question in sequence:
+ if len(question.answers)==0:
+ questionFileNames.append(imageFilePrefix+onImgStr)
+ onImg += 1
+ onImgStr = str(onImg)
+ onQ = 0
+ else:
+ onQ += 1
+ questionFileNames.append(imageFilePrefix+onImgStr+"q"+str(onQ))
+ maxNum = len(questionFileNames)-1
+
+ # Make convert.bat to convert questionText into audio files
+ f = codecs.open(odpFileDirectory+os.sep+"convert.bat",
encoding='utf-8', mode='w+')
+ os.chmod(odpFileDirectory+os.sep+"convert.bat",stat.S_IRWXU)
+ onImg = minNum
+ onImgStr = str(onImg)
+ onQ = 0
+ oggList = []
+ for position, question in enumerate(sequence):
+
+ # write convert.bat
+ if len(question.answers)==0:
+ convertItem(f," ".join(question.questionTexts),onImgStr)
+ oggList.append(onImgStr)
+ onImg += 1
+ onImgStr = str(onImg)
+ onQ = 0
+ else:
+ onQ += 1
+
convertItem(f," ".join(question.questionTexts),onImgStr+"q"+str(onQ))
+ oggList.append(onImgStr+"q"+str(onQ))
+ onAns = 0
+ for answer in question.answers:
+
convertItem(f,answer.answerText,onImgStr+"q"+str(onQ)+"a"+str(onAns))
+ oggList.append(onImgStr+"q"+str(onQ)+"a"+str(onAns))
+ if len(answer.responseText)>0:
+
convertItem(f,answer.responseText,onImgStr+"q"+str(onQ)+"r"+str(onAns))
+ oggList.append(onImgStr+"q"+str(onQ)+"r"+str(onAns))
+ onAns += 1
+
+ # Write concatenation of all .ogg files into all.ogg
+ f.write('cd "'+odpFileSubdirectory+'"\n')
+ if sys.platform.startswith("win"):
+ f.write('"'+savePath+os.sep+'sox.exe" ')
+ elif sys.platform.startswith("darwin"):
+ f.write("~/bin/sox ")
+ else:
+ f.write("/usr/bin/sox ")
+ for item in oggList:
+ f.write(imageFilePrefix+item+".ogg ")
+ f.write('"'+savePath+os.sep+'silence.ogg" ')
+ f.write("all.ogg\n")
+ f.close()
+
+def fetchAudioFileTimes():
+ os.chdir(odpFileSubdirectory)
+ dir = os.listdir(odpFileSubdirectory)
+ ogg = [file for file in dir if file.lower().endswith(".ogg")]
+ oggDict = {}
+
+ for file in ogg:
+ # Parse out file name stem
+ (stem, audioFileSuffix) = file.split(".")
+ # soxi -D returns the duration in seconds of the audio file as a
float
+ if sys.platform.startswith("win"):
+ # Unfortunately, there is a requirement that soxi (with an
implict .exe)
+ # be the command to check audio file duration in Win32
+ # but soxi is the name of the unix version of this utility
+ # So we need to delete the (unix) file called soxi so the
command line call
+ # to soxi will run soxi.exe
+ if os.path.isfile(savePath+os.sep+"soxi"):
+ os.remove(savePath+os.sep+"soxi")
+ command =
[savePath+os.sep+"soxi","-D",odpFileSubdirectory+os.sep+file]
+ elif sys.platform.startswith("darwin"):
+ if os.path.isfile(savePath+os.sep+"soxi"):
+ command =
[savePath+os.sep+"soxi","-D",odpFileSubdirectory+os.sep+file]
+ elif os.path.isfile(savePath+os.sep+"Contents/Resources/soxi"):
+ command =
[savePath+os.sep+"Contents/Resources/soxi","-D",odpFileSubdirectory+os.sep+file]
+ else:
+ command = ["soxi","-D",odpFileSubdirectory+os.sep+file]
+ else :
+ command = ["soxi","-D",odpFileSubdirectory+os.sep+file]
+
+ process = subprocess.Popen(command,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ universal_newlines=True)
+ output = process.communicate()
+ retcode = process.poll()
+ if retcode:
+ print "No time available"
+ oggDict[stem]=int(float(output[0].strip())*1000)
+ return oggDict
+
+def writeHtml(sequence, audioFileTimes):
+
+ # Make list of question file names for navigation
+ questionFileNames = []
+ onImg = minNum
+ onImgStr = str(onImg)
+ onQ = 0
+ for question in sequence:
+ if len(question.answers)==0:
+ questionFileNames.append(imageFilePrefix+onImgStr)
+ onImg += 1
+ onImgStr = str(onImg)
+ onQ = 0
+ else:
+ onQ += 1
+ questionFileNames.append(imageFilePrefix+onImgStr+"q"+str(onQ))
+ maxNum = len(questionFileNames)-1
+
+ onImg = minNum
+ onImgStr = str(onImg)
+ onQ = 0
+ for position, question in enumerate(sequence):
+
+ if position==0:
+ # Create first .htm file in same directory as odpFile
+ htmlFile = codecs.open(odpFileDirectory+os.sep+odpName+".htm",
encoding='utf-8', mode='w+')
+ else:
+ # Create subsequent .htm files in folder in same directory as
odpFile
+ htmlFile =
codecs.open(odpFileSubdirectory+os.sep+questionFileNames[position]+".htm",
encoding='utf-8', mode='w+')
+
+ writeHtmlHeader(htmlFile)
+
+ if len(question.answers)==0:
+ writeHtmlHeader2(htmlFile)
+ writeHtmlFileNavigation(htmlFile, questionFileNames, maxNum,
position)
+ else:
+ writeHtmlJavascript(htmlFile, questionFileNames, question,
position,
+ audioFileTimes)
+ writeHtmlHeader2(htmlFile)
+ writeHtmlFileNavigation(htmlFile, questionFileNames, maxNum,
position)
+
+ if len(question.answers)==0:
+ # image src and link to next slide
+ # Last page which is not (also) the first page
+ if (position==maxNum and position>0):
+ # src but no link
+ htmlFile.write( \
+ '<img src="' +
+ questionFileNames[position] + '.' + imageFileSuffix +
+ '" style="border:0px"><br>\n')
+ # Last page which is also the first page
+ elif (position==maxNum and position==0):
+ # src but no link
+ htmlFile.write( \
+ '<img src="' +
+ odpName+"/"+questionFileNames[position] + '.' +
imageFileSuffix +
+ '" style="border:0px"><br>\n')
+ # First page
+ elif position==0:
+ htmlFile.write( \
+ '<a href="' +
+ odpName+"/"+questionFileNames[position+1] +
+ '.htm">')
+ htmlFile.write( \
+ '<img src="' +
+ odpName +"/" + questionFileNames[position] + '.' +
imageFileSuffix +
+ '" style="border:0px"></a><br>\n')
+ # Rest of pages
+ else:
+ htmlFile.write( \
+ '<a href="' +
+ questionFileNames[position+1] +
+ '.htm">')
+ htmlFile.write( \
+ '<img src="' +
+ questionFileNames[position] + '.' + imageFileSuffix +
+ '" style="border:0px"></a><br>\n')
+
+ # Add source link, if any
+ if 0<len(question.sourceLink):
+ htmlFile.write( \
+ '<a href="' + question.sourceLink + '">' +
question.sourceLink + '</a><br>\n')
+
+ else:
+ htmlFile.write("<br><br><hr><br><center>\n")
+ if len(question.linkToShow)>0:
+ # src but no link
+ htmlFile.write( \
+ '<img src="' +
+ question.linkToShow +
+ '" style="border:0px"><br>\n')
+ htmlFile.write("""<table width="400"
style="text-align:left"><tbody>
+<tr><td>""")
+
htmlFile.write(" ".join(question.questionTexts)+ "</td></tr>\n" )
+
+ for answerNumber, answer in enumerate(question.answers):
+ if len(answer.answerText)>0:
+ htmlFile.write('<tr><td><div id="a'+
+ str(answerNumber)+
+ '"><a href="javascript:respond'+
+ str(answerNumber)+
+ '();">'+
+ answer.answerText +
+ '</a></div></td></tr>\n')
+ htmlFile.write("""</tbody>
+</table>
+</center><br><hr>""")
+
+ # include audio
+ # First page
+ if position==0:
+ pathToAudio = odpName+'/'+questionFileNames[position]
+ else:
+ pathToAudio = questionFileNames[position]
+ # For Safari
+ htmlFile.write( \
+ '<p id="playaudio">' +
+ '<audio controls autoplay><source src="' +
+ pathToAudio +
+ '.mp3" />')
+ # For Firefox
+ htmlFile.write( \
+ '<source src="' +
+ pathToAudio +
+ '.ogg" />\n')
+ # For others
+ htmlFile.write( \
+ 'Your browser does not support the <code>audio</code>
element.\n</audio>\n')
+ htmlFile.write( \
+ '</p>\n')
+ # For Internet Explorer
+ htmlFile.write( \
+ '<!--[if lte IE 8]>\n' +
+ '<script>\n' +
+ 'document.getElementById("playaudio").innerHTML=' + "'" +
+ '<embed src="' +
+ pathToAudio +
+ '.mp3" autostart="true">' + "'" + ';\n' +
+ '</script>\n' +
+ '<![endif]-->\n')
+
+ htmlFile.write('</center>' + "\n")
+ htmlFile.write('</body>\n</html>\n')
+ htmlFile.close()
+
+
+sequence =
scriptParser.parseTxtFile(odpFileSubdirectory+os.sep+"script.txt")
+makeConvert(sequence)
+os.chdir(odpFileDirectory)
+p =
subprocess.Popen('"'+odpFileDirectory+os.sep+'convert.bat"',shell=True).wait()
+audioFileTimes = fetchAudioFileTimes()
+writeHtml(sequence, audioFileTimes)
+
+
+## Step 3 - create makeVid.bat
+os.chdir(odpFileSubdirectory)
+dir = os.listdir(odpFileSubdirectory)
+ogg = [file for file in dir if file.lower().endswith(".ogg")]
+oggDict = {}
+for file in ogg:
+ # Parse out file name stem (which includes number) and imageFileSuffix
+ (stem, audioFileSuffix) = file.split(".")
+
+ # Parse out just number (num) and imageFilePrefix
+ if stem.startswith("Slide"):
+ numberPart = file[5:].split(".")[0]
+ if numberPart.isdigit():
+ oggDict[int(numberPart)] = file
+ else:
+ # imgXX.ogg
+ numberPart = file[3:].split(".")[0]
+ if numberPart.isdigit():
+ oggDict[int(file[3:].split(".")[0])] = file
+sortedOgg = oggDict.values()
+times = []
+for file in sortedOgg:
+ # soxi -D returns the duration in seconds of the audio file as a float
+ if sys.platform.startswith("win"):
+ # Unfortunately, there is a requirement that soxi (with an
implict .exe)
+ # be the command to check audio file duration in Win32
+ # but soxi is the name of the unix version of this utility
+ # So we need to delete the (unix) file called soxi so the command
line call
+ # to soxi will run soxi.exe
+ if os.path.isfile(savePath+os.sep+"soxi"):
+ os.remove(savePath+os.sep+"soxi")
+ command =
[savePath+os.sep+"soxi","-D",odpFileSubdirectory+os.sep+file]
+ elif sys.platform.startswith("darwin"):
+ if os.path.isfile(savePath+os.sep+"soxi"):
+ command =
[savePath+os.sep+"soxi","-D",odpFileSubdirectory+os.sep+file]
+ elif os.path.isfile(savePath+os.sep+"Contents/Resources/soxi"):
+ command =
[savePath+os.sep+"Contents/Resources/soxi","-D",odpFileSubdirectory+os.sep+file]
+ else:
+ command = ["soxi","-D",odpFileSubdirectory+os.sep+file]
+ else:
+ command = ["soxi","-D",odpFileSubdirectory+os.sep+file]
+ process = subprocess.Popen(command,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ universal_newlines=True)
+ output = process.communicate()
+ retcode = process.poll()
+ if retcode:
+ print "No time available"
+ times.append(float(output[0].strip()))
+
+# Create makeVid.bat in odpFileDirectory for Windows
+f = open(odpFileDirectory+os.sep+"makeVid.bat","w")
+os.chmod(odpFileDirectory+os.sep+"makeVid.bat",stat.S_IRWXU)
+
+if sys.platform.startswith("win"):
+ # find out if mklink is available
+ mklinkAvailable = False
+ if os.path.isfile("win32_mklink_test"):
+ subprocess.Popen(["del","win32_mklink_test"],shell=True)
+ os.chdir(odpFileDirectory)
+
subprocess.Popen(["mklink","/h","win32_mklink_test","convert.bat"],shell=True).wait()
+ if os.path.isfile("win32_mklink_test"):
+ mklinkAvailable = True
+ subprocess.Popen(["del","win32_mklink_test"],shell=True)
+
+ f.write("echo off\ncls\n")
+ f.write("if exist output.mp4 (del output.mp4)\n")
+ if os.path.isfile(savePath+os.sep+"MP4Box.exe"):
+ catCommand = '"'+savePath+os.sep+'MP4Box"'
+ else:
+ catCommand = "MP4Box"
+ for i, file in enumerate(sortedOgg):
+ stem, suffix = file.split(".")
+ # Add the slide video to the list of videos to be concatenated
+ catCommand += " -cat "+stem+".mp4"
+ tenthsOfSeconds = int(math.floor(times[i]*10))
+ # If we are on the last slide, add enough frames
+ # to give audio time to finish
+ if sortedOgg[i]==sortedOgg[-1]:
+ tenthsOfSeconds += 20
+ # Make a symlink to the slide image for each second the audio runs
+ # Only 999 symlinks are allowed per image, so, if there are more
+ # than this number, we need to also make additional copies of the
+ # slide image to link to
+ for j in range(tenthsOfSeconds):
+ if ((j > 0) and (j % 900 == 0)):
+ f.write("copy "+stem+'.jpg '+stem+str(j)+'.jpg\n')
+ extraStem = ""
+ for j in range(tenthsOfSeconds):
+ if ((j > 0) and (j % 900 == 0)):
+ extraStem = str(j)
+ if mklinkAvailable:
+ f.write("mklink
/h "+stem+'_'+str(j).zfill(5)+'.jpg '+stem+extraStem+'.jpg\n')
+ else:
+
f.write("copy "+stem+'.jpg '+stem+'_'+str(j).zfill(5)+'.jpg\n')
+ # Convert the images to a video of that slide with voice over
+ # NOTE: Little trick here -- Windows wants to substitute the batch
file name
+ # into %0 so we use %1 and pass %0 as the first parameter
+ f.write('"'+savePath+os.sep+'ffmpeg" -i '+stem+'.mp3 -r 10
-i "'+stem+'_%15d.jpg" -ab 64k '+stem+".mp4\n")
+ # Delete the symlinks
+ for j in range(tenthsOfSeconds):
+ f.write("del "+stem+'_'+str(j).zfill(5)+'.jpg\n')
+ # Delete any extra copies
+ for j in range(tenthsOfSeconds):
+ if ((j > 0) and (j % 900 == 0)):
+ f.write("del "+stem+str(j)+'.jpg\n')
+ # Add an output file name for the concatenation
+ catCommand += " output.mp4\n"
+ f.write(catCommand)
+ # Delete all the single slide videos
+ for file in sortedOgg:
+ stem, suffix = file.split(".")
+ f.write('del '+stem+'.mp4\n')
+ f.close()
+
+elif sys.platform.startswith("darwin"):
+ # For Mac OSX
+ # ffmpeg -i Slide1.mp3 -r 1 -i Slide1_%03d.png -ab 64k output.mp4
+ f.write("clear\n")
+ f.write("if [ -f output.mp4 ]\n")
+ f.write("then rm output.mp4\n")
+ f.write("fi\n")
+ if os.path.isfile(savePath+os.sep+"MP4Box"):
+ # for uncompiled run
+ catCommand = '"'+savePath+os.sep+'MP4Box"'
+ elif os.path.isfile(savePath+os.sep+"Contents/Resources/MP4Box"):
+ # for compiled run
+ catCommand = '"'+savePath+os.sep+'Contents/Resources/MP4Box"'
+ else:
+ # for when MP4Box is not distributed but is installed
+ catCommand = "MP4Box"
+ # save another copy for subsequent cat lines if more than 20 slides
+ catCommand2 = catCommand
+ tempFilesToDelete = []
+ for i, file in enumerate(sortedOgg):
+ stem, suffix = file.split(".")
+ # Add the slide video to the list of videos to be concatenated
+ catCommand += " -cat "+stem+".mp4"
+ if ((i>0) and (i % 18 == 0)):
+ tempFilesToDelete.append("temp"+ str(i) +".mp4")
+ # add a temp.mp4 for output and then input on next line
+ catCommand += " temp" + str(i) +".mp4\n"+catCommand2+" -cat
temp" + str(i) +".mp4"
+ tenthsOfSeconds = int(math.floor(times[i]*10))
+ # If we are on the last slide, add enough frames
+ # to give audio time to finish
+ if sortedOgg[i]==sortedOgg[-1]:
+ tenthsOfSeconds += 20
+ # Make a symlink to the slide image for each second the audio runs
+ for j in range(tenthsOfSeconds):
+ # ln -s Slide2.png Slide2_001.png
***The diff for this file has been truncated for email.***
=======================================
--- /dev/null
+++ /odp2wts/exeMaker.py Mon Dec 5 17:25:29 2011
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#-------------------------------------------------------------------------------
+# Name: exeMaker.py
+# Purpose: Compile SlideSpeechConverter.py into Windows .exe
+# Author: John Graves
+#
+# Created: 1 September 2011
+# Modified: 6 December 2011
+# Copyright: (c) John Graves 2011
+# License: MIT License
+#
+# Usage: python exeMaker.py
+#-------------------------------------------------------------------------------
+
+from setuptools import setup
+import operator
+import os
+import py2exe
+import shutil
+import sys
+
+
+class BuildApp:
+ def __init__(self):
+ self.APP = ['SlideSpeechConverter.py']
+ self.DATA_FILES = [(".",['CHANGES.txt',
+ 'ethics_notice.txt',
+ 'README.txt',
+ 'LICENSE.txt',
+ 'lame.exe',
+ 'MP4Box.exe',
+ 'ffmpeg.exe',
+ 'js32.dll',
+ 'sapi2wav.exe',
+ 'silence.ogg',
+ 'sox.exe',
+ 'soxi.exe',
+ 'pthreadgc2.dll',
+ 'libgomp-1.dll',
+ 'zlib1.dll',
+ '_tkinter.pyd',
+ 'tcl85.dll',
+ 'tk85.dll']),]
+ self.OPTIONS = { "dll_excludes": ["POWRPROF.dll",
+ "tk85.dll",
+ "tcl85.dll"],
+ "bundle_files" : 1,
+
+ }
+ #Dist directory
+ self.dist_dir ='dist'
+
+ def run(self):
+ if os.path.isdir(self.dist_dir): #Erase previous destination dir
+ shutil.rmtree(self.dist_dir)
+
+ setup(
+ console=self.APP,
+ data_files=self.DATA_FILES,
+ options={'py2exe': self.OPTIONS},
+ setup_requires=['py2exe'],
+ )
+
+ if os.path.isdir('build'): #Clean up build dir
+ shutil.rmtree('build',True)
+
+if __name__ == '__main__':
+ if operator.lt(len(sys.argv), 2):
+ sys.argv.append('py2exe')
+ BuildApp().run() #Run generation
+
=======================================
--- /dev/null
+++ /odp2wts/icon.svg Mon Dec 5 17:25:29 2011
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 15.0.2, SVG Export Plug-In . SVG
Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG
1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="170.08px" height="99.21px" viewBox="0 0 170.08 99.21"
enable-background="new 0 0 170.08 99.21" xml:space="preserve">
+<g>
+ <g>
+ <path fill="#1C8ECE"
d="M129.35,72.576c-8.148,5.139-18.537,8.219-29.85,8.219c-26.175,0-47.393-16.483-47.393-36.813
+
S73.325,7.171,99.5,7.171c26.174,0,47.391,16.482,47.391,36.812c0,7.41-2.817,14.306-7.666,20.082l21.887,28.148L129.35,72.576z"
+ />
+ <path fill="#575756"
d="M34.684,69.785c6.491,4.094,14.764,6.545,23.774,6.545c20.846,0,37.745-13.125,37.745-29.32
+
c0-16.191-16.899-29.318-37.745-29.318c-20.848,0-37.747,13.126-37.747,29.318c0,5.903,2.246,11.395,6.107,15.996L9.386,85.426
+ L34.684,69.785z"/>
+ </g>
+</g>
+</svg>
=======================================
--- /dev/null
+++ /odp2wts/slideSpeechLogo32.png Mon Dec 5 17:25:29 2011
Binary file, no diff available.
=======================================
--- /dev/null
+++ /odp2wts/slidespeech.ico Mon Dec 5 17:25:29 2011
Binary file, no diff available.
=======================================
--- /dev/null
+++ /wikitospeech/exeMaker.py Mon Dec 5 17:25:29 2011
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+"""
+exeMaker.py
+a component of SlideSpeech.py
+
+Creates SlideSpeech.exe for Windows
+
+python ss2exe.py py2exe
+
+Copyright (c) 2011 John Graves
+
+MIT License: see LICENSE.txt
+"""
+from distutils.core import setup
+import py2exe
+
+setup(console=['SlideSpeech.py'],
+ options = {"py2exe": { "dll_excludes": ["POWRPROF.dll",
+ "tk85.dll",
+ "tcl85.dll"],
+ "bundle_files" : 1,
+ }
+ },
+ data_files=[(".",['CHANGES.txt',
+ 'ethics_notice.txt',
+ 'README.txt',
+ 'LICENSE.txt',
+ 'script.txt',
+ 'SayStatic.exe']),
+ ("static/test",["static/test/Slide1.PNG",
+ "static/test/Slide2.PNG"])],
+ zipfile=None,
+ )
=======================================
--- /MANIFEST.in Wed Apr 28 08:16:52 2010
+++ /dev/null
@@ -1,4 +0,0 @@
-include openallure/*.py
-include openallure/*.txt
-include openallure/*.jpg
-include openallure/docs/*
=======================================
--- /odp2wts/SlideSpeech-from-ODP.nsi Mon Dec 5 15:09:35 2011
+++ /dev/null
@@ -1,132 +0,0 @@
-; check for latest version of UltraModernUI at
https://sourceforge.net/projects/ultramodernui/files/UltraModernUI/
-!include "D:\open-allure-ds\odp2wts\UMUI.nsh"
-!include Library.nsh
-
-Var ALREADY_INSTALLED ; variable - no {} needed, just $
-
-!define SLIDESPEECH_VERSION "0.1.28" ; define - ${} needed
-
-!define MUI_ICON "D:\open-allure-ds\odp2wts\slidespeech.ico"
-!define UMUI_LEFTIMAGE_BMP "D:\open-allure-ds\odp2wts\openallure_left.bmp"
-
-; Welcome
-!define MUI_PAGE_HEADER_TEXT "SlideSpeech from ODP"
-!define MUI_WELCOMEPAGE_TITLE "SlideSpeech from ODP
v.${SLIDESPEECH_VERSION}"
-!define MUI_WELCOMEPAGE_TEXT "SlideSpeech from ODP turns your presentation
\
-into a slide show with voice over and a video. \
-\r\n\r\nClick Next to start installation."
-!insertmacro MUI_PAGE_WELCOME
-
-; Licence
-; !insertmacro MUI_PAGE_LICENSE "D:\open-allure-ds\odp2wts\LICENSE.txt"
-
-; Location folder
-!insertmacro MUI_PAGE_DIRECTORY
-
-; Install files
-!insertmacro MUI_PAGE_INSTFILES
-
-; Finish
-!define MUI_FINISHPAGE_TITLE "SlideSpeech from ODP should now be ready to
use"
-!define MUI_FINISHPAGE_TEXT "See on-line documentation for details."
-!define MUI_FINISHPAGE_LINK "SlideSpeech Documentation"
-!define
MUI_FINISHPAGE_LINK_LOCATION "http://code.google.com/p/slidespeech/wiki/Resources"
-!insertmacro MUI_PAGE_FINISH
-
-; Uninstall
-!insertmacro MUI_UNPAGE_CONFIRM
-!insertmacro MUI_UNPAGE_INSTFILES
-
-;
http://forums.winamp.com/printthread.php?s=4d90165acf6fa187290bbbe00bb790b7&threadid=202244
-;after all the other insertmacros
-!insertmacro MUI_LANGUAGE "English"
-
-;--------------------------------
-
-; The name of the installer
-Name "SlideSpeech from ODP"
-
-; The file to write
-OutFile "SlideSpeech-from-ODP-win-${SLIDESPEECH_VERSION}.exe"
-
-; Default installation directory
-InstallDir "$PROGRAMFILES\SlideSpeech-from-ODP"
-; The text to prompt the user to enter a directory
-DirText "Please choose the directory where you would like SlideSpeech from
ODP installed."
-;ShowInstDetails show
-;ShowUnInstDetails show
-
-; Request application privileges for Windows Vista
-RequestExecutionLevel admin #
http://forums.gforums.winamp.com/showthread.php?s=8a0906800e16ffd51be2d2fda1a23c4c&threadid=306563
-; user is the other option I used to use
-
-; http://nsis.sourceforge.net/Docs/Chapter4.html#4.8.1
-;4.8.1.32 RequestExecutionLevel
-;none|user|highest|admin
-;Specifies the requested execution level for Windows Vista and Windows 7.
The value is embedded in the installer and
-; uninstaller's XML manifest and tells Vista/7, and probably future
versions of Windows, what privileges level the
-; installer requires. user requests the a normal user's level with no
administrative privileges. highest will request
-; the highest execution level available for the current user and will
cause Windows to prompt the user to verify
-; privilege escalation. The prompt might request for the user's password.
admin requests administrator level and
-; will cause Windows to prompt the user as well. Specifying none, which is
also the default, will keep the manifest
-; empty and let Windows decide which execution level is required. Windows
Vista/7 automatically identifies NSIS
-; installers and decides administrator privileges are required. Because of
this, none and admin have virtually the same effect.
-;It's recommended, at least by Microsoft, that every application will be
marked with the required execution level.
-; Unmarked installers are subject to compatibility mode. Workarounds of
this mode include automatically moving any
-; shortcuts created in the user's start menu to all users' start menu.
Installers that need not install anything into
-; system folders or write to the local machine registry (HKLM) should
specify user execution level.
-;More information about this topic can be found at MSDN. Keywords
include "UAC", "requested execution level",
-; "vista manifest" and "vista security".
-
-;--------------------------------
-
-Section ""
-
- CreateDirectory $INSTDIR
- SetOutPath "$INSTDIR"
-
- File "D:\open-allure-ds\odp2wts\slidespeech.ico"
-
- File /r "D:\open-allure-ds\odp2wts\dist\*.*"
-
- ; http://osdir.com/ml/python.db.pysqlite.user/2005-05/msg00040.html (can
be missing)
- ; http://nsis.sourceforge.net/Docs/AppendixB.html
- IfFileExists "$INSTDIR\odp2ss.exe" 0 new_installation ; continue if
true, else jump to new_installation label
- StrCpy $ALREADY_INSTALLED 1 ;set to non-zero value if already installed
- new_installation: ; already installed stays as initialised
-
- ; WAIT FOR JOHN TO SUPPLY
- ;!insertmacro InstallLib REGDLL $ALREADY_INSTALLED
REBOOT_NOTPROTECTED "G:\openallure\msvcr71.dll" $SYSDIR\msvcr100.dll $SYSDIR
-
- SetOutPath $INSTDIR
- ;Create shortcuts etc
- ;create desktop shortcut
- CreateShortCut "$DESKTOP\SlideSpeech from ODP.lnk" \
- "$INSTDIR\odp2ss.exe" "" \
- "$INSTDIR\slidespeech.ico"
-
- CreateDirectory "$SMPROGRAMS\SlideSpeech"
- CreateShortCut "$SMPROGRAMS\SlideSpeech\SlideSpeech from ODP.lnk" \
- "$INSTDIR\odp2ss.exe" "" \
- "$INSTDIR\slidespeech.ico"
- CreateShortCut "$SMPROGRAMS\SlideSpeech\Uninstall.lnk" \
- "$INSTDIR\Uninstall.exe" "" \
- "$INSTDIR\uninstall.exe" 0
- ;Create uninstaller
- WriteUninstaller "$INSTDIR\Uninstall.exe"
-
-SectionEnd ; end the section
-
-;--------------------------------
-
-;Uninstaller Section
-
-Section "Uninstall"
-
- RMDir /r /REBOOTOK $INSTDIR
- RMDir /r /REBOOTOK "$PROFILE\SlideSpeech"
- RMDir /r "$SMPROGRAMS\SlideSpeech"
- Delete "$DESKTOP\SlideSpeech from ODP.lnk"
-
-SectionEnd
-
=======================================
--- /odp2wts/Wiki-to-Speech-from-ODP.nsi Sun Nov 20 19:03:21 2011
+++ /dev/null
@@ -1,132 +0,0 @@
-; check for latest version of UltraModernUI at
https://sourceforge.net/projects/ultramodernui/files/UltraModernUI/
-!include "D:\open-allure-ds\odp2wts\UMUI.nsh"
-!include Library.nsh
-
-Var ALREADY_INSTALLED ; variable - no {} needed, just $
-
-!define WIKI_TO_SPEECH_VERSION "0.1.27" ; define - ${} needed
-
-!define MUI_ICON "D:\open-allure-ds\odp2wts\openallure_32x32.ico"
-!define UMUI_LEFTIMAGE_BMP "D:\open-allure-ds\odp2wts\openallure_left.bmp"
-
-; Welcome
-!define MUI_PAGE_HEADER_TEXT "Wiki-to-Speech from ODP"
-!define MUI_WELCOMEPAGE_TITLE "Wiki-to-Speech from ODP
v.${WIKI_TO_SPEECH_VERSION}"
-!define MUI_WELCOMEPAGE_TEXT "Wiki-to-Speech from ODP turns your
presentation \
-into a slide show with voice over and a video. \
-\r\n\r\nClick Next to start installation."
-!insertmacro MUI_PAGE_WELCOME
-
-; Licence
-; !insertmacro MUI_PAGE_LICENSE "D:\open-allure-ds\odp2wts\LICENSE.txt"
-
-; Location folder
-!insertmacro MUI_PAGE_DIRECTORY
-
-; Install files
-!insertmacro MUI_PAGE_INSTFILES
-
-; Finish
-!define MUI_FINISHPAGE_TITLE "Wiki-to-Speech from ODP should now be ready
to use"
-!define MUI_FINISHPAGE_TEXT "See on-line documentation for details."
-!define MUI_FINISHPAGE_LINK "Wiki-to-Speech Documentation"
-!define
MUI_FINISHPAGE_LINK_LOCATION "http://code.google.com/p/wiki-to-speech/wiki/Resources"
-!insertmacro MUI_PAGE_FINISH
-
-; Uninstall
-!insertmacro MUI_UNPAGE_CONFIRM
-!insertmacro MUI_UNPAGE_INSTFILES
-
-;
http://forums.winamp.com/printthread.php?s=4d90165acf6fa187290bbbe00bb790b7&threadid=202244
-;after all the other insertmacros
-!insertmacro MUI_LANGUAGE "English"
-
-;--------------------------------
-
-; The name of the installer
-Name "Wiki-to-Speech from ODP"
-
-; The file to write
-OutFile "Wiki-to-Speech-from-ODP-win-${WIKI_TO_SPEECH_VERSION}.exe"
-
-; Default installation directory
-InstallDir "$PROGRAMFILES\Wiki-to-Speech-from-ODP"
-; The text to prompt the user to enter a directory
-DirText "Please choose the directory where you would like Wiki-to-Speech
from ODP installed."
-;ShowInstDetails show
-;ShowUnInstDetails show
-
-; Request application privileges for Windows Vista
-RequestExecutionLevel admin #
http://forums.gforums.winamp.com/showthread.php?s=8a0906800e16ffd51be2d2fda1a23c4c&threadid=306563
-; user is the other option I used to use
-
-; http://nsis.sourceforge.net/Docs/Chapter4.html#4.8.1
-;4.8.1.32 RequestExecutionLevel
-;none|user|highest|admin
-;Specifies the requested execution level for Windows Vista and Windows 7.
The value is embedded in the installer and
-; uninstaller's XML manifest and tells Vista/7, and probably future
versions of Windows, what privileges level the
-; installer requires. user requests the a normal user's level with no
administrative privileges. highest will request
-; the highest execution level available for the current user and will
cause Windows to prompt the user to verify
-; privilege escalation. The prompt might request for the user's password.
admin requests administrator level and
-; will cause Windows to prompt the user as well. Specifying none, which is
also the default, will keep the manifest
-; empty and let Windows decide which execution level is required. Windows
Vista/7 automatically identifies NSIS
-; installers and decides administrator privileges are required. Because of
this, none and admin have virtually the same effect.
-;It's recommended, at least by Microsoft, that every application will be
marked with the required execution level.
-; Unmarked installers are subject to compatibility mode. Workarounds of
this mode include automatically moving any
-; shortcuts created in the user's start menu to all users' start menu.
Installers that need not install anything into
-; system folders or write to the local machine registry (HKLM) should
specify user execution level.
-;More information about this topic can be found at MSDN. Keywords
include "UAC", "requested execution level",
-; "vista manifest" and "vista security".
-
-;--------------------------------
-
-Section ""
-
- CreateDirectory $INSTDIR
- SetOutPath "$INSTDIR"
-
- File "D:\open-allure-ds\odp2wts\openallure_32x32.ico"
-
- File /r "D:\open-allure-ds\odp2wts\dist\*.*"
-
- ; http://osdir.com/ml/python.db.pysqlite.user/2005-05/msg00040.html (can
be missing)
- ; http://nsis.sourceforge.net/Docs/AppendixB.html
- IfFileExists "$INSTDIR\odp2wts.exe" 0 new_installation ; continue if
true, else jump to new_installation label
- StrCpy $ALREADY_INSTALLED 1 ;set to non-zero value if already installed
- new_installation: ; already installed stays as initialised
-
- ; WAIT FOR JOHN TO SUPPLY
- ;!insertmacro InstallLib REGDLL $ALREADY_INSTALLED
REBOOT_NOTPROTECTED "G:\openallure\msvcr71.dll" $SYSDIR\msvcr100.dll $SYSDIR
-
- SetOutPath $INSTDIR
- ;Create shortcuts etc
- ;create desktop shortcut
- CreateShortCut "$DESKTOP\Wiki-to-Speech from ODP.lnk" \
- "$INSTDIR\odp2wts.exe" "" \
- "$INSTDIR\openallure_32x32.ico"
-
- CreateDirectory "$SMPROGRAMS\Wiki-to-Speech"
- CreateShortCut "$SMPROGRAMS\Wiki-to-Speech\Wiki-to-Speech from ODP.lnk" \
- "$INSTDIR\odp2wts.exe" "" \
- "$INSTDIR\openallure_32x32.ico"
- CreateShortCut "$SMPROGRAMS\Wiki-to-Speech\Uninstall.lnk" \
- "$INSTDIR\Uninstall.exe" "" \
- "$INSTDIR\uninstall.exe" 0
- ;Create uninstaller
- WriteUninstaller "$INSTDIR\Uninstall.exe"
-
-SectionEnd ; end the section
-
-;--------------------------------
-
-;Uninstaller Section
-
-Section "Uninstall"
-
- RMDir /r /REBOOTOK $INSTDIR
- RMDir /r /REBOOTOK "$PROFILE\Wiki-to-Speech"
- RMDir /r "$SMPROGRAMS\Wiki-to-Speech"
- Delete "$DESKTOP\Wiki-to-Speech from ODP.lnk"
-
-SectionEnd
-
=======================================
--- /odp2wts/make_exe.cmd Sun Dec 4 19:53:35 2011
+++ /dev/null
@@ -1,9 +0,0 @@
-echo off
-rem 20110910 JG Build script for odp2wts.exe for Windows
-rem 20111129 JG Modified to run for SlideSpeech
-echo ====================
-echo Building odp2ss.exe
-echo ====================
-if exist dist (rmdir dist /S)
-python odp2exe.py py2exe
-D:\NSIS\makensisw.exe SlideSpeech-from-ODP.nsi
=======================================
--- /odp2wts/odp2exe.py Mon Dec 5 01:07:53 2011
+++ /dev/null
@@ -1,72 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# Name: odp2exe.py
-# Purpose: Compile odp2ss.py into Windows .exe
-# Author: John Graves
-#
-# Created: 1 September 2011
-# Modified: 29 November 2011
-# Copyright: (c) John Graves 2011
-# License: MIT License
-#
-# Usage: python odp2exe.py
-#-------------------------------------------------------------------------------
-
-from setuptools import setup
-import operator
-import os
-import py2exe
-import shutil
-import sys
-
-
-class BuildApp:
- def __init__(self):
- self.APP = ['odp2ss.py']
- self.DATA_FILES = [(".",['CHANGES.txt',
- 'ethics_notice.txt',
- 'README.txt',
- 'LICENSE.txt',
- 'lame.exe',
- 'MP4Box.exe',
- 'ffmpeg.exe',
- 'js32.dll',
- 'sapi2wav.exe',
- 'silence.ogg',
- 'sox.exe',
- 'soxi.exe',
- 'pthreadgc2.dll',
- 'libgomp-1.dll',
- 'zlib1.dll',
- '_tkinter.pyd',
- 'tcl85.dll',
- 'tk85.dll']),]
- self.OPTIONS = { "dll_excludes": ["POWRPROF.dll",
- "tk85.dll",
- "tcl85.dll"],
- "bundle_files" : 1,
-
- }
- #Dist directory
- self.dist_dir ='dist'
-
- def run(self):
- if os.path.isdir(self.dist_dir): #Erase previous destination dir
- shutil.rmtree(self.dist_dir)
-
- setup(
- console=self.APP,
- data_files=self.DATA_FILES,
- options={'py2exe': self.OPTIONS},
- setup_requires=['py2exe'],
- )
-
- if os.path.isdir('build'): #Clean up build dir
- shutil.rmtree('build',True)
-
-if __name__ == '__main__':
- if operator.lt(len(sys.argv), 2):
- sys.argv.append('py2exe')
- BuildApp().run() #Run generation
-
=======================================
--- /odp2wts/odp2ss.py Mon Dec 5 00:36:38 2011
+++ /dev/null
@@ -1,1068 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-odp2ss.py
-a component of SlideSpeech.py
-
-Extract speaker notes from .odp file and prepare script.txt for SlideSpeech
-
-Copyright (c) 2011 John Graves
-
-MIT License: see LICENSE.txt
-
-20110825 Add version to title bar
-20110901 Add direct to video output
-20110901 Switch to jpg, mklink with /h
-20110902 Switch to jpg for Windows and png for Mac
-20110907 More tweaks to joinContents
-20110909 Allow over 20 slides in MP4Box to cat for Mac
-20110910 Coping with unavailable mklink in Windows and path names
containing spaces
-20110913 Remove [] from script output and wrap ctypes import with win32
test
-20110915 Added boilerplate script comments including version number
-20110916 Read Unicode
-20110917 Write out bits of Question/Answer/Response
-20111118 Show image along with question. Requires slide with comment first.
- Example:
- Comment on Slide4
- [questions=on]
- Question for slide 4:
- Answer 1 ; Response 1
- [questions=off]
-
- NOTE: last slide must not have questions
-
-img1.png > img1.htm > img1.mp3
-[questions=on]
-How many slides have we seen? > q/img1q1.htm > q/img1q1.mp3
-One ;; > q/img1q1a1.mp3
-Two ;; > q/img1q1a1.mp3
-
-What slide is next? > q/img1q2.htm > q/img1q2.mp3
-Third ;; > q/img1q2a1.mp3
-Fourth; No, just the third. > q/img1q2a2.mp3, > q/img1q2r2.mp3
-
-[questions=off]
-
-20111112 If script has [Source=http:// ...] add this link to the question
page
-20111121 Turn off debug2.txt and put quotes around calls in makeVid.bat
-20111128 Working Linux version once dependencies are installed
- Linux dependencies include:
- sox
- ffmpeg
- mencode
- espeak
-20111205 Allow for direct script and image creation from PowerPoint files
-"""
-__version__ = "0.1.30"
-
-import BeautifulSoup
-from BeautifulSoup import BeautifulStoneSoup
-from ConfigParser import ConfigParser
-import codecs
-import easygui
-import math
-import os
-import os.path
-import shutil
-import scriptParser
-import stat
-import subprocess
-import sys
-import time
-if sys.platform.startswith("win"):
- import win32com.client
-import webbrowser
-from zipfile import ZipFile
-
-def ensure_dir(d):
- """Make a directory if it does not exist"""
- if not os.path.exists(d):
- os.makedirs(d)
-
-# Find location of Windows common application data files for odp2ss.ini
-iniDirectory = None
-if sys.platform.startswith("win"):
- import ctypes
- from ctypes import wintypes, windll
- CSIDL_COMMON_APPDATA = 35
-
- _SHGetFolderPath = windll.shell32.SHGetFolderPathW
- _SHGetFolderPath.argtypes = [wintypes.HWND,
- ctypes.c_int,
- wintypes.HANDLE,
- wintypes.DWORD, wintypes.LPCWSTR]
-
-
- path_buf = wintypes.create_unicode_buffer(wintypes.MAX_PATH)
- result = _SHGetFolderPath(0, CSIDL_COMMON_APPDATA, 0, 0, path_buf)
- iniDirectory = path_buf.value+os.sep+"SlideSpeech"
-else:
- iniDirectory = os.path.expanduser('~')+os.sep+".SlideSpeech"
-
-ensure_dir(iniDirectory)
-
-if sys.platform.startswith("win"):
- imageFileSuffix = ".jpg"
-else:
- imageFileSuffix = ".png"
-
-## Obtain odpFile name and directory
-
-# Check for last .odp file in config file
-if sys.platform.startswith("win"):
- lastOdpFile = '~/*.ppt*'
-else:
- lastOdpFile = '~/*.odp'
-config = ConfigParser()
-try:
- config.read(iniDirectory+os.sep+'odp2ss.ini')
- lastOdpFile = config.get("Files","lastOdpFile")
-except:
- config.add_section("Files")
- config.set("Files","lastOdpFile","")
- with open(iniDirectory+os.sep+'odp2ss.ini', 'wb') as configfile:
- config.write(configfile)
-
-if not os.path.isfile(lastOdpFile):
- lastOdpFile = None
-
-if sys.platform.startswith("win"):
- odpFilePath = easygui.fileopenbox(title="SlideSpeech from PPT
Converter "+__version__, msg="Select a .ppt file",
- default=lastOdpFile, filetypes=None)
-else:
- odpFilePath = easygui.fileopenbox(title="SlideSpeech from ODP
Converter "+__version__, msg="Select an .odp file",
- default=lastOdpFile, filetypes=None)
-
-if odpFilePath == None:
- sys.exit()
-
-(odpFileDirectory, odpFile) = os.path.split(odpFilePath)
-(odpName, odpSuffix) = odpFile.split(".")
-
-## Find or create list of .png or .jpg files
-
-odpFileSubdirectory = odpFileDirectory+os.sep+odpName
-# Create a subdirectory for generated files (if needed)
-ensure_dir(odpFileSubdirectory)
-
-scriptAndImagesCreated = False
-if sys.platform.startswith("win") and odpSuffix.startswith("ppt"):
- # create .jpg files
- slideNotes = []
- try:
- Application = win32com.client.Dispatch("PowerPoint.Application")
- except:
- easygui.msgbox("PowerPoint not available.")
- sys.exit()
- Application.Visible = True
- Presentation = Application.Presentations.Open(odpFilePath)
- onSlide = 0
- for Slide in Presentation.Slides:
- imageName = "Slide" + str(onSlide) + ".jpg"
- onSlide += 1
- Slide.Export(odpFileSubdirectory+os.sep+imageName,"JPG",800,600)
-
- for Shape in Slide.NotesPage.Shapes:
- if Shape.HasTextFrame:
- if Shape.TextFrame.HasText:
- text = Shape.TextFrame.TextRange.Text
- if not text.isdigit():
- slideNotes.append(text)
- Application.Quit()
-
- # Look for .jpg files (slide images) in the odpName subdirectory
- dir = os.listdir(odpFileSubdirectory)
- imageFileList = [file for file in dir if
file.lower().endswith(imageFileSuffix)]
-
- outFile = open(odpFileSubdirectory+os.sep+"script.txt","w")
- onSlide = 0
- for item in slideNotes:
- imageName = "Slide" + str(onSlide) + ".jpg\n"
- onSlide += 1
- outFile.write(imageName)
- outFile.write(item + "\n\n")
- outFile.close()
-
- if ((0 != len(odpFile)) and (os.path.exists(odpFilePath))):
- # Save file name to config file
- config.set("Files","lastOdpFile",odpFilePath)
- with open(iniDirectory+os.sep+'odp2ss.ini', 'wb') as configfile:
- config.write(configfile)
-
- scriptAndImagesCreated = True
-else:
- # Look for .jpg files (slide images) in the odpName subdirectory
- dir = os.listdir(odpFileSubdirectory)
- imageFileList = [file for file in dir if
file.lower().endswith(imageFileSuffix)]
-
- # If no image files found there ...
- if len(imageFileList)==0:
- # ... look for image files in odpFileDirectory and copy to odpName
subdirectory
- dir = os.listdir(odpFileDirectory)
- imageFileList = [file for file in dir if
file.lower().endswith(imageFileSuffix)]
- # If still no image files, request some.
- if len(imageFileList)==0:
- easygui.msgbox("Need some slide image files for this
presentation.\n.jpg for Windows or .png for Mac OSX.")
- sys.exit()
- else:
- for file in imageFileList:
- shutil.copy(odpFileDirectory+os.sep+file,
odpFileSubdirectory)
-
-# Find minimum value for slide number for linking to First Slide
-# Find maximum value for slide number for linking to Last Slide
-# Find imageFilePrefix, imageFileSuffix
-# Default values
-minNum = 0
-maxNum = 0
-wrongStem = False
-
-for file in imageFileList:
- # Parse out file name stem (which includes number) and imageFileSuffix
- (stem, imageFileSuffix) = file.split(".")
-
- # Parse out just number (num) and imageFilePrefix
- if stem.startswith("Slide"):
- # PowerPoint Slide images are output to jpg with starting index of
0
- imageFilePrefix = "Slide"
- minNum=0
- num = int(stem[5:])
- elif stem.startswith("img"):
- # ODP slide images are output to img with starting index of 0
- imageFilePrefix = "img"
- num = int(stem[3:])
- else:
- wrongStem = True
- if num>maxNum:
- maxNum=num
-
-if wrongStem:
- easygui.msgbox("Need slide image files for this presentation\n"+
- "with consistent stem: Slide* or img*\n\nCheck
in "+odpFileSubdirectory)
- sys.exit()
-
-if not scriptAndImagesCreated:
- ## Step 1 - parse the .odp file, prepare script.txt and .zip file
-
- def joinContents(textPList):
- """Combine tagged XML into single string
-
- Needs to handle this from PowerPoint:
- <text:p text:style-name="a785" text:class-names=""
text:cond-style-name="">
- <text:span text:style-name="a783" text:class-names="">Voice over
1</text:span>
- <text:span text:style-name="a784" text:class-names=""/>
- </text:p>
-
- or worse, this:
- <text:p text:style-name="a786" text:class-names=""
text:cond-style-name="">
- <text:span text:style-name="a783" text:class-names="">
- Voice
- <text:s text:c="1"/>
- </text:span>
- <text:span text:style-name="a784" text:class-names="">
- over 1
- <text:s text:c="1"/>
- asdf
- </text:span>
- <text:span text:style-name="a785" text:class-names=""/>
- </text:p>
-
- """
- # item is list of all the XML for a single slide
- joinedItems = ""
- if len(textPList)>0:
- textItems = []
- i = 0
- for textP in textPList:
- textSpans = []
- # break the XML into a list of tagged pieces (text:span)
- for item in textP:
- if type(item)==BeautifulSoup.Tag:
- tagContents = item.contents
- if type(tagContents)==type([]):
- for item2 in tagContents:
- if type(item2)==BeautifulSoup.Tag:
- textSpans.append([item2.contents])
- else:
- textSpans.append([item2])
- else:
- textSpans.append([tagContents])
- else:
- textSpans.append([item])
-
- # flatten list
- textSpans1 = [item for sublist in textSpans for item in
sublist]
- # clean up
- textSpans1b = []
- for item in textSpans1:
- if type(item)==BeautifulSoup.NavigableString:
- textSpans1b.append(item)
- elif type(item)==type([]):
- if len(item)==0:
- pass
- elif len(item)==1:
- textSpans1b.append(item[0])
- else:
- for itemInList in item:
- textSpans1b.append(itemInList)
- # find the contents of these pieces if they are still
tagged (text:s)
- textSpans2 = []
- for textSpan in textSpans1b:
- if type(textSpan)==BeautifulSoup.Tag:
- textSpans2.append(textSpan.text)
- else:
- if (type(textSpan)==type([]) and len(textSpan)>0):
- textSpans2.append(unicode(textSpan[0]))
- else:
- textSpans2.append(unicode(textSpan))
-
- justText = u""
- for item in textSpans2:
- # deal with single quote and double quotes and dashes
- # \u2018 LEFT SINGLE QUOTATION MARK
- justText = justText + item + u" "
- textItems.append(justText)
- joinedItems = "\n".join(textItems)
- return joinedItems
-
- if ((0 != len(odpFile)) and (os.path.exists(odpFilePath))):
- # Save file name to config file
- config.set("Files","lastOdpFile",odpFilePath)
- with open(iniDirectory+os.sep+'odp2ss.ini', 'wb') as configfile:
- config.write(configfile)
-
- odpName = odpFile.replace(".odp","")
- odp = ZipFile(odpFilePath,'r')
- f = odp.read(u'content.xml')
- soup = BeautifulStoneSoup(f)
- notes = soup.findAll(attrs={"presentation:class":"notes"})
- noteTextPLists = [item.findAll("text:p") for item in notes]
- noteText = [joinContents(noteTextPList) for noteTextPList in
noteTextPLists]
- else:
- sys.exit()
-
- # Create script.txt file
- scriptFile = codecs.open(odpFileSubdirectory+os.sep+'script.txt',
encoding='utf-8', mode='w+')
- scriptFile.write("""#[path=]
- #
- # Script created with SlideSpeech from ODP version """+__version__+
- "\n# http://slidespeech.org\n"+
- "# Date: "+time.strftime("%a, %d %b %Y %H:%M:%S",
time.localtime())+"""
- #
- # Title:
- # Author:
- #
- # SlideSpeech Slide show version
- # http://
- #
- # SlideSpeech Video version
- # http://
- #
- # SlideSpeech script:
- """)
- onImg = minNum
- for item in noteText:
- if onImg-minNum == 0: # first slide
- # insert line with link to first slide image after parameter
lines
- # For example, noteText could start with [path=...]
- lines = item.split("\n")
- slideOnLine = -1
- for linenum, line in enumerate(lines):
- if len(line.strip())>0:
- if line.startswith("["):
- scriptFile.write(line+"\n")
- elif slideOnLine == -1:
-
scriptFile.write(imageFilePrefix+str(onImg)+"."+imageFileSuffix+"\n")
- slideOnLine = linenum
- scriptFile.write(line+"\n")
- else:
- scriptFile.write(line+"\n")
- else:
- scriptFile.write("\n")
- else:
- # Add a line with a link to each slide
-
scriptFile.write(imageFilePrefix+str(onImg)+"."+imageFileSuffix+"\n")
- # followed by the voice over text for the slide
- scriptFile.write(item+"\n")
- scriptFile.write("\n")
- onImg += 1
- scriptFile.close()
-
-# Collect script and image files into ZIP file
-outputFile = ZipFile(odpFileDirectory+os.sep+odpName+".zip",'w')
-savePath = os.getcwd()
-os.chdir(odpFileSubdirectory)
-outputFile.write("script.txt")
-for file in imageFileList:
- outputFile.write(file)
-os.chdir(savePath)
-easygui.msgbox("Zipped script.txt and image files
to "+odpFileDirectory+os.sep+odpName+".zip")
-
-## Step 2 - Sequence script and make and run convert.bat
-def convertItem(f,item,onImgStr):
- if sys.platform.startswith("win"):
- # For Windows
-
f.write('"'+savePath+os.sep+'sapi2wav.exe" '+imageFilePrefix+onImgStr+'.wav
1 -t "')
- lines = item.split("\n")
- for linenum, line in enumerate(lines):
- if not line.startswith("["):
- line.replace('"',' ').replace('`',' ').replace(';',' ')
- if not line.startswith("#"):
- f.write(line+" ")
- elif linenum>0:
- break
- f.write('"\n')
- f.write('"'+savePath+os.sep+'lame.exe"
-h '+imageFilePrefix+onImgStr+'.wav '+ '"' + \
-
odpFileSubdirectory+os.sep+imageFilePrefix+onImgStr+'.mp3"\n')
-
f.write('"'+savePath+os.sep+'sox.exe" '+imageFilePrefix+onImgStr+'.wav '+ '"'
+ \
-
odpFileSubdirectory+os.sep+imageFilePrefix+onImgStr+'.ogg"\n')
- f.write('del '+imageFilePrefix+onImgStr+'.wav\n')
- elif sys.platform.startswith("darwin"):
- # For Mac OSX
- f.write("/usr/bin/say -o "+imageFilePrefix+onImgStr+'.aiff "')
- lines = item.split("\n")
- for linenum, line in enumerate(lines):
- line.replace('"',' ').replace('`',' ').replace(';',' ')
- if not line.startswith("["):
- f.write(line+" ")
- elif linenum>0:
- break
- # f.write(item)
- f.write('"\n')
- f.write("~/bin/sox "+imageFilePrefix+onImgStr+'.aiff "'+
- odpFileSubdirectory+os.sep+imageFilePrefix+onImgStr+'.ogg"\n')
- f.write("~/bin/sox "+imageFilePrefix+onImgStr+'.aiff "'+
- odpFileSubdirectory+os.sep+imageFilePrefix+onImgStr+'.mp3"\n')
- f.write("rm "+imageFilePrefix+onImgStr+'.aiff\n')
-
- else:
- # For Linux
- f.write("/usr/bin/espeak -w "+imageFilePrefix+onImgStr+'.wav "')
- lines = item.split("\n")
- for linenum, line in enumerate(lines):
- line.replace('"',' ').replace('`',' ').replace(';',' ')
- if not line.startswith("["):
- f.write(line+" ")
- elif linenum>0:
- break
- # f.write(item)
- f.write('"\n')
- f.write("/usr/bin/sox "+imageFilePrefix+onImgStr+'.wav "'+
- odpFileSubdirectory+os.sep+imageFilePrefix+onImgStr+'.ogg"\n')
- f.write("/usr/bin/sox "+imageFilePrefix+onImgStr+'.wav "'+
- odpFileSubdirectory+os.sep+imageFilePrefix+onImgStr+'.mp3"\n')
- # f.write("rm "+imageFilePrefix+onImgStr+'.wav\n')
-
-def writeHtmlHeader(htmlFile):
- htmlFile.write('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01
Transitional//EN"' + "\n")
- htmlFile.write('"http://www.w3.org/TR/html4/transitional.dtd">' + "\n")
- htmlFile.write("<html>\n<head>\n")
- htmlFile.write('<meta HTTP-EQUIV=CONTENT-TYPE CONTENT="text/html;
charset=utf-8">' + "\n")
- htmlFile.write('<title>SlideSpeech</title>\n')
-
-def writeHtmlHeader2(htmlFile):
- htmlFile.write('</head>\n')
- htmlFile.write('<body text="#000000" bgcolor="#FFFFFF" link="#000080"
vlink="#0000CC" alink="#000080">' + "\n")
- htmlFile.write('<center>' + "\n")
-
-def writeHtmlFileNavigation(htmlFile, questionFileNames, maxNum,
position):
- # First page and Back navigation
- # First page
- if position==0:
- htmlFile.write("""First page Back """)
- # Second page
- elif position==1:
- htmlFile.write("""<a href="../""" + odpName +""".htm">First
page</a> <a href="../""" +
- odpName +""".htm">Back</a> """)
- # Rest of pages
- else:
- htmlFile.write("""<a href="../""" + odpName +""".htm">First
page</a> <a href=""" + '"' +
-
questionFileNames[position-1]+""".htm">Back</a> """)
-
- # Continue and Last Page navigation
- # Last page
- if position==maxNum:
- htmlFile.write('Continue Last page<br>\n')
- # First page
- elif position==0:
- htmlFile.write( \
- '<a href="'+
- odpName+"/"+questionFileNames[position+1]+
- '.htm">Continue</a> ')
- htmlFile.write( \
- '<a href="'+
- odpName+"/"+questionFileNames[-1]+
- '.htm">Last page</a><br>\n')
- # Rest of pages
- else:
- htmlFile.write( \
- '<a href="'+
- questionFileNames[position+1]+
- '.htm">Continue</a> ')
- htmlFile.write( \
- '<a href="' +
- questionFileNames[-1] +
- '.htm">Last page</a><br>\n')
-
-def writeHtmlJavascript(htmlFile,
- questionFileNames,
- question,
- position,
- audioFileTimes):
- """
- <script language="javascript" type="text/javascript">
- var t;
- function respond0()
- {
- clearTimeout(t)
- document.getElementById("a0").innerHTML = "Separators of plus
and quotes";
- document.getElementById("a0").style.color = "grey";
- document.getElementById('playaudio').innerHTML='<audio
controls autoplay><source src="img8q1r0.mp3" /><source src="img8q1r0.ogg"
/>Your browser does not support the <code>audio</code>
element.</audio><!--[if lte IE 8]><embed src="img8q1r0.mp3"
autostart="true"><![endif]-->';
- }
- function respond1()
- {
- clearTimeout(t)
- document.getElementById('playaudio').innerHTML='<audio
controls autoplay><source src="img8q1r1.mp3" /><source src="img8q1r1.ogg"
/>Your browser does not support the <code>audio</code>
element.</audio><!--[if lte IE 8]><embed src="img8q1r1.mp3"
autostart="true"><![endif]-->';
- t=setTimeout("advance1()",12762);
- }
- function advance1()
- {
- location.href="img8q2.htm";
- }
-
- </script>
- """
- htmlFile.write('<script language="javascript"
type="text/javascript">\nvar t;\n')
- for answerNum, answer in enumerate(question.answers):
- if len(answer.answerText)>0:
- htmlFile.write('function respond'+
- str(answerNum)+
- '()\n{\n clearTimeout(t);\n')
- if not answer.action > 0:
- htmlFile.write(' document.getElementById("a'+
- str(answerNum)+'").innerHTML = "'+
- answer.answerText+
- '";\n')
- htmlFile.write(' document.getElementById("a'+
- str(answerNum)+
- '").style.color = "grey";\n')
-
- if len(answer.responseText)>0:
- if position==0:
- pathToAudio =
odpName+'/'+questionFileNames[position]+'r'+str(answerNum)
- else:
- pathToAudio =
questionFileNames[position]+'r'+str(answerNum)
-
- htmlFile.write( \
- " document.getElementById('playaudio').innerHTML=" +
- "'<audio controls autoplay><source src=" +
- '"' + pathToAudio +
- '.mp3" />')
- htmlFile.write('<source src="' +
- pathToAudio +
- '.ogg" />')
- htmlFile.write( \
- "<embed src=" +
- '"' + pathToAudio +
- '.mp3' +
- '" autostart="true"></audio>' + "';\n")
- if answer.action > 0:
- htmlFile.write(' t=setTimeout("advance'+
- str(answerNum)+
- '()",'+
- str(audioFileTimes[pathToAudio]+1000)+
- ');\n')
- elif answer.action > 0:
- htmlFile.write(" advance"+
- str(answerNum)+
- '();\n')
- htmlFile.write('}\n')
-
- if (answer.action > 0 and position+answer.action <
len(questionFileNames)):
- htmlFile.write('function advance'+
- str(answerNum)+
- '()\n{\n')
- htmlFile.write(' location.href="'+
- questionFileNames[position+answer.action]+
- '.htm";\n}\n')
- htmlFile.write('</script>\n')
-
-
-def makeConvert(sequence):
- # Make list of question file names for navigation
- questionFileNames = []
- onImg = minNum
- onImgStr = str(onImg)
- onQ = 0
- for question in sequence:
- if len(question.answers)==0:
- questionFileNames.append(imageFilePrefix+onImgStr)
- onImg += 1
- onImgStr = str(onImg)
- onQ = 0
- else:
- onQ += 1
- questionFileNames.append(imageFilePrefix+onImgStr+"q"+str(onQ))
- maxNum = len(questionFileNames)-1
-
- # Make convert.bat to convert questionText into audio files
- f = codecs.open(odpFileDirectory+os.sep+"convert.bat",
encoding='utf-8', mode='w+')
- os.chmod(odpFileDirectory+os.sep+"convert.bat",stat.S_IRWXU)
- onImg = minNum
- onImgStr = str(onImg)
- onQ = 0
- oggList = []
- for position, question in enumerate(sequence):
-
- # write convert.bat
- if len(question.answers)==0:
- convertItem(f," ".join(question.questionTexts),onImgStr)
- oggList.append(onImgStr)
- onImg += 1
- onImgStr = str(onImg)
- onQ = 0
- else:
- onQ += 1
-
convertItem(f," ".join(question.questionTexts),onImgStr+"q"+str(onQ))
- oggList.append(onImgStr+"q"+str(onQ))
- onAns = 0
- for answer in question.answers:
-
convertItem(f,answer.answerText,onImgStr+"q"+str(onQ)+"a"+str(onAns))
- oggList.append(onImgStr+"q"+str(onQ)+"a"+str(onAns))
- if len(answer.responseText)>0:
-
convertItem(f,answer.responseText,onImgStr+"q"+str(onQ)+"r"+str(onAns))
- oggList.append(onImgStr+"q"+str(onQ)+"r"+str(onAns))
- onAns += 1
-
- # Write concatenation of all .ogg files into all.ogg
- f.write('cd "'+odpFileSubdirectory+'"\n')
- if sys.platform.startswith("win"):
- f.write('"'+savePath+os.sep+'sox.exe" ')
- elif sys.platform.startswith("darwin"):
- f.write("~/bin/sox ")
- else:
- f.write("/usr/bin/sox ")
- for item in oggList:
- f.write(imageFilePrefix+item+".ogg ")
- f.write('"'+savePath+os.sep+'silence.ogg" ')
- f.write("all.ogg\n")
- f.close()
-
-def fetchAudioFileTimes():
- os.chdir(odpFileSubdirectory)
- dir = os.listdir(odpFileSubdirectory)
- ogg = [file for file in dir if file.lower().endswith(".ogg")]
- oggDict = {}
-
- for file in ogg:
- # Parse out file name stem
- (stem, audioFileSuffix) = file.split(".")
- # soxi -D returns the duration in seconds of the audio file as a
float
- if sys.platform.startswith("win"):
- # Unfortunately, there is a requirement that soxi (with an
implict .exe)
- # be the command to check audio file duration in Win32
- # but soxi is the name of the unix version of this utility
- # So we need to delete the (unix) file called soxi so the
command line call
- # to soxi will run soxi.exe
- if os.path.isfile(savePath+os.sep+"soxi"):
- os.remove(savePath+os.sep+"soxi")
- command =
[savePath+os.sep+"soxi","-D",odpFileSubdirectory+os.sep+file]
- elif sys.platform.startswith("darwin"):
- if os.path.isfile(savePath+os.sep+"soxi"):
- command =
[savePath+os.sep+"soxi","-D",odpFileSubdirectory+os.sep+file]
- elif os.path.isfile(savePath+os.sep+"Contents/Resources/soxi"):
- command =
[savePath+os.sep+"Contents/Resources/soxi","-D",odpFileSubdirectory+os.sep+file]
- else:
- command = ["soxi","-D",odpFileSubdirectory+os.sep+file]
- else :
- command = ["soxi","-D",odpFileSubdirectory+os.sep+file]
-
- process = subprocess.Popen(command,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- universal_newlines=True)
- output = process.communicate()
- retcode = process.poll()
- if retcode:
- print "No time available"
- oggDict[stem]=int(float(output[0].strip())*1000)
- return oggDict
-
-def writeHtml(sequence, audioFileTimes):
-
- # Make list of question file names for navigation
- questionFileNames = []
- onImg = minNum
- onImgStr = str(onImg)
- onQ = 0
- for question in sequence:
- if len(question.answers)==0:
- questionFileNames.append(imageFilePrefix+onImgStr)
- onImg += 1
- onImgStr = str(onImg)
- onQ = 0
- else:
- onQ += 1
- questionFileNames.append(imageFilePrefix+onImgStr+"q"+str(onQ))
- maxNum = len(questionFileNames)-1
-
- onImg = minNum
- onImgStr = str(onImg)
- onQ = 0
- for position, question in enumerate(sequence):
-
- if position==0:
- # Create first .htm file in same directory as odpFile
- htmlFile = codecs.open(odpFileDirectory+os.sep+odpName+".htm",
encoding='utf-8', mode='w+')
- else:
- # Create subsequent .htm files in folder in same directory as
odpFile
- htmlFile =
codecs.open(odpFileSubdirectory+os.sep+questionFileNames[position]+".htm",
encoding='utf-8', mode='w+')
-
- writeHtmlHeader(htmlFile)
-
- if len(question.answers)==0:
- writeHtmlHeader2(htmlFile)
- writeHtmlFileNavigation(htmlFile, questionFileNames, maxNum,
position)
- else:
- writeHtmlJavascript(htmlFile, questionFileNames, question,
position,
- audioFileTimes)
- writeHtmlHeader2(htmlFile)
- writeHtmlFileNavigation(htmlFile, questionFileNames, maxNum,
position)
-
- if len(question.answers)==0:
- # image src and link to next slide
- # Last page which is not (also) the first page
- if (position==maxNum and position>0):
- # src but no link
- htmlFile.write( \
- '<img src="' +
- questionFileNames[position] + '.' + imageFileSuffix +
- '" style="border:0px"><br>\n')
- # Last page which is also the first page
- elif (position==maxNum and position==0):
- # src but no link
- htmlFile.write( \
- '<img src="' +
- odpName+"/"+questionFileNames[position] + '.' +
imageFileSuffix +
- '" style="border:0px"><br>\n')
- # First page
- elif position==0:
- htmlFile.write( \
- '<a href="' +
- odpName+"/"+questionFileNames[position+1] +
- '.htm">')
- htmlFile.write( \
- '<img src="' +
- odpName +"/" + questionFileNames[position] + '.' +
imageFileSuffix +
- '" style="border:0px"></a><br>\n')
- # Rest of pages
- else:
- htmlFile.write( \
- '<a href="' +
- questionFileNames[position+1] +
- '.htm">')
- htmlFile.write( \
- '<img src="' +
- questionFileNames[position] + '.' + imageFileSuffix +
- '" style="border:0px"></a><br>\n')
-
- # Add source link, if any
- if 0<len(question.sourceLink):
- htmlFile.write( \
- '<a href="' + question.sourceLink + '">' +
question.sourceLink + '</a><br>\n')
-
- else:
- htmlFile.write("<br><br><hr><br><center>\n")
- if len(question.linkToShow)>0:
- # src but no link
- htmlFile.write( \
- '<img src="' +
- question.linkToShow +
- '" style="border:0px"><br>\n')
- htmlFile.write("""<table width="400"
style="text-align:left"><tbody>
-<tr><td>""")
-
htmlFile.write(" ".join(question.questionTexts)+ "</td></tr>\n" )
-
- for answerNumber, answer in enumerate(question.answers):
- if len(answer.answerText)>0:
- htmlFile.write('<tr><td><div id="a'+
- str(answerNumber)+
- '"><a href="javascript:respond'+
- str(answerNumber)+
- '();">'+
- answer.answerText +
- '</a></div></td></tr>\n')
- htmlFile.write("""</tbody>
-</table>
-</center><br><hr>""")
-
- # include audio
- # First page
- if position==0:
- pathToAudio = odpName+'/'+questionFileNames[position]
- else:
- pathToAudio = questionFileNames[position]
- # For Safari
- htmlFile.write( \
- '<p id="playaudio">' +
- '<audio controls autoplay><source src="' +
- pathToAudio +
- '.mp3" />')
- # For Firefox
- htmlFile.write( \
- '<source src="' +
- pathToAudio +
- '.ogg" />\n')
- # For others
- htmlFile.write( \
- 'Your browser does not support the <code>audio</code>
element.\n</audio>\n')
- htmlFile.write( \
- '</p>\n')
- # For Internet Explorer
- htmlFile.write( \
- '<!--[if lte IE 8]>\n' +
- '<script>\n' +
- 'document.getElementById("playaudio").innerHTML=' + "'" +
- '<embed src="' +
- pathToAudio +
- '.mp3" autostart="true">' + "'" + ';\n' +
- '</script>\n' +
- '<![endif]-->\n')
-
- htmlFile.write('</center>' + "\n")
- htmlFile.write('</body>\n</html>\n')
- htmlFile.close()
-
-
-sequence =
scriptParser.parseTxtFile(odpFileSubdirectory+os.sep+"script.txt")
-makeConvert(sequence)
-os.chdir(odpFileDirectory)
-p =
subprocess.Popen('"'+odpFileDirectory+os.sep+'convert.bat"',shell=True).wait()
-audioFileTimes = fetchAudioFileTimes()
-writeHtml(sequence, audioFileTimes)
-
-
-## Step 3 - create makeVid.bat
-os.chdir(odpFileSubdirectory)
-dir = os.listdir(odpFileSubdirectory)
-ogg = [file for file in dir if file.lower().endswith(".ogg")]
-oggDict = {}
-for file in ogg:
- # Parse out file name stem (which includes number) and imageFileSuffix
- (stem, audioFileSuffix) = file.split(".")
-
- # Parse out just number (num) and imageFilePrefix
- if stem.startswith("Slide"):
- numberPart = file[5:].split(".")[0]
- if numberPart.isdigit():
- oggDict[int(numberPart)] = file
- else:
- # imgXX.ogg
- numberPart = file[3:].split(".")[0]
- if numberPart.isdigit():
- oggDict[int(file[3:].split(".")[0])] = file
-sortedOgg = oggDict.values()
-times = []
-for file in sortedOgg:
- # soxi -D returns the duration in seconds of the audio file as a float
- if sys.platform.startswith("win"):
- # Unfortunately, there is a requirement that soxi (with an
implict .exe)
- # be the command to check audio file duration in Win32
- # but soxi is the name of the unix version of this utility
- # So we need to delete the (unix) file called soxi so the command
line call
- # to soxi will run soxi.exe
- if os.path.isfile(savePath+os.sep+"soxi"):
- os.remove(savePath+os.sep+"soxi")
- command =
[savePath+os.sep+"soxi","-D",odpFileSubdirectory+os.sep+file]
- elif sys.platform.startswith("darwin"):
- if os.path.isfile(savePath+os.sep+"soxi"):
- command =
[savePath+os.sep+"soxi","-D",odpFileSubdirectory+os.sep+file]
- elif os.path.isfile(savePath+os.sep+"Contents/Resources/soxi"):
- command =
[savePath+os.sep+"Contents/Resources/soxi","-D",odpFileSubdirectory+os.sep+file]
- else:
- command = ["soxi","-D",odpFileSubdirectory+os.sep+file]
- else:
- command = ["soxi","-D",odpFileSubdirectory+os.sep+file]
- process = subprocess.Popen(command,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- universal_newlines=True)
- output = process.communicate()
- retcode = process.poll()
- if retcode:
- print "No time available"
- times.append(float(output[0].strip()))
-
-# Create makeVid.bat in odpFileDirectory for Windows
-f = open(odpFileDirectory+os.sep+"makeVid.bat","w")
-os.chmod(odpFileDirectory+os.sep+"makeVid.bat",stat.S_IRWXU)
-
-if sys.platform.startswith("win"):
- # find out if mklink is available
- mklinkAvailable = False
- if os.path.isfile("win32_mklink_test"):
- subprocess.Popen(["del","win32_mklink_test"],shell=True)
- os.chdir(odpFileDirectory)
-
subprocess.Popen(["mklink","/h","win32_mklink_test","convert.bat"],shell=True).wait()
- if os.path.isfile("win32_mklink_test"):
- mklinkAvailable = True
- subprocess.Popen(["del","win32_mklink_test"],shell=True)
-
- f.write("echo off\ncls\n")
- f.write("if exist output.mp4 (del output.mp4)\n")
- if os.path.isfile(savePath+os.sep+"MP4Box.exe"):
- catCommand = '"'+savePath+os.sep+'MP4Box"'
- else:
- catCommand = "MP4Box"
- for i, file in enumerate(sortedOgg):
- stem, suffix = file.split(".")
- # Add the slide video to the list of videos to be concatenated
- catCommand += " -cat "+stem+".mp4"
- tenthsOfSeconds = int(math.floor(times[i]*10))
- # If we are on the last slide, add enough frames
- # to give audio time to finish
- if sortedOgg[i]==sortedOgg[-1]:
- tenthsOfSeconds += 20
- # Make a symlink to the slide image for each second the audio runs
- # Only 999 symlinks are allowed per image, so, if there are more
- # than this number, we need to also make additional copies of the
- # slide image to link to
- for j in range(tenthsOfSeconds):
- if ((j > 0) and (j % 900 == 0)):
- f.write("copy "+stem+'.jpg '+stem+str(j)+'.jpg\n')
- extraStem = ""
- for j in range(tenthsOfSeconds):
- if ((j > 0) and (j % 900 == 0)):
- extraStem = str(j)
- if mklinkAvailable:
- f.write("mklink
/h "+stem+'_'+str(j).zfill(5)+'.jpg '+stem+extraStem+'.jpg\n')
- else:
-
f.write("copy "+stem+'.jpg '+stem+'_'+str(j).zfill(5)+'.jpg\n')
- # Convert the images to a video of that slide with voice over
- # NOTE: Little trick here -- Windows wants to substitute the batch
file name
- # into %0 so we use %1 and pass %0 as the first parameter
- f.write('"'+savePath+os.sep+'ffmpeg" -i '+stem+'.mp3 -r 10
-i "'+stem+'_%15d.jpg" -ab 64k '+stem+".mp4\n")
- # Delete the symlinks
- for j in range(tenthsOfSeconds):
- f.write("del "+stem+'_'+str(j).zfill(5)+'.jpg\n')
- # Delete any extra copies
- for j in range(tenthsOfSeconds):
- if ((j > 0) and (j % 900 == 0)):
- f.write("del "+stem+str(j)+'.jpg\n')
- # Add an output file name for the concatenation
- catCommand += " output.mp4\n"
- f.write(catCommand)
- # Delete all the single slide videos
- for file in sortedOgg:
- stem, suffix = file.split(".")
- f.write('del '+stem+'.mp4\n')
- f.close()
-
-elif sys.platform.startswith("darwin"):
- # For Mac OSX
- # ffmpeg -i Slide1.mp3 -r 1 -i Slide1_%03d.png -ab 64k output.mp4
- f.write("clear\n")
- f.write("if [ -f output.mp4 ]\n")
- f.write("then rm output.mp4\n")
- f.write("fi\n")
- if os.path.isfile(savePath+os.sep+"MP4Box"):
- # for uncompiled run
- catCommand = '"'+savePath+os.sep+'MP4Box"'
- elif os.path.isfile(savePath+os.sep+"Contents/Resources/MP4Box"):
- # for compiled run
- catCommand = '"'+savePath+os.sep+'Contents/Resources/MP4Box"'
- else:
- # for when MP4Box is not distributed but is installed
- catCommand = "MP4Box"
- # save another copy for subsequent cat lines if more than 20 slides
- catCommand2 = catCommand
- tempFilesToDelete = []
- for i, file in enumerate(sortedOgg):
- stem, suffix = file.split(".")
- # Add the slide video to the list of videos to be concatenated
- catCommand += " -cat "+stem+".mp4"
- if ((i>0) and (i % 18 == 0)):
- tempFilesToDelete.append("temp"+ str(i) +".mp4")
- # add a temp.mp4 for output and then input on next line
- catCommand += " temp" + str(i) +".mp4\n"+catCommand2+" -cat
temp" + str(i) +".mp4"
- tenthsOfSeconds = int(math.floor(times[i]*10))
- # If we are on the last slide, add enough frames
- # to give audio time to finish
- if sortedOgg[i]==sortedOgg[-1]:
- tenthsOfSeconds += 20
- # Make a symlink to the slide image for each second the audio runs
- for j in range(tenthsOfSeconds):
- # ln -s Slide2.png Slide2_001.png
- f.write("ln
-s "+stem+'.png '+stem+'_'+str(j).zfill(5)+'.png\n')
- f.write('"'+savePath+os.sep+'ffmpeg" -i '+stem+'.mp3 -r 10
-i "'+stem+'_%05d.png" -ab 64k '+stem+".mp4\n")
- # Delete the symlinks
- for j in range(tenthsOfSeconds):
- f.write("rm "+stem+'_'+str(j).zfill(5)+'.png\n')
***The diff for this file has been truncated for email.***
=======================================
--- /odp2wts/odp2wts.py Mon Nov 28 00:38:44 2011
+++ /dev/null
@@ -1,1011 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-odp2wts.py
-a component of SlideSpeech.py
-
-Extract speaker notes from .odp file and prepare script.txt for SlideSpeech
-
-Copyright (c) 2011 John Graves
-
-MIT License: see LICENSE.txt
-
-20110825 Add version to title bar
-20110901 Add direct to video output
-20110901 Switch to jpg, mklink with /h
-20110902 Switch to jpg for Windows and png for Mac
-20110907 More tweaks to joinContents
-20110909 Allow over 20 slides in MP4Box to cat for Mac
-20110910 Coping with unavailable mklink in Windows and path names
containing spaces
-20110913 Remove [] from script output and wrap ctypes import with win32
test
-20110915 Added boilerplate script comments including version number
-20110916 Read Unicode
-20110917 Write out bits of Question/Answer/Response
-20111118 Show image along with question. Requires slide with comment first.
- Example:
- Comment on Slide4
- [questions=on]
- Question for slide 4:
- Answer 1 ; Response 1
- [questions=off]
-
- NOTE: last slide must not have questions
-
-img1.png > img1.htm > img1.mp3
-[questions=on]
-How many slides have we seen? > q/img1q1.htm > q/img1q1.mp3
-One ;; > q/img1q1a1.mp3
-Two ;; > q/img1q1a1.mp3
-
-What slide is next? > q/img1q2.htm > q/img1q2.mp3
-Third ;; > q/img1q2a1.mp3
-Fourth; No, just the third. > q/img1q2a2.mp3, > q/img1q2r2.mp3
-
-[questions=off]
-
-20111112 If script has [Source=http:// ...] add this link to the question
page
-20111121 Turn off debug2.txt and put quotes around calls in makeVid.bat
-20111128 Working Linux version once dependencies are installed
- Linux dependencies include:
- sox
- ffmpeg
- mencode
- espeak
-"""
-__version__ = "0.1.28"
-
-import BeautifulSoup
-from BeautifulSoup import BeautifulStoneSoup
-from ConfigParser import ConfigParser
-import codecs
-import easygui
-import math
-import os
-import os.path
-import shutil
-import scriptParser
-import stat
-import subprocess
-import sys
-import time
-import webbrowser
-from zipfile import ZipFile
-
-def ensure_dir(d):
- """Make a directory if it does not exist"""
- if not os.path.exists(d):
- os.makedirs(d)
-
-# Find location of Windows common application data files for odp2wts.ini
-iniDirectory = None
-if sys.platform.startswith("win"):
- import ctypes
- from ctypes import wintypes, windll
- CSIDL_COMMON_APPDATA = 35
-
- _SHGetFolderPath = windll.shell32.SHGetFolderPathW
- _SHGetFolderPath.argtypes = [wintypes.HWND,
- ctypes.c_int,
- wintypes.HANDLE,
- wintypes.DWORD, wintypes.LPCWSTR]
-
-
- path_buf = wintypes.create_unicode_buffer(wintypes.MAX_PATH)
- result = _SHGetFolderPath(0, CSIDL_COMMON_APPDATA, 0, 0, path_buf)
- iniDirectory = path_buf.value+os.sep+"SlideSpeech"
-else:
- iniDirectory = os.path.expanduser('~')+os.sep+".SlideSpeech"
-
-ensure_dir(iniDirectory)
-
-if sys.platform.startswith("win"):
- imageFileSuffix = ".jpg"
-else:
- imageFileSuffix = ".png"
-
-## Obtain odpFile name and directory
-
-# Check for last .odp file in config file
-lastOdpFile = '~/*.odp'
-config = ConfigParser()
-try:
- config.read(iniDirectory+os.sep+'odp2wts.ini')
- lastOdpFile = config.get("Files","lastOdpFile")
-except:
- config.add_section("Files")
- config.set("Files","lastOdpFile","")
- with open(iniDirectory+os.sep+'odp2wts.ini', 'wb') as configfile:
- config.write(configfile)
-
-if not os.path.isfile(lastOdpFile):
- lastOdpFile = None
-
-odpFilePath = easygui.fileopenbox(title="SlideSpeech from ODP
Converter "+__version__, msg="Select an .odp file",
- default=lastOdpFile, filetypes=None)
-if odpFilePath == None:
- sys.exit()
-
-(odpFileDirectory, odpFile) = os.path.split(odpFilePath)
-(odpName, odpSuffix) = odpFile.split(".")
-
-## Find list of .png or .jpg files
-
-odpFileSubdirectory = odpFileDirectory+os.sep+odpName
-# Create a subdirectory for generated files (if needed)
-ensure_dir(odpFileSubdirectory)
-
-# Look for .jpg files (slide images) in the odpName subdirectory
-dir = os.listdir(odpFileSubdirectory)
-imageFileList = [file for file in dir if
file.lower().endswith(imageFileSuffix)]
-
-# If no image files found there ...
-if len(imageFileList)==0:
- # ... look for image files in odpFileDirectory and copy to odpName
subdirectory
- dir = os.listdir(odpFileDirectory)
- imageFileList = [file for file in dir if
file.lower().endswith(imageFileSuffix)]
- # If still no image files, request some.
- if len(imageFileList)==0:
- easygui.msgbox("Need some slide image files for this
presentation.\n.jpg for Windows or .png for Mac OSX.")
- sys.exit()
- else:
- for file in imageFileList:
- shutil.copy(odpFileDirectory+os.sep+file, odpFileSubdirectory)
-
-# Find minimum value for slide number for linking to First Slide
-# Find maximum value for slide number for linking to Last Slide
-# Find imageFilePrefix, imageFileSuffix
-# Default values
-minNum = 0
-maxNum = 0
-wrongStem = False
-# Test contents of image file list
-print imageFileList
-for file in imageFileList:
- # Parse out file name stem (which includes number) and imageFileSuffix
- (stem, imageFileSuffix) = file.split(".")
-
- # Parse out just number (num) and imageFilePrefix
- if stem.startswith("Slide"):
- # PowerPoint Slide images are output to jpg with starting index of
1
- imageFilePrefix = "Slide"
- minNum=1
- num = int(stem[5:])
- elif stem.startswith("img"):
- # ODP slide images are output to img with starting index of 0
- imageFilePrefix = "img"
- num = int(stem[3:])
- else:
- wrongStem = True
- if num>maxNum:
- maxNum=num
-
-if wrongStem:
- easygui.msgbox("Need slide image files for this presentation\n"+
- "with consistent stem: Slide* or img*\n\nCheck
in "+odpFileSubdirectory)
- sys.exit()
-
-## Step 1 - parse the .odp file, prepare script.txt and .zip file
-
-def joinContents(textPList):
- """Combine tagged XML into single string
-
-Needs to handle this from PowerPoint:
- <text:p text:style-name="a785" text:class-names=""
text:cond-style-name="">
- <text:span text:style-name="a783" text:class-names="">Voice over
1</text:span>
- <text:span text:style-name="a784" text:class-names=""/>
- </text:p>
-
-or worse, this:
- <text:p text:style-name="a786" text:class-names=""
text:cond-style-name="">
- <text:span text:style-name="a783" text:class-names="">
- Voice
- <text:s text:c="1"/>
- </text:span>
- <text:span text:style-name="a784" text:class-names="">
- over 1
- <text:s text:c="1"/>
- asdf
- </text:span>
- <text:span text:style-name="a785" text:class-names=""/>
- </text:p>
-
- """
- # item is list of all the XML for a single slide
- joinedItems = ""
- if len(textPList)>0:
- textItems = []
- i = 0
- for textP in textPList:
- textSpans = []
- # break the XML into a list of tagged pieces (text:span)
- for item in textP:
- if type(item)==BeautifulSoup.Tag:
- tagContents = item.contents
- if type(tagContents)==type([]):
- for item2 in tagContents:
- if type(item2)==BeautifulSoup.Tag:
- textSpans.append([item2.contents])
- else:
- textSpans.append([item2])
- else:
- textSpans.append([tagContents])
- else:
- textSpans.append([item])
-
- # flatten list
- textSpans1 = [item for sublist in textSpans for item in
sublist]
- # clean up
- textSpans1b = []
- for item in textSpans1:
- if type(item)==BeautifulSoup.NavigableString:
- textSpans1b.append(item)
- elif type(item)==type([]):
- if len(item)==0:
- pass
- elif len(item)==1:
- textSpans1b.append(item[0])
- else:
- for itemInList in item:
- textSpans1b.append(itemInList)
- # find the contents of these pieces if they are still tagged
(text:s)
- textSpans2 = []
- for textSpan in textSpans1b:
- if type(textSpan)==BeautifulSoup.Tag:
- textSpans2.append(textSpan.text)
- else:
- if (type(textSpan)==type([]) and len(textSpan)>0):
- textSpans2.append(unicode(textSpan[0]))
- else:
- textSpans2.append(unicode(textSpan))
-
- justText = u""
- for item in textSpans2:
- # deal with single quote and double quotes and dashes
- # \u2018 LEFT SINGLE QUOTATION MARK
- justText = justText + item + u" "
- textItems.append(justText)
- joinedItems = "\n".join(textItems)
- return joinedItems
-
-if ((0 != len(odpFile)) and (os.path.exists(odpFilePath))):
- # Save file name to config file
- config.set("Files","lastOdpFile",odpFilePath)
- with open(iniDirectory+os.sep+'odp2wts.ini', 'wb') as configfile:
- config.write(configfile)
-
- odpName = odpFile.replace(".odp","")
- odp = ZipFile(odpFilePath,'r')
- f = odp.read(u'content.xml')
- soup = BeautifulStoneSoup(f)
- notes = soup.findAll(attrs={"presentation:class":"notes"})
- noteTextPLists = [item.findAll("text:p") for item in notes]
- noteText = [joinContents(noteTextPList) for noteTextPList in
noteTextPLists]
-else:
- sys.exit()
-
-# Create script.txt file
-scriptFile = codecs.open(odpFileSubdirectory+os.sep+'script.txt',
encoding='utf-8', mode='w+')
-scriptFile.write("""#[path=]
-#
-# Script created with SlideSpeech from ODP version """+__version__+
-"\n# http://slidespeech.org\n"+
-"# Date: "+time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())+"""
-#
-# Title:
-# Author:
-#
-# SlideSpeech Slide show version
-# http://
-#
-# SlideSpeech Video version
-# http://
-#
-# SlideSpeech script:
-""")
-onImg = minNum
-for item in noteText:
- if onImg-minNum == 0: # first slide
- # insert line with link to first slide image after parameter lines
- # For example, noteText could start with [path=...]
- lines = item.split("\n")
- slideOnLine = -1
- for linenum, line in enumerate(lines):
- if len(line.strip())>0:
- if line.startswith("["):
- scriptFile.write(line+"\n")
- elif slideOnLine == -1:
-
scriptFile.write(imageFilePrefix+str(onImg)+"."+imageFileSuffix+"\n")
- slideOnLine = linenum
- scriptFile.write(line+"\n")
- else:
- scriptFile.write(line+"\n")
- else:
- scriptFile.write("\n")
- else:
- # Add a line with a link to each slide
-
scriptFile.write(imageFilePrefix+str(onImg)+"."+imageFileSuffix+"\n")
- # followed by the voice over text for the slide
- scriptFile.write(item+"\n")
- scriptFile.write("\n")
- onImg += 1
-scriptFile.close()
-
-# Collect script and image files into ZIP file
-outputFile = ZipFile(odpFileDirectory+os.sep+odpName+".zip",'w')
-savePath = os.getcwd()
-os.chdir(odpFileSubdirectory)
-outputFile.write("script.txt")
-for file in imageFileList:
- outputFile.write(file)
-os.chdir(savePath)
-easygui.msgbox("Zipped script.txt and image files
to "+odpFileDirectory+os.sep+odpName+".zip")
-
-## Step 2 - Sequence script and make and run convert.bat
-def convertItem(f,item,onImgStr):
- if sys.platform.startswith("win"):
- # For Windows
-
f.write('"'+savePath+os.sep+'sapi2wav.exe" '+imageFilePrefix+onImgStr+'.wav
1 -t "')
- lines = item.split("\n")
- for linenum, line in enumerate(lines):
- if not line.startswith("["):
- line.replace('"',' ').replace('`',' ').replace(';',' ')
- if not line.startswith("#"):
- f.write(line+" ")
- elif linenum>0:
- break
- f.write('"\n')
- f.write('"'+savePath+os.sep+'lame.exe"
-h '+imageFilePrefix+onImgStr+'.wav '+ '"' + \
-
odpFileSubdirectory+os.sep+imageFilePrefix+onImgStr+'.mp3"\n')
-
f.write('"'+savePath+os.sep+'sox.exe" '+imageFilePrefix+onImgStr+'.wav '+ '"'
+ \
-
odpFileSubdirectory+os.sep+imageFilePrefix+onImgStr+'.ogg"\n')
- f.write('del '+imageFilePrefix+onImgStr+'.wav\n')
- elif sys.platform.startswith("darwin"):
- # For Mac OSX
- f.write("/usr/bin/say -o "+imageFilePrefix+onImgStr+'.aiff "')
- lines = item.split("\n")
- for linenum, line in enumerate(lines):
- line.replace('"',' ').replace('`',' ').replace(';',' ')
- if not line.startswith("["):
- f.write(line+" ")
- elif linenum>0:
- break
- # f.write(item)
- f.write('"\n')
- f.write("~/bin/sox "+imageFilePrefix+onImgStr+'.aiff "'+
- odpFileSubdirectory+os.sep+imageFilePrefix+onImgStr+'.ogg"\n')
- f.write("~/bin/sox "+imageFilePrefix+onImgStr+'.aiff "'+
- odpFileSubdirectory+os.sep+imageFilePrefix+onImgStr+'.mp3"\n')
- f.write("rm "+imageFilePrefix+onImgStr+'.aiff\n')
-
- else:
- # For Linux
- f.write("/usr/bin/espeak -w "+imageFilePrefix+onImgStr+'.wav "')
- lines = item.split("\n")
- for linenum, line in enumerate(lines):
- line.replace('"',' ').replace('`',' ').replace(';',' ')
- if not line.startswith("["):
- f.write(line+" ")
- elif linenum>0:
- break
- # f.write(item)
- f.write('"\n')
- f.write("/usr/bin/sox "+imageFilePrefix+onImgStr+'.wav "'+
- odpFileSubdirectory+os.sep+imageFilePrefix+onImgStr+'.ogg"\n')
- f.write("/usr/bin/sox "+imageFilePrefix+onImgStr+'.wav "'+
- odpFileSubdirectory+os.sep+imageFilePrefix+onImgStr+'.mp3"\n')
- # f.write("rm "+imageFilePrefix+onImgStr+'.wav\n')
-
-def writeHtmlHeader(htmlFile):
- htmlFile.write('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01
Transitional//EN"' + "\n")
- htmlFile.write('"http://www.w3.org/TR/html4/transitional.dtd">' + "\n")
- htmlFile.write("<html>\n<head>\n")
- htmlFile.write('<meta HTTP-EQUIV=CONTENT-TYPE CONTENT="text/html;
charset=utf-8">' + "\n")
- htmlFile.write('<title>SlideSpeech</title>\n')
-
-def writeHtmlHeader2(htmlFile):
- htmlFile.write('</head>\n')
- htmlFile.write('<body text="#000000" bgcolor="#FFFFFF" link="#000080"
vlink="#0000CC" alink="#000080">' + "\n")
- htmlFile.write('<center>' + "\n")
-
-def writeHtmlFileNavigation(htmlFile, questionFileNames, maxNum,
position):
- # First page and Back navigation
- # First page
- if position==0:
- htmlFile.write("""First page Back """)
- # Second page
- elif position==1:
- htmlFile.write("""<a href="../""" + odpName +""".htm">First
page</a> <a href="../""" +
- odpName +""".htm">Back</a> """)
- # Rest of pages
- else:
- htmlFile.write("""<a href="../""" + odpName +""".htm">First
page</a> <a href=""" + '"' +
-
questionFileNames[position-1]+""".htm">Back</a> """)
-
- # Continue and Last Page navigation
- # Last page
- if position==maxNum:
- htmlFile.write('Continue Last page<br>\n')
- # First page
- elif position==0:
- htmlFile.write( \
- '<a href="'+
- odpName+"/"+questionFileNames[position+1]+
- '.htm">Continue</a> ')
- htmlFile.write( \
- '<a href="'+
- odpName+"/"+questionFileNames[-1]+
- '.htm">Last page</a><br>\n')
- # Rest of pages
- else:
- htmlFile.write( \
- '<a href="'+
- questionFileNames[position+1]+
- '.htm">Continue</a> ')
- htmlFile.write( \
- '<a href="' +
- questionFileNames[-1] +
- '.htm">Last page</a><br>\n')
-
-def writeHtmlJavascript(htmlFile,
- questionFileNames,
- question,
- position,
- audioFileTimes):
- """
- <script language="javascript" type="text/javascript">
- var t;
- function respond0()
- {
- clearTimeout(t)
- document.getElementById("a0").innerHTML = "Separators of plus
and quotes";
- document.getElementById("a0").style.color = "grey";
- document.getElementById('playaudio').innerHTML='<audio
controls autoplay><source src="img8q1r0.mp3" /><source src="img8q1r0.ogg"
/>Your browser does not support the <code>audio</code>
element.</audio><!--[if lte IE 8]><embed src="img8q1r0.mp3"
autostart="true"><![endif]-->';
- }
- function respond1()
- {
- clearTimeout(t)
- document.getElementById('playaudio').innerHTML='<audio
controls autoplay><source src="img8q1r1.mp3" /><source src="img8q1r1.ogg"
/>Your browser does not support the <code>audio</code>
element.</audio><!--[if lte IE 8]><embed src="img8q1r1.mp3"
autostart="true"><![endif]-->';
- t=setTimeout("advance1()",12762);
- }
- function advance1()
- {
- location.href="img8q2.htm";
- }
-
- </script>
- """
- htmlFile.write('<script language="javascript"
type="text/javascript">\nvar t;\n')
- for answerNum, answer in enumerate(question.answers):
- if len(answer.answerText)>0:
- htmlFile.write('function respond'+
- str(answerNum)+
- '()\n{\n clearTimeout(t);\n')
- if not answer.action > 0:
- htmlFile.write(' document.getElementById("a'+
- str(answerNum)+'").innerHTML = "'+
- answer.answerText+
- '";\n')
- htmlFile.write(' document.getElementById("a'+
- str(answerNum)+
- '").style.color = "grey";\n')
-
- if len(answer.responseText)>0:
- if position==0:
- pathToAudio =
odpName+'/'+questionFileNames[position]+'r'+str(answerNum)
- else:
- pathToAudio =
questionFileNames[position]+'r'+str(answerNum)
-
- htmlFile.write( \
- " document.getElementById('playaudio').innerHTML=" +
- "'<audio controls autoplay><source src=" +
- '"' + pathToAudio +
- '.mp3" />')
- htmlFile.write('<source src="' +
- pathToAudio +
- '.ogg" />')
- htmlFile.write( \
- "<embed src=" +
- '"' + pathToAudio +
- '.mp3' +
- '" autostart="true"></audio>' + "';\n")
- if answer.action > 0:
- htmlFile.write(' t=setTimeout("advance'+
- str(answerNum)+
- '()",'+
- str(audioFileTimes[pathToAudio]+1000)+
- ');\n')
- elif answer.action > 0:
- htmlFile.write(" advance"+
- str(answerNum)+
- '();\n')
- htmlFile.write('}\n')
-
- if (answer.action > 0 and position+answer.action <
len(questionFileNames)):
- htmlFile.write('function advance'+
- str(answerNum)+
- '()\n{\n')
- htmlFile.write(' location.href="'+
- questionFileNames[position+answer.action]+
- '.htm";\n}\n')
- htmlFile.write('</script>\n')
-
-
-def makeConvert(sequence):
- # Make list of question file names for navigation
- questionFileNames = []
- onImg = minNum
- onImgStr = str(onImg)
- onQ = 0
- for question in sequence:
- if len(question.answers)==0:
- questionFileNames.append(imageFilePrefix+onImgStr)
- onImg += 1
- onImgStr = str(onImg)
- onQ = 0
- else:
- onQ += 1
- questionFileNames.append(imageFilePrefix+onImgStr+"q"+str(onQ))
- maxNum = len(questionFileNames)-1
-
- # Make convert.bat to convert questionText into audio files
- f = codecs.open(odpFileDirectory+os.sep+"convert.bat",
encoding='utf-8', mode='w+')
- os.chmod(odpFileDirectory+os.sep+"convert.bat",stat.S_IRWXU)
- onImg = minNum
- onImgStr = str(onImg)
- onQ = 0
- oggList = []
- for position, question in enumerate(sequence):
-
- # write convert.bat
- if len(question.answers)==0:
- convertItem(f," ".join(question.questionTexts),onImgStr)
- oggList.append(onImgStr)
- onImg += 1
- onImgStr = str(onImg)
- onQ = 0
- else:
- onQ += 1
-
convertItem(f," ".join(question.questionTexts),onImgStr+"q"+str(onQ))
- oggList.append(onImgStr+"q"+str(onQ))
- onAns = 0
- for answer in question.answers:
-
convertItem(f,answer.answerText,onImgStr+"q"+str(onQ)+"a"+str(onAns))
- oggList.append(onImgStr+"q"+str(onQ)+"a"+str(onAns))
- if len(answer.responseText)>0:
-
convertItem(f,answer.responseText,onImgStr+"q"+str(onQ)+"r"+str(onAns))
- oggList.append(onImgStr+"q"+str(onQ)+"r"+str(onAns))
- onAns += 1
-
- # Write concatenation of all .ogg files into all.ogg
- f.write('cd "'+odpFileSubdirectory+'"\n')
- if sys.platform.startswith("win"):
- f.write('"'+savePath+os.sep+'sox.exe" ')
- elif sys.platform.startswith("darwin"):
- f.write("~/bin/sox ")
- else:
- f.write("/usr/bin/sox ")
- for item in oggList:
- f.write(imageFilePrefix+item+".ogg ")
- f.write('"'+savePath+os.sep+'silence.ogg" ')
- f.write("all.ogg\n")
- f.close()
-
-def fetchAudioFileTimes():
- os.chdir(odpFileSubdirectory)
- dir = os.listdir(odpFileSubdirectory)
- ogg = [file for file in dir if file.lower().endswith(".ogg")]
- oggDict = {}
-
- for file in ogg:
- # Parse out file name stem
- (stem, audioFileSuffix) = file.split(".")
- # soxi -D returns the duration in seconds of the audio file as a
float
- if sys.platform.startswith("win"):
- # Unfortunately, there is a requirement that soxi (with an
implict .exe)
- # be the command to check audio file duration in Win32
- # but soxi is the name of the unix version of this utility
- # So we need to delete the (unix) file called soxi so the
command line call
- # to soxi will run soxi.exe
- if os.path.isfile(savePath+os.sep+"soxi"):
- os.remove(savePath+os.sep+"soxi")
- command =
[savePath+os.sep+"soxi","-D",odpFileSubdirectory+os.sep+file]
- elif sys.platform.startswith("darwin"):
- if os.path.isfile(savePath+os.sep+"soxi"):
- command =
[savePath+os.sep+"soxi","-D",odpFileSubdirectory+os.sep+file]
- elif os.path.isfile(savePath+os.sep+"Contents/Resources/soxi"):
- command =
[savePath+os.sep+"Contents/Resources/soxi","-D",odpFileSubdirectory+os.sep+file]
- else:
- command = ["soxi","-D",odpFileSubdirectory+os.sep+file]
- else :
- command = ["soxi","-D",odpFileSubdirectory+os.sep+file]
- print command
- process = subprocess.Popen(command,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- universal_newlines=True)
- output = process.communicate()
- retcode = process.poll()
- if retcode:
- print "No time available"
- oggDict[stem]=int(float(output[0].strip())*1000)
- return oggDict
-
-def writeHtml(sequence, audioFileTimes):
-
- # Make list of question file names for navigation
- questionFileNames = []
- onImg = minNum
- onImgStr = str(onImg)
- onQ = 0
- for question in sequence:
- if len(question.answers)==0:
- questionFileNames.append(imageFilePrefix+onImgStr)
- onImg += 1
- onImgStr = str(onImg)
- onQ = 0
- else:
- onQ += 1
- questionFileNames.append(imageFilePrefix+onImgStr+"q"+str(onQ))
- maxNum = len(questionFileNames)-1
-
- onImg = minNum
- onImgStr = str(onImg)
- onQ = 0
- for position, question in enumerate(sequence):
-
- if position==0:
- # Create first .htm file in same directory as odpFile
- htmlFile = codecs.open(odpFileDirectory+os.sep+odpName+".htm",
encoding='utf-8', mode='w+')
- else:
- # Create subsequent .htm files in folder in same directory as
odpFile
- htmlFile =
codecs.open(odpFileSubdirectory+os.sep+questionFileNames[position]+".htm",
encoding='utf-8', mode='w+')
-
- writeHtmlHeader(htmlFile)
-
- if len(question.answers)==0:
- writeHtmlHeader2(htmlFile)
- writeHtmlFileNavigation(htmlFile, questionFileNames, maxNum,
position)
- else:
- writeHtmlJavascript(htmlFile, questionFileNames, question,
position,
- audioFileTimes)
- writeHtmlHeader2(htmlFile)
- writeHtmlFileNavigation(htmlFile, questionFileNames, maxNum,
position)
-
- if len(question.answers)==0:
- # image src and link to next slide
- # Last page which is not (also) the first page
- if (position==maxNum and position>0):
- # src but no link
- htmlFile.write( \
- '<img src="' +
- questionFileNames[position] + '.' + imageFileSuffix +
- '" style="border:0px"><br>\n')
- # Last page which is also the first page
- elif (position==maxNum and position==0):
- # src but no link
- htmlFile.write( \
- '<img src="' +
- odpName+"/"+questionFileNames[position] + '.' +
imageFileSuffix +
- '" style="border:0px"><br>\n')
- # First page
- elif position==0:
- htmlFile.write( \
- '<a href="' +
- odpName+"/"+questionFileNames[position+1] +
- '.htm">')
- htmlFile.write( \
- '<img src="' +
- odpName +"/" + questionFileNames[position] + '.' +
imageFileSuffix +
- '" style="border:0px"></a><br>\n')
- # Rest of pages
- else:
- htmlFile.write( \
- '<a href="' +
- questionFileNames[position+1] +
- '.htm">')
- htmlFile.write( \
- '<img src="' +
- questionFileNames[position] + '.' + imageFileSuffix +
- '" style="border:0px"></a><br>\n')
-
- # Add source link, if any
- if 0<len(question.sourceLink):
- htmlFile.write( \
- '<a href="' + question.sourceLink + '">' +
question.sourceLink + '</a><br>\n')
-
- else:
- htmlFile.write("<br><br><hr><br><center>\n")
- if len(question.linkToShow)>0:
- # src but no link
- htmlFile.write( \
- '<img src="' +
- question.linkToShow +
- '" style="border:0px"><br>\n')
- htmlFile.write("""<table width="400"
style="text-align:left"><tbody>
-<tr><td>""")
-
htmlFile.write(" ".join(question.questionTexts)+ "</td></tr>\n" )
-
- for answerNumber, answer in enumerate(question.answers):
- if len(answer.answerText)>0:
- htmlFile.write('<tr><td><div id="a'+
- str(answerNumber)+
- '"><a href="javascript:respond'+
- str(answerNumber)+
- '();">'+
- answer.answerText +
- '</a></div></td></tr>\n')
- htmlFile.write("""</tbody>
-</table>
-</center><br><hr>""")
-
- # include audio
- # First page
- if position==0:
- pathToAudio = odpName+'/'+questionFileNames[position]
- else:
- pathToAudio = questionFileNames[position]
- # For Safari
- htmlFile.write( \
- '<p id="playaudio">' +
- '<audio controls autoplay><source src="' +
- pathToAudio +
- '.mp3" />')
- # For Firefox
- htmlFile.write( \
- '<source src="' +
- pathToAudio +
- '.ogg" />\n')
- # For others
- htmlFile.write( \
- 'Your browser does not support the <code>audio</code>
element.\n</audio>\n')
- htmlFile.write( \
- '</p>\n')
- # For Internet Explorer
- htmlFile.write( \
- '<!--[if lte IE 8]>\n' +
- '<script>\n' +
- 'document.getElementById("playaudio").innerHTML=' + "'" +
- '<embed src="' +
- pathToAudio +
- '.mp3" autostart="true">' + "'" + ';\n' +
- '</script>\n' +
- '<![endif]-->\n')
-
- htmlFile.write('</center>' + "\n")
- htmlFile.write('</body>\n</html>\n')
- htmlFile.close()
-
-
-sequence =
scriptParser.parseTxtFile(odpFileSubdirectory+os.sep+"script.txt")
-makeConvert(sequence)
-os.chdir(odpFileDirectory)
-p =
subprocess.Popen('"'+odpFileDirectory+os.sep+'convert.bat"',shell=True).wait()
-audioFileTimes = fetchAudioFileTimes()
-writeHtml(sequence, audioFileTimes)
-
-
-## Step 3 - create makeVid.bat
-os.chdir(odpFileSubdirectory)
-dir = os.listdir(odpFileSubdirectory)
-ogg = [file for file in dir if file.lower().endswith(".ogg")]
-oggDict = {}
-for file in ogg:
- # Parse out file name stem (which includes number) and imageFileSuffix
- (stem, audioFileSuffix) = file.split(".")
-
- # Parse out just number (num) and imageFilePrefix
- if stem.startswith("Slide"):
- numberPart = file[5:].split(".")[0]
- if numberPart.isdigit():
- oggDict[int(numberPart)] = file
- else:
- # imgXX.ogg
- numberPart = file[3:].split(".")[0]
- if numberPart.isdigit():
- oggDict[int(file[3:].split(".")[0])] = file
-sortedOgg = oggDict.values()
-times = []
-for file in sortedOgg:
- # soxi -D returns the duration in seconds of the audio file as a float
- if sys.platform.startswith("win"):
- # Unfortunately, there is a requirement that soxi (with an
implict .exe)
- # be the command to check audio file duration in Win32
- # but soxi is the name of the unix version of this utility
- # So we need to delete the (unix) file called soxi so the command
line call
- # to soxi will run soxi.exe
- if os.path.isfile(savePath+os.sep+"soxi"):
- os.remove(savePath+os.sep+"soxi")
- command =
[savePath+os.sep+"soxi","-D",odpFileSubdirectory+os.sep+file]
- elif sys.platform.startswith("darwin"):
- if os.path.isfile(savePath+os.sep+"soxi"):
- command =
[savePath+os.sep+"soxi","-D",odpFileSubdirectory+os.sep+file]
- elif os.path.isfile(savePath+os.sep+"Contents/Resources/soxi"):
- command =
[savePath+os.sep+"Contents/Resources/soxi","-D",odpFileSubdirectory+os.sep+file]
- else:
- command = ["soxi","-D",odpFileSubdirectory+os.sep+file]
- else:
- command = ["soxi","-D",odpFileSubdirectory+os.sep+file]
- process = subprocess.Popen(command,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- universal_newlines=True)
- output = process.communicate()
- retcode = process.poll()
- if retcode:
- print "No time available"
- times.append(float(output[0].strip()))
-
-# Create makeVid.bat in odpFileDirectory for Windows
-f = open(odpFileDirectory+os.sep+"makeVid.bat","w")
-os.chmod(odpFileDirectory+os.sep+"makeVid.bat",stat.S_IRWXU)
-
-if sys.platform.startswith("win"):
- # find out if mklink is available
- mklinkAvailable = False
- if os.path.isfile("win32_mklink_test"):
- subprocess.Popen(["del","win32_mklink_test"],shell=True)
- os.chdir(odpFileDirectory)
-
subprocess.Popen(["mklink","/h","win32_mklink_test","convert.bat"],shell=True).wait()
- if os.path.isfile("win32_mklink_test"):
- mklinkAvailable = True
- subprocess.Popen(["del","win32_mklink_test"],shell=True)
-
- f.write("echo off\ncls\n")
- f.write("if exist output.mp4 (del output.mp4)\n")
- if os.path.isfile(savePath+os.sep+"MP4Box.exe"):
- catCommand = '"'+savePath+os.sep+'MP4Box"'
- else:
- catCommand = "MP4Box"
- for i, file in enumerate(sortedOgg):
- stem, suffix = file.split(".")
- # Add the slide video to the list of videos to be concatenated
- catCommand += " -cat "+stem+".mp4"
- tenthsOfSeconds = int(math.floor(times[i]*10))
- # If we are on the last slide, add enough frames
- # to give audio time to finish
- if sortedOgg[i]==sortedOgg[-1]:
- tenthsOfSeconds += 20
- # Make a symlink to the slide image for each second the audio runs
- # Only 999 symlinks are allowed per image, so, if there are more
- # than this number, we need to also make additional copies of the
- # slide image to link to
- for j in range(tenthsOfSeconds):
- if ((j > 0) and (j % 900 == 0)):
- f.write("copy "+stem+'.jpg '+stem+str(j)+'.jpg\n')
- extraStem = ""
- for j in range(tenthsOfSeconds):
- if ((j > 0) and (j % 900 == 0)):
- extraStem = str(j)
- if mklinkAvailable:
- f.write("mklink
/h "+stem+'_'+str(j).zfill(5)+'.jpg '+stem+extraStem+'.jpg\n')
- else:
-
f.write("copy "+stem+'.jpg '+stem+'_'+str(j).zfill(5)+'.jpg\n')
- # Convert the images to a video of that slide with voice over
- # NOTE: Little trick here -- Windows wants to substitute the batch
file name
- # into %0 so we use %1 and pass %0 as the first parameter
- f.write('"'+savePath+os.sep+'ffmpeg" -i '+stem+'.mp3 -r 10
-i "'+stem+'_%15d.jpg" -ab 64k '+stem+".mp4\n")
- # Delete the symlinks
- for j in range(tenthsOfSeconds):
- f.write("del "+stem+'_'+str(j).zfill(5)+'.jpg\n')
- # Delete any extra copies
- for j in range(tenthsOfSeconds):
- if ((j > 0) and (j % 900 == 0)):
- f.write("del "+stem+str(j)+'.jpg\n')
- # Add an output file name for the concatenation
- catCommand += " output.mp4\n"
- f.write(catCommand)
- # Delete all the single slide videos
- for file in sortedOgg:
- stem, suffix = file.split(".")
- f.write('del '+stem+'.mp4\n')
- f.close()
-
-elif sys.platform.startswith("darwin"):
- # For Mac OSX
- # ffmpeg -i Slide1.mp3 -r 1 -i Slide1_%03d.png -ab 64k output.mp4
- f.write("clear\n")
- f.write("if [ -f output.mp4 ]\n")
- f.write("then rm output.mp4\n")
- f.write("fi\n")
- if os.path.isfile(savePath+os.sep+"MP4Box"):
- # for uncompiled run
- catCommand = '"'+savePath+os.sep+'MP4Box"'
- elif os.path.isfile(savePath+os.sep+"Contents/Resources/MP4Box"):
- # for compiled run
- catCommand = '"'+savePath+os.sep+'Contents/Resources/MP4Box"'
- else:
- # for when MP4Box is not distributed but is installed
- catCommand = "MP4Box"
- # save another copy for subsequent cat lines if more than 20 slides
- catCommand2 = catCommand
- tempFilesToDelete = []
- for i, file in enumerate(sortedOgg):
- stem, suffix = file.split(".")
- # Add the slide video to the list of videos to be concatenated
- catCommand += " -cat "+stem+".mp4"
- if ((i>0) and (i % 18 == 0)):
- tempFilesToDelete.append("temp"+ str(i) +".mp4")
- # add a temp.mp4 for output and then input on next line
- catCommand += " temp" + str(i) +".mp4\n"+catCommand2+" -cat
temp" + str(i) +".mp4"
- tenthsOfSeconds = int(math.floor(times[i]*10))
- # If we are on the last slide, add enough frames
- # to give audio time to finish
- if sortedOgg[i]==sortedOgg[-1]:
- tenthsOfSeconds += 20
- # Make a symlink to the slide image for each second the audio runs
- for j in range(tenthsOfSeconds):
- # ln -s Slide2.png Slide2_001.png
- f.write("ln
-s "+stem+'.png '+stem+'_'+str(j).zfill(5)+'.png\n')
- f.write('"'+savePath+os.sep+'ffmpeg" -i '+stem+'.mp3 -r 10
-i "'+stem+'_%05d.png" -ab 64k '+stem+".mp4\n")
- # Delete the symlinks
- for j in range(tenthsOfSeconds):
- f.write("rm "+stem+'_'+str(j).zfill(5)+'.png\n')
- # Add an output file name for the concatenation
- catCommand += " output.mp4\n"
- f.write(catCommand)
- # Delete all the single slide videos
- for file in sortedOgg:
- stem, suffix = file.split(".")
- f.write('rm '+stem+'.mp4\n')
- for file in tempFilesToDelete:
- f.write('rm '+file+"\n")
- f.close()
-
-else:
- # For Linux
- # ffmpeg -i Slide1.mp3 -r 1 -i Slide1_%03d.png -ab 64k output.mp4
- f.write("clear\n")
- f.write("if [ -f output.mp4 ]\n")
- f.write("then rm output.mp4\n")
- f.write("fi\n")
-
- # We need to do this:
- # cat img0.avi img1.avi > output.avi
- # mencoder output.avi -o final.avi -forceidx -ovc copy -oac copy
- catCommand = "cat "
- catCommand2= "cat "
-
- # save another copy for subsequent cat lines if more than 20 slides
- tempFilesToDelete = []
- for i, file in enumerate(sortedOgg):
- stem, suffix = file.split(".")
- # Add the slide video to the list of videos to be concatenated
- catCommand += " " + stem+".avi"
- if ((i>0) and (i % 18 == 0)):
- tempFilesToDelete.append("temp"+ str(i) +".avi")
- # add a temp.mp4 for output and then input on next line
- catCommand += " temp" + str(i) +".avi\n"+catCommand2+" temp" +
str(i) +".avi"
- tenthsOfSeconds = int(math.floor(times[i]*10))
- # If we are on the last slide, add enough frames
- # to give audio time to finish
- if sortedOgg[i]==sortedOgg[-1]:
- tenthsOfSeconds += 20
- # Make a symlink to the slide image for each second the audio runs
- for j in range(tenthsOfSeconds):
- # ln -s Slide2.png Slide2_001.png
- f.write("ln
-s "+stem+'.png '+stem+'_'+str(j).zfill(5)+'.png\n')
- f.write('ffmpeg -i '+stem+'.mp3 -r 10 -i "'+stem+'_%05d.png" -ab
64k '+stem+".avi\n")
- # Delete the symlinks
- for j in range(tenthsOfSeconds):
- f.write("rm "+stem+'_'+str(j).zfill(5)+'.png\n')
- # Add an output file name for the concatenation
- catCommand += " > output.avi\n mencoder output.avi -o final.avi
-forceidx -ovc copy -oac copy\n"
- f.write(catCommand)
- # Delete all the single slide videos
- for file in sortedOgg:
- stem, suffix = file.split(".")
- f.write('rm '+stem+'.avi\n')
- for file in tempFilesToDelete:
- f.write('rm '+file+"\n")
***The diff for this file has been truncated for email.***
=======================================
--- /odp2wts/openallure_32x32.ico Fri Sep 9 18:30:38 2011
+++ /dev/null
Binary file, no diff available.
=======================================
--- /odp2wts/script.txt Wed Aug 24 19:01:42 2011
+++ /dev/null
@@ -1,6 +0,0 @@
-img0.png
-This talk has been prepared and delivered using Wiki to Speech. This voice
is Rachel from iVox. Please click anywhere on the slide to advance.
-
-img1.png
-The first step is to obtain the software
-
=======================================
--- /odp2wts/setup.nsi Sun Sep 4 20:48:11 2011
+++ /dev/null
@@ -1,34 +0,0 @@
-!define py2exeOutputDirectory 'dist'
-!define exe 'odp2wts.exe'
-
-; Comment out the "SetCompress Off" line and uncomment
-; the next line to enable compression. Startup times
-; will be a little slower but the executable will be
-; quite a bit smaller
-SetCompress Off
-;SetCompressor lzma
-
-Name 'odp2wts'
-OutFile ${exe}
-SilentInstall silent
-;Icon 'icon.ico'
-
-Section
- InitPluginsDir
- SetOutPath '$PLUGINSDIR'
- File '${py2exeOutputDirectory}\*.*'
-
- GetTempFileName $0
- DetailPrint $0
- Delete $0
- StrCpy $0 '$0.bat'
- FileOpen $1 $0 'w'
- FileWrite $1 '@echo off$\r$\n'
- StrCpy $2 $TEMP 2
- FileWrite $1 '$2$\r$\n'
- FileWrite $1 'cd $PLUGINSDIR$\r$\n'
- FileWrite $1 '${exe}$\r$\n'
- FileClose $1
- nsExec::Exec $0
- Delete $0
-SectionEnd
=======================================
--- /wikitospeech/Wiki-to-Speech.py Fri Nov 25 16:43:11 2011
+++ /dev/null
@@ -1,212 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-Wiki-to-Speech.py
-
-Use browser and local text-to-speech engine
-to display and play Wiki-to-Speech scripts
-
-Copyright (c) 2011 John Graves
-
-MIT License: see LICENSE.txt
-
-20110818 Changes to enable reading
http://aucklandunitarian.pagekite.me/Test20110818b
-20110819 Tested on Mac:
-http://aucklandunitarian.pagekite.me/Test20110814
-http://aucklandunitarian.pagekite.me/Test20110819 which calls another
script
-http://aucklandunitarian.pagekite.me/Test20110819b which has
[path=pathToImageFiles] and
- combined image with question
-20110822 Added version number to input form
-20110824 Pass __version__ to input form
- Ensure static directory exists
-20110825 Add __version__ to title
-20110909 Added question number to showQuestion (so going back should work)
-20110913 Make symbolic links from static directory to location of
script.txt png images
-20111126 Titanpad in addition to iEtherpad. Jump to 0.1.26 to sync with
odp2wts (SlideSpeech)
-"""
-import cherrypy
-import os.path
-import Queue
-import threading
-import webbrowser
-
-import forms
-import objects
-import scriptParser
-import sys
-import voice
-
-__version__ = "0.1.26"
-
-if not os.path.exists('static'):
- os.makedirs('static')
-
-class WelcomePage:
-
- def index(self):
- # Ask for the script name.
- return forms.scriptInputFormWithErrorMessage(__version__,"")
- index.exposed = True
- webbrowser.open_new_tab('http://localhost:8080')
-
- def getScriptName(self, name = None):
- #name = "http://dl.dropbox.com/u/12838403/20110428a.txt"
- if name:
- if name=="exit":
- sys.exit()
- seq.sequence = scriptParser.parseScript(name)
- if seq.sequence:
- seq.onQuestion = 0
- return speakAndReturnForm()
- else:
- return forms.scriptInputFormWithErrorMessage( \
- __version__,
- "<i>Could not open "+name+"</i>")
- else:
- # No name was specified
- return forms.scriptInputFormWithErrorMessage( \
- __version__,
- "<i>Please enter a file name or link on the
web.</i>")
- getScriptName.exposed = True
-
- def nextSlide(self):
- clearQueue()
- seq.onQuestion += 1
- if seq.onQuestion > len(seq.sequence) - 1:
- return forms.scriptInputFormWithErrorMessage(__version__,"")
- else:
- return speakAndReturnForm()
- nextSlide.exposed = True
-
- def nextSlideFromAnswer0(self, q):
- return respondToAnswer(0, q)
- nextSlideFromAnswer0.exposed = True
-
- def nextSlideFromAnswer1(self, q):
- return respondToAnswer(1, q)
- nextSlideFromAnswer1.exposed = True
-
- def nextSlideFromAnswer2(self, q):
- return respondToAnswer(2, q)
- nextSlideFromAnswer2.exposed = True
-
- def nextSlideFromAnswer3(self, q):
- return respondToAnswer(3, q)
- nextSlideFromAnswer3.exposed = True
-
- def nextSlideFromAnswer4(self, q):
- return respondToAnswer(4, q)
- nextSlideFromAnswer4.exposed = True
-
- def nextSlideFromAnswer5(self, q):
- return respondToAnswer(5, q)
- nextSlideFromAnswer5.exposed = True
-
- def nextSlideFromAnswer6(self, q):
- return respondToAnswer(6, q)
- nextSlideFromAnswer6.exposed = True
-
-seq = objects.Sequence()
-voiceInstance = voice.Voice()
-
-def speakAndReturnForm():
- # Check for visited answers. If found, do not re-read question
- noVisitedAnswers = True
- for a in seq.sequence[seq.onQuestion].answers:
- if a.visited:
- noVisitedAnswers = False
- if noVisitedAnswers:
- speakList(seq.sequence[seq.onQuestion].questionTexts)
- for a in seq.sequence[seq.onQuestion].answers:
- speakList([a.answerText])
- linkToShow = seq.sequence[seq.onQuestion].linkToShow
-
- if linkToShow.lower().endswith(".pdf"):
- return forms.showPDFSlide(seq.sequence[seq.onQuestion].linkToShow)
-
- elif linkToShow.lower().endswith(".jpg") or
linkToShow.lower().endswith(".png"):
- if linkToShow.startswith("Slide") or linkToShow.startswith("img")
or \
- linkToShow.find("\Slide")!=-1 or linkToShow.find("/img")!=-1:
- if len(seq.sequence[seq.onQuestion].pathToImageFiles)>0:
- linkToShow = seq.sequence[seq.onQuestion].pathToImageFiles
+ linkToShow
- else:
- linkToShow = "static/" + linkToShow
- if len(seq.sequence[seq.onQuestion].answers)>0:
- return forms.showJPGSlideWithQuestion(linkToShow, \
- seq.sequence[seq.onQuestion] )
- else:
- return forms.showJPGSlide(linkToShow)
-
- elif linkToShow.lower().endswith(".htm"):
- return forms.showDHTML()
- elif linkToShow.lower().endswith(".swf"):
- return forms.showSWF()
- elif len(linkToShow)>0:
- #return forms.showWebsite(seq.sequence[seq.onQuestion])
- return forms.showQuestionAndWebsite(seq.sequence[seq.onQuestion],
seq.onQuestion)
- else: # no match for linkToShow
- return forms.showQuestion(seq.sequence[seq.onQuestion],
seq.onQuestion)
-
-def respondToAnswer(n, q):
- clearQueue()
- response = ""
- seq.onQuestion = int(q)
- if n<len(seq.sequence[seq.onQuestion].answers):
- # mark answer as visited
- seq.sequence[seq.onQuestion].answers[n].visited = True
- # say what there is to say
- response = seq.sequence[seq.onQuestion].answers[n].responseText
- if response != "":
- speakList([response])
- # follow any response side link
- responseSideLink =
seq.sequence[seq.onQuestion].answers[n].responseSideLink
- if len(responseSideLink)>0:
- seq.sequence = scriptParser.parseScript(responseSideLink)
- if seq.sequence:
- seq.onQuestion = 0
- return speakAndReturnForm()
- #TODO error recovery
- # move to whichever question comes next
- if seq.sequence[seq.onQuestion].answers[n].action != 0:
- nextQ = seq.onQuestion +
seq.sequence[seq.onQuestion].answers[n].action
- if 0 <= nextQ <= len(seq.sequence):
- seq.onQuestion = nextQ
- if seq.onQuestion<len(seq.sequence):
- return speakAndReturnForm()
- else:
- # past end of sequence
- speakList(["You have reached the end. Please select another
script."])
- return forms.scriptInputFormWithErrorMessage(__version__,"")
-
-def clearQueue():
- while not q.empty():
- q.get()
-
-def worker():
- while True:
- text = q.get()
- voiceInstance.speak(text, "")
- q.task_done()
-
-q = Queue.Queue()
-t = threading.Thread(target=worker)
-t.daemon = True
-t.start()
-
-def speakList(textList):
- for item in textList:
- q.put(item)
- #q.join() # block until all tasks are done
-
-if __name__ == '__main__':
- # CherryPy always starts with app.root when trying to map request URIs
- # to objects, so we need to mount a request handler root. A request
- # to '/' will be mapped to HelloWorld().index().
- cherrypy.config.update({'server.socket_host': '127.0.0.1',
- 'server.socket_port': 8080,
- 'server.thread_pool': 10,
- })
- config = {'/': {'tools.staticdir.root': os.path.abspath(os.curdir)},
- '/static':{'tools.staticdir.on':True,
- 'tools.staticdir.dir':"static"}}
- cherrypy.quickstart(WelcomePage(), config=config)
-
=======================================
--- /wikitospeech/make_exe.cmd Mon Dec 5 01:07:53 2011
+++ /dev/null
@@ -1,8 +0,0 @@
-echo off
-rem 20111205 JG Modified to run for SlideSpeech
-echo ====================
-echo Building SlideSpeech.exe
-echo ====================
-if exist dist (rmdir dist /S)
-python ss2exe.py py2exe
-D:\NSIS\makensisw.exe SlideSpeech.nsi
=======================================
--- /wikitospeech/ss2exe.py Mon Dec 5 01:07:53 2011
+++ /dev/null
@@ -1,33 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-ss2exe.py
-a component of SlideSpeech.py
-
-Creates SlideSpeech.exe for Windows
-
-python ss2exe.py py2exe
-
-Copyright (c) 2011 John Graves
-
-MIT License: see LICENSE.txt
-"""
-from distutils.core import setup
-import py2exe
-
-setup(console=['SlideSpeech.py'],
- options = {"py2exe": { "dll_excludes": ["POWRPROF.dll",
- "tk85.dll",
- "tcl85.dll"],
- "bundle_files" : 1,
- }
- },
- data_files=[(".",['CHANGES.txt',
- 'ethics_notice.txt',
- 'README.txt',
- 'LICENSE.txt',
- 'script.txt',
- 'SayStatic.exe']),
- ("static/test",["static/test/Slide1.PNG",
- "static/test/Slide2.PNG"])],
- zipfile=None,
- )
=======================================
--- /wikitospeech/wts2app.py Mon Nov 28 00:38:44 2011
+++ /dev/null
@@ -1,48 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#-------------------------------------------------------------------------------
-# Name: wts2app.py
-# Purpose: Compile Wiki-to-Speech.py into Mac OSX Application
-#
-# Author: John Graves
-#
-# Created: 24 August 2011
-# Modified: 28 November 2011
-# Copyright: (c) John Graves 2011
-# License: MIT License
-#-------------------------------------------------------------------------------
-
-from setuptools import setup
-import operator
-import os
-import shutil
-import sys
-
-
-class BuildApp:
- def __init__(self):
- self.APP = ['Wiki-to-Speech.py']
- self.DATA_FILES =
['CHANGES.txt','ethics_notice.txt','README.txt','LICENSE.txt']
- self.OPTIONS = {'argv_emulation':
True, 'iconfile': 'slidespeech.icns',}
- #Dist directory
- self.dist_dir ='dist'
-
- def run(self):
- if os.path.isdir(self.dist_dir): #Erase previous destination dir
- shutil.rmtree(self.dist_dir)
-
- setup(
- app=self.APP,
- data_files=self.DATA_FILES,
- options={'py2app': self.OPTIONS},
- setup_requires=['py2app'],
- )
-
- if os.path.isdir('build'): #Clean up build dir
- shutil.rmtree('build')
-
-if __name__ == '__main__':
- if operator.lt(len(sys.argv), 2):
- sys.argv.append('py2app')
- BuildApp().run() #Run generation
- raw_input("Press any key to continue") #Pause to let user see that
things ends
=======================================
--- /wikitospeech/wts2exe.py Wed Aug 24 19:13:11 2011
+++ /dev/null
@@ -1,33 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-wts2exe.py
-a component of Wiki-to-Speech.py
-
-Creates Wiki-to-Speech.exe for Windows
-
-python wts2exe.py py2exe
-
-Copyright (c) 2011 John Graves
-
-MIT License: see LICENSE.txt
-"""
-from distutils.core import setup
-import py2exe
-
-setup(console=['Wiki-to-Speech.py'],
- options = {"py2exe": { "dll_excludes": ["POWRPROF.dll",
- "tk85.dll",
- "tcl85.dll"],
- "bundle_files" : 1,
- }
- },
- data_files=[(".",['CHANGES.txt',
- 'ethics_notice.txt',
- 'README.txt',
- 'LICENSE.txt',
- 'script.txt',
- 'SayStatic.exe']),
- ("static/test",["static/test/Slide1.PNG",
- "static/test/Slide2.PNG"])],
- zipfile=None,
- )
=======================================
--- /README.txt Wed Aug 24 19:07:18 2011
+++ /README.txt Mon Dec 5 17:25:29 2011
@@ -1,29 +1,16 @@
===========
-Open Allure
+SlideSpeech
===========

-Open Allure is a voice-and-vision enabled dialog system
-
-For the latest version, please check http://openallureds.org
-
-Dependencies
-============
-
-These may vary depending on the platform
-
-BeautifulSoup
-http://www.crummy.com/software/BeautifulSoup/download/3.x/BeautifulSoup-3.0.8.tar.gz
-Version 3.0.8 was used during development of Open Allure
-(easy_install BeautifulSoup)
-
-SoX Sound eXchange
-http://sourceforge.net/projects/sox/files/sox/14.3.2/sox-14.3.2-win32.exe/download
+SlideSpeech is a voice enabled interactive presentations system
+
+For the latest version and documentation, please check
http://slidespeech.org


License
=======

-Copyright (c) 2010 John Graves
+Copyright (c) 2010, 2011 John Graves

MIT License. See LICENSE.txt

@@ -31,4 +18,4 @@
Contributors
============
John Graves, <john....@aut.ac.nz>
-Brian Thorne, <brian....@canterbury.ac.nz>
+Stefan Shliebs, <ssch...@aut.ac.nz>

Reply all
Reply to author
Forward
0 new messages