Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
613e9f1
lvdocview: add nextSentence(), for iterating over all sentences
teleshoes Apr 7, 2023
740d612
jni[docview]: getAllSentences() to get coords and text of all sentences
teleshoes Apr 7, 2023
31c4b04
tts: fetch all sentences when initializing TTS
teleshoes Apr 7, 2023
b8b5200
audiobook: implement playing audiobooks in TTS, using vosk word timings
teleshoes Apr 8, 2023
da7fa80
audiobook: add audiobook navigation in TTS for vosk *.wordtiming files
teleshoes Apr 8, 2023
9fb68ea
audiobook: run parseWordTimingsFile() in a worker thread to prevent ANR
teleshoes Apr 10, 2023
be9e6ff
audiobook: add option 'app.tts.use.audiobook' to settings in TTS ui
teleshoes Apr 10, 2023
1eb9cc8
audiobook: replace '.split()' for performance (11s => 4s)
teleshoes Apr 11, 2023
25169b7
audiobook: replace WORD_TIMING_REGEX for performance (4s => 1s)
teleshoes Apr 11, 2023
04ec3f7
audiobook: re-use the same File object across sentences
teleshoes Apr 11, 2023
5629c87
audiobook: start each audio file at 0.0s
teleshoes Apr 11, 2023
c8d5a38
audiobook: do not select next sentence multiple times for new audiofiles
teleshoes Apr 11, 2023
08cd5bd
audiobook: null-check MediaPlayer
teleshoes Apr 12, 2023
e90f8f4
tts: pull all TTS control buttons to class vars
teleshoes Apr 13, 2023
da4aa91
audiobook: hide the top row of buttons while calculating word timings
teleshoes Apr 13, 2023
fc9ead0
audiobook: use startPos doc position strings instead of (x,y) coords
teleshoes Apr 15, 2023
39da35a
audiobook: cache sentence info next to the ebook
teleshoes Apr 15, 2023
7bd8a70
audiobook: close the word timings file handle
teleshoes Apr 15, 2023
18c8191
audiobook: add fuzzier word-matching for ebook words vs ebook sentences
teleshoes Apr 18, 2023
c69b1c0
audiobook: read wordtiming+sentencecache using try-with-resources
teleshoes Apr 19, 2023
9688d8d
sentenceinfo: implement exportSentenceInfo(infile, outfile) in lvdocview
teleshoes Apr 21, 2023
8b24ea4
cr3qt: add -s CLI wrapper around lvdocview.exportSentenceInfo(inF,outF)
teleshoes Apr 21, 2023
9cf9857
audiobook: allow different file extensions for audio files vs wordtiming
teleshoes Apr 25, 2023
02fe65f
sentenceinfo: fix bug where last sentence could be omitted
teleshoes Apr 25, 2023
23cd526
sentenceinfo: remove unnecessary call to checkRender()
teleshoes Apr 25, 2023
36b7b0e
sentenceinfo: do not call thisSentenceStart() twice when not necessary
teleshoes Apr 25, 2023
63df827
audiobook: allow scripts other than latin for splitting sentences
teleshoes Aug 2, 2023
a7a6d5e
audiobook: allow scripts other than latin for comparing words
teleshoes Aug 2, 2023
297d1bb
audiobook: remove unused import android.util.Log
teleshoes Aug 2, 2023
79222cc
audiobook: add a main() method for debugging wordtiming+sentenceinfo
teleshoes Aug 2, 2023
cc00722
audiobook: add non-android implementation of L.java for debugging
teleshoes Aug 2, 2023
e59e654
audiobook: add script to run WordTimingAudiobookMatcher java class
teleshoes Aug 2, 2023
f4e3293
sentenceinfo: treat semi-colon as sentence break
teleshoes Jul 6, 2024
31ccca4
text: treat ':' and ';' like '.'+'!'+'?' when measuring text
teleshoes Jul 6, 2024
02e6f9c
wordtimings: do not store wordtiming CSV lines in RAM while processing
teleshoes Jul 6, 2024
0024fa6
lvtinydom: pull getChar() to a util fct
teleshoes Feb 13, 2025
84604c3
lvtinydom: pull getPrevNonSpaceChar() to a util fct
teleshoes Feb 13, 2025
f287f0b
lvtinydom: pull isCharSentenceEndMark() to a util fct
teleshoes Feb 13, 2025
b7706ae
lvtinydom: pull isCharDoubleQuoteEnd() to a util fct
teleshoes Feb 13, 2025
a2d695d
lvtinydom: tiny refactor, rename prev*NonSpace => prev*NonSpaceCh
teleshoes Feb 13, 2025
542ee81
lvtinydom: small refactor of isSentenceStart/End for clarity (no-op)
teleshoes Feb 13, 2025
ab9774e
lvtinydom: add getNextNonSpaceChar() util fct
teleshoes Feb 13, 2025
8ef8aeb
text: do not treat the start of nodes as sentence end
teleshoes Feb 13, 2025
e860909
text: never treat sentence end punctuation marks as sentence start
teleshoes Feb 13, 2025
2042a05
text: treat multiple end punctuation marks as a single sentence end
teleshoes Feb 13, 2025
fa5d0ee
audiobook: calculate position+duration in full audiobook for sentences
teleshoes Jun 19, 2025
fec534e
audiobook: swap tts rate bar widget for pos+duration display
teleshoes Jun 19, 2025
2f550b3
audiobook: rename sentenceinfo-cache-file => sentenceinfo-file
teleshoes Jun 20, 2025
12fa96e
audiobook: do not persist split "words" in SentenceInfo
teleshoes Jun 23, 2025
560ead5
audiobook: pull SentenceTiming state out of SentenceInfo
teleshoes Jun 23, 2025
e4aefa1
audiobook: do not persist WordTiming after generating SentenceTIming
teleshoes Jun 23, 2025
8cad79a
audiobook: persist a two-way unique map of audioFile<=>path
teleshoes Jun 23, 2025
abe6c66
audiobook: add stateful isSentenceTimingReady() method
teleshoes Jun 23, 2025
f80f148
audiobook: reduce visibility of getAudioFileDuration() public=>private
teleshoes Jun 23, 2025
a96b6c8
audiobook: pull updateSentenceInfoNextSentence() to a method
teleshoes Jun 23, 2025
aa87062
audiobook: add cache support for read/write full parsed sentence timing
teleshoes Jun 23, 2025
bf78780
audiobook: autocache sentence timing in *.sentencetimingcache file
teleshoes Jun 23, 2025
faff838
audiobook: pull getAltAudioFile() to Utils, generalize over file ext
teleshoes Aug 7, 2025
992a6bc
audiobook: get duration from alternative file ext, if orig file missing
teleshoes Aug 7, 2025
65f7db4
audiobook: fix SentenceTiming null pointers on weird edge cases
teleshoes Feb 27, 2026
2141a38
audiobook: fix index-out-of-bounds bug
teleshoes Mar 16, 2026
00924d6
audiobook: persist current SentenceInfo on every getSentence() call
teleshoes Jul 2, 2026
5498d36
audiobook: reuse prev sentenceinfo if startPos is missing from si cache
teleshoes Jul 2, 2026
aac30d2
audiobook: try strip /autoBoxing/ if cur startPos missing sentenceinfo
teleshoes Jul 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions android/debug/org.coolreader.crengine.L.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package org.coolreader.crengine;

