Skip to content

Commit

Permalink
feat: add docx format
Browse files Browse the repository at this point in the history
  • Loading branch information
thewh1teagle committed Oct 15, 2024
1 parent 05f1631 commit ca0566b
Show file tree
Hide file tree
Showing 8 changed files with 92 additions and 17 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
- 🎙️ Transcribe audio / video
- 🎶 Option to transcribe audio from popular websites (YouTube, Vimeo, Facebook, Twitter and more!)
- 📂 Batch transcribe multiple files!
- 📝 Support `SRT`, `VTT`, `TXT`, `HTML`, `PDF`, `JSON` formats
- 📝 Support `SRT`, `VTT`, `TXT`, `HTML`, `PDF`, `JSON`, `DOCX` formats
- 👀 Realtime preview
- ✨ Summarize transcripts: Get concise summaries of transcripts via Claude API integration
- 🌐 Translate to English from any language
Expand Down
Binary file modified desktop/bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"@tauri-apps/plugin-store": "^2.0.0",
"@tauri-apps/plugin-updater": "^2.0.0",
"@tauri-apps/plugin-window-state": "^2.0.0",
"docx": "^9.0.2",
"format-duration": "^3.0.2",
"i18next": "^23.14.0",
"i18next-resources-to-backend": "^1.2.1",
Expand Down
10 changes: 6 additions & 4 deletions desktop/src/components/FormatSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Dispatch, SetStateAction } from 'react'
import { useTranslation } from 'react-i18next'

export type TextFormat = 'normal' | 'srt' | 'vtt' | 'html' | 'pdf' | 'json'
export type TextFormat = 'normal' | 'srt' | 'vtt' | 'html' | 'pdf' | 'json' | 'docx'
export type FormatExtensions = {
[name in TextFormat]: string
}
Expand All @@ -12,6 +12,7 @@ export const formatExtensions: FormatExtensions = {
html: '.html',
pdf: '.pdf',
json: '.json',
docx: '.docx',
}

interface FormatSelectProps {
Expand All @@ -32,9 +33,10 @@ export default function FormatSelect({ format, setFormat }: FormatSelectProps) {
}}
className="select select-bordered">
<option value="normal">{t('common.mode-text')}</option>
<option value="srt">SRT</option>
<option value="vtt">VTT</option>
<option value="json">JSON</option>
<option value="srt">srt</option>
<option value="docx">docx</option>
<option value="vtt">vtt</option>
<option value="json">json</option>
</select>
</label>
)
Expand Down
9 changes: 7 additions & 2 deletions desktop/src/components/HtmlView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,15 @@ interface HTMLViewProps {
preference: Preference
}

