Answers.Body: <p>Here is one way to find the number of words in an epub file sorted by their frequency, with the words used the most at the top of the list.  </p>
  
  <p>This is done on a Mac laptop and will also work on Unix hosts. </p>
  
  <p>The overview of the process:</p>
  
  <ol>
  <li>Install Calibre</li>
  <li>Use the <code>./ebook-convert</code> command in Calibre to convert the epub file to text </li>
  <li>Transform the entire text file to lowercase (so "Word" and "word" match) </li>
  <li>Convert punctuation to whitespace (so "period." and "period " match) </li>
  <li>Convert all whitespace to a new line.  This puts each word on its own line. </li>
  <li>Exclude any blank lines from the list</li>
  <li>Sort the list of words alphabetically</li>
  <li>Pipe (send) that list of words through <code>uniq -c</code> You now have a count of how often each word appears.</li>
  <li>Sort the result in numerical order.  If you use the sort command with the <code>-r</code> argument, the most frequent words are at the top.</li>
  </ol>
  
  <p>Here's an example of steps (2) through (9).  The <code>head</code> command lists the top ten words in the final output.  </p>
  
  <pre><code>$ ./ebook-convert ./book.epub ./book.txt
  $ cat   ./book.txt | tr '[:upper:]' '[:lower:]' | tr "“" " " | tr "”" " " | tr "," " " | tr "." " " | tr " " "\n" | grep -v ^$ |  sort | uniq -c | sort -gr  | head
   5303 the
   1960 and
   1934 of
   1910 to
   1874 a
   1168 i
   1067 you
    844 in
    812 that
    703 it
   $
  </code></pre>
  
  <p>The result is pretty boring.  The word 'the" appears 5303 times, while the word 'it' appears 703 times. </p>
  
  <p>I suspect in most books the most common words are the tiny conjunctions, articles, prepositions and pronouns.  Perhaps on something that is not a novel this might be more interesting.</p>
  
  <p>Good luck!</p>
  