public class L {
public static class LoggerImpl implements Logger {
public void i(String msg) {
System.out.println(msg);
}
public void i(String msg, Exception e) {
System.out.println(msg);
}
public void w(String msg) {
System.out.println(msg);
}
public void w(String msg, Exception e) {
System.out.println(msg);
}
public void e(String msg) {
System.out.println(msg);
}
public void e(String msg, Exception e) {
System.out.println(msg);
}
public void d(String msg) {
System.out.println(msg);
}
public void d(String msg, Exception e) {
System.out.println(msg);
}
public void v(String msg) {
System.out.println(msg);
}
public void v(String msg, Exception e) {
System.out.println(msg);
}
public void setLevel(int level) {
}
}

public static void i(String msg) {
System.out.println(msg);
}
public static void i(String msg, Exception e) {
System.out.println(msg);
}
public static void w(String msg) {
System.out.println(msg);
}
public static void w(String msg, Exception e) {
System.out.println(msg);
}
public static void e(String msg) {
System.out.println(msg);
}
public static void e(String msg, Exception e) {
System.out.println(msg);
}
public static void d(String msg) {
System.out.println(msg);
}
public static void d(String msg, Exception e) {
System.out.println(msg);
}
public static void v(String msg) {
System.out.println(msg);
}
public static void v(String msg, Exception e) {
System.out.println(msg);
}
public static Logger create(String name) {
return new LoggerImpl();
}
public static Logger create(String name, int level) {
return new LoggerImpl();
}
}
58 changes: 58 additions & 0 deletions android/jni/docview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1808,6 +1808,64 @@ JNIEXPORT jobject JNICALL Java_org_coolreader_crengine_DocView_getCurrentPageBoo
return obj;
}