function formatDuration(start: number, stop: number) {
export function formatDuration(start: number, stop: number, direction: 'rtl' | 'ltr' = 'ltr') {
const startFmt = formatTimestamp(start, false, '', false)
const stopFmt = formatTimestamp(stop, false, '', false)
return `${startFmt} --> ${stopFmt}`
const duration = `${startFmt} --> ${stopFmt}`

if (direction === 'rtl') {
return `\u202B${duration}\u202C` // Use Unicode characters for right-to-left embedding
}
return duration
}

export default function HTMLView({ segments, file, preference }: HTMLViewProps) {
Expand Down
27 changes: 20 additions & 7 deletions desktop/src/components/TextArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import HTMLView from './HtmlView'
import toast from 'react-hot-toast'
import { invoke } from '@tauri-apps/api/core'
import * as clipboard from '@tauri-apps/plugin-clipboard-manager'
import { toDocx } from '~/lib/docx'
import { path } from '@tauri-apps/api'

function Copy({ text }: { text: string }) {
const { t } = useTranslation()
Expand Down Expand Up @@ -167,6 +169,7 @@ export default function TextArea({
window.print()
return
}

const ext = formatExtensions[format].slice(1)
const defaultPath = await invoke<NamedPath>('get_save_path', { srcPath: file.path, targetExt: ext })
const filePath = await dialog.save({
Expand All @@ -179,8 +182,17 @@ export default function TextArea({
canCreateDirectories: true,
defaultPath: defaultPath.path,
})

if (filePath) {
fs.writeTextFile(filePath, text)
if (format === 'docx') {
const fileName = await path.basename(filePath)
const doc = await toDocx(fileName, segments!, preference.textAreaDirection)
const arrayBuffer = await doc.arrayBuffer()
const buffer = new Uint8Array(arrayBuffer)
fs.writeFile(filePath, buffer)
} else {
fs.writeTextFile(filePath, text)
}

toast(
(mytoast) => (
Expand Down Expand Up @@ -295,15 +307,16 @@ export default function TextArea({
}}
className="select select-bordered">
<option value="normal">{t('common.mode-text')}</option>
<option value="html">HTML</option>
<option value="pdf">PDF</option>
<option value="srt">SRT</option>
<option value="vtt">VTT</option>
<option value="json">JSON</option>
<option value="html">html</option>
<option value="pdf">pdf</option>
<option value="docx">docx</option>
<option value="srt">srt</option>
<option value="vtt">vtt</option>
<option value="json">json</option>
</select>
</div>
</div>
{['html', 'pdf'].includes(preference.textFormat) ? (
{['html', 'pdf', 'docx'].includes(preference.textFormat) ? (
<HTMLView preference={preference} segments={segments ?? []} file={file} />
) : (
<textarea
Expand Down
43 changes: 43 additions & 0 deletions desktop/src/lib/docx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Document, Packer, Paragraph, TextRun, AlignmentType } from 'docx'
import { Segment } from './transcript'
import { formatDuration } from '~/components/HtmlView'

export async function toDocx(title: string, segments: Segment[], direction: 'rtl' | 'ltr') {
const doc = new Document({
sections: [
{
properties: {
page: {
textDirection: direction === 'rtl' ? 'lrTb' : 'tbRl',
},
},
children: [
// Add the title as a centered paragraph
new Paragraph({
alignment: AlignmentType.CENTER,
children: [
new TextRun({
text: title,
color: '1565C0',
size: 36 * 1.5,
bold: true,
}),
],
}),
// Add an empty paragraph for spacing after the title
new Paragraph({}),
...segments.map((segment) => {
const duration = formatDuration(segment.start, segment.stop, direction)
return new Paragraph({
alignment: direction === 'rtl' ? AlignmentType.RIGHT : AlignmentType.LEFT,
children: [new TextRun({ text: duration, bold: true }), new TextRun({ text: `\n${segment.text}`, break: 1 })],
})
}),
],
},
],
})

const blob = await Packer.toBlob(doc)
return blob
}
17 changes: 14 additions & 3 deletions desktop/src/pages/batch/viewModel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import * as webview from '@tauri-apps/api/webviewWindow'
import * as dialog from '@tauri-apps/plugin-dialog'
import * as config from '~/lib/config'
import successSound from '~/assets/success.mp3'
import { writeTextFile } from '@tauri-apps/plugin-fs'
import * as fs from '@tauri-apps/plugin-fs'
import { emit, listen } from '@tauri-apps/api/event'
import { usePreferenceProvider } from '~/providers/Preference'
import { useFilesContext } from '~/providers/FilesProvider'
import { basename } from '@tauri-apps/api/path'
import { askLlm } from '~/lib/llm'
import * as transcript from '~/lib/transcript'
import { path } from '@tauri-apps/api'
import { toDocx } from '~/lib/docx'

export function viewModel() {
const { files, setFiles } = useFilesContext()
Expand Down Expand Up @@ -138,9 +140,18 @@ export function viewModel() {
}
}

// Write file
const dst = await invoke<string>('get_path_dst', { src: file.path, suffix: formatExtensions[format] })
await writeTextFile(dst, getText(res.segments, format))
// Write file
if (format === 'docx') {
const fileName = await path.basename(dst)
const doc = await toDocx(fileName, res.segments, preference.textAreaDirection)
const arrayBuffer = await doc.arrayBuffer()
const buffer = new Uint8Array(arrayBuffer)
fs.writeFile(dst, buffer)
} else {
await fs.writeTextFile(dst, getText(res.segments, format))
}

localIndex += 1
await new Promise((resolve) => setTimeout(resolve, 100))
setCurrentIndex(localIndex)
Expand Down

0 comments on commit ca0566b

Please sign in to comment.