Answers.ContentLicense: CC BY-SA 3.0
Answers.CreationDate: 2017-03-08T20:37:07.687Z
Answers.Text: Thank you for the detailed answer. As a windows user, I would like to know if all the steps you described are done in Calibre.
Answers.UserDisplayName: Karel Capek
Answers.ContentLicense: CC BY-SA 3.0
Answers.CreationDate: 2017-03-08T23:24:45.013Z
Answers.Text: Step 1 is done at the OS level. Step 2 is done in Calibre from the command line.  The remaining steps are done at the Mac OS command line using  the  program named Terminal.  I know the Unix tools much better than I know the MS Windows tools. I wish you good luck!
Answers.UserDisplayName: StandardEyre
Answers.ContentLicense: CC BY-SA 3.0
Answers.CreationDate: 2016-04-15T03:43:32.340Z
Answers.LastActivityDate: 2016-04-15T03:43:32.340Z
Answers.UserDisplayName: StandardEyre
Answers.Body: <p>We can achieve this using Windows PowerShell (with <a href="https://idpf.github.io/epub3-samples/30/samples.html" rel="nofollow noreferrer">an example epub</a> from
  IDPF):</p>
  <pre><code>$book = [System.IO.File]::ReadAllBytes(&quot;$env:TEMP\cc-shared-culture.epub&quot;)
  [System.Reflection.Assembly]::LoadWithPartialName('System.IO.Compression')
  # The epub format is essentially a structured wrapper for zip archival
  $zipStream = New-Object System.IO.Memorystream # Rather than expanding the archive to disk, let's do the vocab work in memory
  $zipStream.Write($book, 0, $book.Count) | Out-Null
  $zipFile = [System.IO.Compression.ZipArchive]::new($zipStream)
  # The chapter files in this epub are like 
  $chapterFiles = $zipFile.Entries | Where-Object -Property FullName -Match &quot;p\d+\.x?html&quot;
  
  # Strip remaining xml markup
  function Remove-XmlMarkup {
      param (
          [System.__ComObject]$xml
      )
      # Check if $HTMLNode is HTMLFile
      if (-not (Test-IsHTMLFile $xml)) {
          Write-Verbose &quot;Node '$xml' is not HTMLFile type; cannot continue&quot;
          #throw exception wrong type
          throw &quot;Get-InnerText: '$xml' is not an HTMLFile&quot;
      }
      if($null -ne $xml -and $null -ne $xml.innerText) {
          return [string](($xml.innerText) -replace &quot;&lt;[^&gt;]+&gt;(?:[^&lt;]+&lt;/[^&gt;]+&gt;)?&quot;,'')
      } else {
          return [string]::Empty
      }
  }
  
  # Create a function to recursively replace each node with its inner text
  function Get-InnerText {
      [CmdletBinding()]
      param (
          [Parameter(Mandatory)]
          [System.__ComObject] $HTMLNode        
      )
      
      begin {
          # Check if $HTMLNode is HTMLFile
          if (-not (Test-IsHTMLFile $HTMLNode)) {
              Write-Verbose &quot;Node '$HTMLNode' is not HTMLFile type; cannot continue&quot;
              #throw exception wrong type
              throw &quot;Get-InnerText: '$HTMLNode' is not an HTMLFile&quot;
          }
          # Check recursion depthl
  
          if (($null -eq $recursionDepth) -or -not (Test-Path variable:\recursionDepth)) {
              New-Variable -Name recursionDepth -Value 0 -Description &quot;Recursion depth&quot; -Option AllScope -Visibility Public -Scope Local
          } else {
              Set-Variable -Name recursionDepth -Value $((Get-Variable -Name recursionDepth -ValueOnly) + 1) -Scope Local
          }
          Write-Verbose &quot;Recursion depth is $recursionDepth&quot;
      }
      
      process {
          #check if $HTMLNode has children
          if ($HTMLNode.hasChildNodes()) {
              Write-Verbose &quot;Node has children to process&quot;
              #Call this function recursively on each child
              foreach ($child in $HTMLNode.childNodes) {
                  #try catch block to check for Type mismatch exception
                  try {
                      #Call this function recursively on each child
                      Get-InnerText $child
                  } catch {
                      Write-Verbose -Message &quot;$($child.innerHTML) could not be splatted&quot;
                  }
              }
          } else {
              # If no children, get the inner text
              return (Remove-XmlMarkup $HTMLNode) 
          }
      }
      end {
          #Reset recursion depth
          Set-Variable -Name recursionDepth -Value $((Get-Variable -Name recursionDepth -ValueOnly) -1) -Scope Local
      }
  }
  foreach ($chapterFile in $chapterFiles) {
      $chapter = $zipFile.GetEntry($chapterFile)
      $chapter.Open().Read(($chContentBytes = [byte[]]::new($chapter.Length)), 0, $($chapter.Length)) | Out-Null
      $chContentStr = [System.Text.Encoding]::Default.GetString($chContentBytes)
      $HTML = New-Object -Com &quot;HTMLFile&quot;
      $HTML.write([ref]$chContentStr) | Out-Null
      [System.Collections.Generic.Dictionary[string, int]] $wordCount = @{}
      [char[]] $delims = @(' ', &quot;`r&quot;, &quot;`n&quot;, '&quot;', &quot;'&quot;, &quot;.&quot;, ',', &quot;’&quot;)
      # Create an array of common English words to ignore
      $ignoreWords = @(&quot;i&quot;, &quot;me&quot;, &quot;my&quot;, &quot;myself&quot;, &quot;we&quot;, &quot;our&quot;, &quot;ours&quot;, &quot;ourselves&quot;, &quot;you&quot;, &quot;your&quot;, &quot;yours&quot;, &quot;yourself&quot;, &quot;yourselves&quot;, &quot;he&quot;, &quot;him&quot;, &quot;his&quot;, &quot;himself&quot;, &quot;she&quot;, &quot;her&quot;, &quot;hers&quot;, &quot;herself&quot;, &quot;it&quot;, &quot;its&quot;, &quot;itself&quot;, &quot;they&quot;, &quot;them&quot;, &quot;their&quot;, &quot;theirs&quot;, &quot;themselves&quot;, &quot;what&quot;, &quot;which&quot;, &quot;who&quot;, &quot;whom&quot;, &quot;this&quot;, &quot;that&quot;, &quot;these&quot;, &quot;those&quot;, &quot;am&quot;, &quot;is&quot;, &quot;are&quot;, &quot;was&quot;, &quot;were&quot;, &quot;be&quot;, &quot;been&quot;, &quot;being&quot;, &quot;have&quot;, &quot;has&quot;, &quot;had&quot;, &quot;having&quot;, &quot;do&quot;, &quot;does&quot;, &quot;did&quot;, &quot;doing&quot;, &quot;a&quot;, &quot;an&quot;, &quot;the&quot;, &quot;and&quot;, &quot;but&quot;, &quot;if&quot;, &quot;or&quot;, &quot;because&quot;, &quot;as&quot;, &quot;until&quot;, &quot;while&quot;, &quot;of&quot;, &quot;at&quot;, &quot;by&quot;, &quot;for&quot;, &quot;with&quot;, &quot;about&quot;, &quot;against&quot;, &quot;between&quot;, &quot;into&quot;, &quot;through&quot;, &quot;during&quot;, &quot;before&quot;, &quot;after&quot;, &quot;above&quot;, &quot;below&quot;, &quot;to&quot;, &quot;from&quot;, &quot;up&quot;, &quot;down&quot;, &quot;in&quot;, &quot;out&quot;, &quot;on&quot;, &quot;off&quot;, &quot;over&quot;, &quot;under&quot;, &quot;again&quot;, &quot;further&quot;, &quot;then&quot;, &quot;once&quot;, &quot;here&quot;, &quot;there&quot;, &quot;when&quot;, &quot;where&quot;, &quot;why&quot;, &quot;how&quot;, &quot;all&quot;, &quot;any&quot;, &quot;both&quot;, &quot;each&quot;, &quot;few&quot;, &quot;more&quot;, &quot;most&quot;, &quot;other&quot;, &quot;some&quot;, &quot;such&quot;, &quot;no&quot;, &quot;nor&quot;, &quot;not&quot;, &quot;only&quot;, &quot;own&quot;, &quot;same&quot;, &quot;so&quot;, &quot;than&quot;, &quot;too&quot;, &quot;very&quot;, &quot;s&quot;, &quot;t&quot;, &quot;can&quot;, &quot;will&quot;, &quot;just&quot;, &quot;don&quot;, &quot;should&quot;, &quot;now&quot;)
      Get-InnerText $HTML | ForEach-Object { 
          $words = $_.Split($delims , @([System.StringSplitOptions]::RemoveEmptyEntries, [System.StringSplitOptions]::TrimEntries )) | Where-Object -FilterScript { ($_.Length -gt 1) -and ($_.ToLowerInvariant() -notmatch &quot;(?:$($ignoreWords -join '|'))&quot;)}
          $words.ForEach( {
                  $count = 0
                  if ($wordCount.TryGetValue($_, [ref]$count)) { 
                      $wordCount[$_] = ++$count 
                  }
                  else { 
                      $wordCount.Add($_, 0)
                  } 
              }) 
      }
  }
  </code></pre>
  <p>The result is in <code>$wordCount</code>.</p>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2021-08-11T03:07:01.820Z
Answers.LastActivityDate: 2021-08-14T04:40:11.003Z
Answers.LastEditDate: 2021-08-14T04:40:11.003Z
Answers.UserDisplayName: Mavaddat Javid
Answers.Body: <p>As English learner maybe you will be also interested in <a href="https://github.com/artemtomchuck/MyKnownWordsDictionary" rel="nofollow noreferrer">https://github.com/artemtomchuck/MyKnownWordsDictionary</a> project.
  This extracts unique words from your book (straight txt should be an input, but it is not a problem to transform your pdf or epub into txt).
  Then it filters out the word you already know (based on your own dictionary).
  Then it prints new words from book ordered by frequency (the most frequent on the top) with their translation to your native language (given that book is written in English or any other language which you are learning).</p>
  <p>Notes:</p>
  <ol>
  <li>in output csv file frequency of word is not printed, but if you understand the source code then you can include this frequency column into your output (currently it is just hidden).</li>
  <li>this mechanism is very simplistic and does not recognize plural/singular forms, names or words with &quot;-&quot; symbol (e.g. single-minded is treated like two separate words: single and minded)</li>
  </ol>
  <p>Full disclosure: this is my personal project and it may not fully suit your purposes or may be complex for using/setup by non-technical people (non-software engineers). But I tried to document setup as best as I can in github.
  The reason why I am posting the answer here: initially I looked some solutions from answers in this question. I found the solution in <a href="https://ebooks.stackexchange.com/a/6557">https://ebooks.stackexchange.com/a/6557</a> , but it was quite a routine for regular usage and that's why I automated process a bit.</p>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2022-10-29T13:02:02.193Z
Answers.LastActivityDate: 2022-10-29T13:02:02.193Z
Answers.UserDisplayName: Артём Томчук
Answers.Body: <p><a href="https://vocaboo.batata.lol/app" rel="nofollow noreferrer">https://vocaboo.batata.lol/app</a></p>
  <p>Seems like app you want to use, has exact functionality for this.</p>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2024-05-27T14:39:09.567Z
Answers.LastActivityDate: 2024-05-27T14:39:09.567Z
Answers.UserDisplayName: Martin Mlynárik
Body: <p>As someone who is interested in learning languages, I want to find a way to know the each word and their repetitions inside a book that I am going to read.</p>
  
Comments.ContentLicense: CC BY-SA 3.0
Comments.CreationDate: 2015-03-28T17:49:58.950Z
Comments.Text: Welcome to the site! Can you clarify a little bit? Are you looking for software that you could run to generate a list of all the words in a book and how many times they are used? Also, are you looking to do this *before* you download or buy a book, or after?
Comments.UserDisplayName: elixenide
Comments.ContentLicense: CC BY-SA 3.0
Comments.CreationDate: 2015-03-28T20:10:30.970Z
Comments.Text: Hi Ed, 
  As you mentioned, I want it to list the words and show how many times they are used. It is like the books which are printed for English Language learners and indicate the words that the reader will run across in the book. For now, I am interested in doing this for the books that are licensed as Creative Commons like in Gutenberg Project.
Comments.UserDisplayName: Karel Capek
Comments.ContentLicense: CC BY-SA 3.0
Comments.CreationDate: 2015-03-29T05:08:37.437Z
Comments.Text: Interesting question -- I don't have an answer, but the clarification may help other users.
Comments.UserDisplayName: elixenide
Comments.ContentLicense: CC BY-SA 3.0
Comments.CreationDate: 2016-04-21T13:12:58.973Z
Comments.Text: @Anthon: Please send me an EPUB that you would like to count the words in, I'll give it a shot. A shorter EPUB is better for testing purposes. I can't PM you so you'll have to PM me to ask for my email address. Thanks.
Comments.UserDisplayName: Bulrush
Comments.ContentLicense: CC BY-SA 3.0
Comments.CreationDate: 2016-04-21T13:52:54.110Z
Comments.Text: @Bulrush I think you are confusing me with the OP.
Comments.UserDisplayName: Anthon
Tags: <epub><pdf>
Title: I am looking for a software or a way to list, extract, count, in short, analyze a book which is in epub or pdf format
UserDisplayName: Karel Capek
ThreadText: # I am looking for a software or a way to list, extract, count, in short, analyze a book which is in epub or pdf format
  
  (Asked by: username_0 on 2015-03-28)
  
  As someone who is interested in learning languages, I want to find a way to know the each word and their repetitions inside a book that I am going to read.
  
  **Tags:** epub, pdf
  
  > **Comments:**
  > **username_1**: Welcome to the site! Can you clarify a little bit? Are you looking for software that you could run to generate a list of all the words in a book and how many times they are used? Also, are you looking to do this *before* you download or buy a book, or after?  
  > **username_0**: Hi Ed,  As you mentioned, I want it to list the words and show how many times they are used. It is like the books which are printed for English Language learners and indicate the words that the reader will run across in the book. For now, I am interested in doing this for the books that are licensed as Creative Commons like in Gutenberg Project.  
  > **username_1**: Interesting question -- I don't have an answer, but the clarification may help other users.  
  > **username_2**: @username_3: Please send me an EPUB that you would like to count the words in, I'll give it a shot. A shorter EPUB is better for testing purposes. I can't PM you so you'll have to PM me to ask for my email address. Thanks.  
  > **username_3**: @username_2 I think you are confusing me with the OP.  
  
  ---
  
  ## 4 answers
  
  ### Answer by username_4 on 2016-04-15. Score: 4
  
  Here is one way to find the number of words in an epub file sorted by their frequency, with the words used the most at the top of the list.  
  This is done on a Mac laptop and will also work on Unix hosts. 
  The overview of the process:
  
  Install Calibre
  Use the ./ebook-convert command in Calibre to convert the epub file to text 
  Transform the entire text file to lowercase (so "Word" and "word" match) 
  Convert punctuation to whitespace (so "period." and "period " match) 
  Convert all whitespace to a new line.  This puts each word on its own line. 
  Exclude any blank lines from the list
  Sort the list of words alphabetically
  Pipe (send) that list of words through uniq -c You now have a count of how often each word appears.
  Sort the result in numerical order.  If you use the sort command with the -r argument, the most frequent words are at the top.
  
  Here's an example of steps (2) through (9).  The head command lists the top ten words in the final output.  
  $ ./ebook-convert ./book.epub ./book.txt
  $ cat   ./book.txt | tr '[:upper:]' '[:lower:]' | tr "“" " " | tr "”" " " | tr "," " " | tr "." " " | tr " " "\n" | grep -v ^$ |  sort | uniq -c | sort -gr  | head
   5303 the
   1960 and
   1934 of
   1910 to
   1874 a
   1168 i
   1067 you
    844 in
    812 that
    703 it
   $
  
  The result is pretty boring.  The word 'the" appears 5303 times, while the word 'it' appears 703 times. 
  I suspect in most books the most common words are the tiny conjunctions, articles, prepositions and pronouns.  Perhaps on something that is not a novel this might be more interesting.
  Good luck!
  
  > **Comments:**
  > **username_0**: Thank you for the detailed answer. As a windows user, I would like to know if all the steps you described are done in Calibre.  
  > **username_4**: Step 1 is done at the OS level. Step 2 is done in Calibre from the command line.  The remaining steps are done at the Mac OS command line using  the  program named Terminal.  I know the Unix tools much better than I know the MS Windows tools. I wish you good luck!  
  
  ---
  
  ### Answer by username_5 on 2021-08-11. Score: 2
  
  We can achieve this using Windows PowerShell (with an example epub from
  IDPF):
  $book = [System.IO.File]::ReadAllBytes("$env:TEMP\cc-shared-culture.epub")
  [System.Reflection.Assembly]::LoadWithPartialName('System.IO.Compression')
  # The epub format is essentially a structured wrapper for zip archival
  $zipStream = New-Object System.IO.Memorystream # Rather than expanding the archive to disk, let's do the vocab work in memory
  $zipStream.Write($book, 0, $book.Count) | Out-Null
  $zipFile = [System.IO.Compression.ZipArchive]::new($zipStream)
  # The chapter files in this epub are like 
  $chapterFiles = $zipFile.Entries | Where-Object -Property FullName -Match "p\d+\.x?html"
  
  # Strip remaining xml markup
  function Remove-XmlMarkup {
      param (
          [System.__ComObject]$xml
      )
      # Check if $HTMLNode is HTMLFile
      if (-not (Test-IsHTMLFile $xml)) {
          Write-Verbose "Node '$xml' is not HTMLFile type; cannot continue"
          #throw exception wrong type
          throw "Get-InnerText: '$xml' is not an HTMLFile"
      }
      if($null -ne $xml -and $null -ne $xml.innerText) {
          return [string](($xml.innerText) -replace "<[^>]+>(?:[^<]+]+>)?",'')
      } else {
          return [string]::Empty
      }
  }
  
  # Create a function to recursively replace each node with its inner text
  function Get-InnerText {
      [CmdletBinding()]
      param (
          [Parameter(Mandatory)]
          [System.__ComObject] $HTMLNode        
      )
      
      begin {
          # Check if $HTMLNode is HTMLFile
          if (-not (Test-IsHTMLFile $HTMLNode)) {
              Write-Verbose "Node '$HTMLNode' is not HTMLFile type; cannot continue"
              #throw exception wrong type
              throw "Get-InnerText: '$HTMLNode' is not an HTMLFile"
          }
          # Check recursion depthl
  
          if (($null -eq $recursionDepth) -or -not (Test-Path variable:\recursionDepth)) {
              New-Variable -Name recursionDepth -Value 0 -Description "Recursion depth" -Option AllScope -Visibility Public -Scope Local
          } else {
              Set-Variable -Name recursionDepth -Value $((Get-Variable -Name recursionDepth -ValueOnly) + 1) -Scope Local
          }
          Write-Verbose "Recursion depth is $recursionDepth"
      }
      
      process {
          #check if $HTMLNode has children
          if ($HTMLNode.hasChildNodes()) {
              Write-Verbose "Node has children to process"
              #Call this function recursively on each child
              foreach ($child in $HTMLNode.childNodes) {
                  #try catch block to check for Type mismatch exception
                  try {
                      #Call this function recursively on each child
                      Get-InnerText $child
                  } catch {
                      Write-Verbose -Message "$($child.innerHTML) could not be splatted"
                  }
              }
          } else {
              # If no children, get the inner text
              return (Remove-XmlMarkup $HTMLNode) 
          }
      }
      end {
          #Reset recursion depth
          Set-Variable -Name recursionDepth -Value $((Get-Variable -Name recursionDepth -ValueOnly) -1) -Scope Local
      }
  }
  foreach ($chapterFile in $chapterFiles) {
      $chapter = $zipFile.GetEntry($chapterFile)
      $chapter.Open().Read(($chContentBytes = [byte[]]::new($chapter.Length)), 0, $($chapter.Length)) | Out-Null
      $chContentStr = [System.Text.Encoding]::Default.GetString($chContentBytes)
      $HTML = New-Object -Com "HTMLFile"
      $HTML.write([ref]$chContentStr) | Out-Null
      [System.Collections.Generic.Dictionary[string, int]] $wordCount = @{}
      [char[]] $delims = @(' ', "`r", "`n", '"', "'", ".", ',', "’")
      # Create an array of common English words to ignore
      $ignoreWords = @("i", "me", "my", "myself", "we", "our", "ours", "ourselves", "you", "your", "yours", "yourself", "yourselves", "he", "him", "his", "himself", "she", "her", "hers", "herself", "it", "its", "itself", "they", "them", "their", "theirs", "themselves", "what", "which", "who", "whom", "this", "that", "these", "those", "am", "is", "are", "was", "were", "be", "been", "being", "have", "has", "had", "having", "do", "does", "did", "doing", "a", "an", "the", "and", "but", "if", "or", "because", "as", "until", "while", "of", "at", "by", "for", "with", "about", "against", "between", "into", "through", "during", "before", "after", "above", "below", "to", "from", "up", "down", "in", "out", "on", "off", "over", "under", "again", "further", "then", "once", "here", "there", "when", "where", "why", "how", "all", "any", "both", "each", "few", "more", "most", "other", "some", "such", "no", "nor", "not", "only", "own", "same", "so", "than", "too", "very", "s", "t", "can", "will", "just", "don", "should", "now")
      Get-InnerText $HTML | ForEach-Object { 
          $words = $_.Split($delims , @([System.StringSplitOptions]::RemoveEmptyEntries, [System.StringSplitOptions]::TrimEntries )) | Where-Object -FilterScript { ($_.Length -gt 1) -and ($_.ToLowerInvariant() -notmatch "(?:$($ignoreWords -join '|'))")}
          $words.ForEach( {
                  $count = 0
                  if ($wordCount.TryGetValue($_, [ref]$count)) { 
                      $wordCount[$_] = ++$count 
                  }
                  else { 
                      $wordCount.Add($_, 0)
                  } 
              }) 
      }
  }
  
  The result is in $wordCount.
  
  
  ---
  
  ### Answer by username_6 on 2022-10-29. Score: 0
  
  As English learner maybe you will be also interested in <https://github.com/artemtomchuck/MyKnownWordsDictionary> project.
  This extracts unique words from your book (straight txt should be an input, but it is not a problem to transform your pdf or epub into txt).
  Then it filters out the word you already know (based on your own dictionary).
  Then it prints new words from book ordered by frequency (the most frequent on the top) with their translation to your native language (given that book is written in English or any other language which you are learning).
  
  Notes:
  
  1. in output csv file frequency of word is not printed, but if you understand the source code then you can include this frequency column into your output (currently it is just hidden).
  2. this mechanism is very simplistic and does not recognize plural/singular forms, names or words with "-" symbol (e.g. single-minded is treated like two separate words: single and minded)
  
  Full disclosure: this is my personal project and it may not fully suit your purposes or may be complex for using/setup by non-technical people (non-software engineers). But I tried to document setup as best as I can in github.
  The reason why I am posting the answer here: initially I looked some solutions from answers in this question. I found the solution in <https://ebooks.stackexchange.com/a/6557> , but it was quite a routine for regular usage and that's why I automated process a bit.
  
  
  ---
  
  ### Answer by username_7 on 2024-05-27. Score: 0
  
  <https://vocaboo.batata.lol/app>
  
  Seems like app you want to use, has exact functionality for this.
  
  
  ---
  
  