/*
* Class: org_coolreader_crengine_DocView
* Method: getAllSentencesInternal
* Signature: ()Ljava/util/ArrayList;
*/
JNIEXPORT jobject JNICALL Java_org_coolreader_crengine_DocView_getAllSentencesInternal
(JNIEnv * _env, jobject _this)
{
CRJNIEnv env(_env);
DocViewNative * p = getNative(_env, _this);
if (!p) {
CRLog::error("Cannot get native view");
return NULL;
}

jclass arrListClass = _env->FindClass("java/util/ArrayList");
jmethodID arrListCtor = _env->GetMethodID(arrListClass, "<init>", "()V");
jmethodID arrListAdd = env->GetMethodID(arrListClass, "add", "(Ljava/lang/Object;)Z");

jclass sentenceInfoClass = _env->FindClass("org/coolreader/crengine/SentenceInfo");
jmethodID sentenceInfoCtor = _env->GetMethodID(sentenceInfoClass, "<init>", "()V");
jmethodID sentenceInfoSetText = _env->GetMethodID(sentenceInfoClass, "setText", "(Ljava/lang/String;)V");
jmethodID sentenceInfoSetStartPos = _env->GetMethodID(sentenceInfoClass, "setStartPos", "(Ljava/lang/String;)V");

jobject arrList = env->NewObject(arrListClass, arrListCtor);


p->_docview->savePosition();
p->_docview->clearSelection();
p->_docview->goToPage(0);
p->_docview->SetPos(0, false);
while(p->_docview->nextSentence()){
jobject sentenceInfo = _env->NewObject(sentenceInfoClass, sentenceInfoCtor);
jint startX = 0;
jint startY = 0;

ldomXRangeList & sel = p->_docview->getDocument()->getSelections();
ldomXRange currSel;
if ( sel.length()>0 ){
currSel = *sel[0];
}
lvPoint startPoint = currSel.getStart().toPoint();
lvPoint endPoint = currSel.getEnd().toPoint();

env->CallVoidMethod(sentenceInfo, sentenceInfoSetText, env->NewStringUTF(
UnicodeToUtf8(currSel.getRangeText()).c_str()
));
env->CallVoidMethod(sentenceInfo, sentenceInfoSetStartPos, env->NewStringUTF(
UnicodeToUtf8(currSel.getStart().toString()).c_str()
));

env->CallBooleanMethod(arrList, arrListAdd, sentenceInfo);
}
p->_docview->restorePosition();

return arrList;
}

/*
* Class: org_coolreader_crengine_DocView
* Method: updateBookInfoInternal
Expand Down
8 changes: 8 additions & 0 deletions android/jni/org_coolreader_crengine_DocView.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions android/res/layout/tts_toolbar.xml
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,19 @@
android:layout_marginRight="2dip"
android:layout_marginBottom="5dip"
android:layout_weight="1" />

<TextView
android:id="@+id/tts_lbl_audio_progress"
android:typeface="monospace"
android:textSize="14sp"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:layout_marginTop="0dip"
android:layout_marginRight="5dip"
android:layout_marginBottom="0dip"
android:layout_weight="1"
android:text="" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
1 change: 1 addition & 0 deletions android/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,7 @@
<string name="options_tts_voice_quality_very_high">Very high quality</string>
<string name="options_tts_google_abbr_workaround">Use a workaround to disable processing of abbreviations at the end of a sentence</string>
<string name="options_tts_google_abbr_workaround_comment">when using "Google Speech Services"</string>
<string name="options_tts_use_audiobook">Use audiobook instead of TTS, if *.wordtiming file exists</string>
<string name="tts_init_failure">TTS engine \"%s\" init failure, disabling it…</string>
<string name="dlg_button_open_book">Open book</string>
<string name="dlg_button_open_folder">Open containing folder</string>
Expand Down
46 changes: 46 additions & 0 deletions android/run-wordtiming-main.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/usr/bin/perl
use strict;
use warnings;

my @classes = qw(
org/coolreader/crengine/WordTimingAudiobookMatcher.java
org/coolreader/crengine/SentenceInfo.java
org/coolreader/crengine/L.java
org/coolreader/crengine/Logger.java
org/coolreader/crengine/SentenceInfoCache.java
);

sub main(@){
system "rm", "-rf", "run-main-tmp/";
system "mkdir", "run-main-tmp/";
my @restoreCommands;
for my $debugClassFile(glob "debug/*"){
if($debugClassFile =~ /^(?:.*\/)?([^\/]+)\.(\w+)\.java$/){
my ($pkg, $class) = ($1, $2);
my $targetDir = $pkg;
$targetDir =~ s/\./\//g;
$targetDir = "src/$targetDir";
system "cp", "$targetDir/$class.java", "run-main-tmp/$pkg\.$class.java";
push @restoreCommands, ["mv", "run-main-tmp/$pkg\.$class.java", "$targetDir/$class.java"];
system "cp", $debugClassFile, "$targetDir/$class.java";
}else{
die "ERROR: malformed debug class $debugClassFile\n";
}
}
system "javac", map {"src/$_"} @classes;
if($? != 0){
die "java failed\n";
}
system "java",
"-cp", "src",
"org.coolreader.crengine.WordTimingAudiobookMatcher",
@_;

for my $cmd(@restoreCommands){
system @$cmd;
}
system "rm", "-rf", "run-main-tmp/";
system "find", "src/", "-name", "*.class", "-delete";
}

&main(@ARGV);
2 changes: 2 additions & 0 deletions android/src/org/coolreader/crengine/BaseActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -1898,6 +1898,8 @@ public Properties loadSettings(BaseActivity activity, File file) {
// By default enable workaround to disable processing of abbreviations at the end of a sentence when using "Google Speech Services".
props.applyDefault(ReaderView.PROP_APP_TTS_GOOGLE_END_OF_SENTENCE_ABBR, "1");

props.applyDefault(ReaderView.PROP_APP_TTS_USE_AUDIOBOOK, "1");

props.applyDefault(ReaderView.PROP_APP_THEME, DeviceInfo.FORCE_HC_THEME ? "HICONTRAST1" : "LIGHT");
props.applyDefault(ReaderView.PROP_APP_THEME_DAY, DeviceInfo.FORCE_HC_THEME ? "HICONTRAST1" : "LIGHT");
props.applyDefault(ReaderView.PROP_APP_THEME_NIGHT, DeviceInfo.FORCE_HC_THEME ? "HICONTRAST2" : "DARK");
Expand Down
12 changes: 12 additions & 0 deletions android/src/org/coolreader/crengine/DocView.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import java.io.IOException;
import java.io.InputStream;

import java.util.List;

public class DocView {

public static final Logger log = L.create("dv");
Expand Down Expand Up @@ -281,6 +283,14 @@ public Bookmark getCurrentPageBookmarkNoRender() {
}
}

public List<SentenceInfo> getAllSentences() {
List<SentenceInfo> sentences;
synchronized(mutex) {
sentences = getAllSentencesInternal();
}
return sentences;
}

/**
* Check whether document is formatted/rendered.
* @return true if document is rendered, and e.g. retrieving of page image will not cause long activity (formatting etc.)
Expand Down Expand Up @@ -470,6 +480,8 @@ private native boolean applySettingsInternal(

private native Bookmark getCurrentPageBookmarkInternal();

private native List<SentenceInfo> getAllSentencesInternal();

private native boolean goToPositionInternal(String xPath, boolean saveToHistory);

private native PositionProperties getPositionPropsInternal(String xPath, boolean precise);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.coolreader.crengine;

public interface InitAudiobookWordTimingsCallback {
public void onComplete();
}
6 changes: 6 additions & 0 deletions android/src/org/coolreader/crengine/OptionsDialog.java
Original file line number Diff line number Diff line change
Expand Up @@ -2655,6 +2655,12 @@ public void onTimedOut() {
mOptionsTTS.add(mTTSVoiceOption);
}
mOptionsTTS.add(new BoolOption(this, getString(R.string.options_tts_google_abbr_workaround), PROP_APP_TTS_GOOGLE_END_OF_SENTENCE_ABBR).setComment(getString(R.string.options_tts_google_abbr_workaround_comment)).setDefaultValue("1").noIcon());
mOptionsTTS.add(
new BoolOption(
this,
getString(R.string.options_tts_use_audiobook),
PROP_APP_TTS_USE_AUDIOBOOK
).setDefaultValue("1").noIcon());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR)
mOptionsTTS.add(new ListOption(this, getString(R.string.options_app_tts_stop_motion_timeout), PROP_APP_MOTION_TIMEOUT).add(mMotionTimeouts, mMotionTimeoutsTitles).setDefaultValue(Integer.toString(mMotionTimeouts[0])).noIcon());
mOptionsTTS.refresh();
Expand Down
5 changes: 5 additions & 0 deletions android/src/org/coolreader/crengine/ReaderView.java
Original file line number Diff line number Diff line change
Expand Up @@ -2424,6 +2424,7 @@ public void onCommand(final ReaderCommand cmd, final int param, final Runnable o
ttsToolbar = TTSToolbarDlg.showDialog(mActivity, ReaderView.this, ttsacc);
ttsToolbar.setOnCloseListener(() -> ttsToolbar = null);
ttsToolbar.setAppSettings(mSettings, null);
ttsToolbar.initAudiobookWordTimings(null);
}));
}
break;
Expand Down Expand Up @@ -3281,6 +3282,10 @@ public BookInfo getBookInfo() {
return mBookInfo;
}

public List<SentenceInfo> getAllSentences() {
return doc.getAllSentences();
}

private int mBatteryState = BATTERY_STATE_DISCHARGING;
private int mBatteryChargingConn = BATTERY_CHARGER_NO;
private int mBatteryChargeLevel = 0;
Expand Down
22 changes: 22 additions & 0 deletions android/src/org/coolreader/crengine/SentenceInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.coolreader.crengine;

import java.io.File;
import java.util.List;

public class SentenceInfo {
public String text;
public String startPos;

public SentenceTiming sentenceTiming = new SentenceTiming();
public SentenceInfo nextSentence;

public SentenceInfo() {
}

public void setStartPos(String startPos){
this.startPos = startPos;
}
public void setText(String text){
this.text = text;
}
}
Loading
Loading