Skip to content

Commit

Permalink
feat: support srt format (#7)
Browse files Browse the repository at this point in the history
* higher processor priority
* feat: support `srt` format
* fix: highlight last line
* chore: refactor
* feat: edit multiline lyrics
  • Loading branch information
eatgrass committed Jan 3, 2024
1 parent faf05bb commit 99b2ea8
Show file tree
Hide file tree
Showing 10 changed files with 408 additions and 120 deletions.
32 changes: 24 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,26 @@ Obsidian Community Plugins

`obsidian://show-plugin?id=lyrics`

### Basic
### Basic Usage

Include an audio source and subtitle contents in the `lrc` code block.

**Supported Subtitle Format**

- [LRC](https://en.wikipedia.org/wiki/LRC_(file_format))
- [SRT](https://en.wikipedia.org/wiki/SubRip)


**Specifying the Audio Source**

Include an audio source and [.lrc format](<https://en.wikipedia.org/wiki/LRC_(file_format)>) lyrics in the `lrc` code block.
You can specify the source of the audio file either as a filepath or as an internal link.

1. Using an internal link source:

<pre>
```lrc
source [[audio_file.mp3]]
[00:01.00] your .lrc format contents
[00:01.00] Your .lrc format contents
[00:02.00] ....
```
</pre>
Expand All @@ -36,8 +45,13 @@ source [[audio_file.mp3]]
<pre>
```lrc
source path/to/your_audio_file.mp3
[00:01.00] your .lrc format contents
[00:02.00] ....
1
00:02:16,612 --> 00:02:19,376
Your .srt format contents

2
00:02:19,482 --> 00:02:21,609
Hello ==Lyrics==
```
</pre>

Expand All @@ -57,14 +71,16 @@ Customize your own styles by utilizing the CSS classes provided below.

```html
<span class="lyrics-line" data-lyid="36" data-time="84160">
<span class="lyrics-timestamp" data-lyid="36" data-time="84160">01:24</span>
<p>Happy birthday.</p>
<span class="lyrics-timestamp" data-lyid="36" data-time="84160">01:24</span>
<div class="lyrics-text">
<p>Hello Lyrics</p>
</div>
</span>
```

- `.lyrics-line`: the entire lyrics line.
- `.lyrics-line .lyrics-timestamp`: timestamp of the lyrics.
- `.lyrics-line p`: text content of the lyrics.
- `.lyrics-line .lyrics-text`: text content of the lyrics.
- `.lyrics-highlighted`: mark the current highlighted lyrics.

---
Expand Down
23 changes: 19 additions & 4 deletions package-lock.json

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

12 changes: 8 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,22 @@
"license": "MIT",
"devDependencies": {
"@tsconfig/svelte": "^5.0.2",
"prettier": "^3.0.3",
"prettier-plugin-svelte": "^3.0.3",
"svelte": "^4.2.2",
"svelte-preprocess": "^5.0.4",
"@types/lodash": "^4.14.202",
"@types/node": "^16.11.6",
"@typescript-eslint/eslint-plugin": "5.29.0",
"@typescript-eslint/parser": "5.29.0",
"builtin-modules": "3.3.0",
"esbuild": "0.17.3",
"esbuild-svelte": "^0.8.0",
"obsidian": "latest",
"prettier": "^3.0.3",
"prettier-plugin-svelte": "^3.0.3",
"svelte": "^4.2.2",
"svelte-preprocess": "^5.0.4",
"tslib": "2.4.0",
"typescript": "5.2.2"
},
"dependencies": {
"lodash": "^4.17.21"
}
}
133 changes: 39 additions & 94 deletions src/LyricsMarkdownRender.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,16 @@
import {
MarkdownRenderChild,
MarkdownRenderer,
type App,
type MarkdownPostProcessorContext,
Menu,
MarkdownView,
} from 'obsidian'
import Player from './Player.svelte'
import type LyricsPlugin from 'main'

type LrcLine = {
timestamp?: number
timestr?: string
text: string
}

const DEFAULT_LRC: LrcLine = {
text: '',
timestr: '',
}
import LyricsPlugin from 'main'
import LyricsRenderer from 'renderers'

export default class LyricsMarkdownRender extends MarkdownRenderChild {
static readonly AUDIO_FILE_REGEX = /^source (?<audio>.*)/i
static readonly LYRICS_PARSE_REGEX =
/^\[(((\d+):)?(\d+):(\d+(\.\d+))?)\](.*)$/
static readonly INTERNAL_LINK_REGEX = /\[\[(?<link>.*)\]\]/

private audioPath?: string
Expand All @@ -36,6 +23,7 @@ export default class LyricsMarkdownRender extends MarkdownRenderChild {
private plugin: LyricsPlugin
private autoScroll: boolean
private sentenceMode: boolean
private lyricsRenderer: LyricsRenderer

constructor(
plugin: LyricsPlugin,
Expand All @@ -51,37 +39,7 @@ export default class LyricsMarkdownRender extends MarkdownRenderChild {
this.path = ctx.sourcePath
this.autoScroll = this.plugin.getSettings().autoScroll
this.sentenceMode = this.plugin.getSettings().sentenceMode
}

static parseLrc(text: string = ''): LrcLine {
const lrc: LrcLine = { ...DEFAULT_LRC, text }

const match = text.match(LyricsMarkdownRender.LYRICS_PARSE_REGEX)

if (text == '' || !match) {
return lrc
}

try {
let hours = match[3] ? parseInt(match[3], 10) : 0
let minutes = match[4] ? parseInt(match[4], 10) : 0
let seconds = match[5] ? parseFloat(match[5]) : 0

const timestamp = hours * 3600 + minutes * 60 + seconds

const inMin = Math.floor(timestamp / 60)
const inSec = Math.floor(timestamp % 60)

const minStr = inMin < 10 ? `0${inMin}` : `${inMin}`
const secStr = inSec < 10 ? `0${inSec}` : `${inSec}`
return {
timestamp,
timestr: `${minStr}:${secStr}`, //normalize the time string
text: match[7],
}
} catch {
return lrc
}
this.lyricsRenderer = new LyricsRenderer(plugin.app)
}

private seek = (e: MouseEvent) => {
Expand Down Expand Up @@ -109,6 +67,7 @@ export default class LyricsMarkdownRender extends MarkdownRenderChild {

if (hl >= 0) {
const hlel = lyrics.item(hl)
console.log(hl, hlel)
if (hlel) {
hlel.addClass('lyrics-highlighted')
if (this.autoScroll) {
Expand All @@ -130,10 +89,10 @@ export default class LyricsMarkdownRender extends MarkdownRenderChild {

private findParentData(element: HTMLElement | null) {
while (element && element.className !== 'lyrics-wrapper') {
if (element.dataset && element.dataset['lyid']) {
if (element.dataset && element.dataset['offset']) {
return {
time: element.dataset['time'],
lyid: element.dataset['lyid'],
offset: element.dataset['offset'],
}
}
element = element.parentElement
Expand Down Expand Up @@ -182,8 +141,9 @@ export default class LyricsMarkdownRender extends MarkdownRenderChild {
this.plugin.app.workspace.getActiveViewOfType(
MarkdownView,
)
if (view && data?.lyid) {
if (view && data?.offset) {
const state = view.getState()
let [from, to] = data.offset.split(',')
state.mode = 'source'
await view.leaf.setViewState({
type: 'markdown',
Expand All @@ -193,33 +153,36 @@ export default class LyricsMarkdownRender extends MarkdownRenderChild {
let start = 0
for (let i = 0; i < lineCount; i++) {
const lineText = view.editor.getLine(i)
// NOTE: can only calculate the first lrc code block position
if (lineText.includes('```lrc')) {
start = i
break
}
}
let lineNumber = parseInt(data.lyid) + start + 2
let lineContent = view.editor.getLine(lineNumber)
let head = this.player ? 2 : 1
let lineFrom = head + parseInt(from) + start
let lineTo = head + parseInt(to) + start
let lineContent = view.editor.getLine(lineTo)
view.editor.focus()
view.editor.setCursor(lineNumber, 0)
view.editor.setCursor(lineFrom, 0)
view.editor.setSelection(
{
line: lineNumber,
line: lineFrom,
ch: 0,
},
{
line: lineNumber,
line: lineTo,
ch: lineContent.length,
},
)
view.editor.scrollIntoView(
{
from: {
line: lineNumber,
line: lineFrom,
ch: 0,
},
to: {
line: lineNumber,
line: lineTo,
ch: lineContent.length,
},
},
Expand Down Expand Up @@ -285,13 +248,17 @@ export default class LyricsMarkdownRender extends MarkdownRenderChild {
}

async onload() {
let fragment = new DocumentFragment()
const playerEl = fragment.createDiv()
playerEl.addClass('player-wrapper')
let lines = this.source.split(/r?\n/)
if (lines.length > 0) {
let eol = this.source.indexOf('\n')

// first line: audio source
if (this.source.length > 0 && eol >= 0) {
let sourceLine = this.source.substring(0, eol)
// render player
let match = lines[0].match(LyricsMarkdownRender.AUDIO_FILE_REGEX)
let fragment = new DocumentFragment()
const playerEl = fragment.createDiv()
playerEl.addClass('player-wrapper')

let match = sourceLine.match(LyricsMarkdownRender.AUDIO_FILE_REGEX)
if (match) {
this.audioPath = match.groups?.audio
let src: string | null = null
Expand Down Expand Up @@ -347,39 +314,17 @@ export default class LyricsMarkdownRender extends MarkdownRenderChild {
div.addEventListener('contextmenu', this.contextMenu)
div.className = 'lyrics-wrapper'
// render lyrcis
let mdEl: HTMLSpanElement[] = await Promise.all(
lines.slice(1).map(async (line, index) => {
const lineEl = div.createSpan()
if (line) {
const lrc = LyricsMarkdownRender.parseLrc(line)
lineEl.className = 'lyrics-line'
lineEl.dataset.lyid = `${index}`
const timeEl = lineEl.createSpan()
timeEl.setText(lrc.timestr || '')
timeEl.className = 'lyrics-timestamp'
timeEl.dataset.lyid = `${index}`
if (lrc.timestamp) {
const millis = Math.floor(lrc.timestamp * 1000)
timeEl.dataset.time = `${millis}`
lineEl.dataset.time = `${millis}`
}
lineEl.append(timeEl)
await MarkdownRenderer.render(
this.app,
lrc.text,
lineEl,
this.path,
this,
)
}
return lineEl
}),
)
if (this.source.length > eol) {
this.lyricsRenderer.render(
this.source.substring(eol + 1),
div,
this.path,
this,
)
}

div.append(...mdEl)
this.container.append(fragment)
}

this.container.append(fragment)
}

private binarySearch(arr: NodeListOf<HTMLElement>, time: number): number {
Expand All @@ -401,7 +346,7 @@ export default class LyricsMarkdownRender extends MarkdownRenderChild {
left = mid + 1
}
} else {
return mid + 1
return arr.length - 1
}
} else if (mt > time) {
if (mid >= 1) {
Expand Down
4 changes: 2 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default class LyricsPlugin extends Plugin {
}

async onload() {
const settings = {...DEFAULT_SETTINGS, ...await this.loadData()}
const settings = { ...DEFAULT_SETTINGS, ...(await this.loadData()) }
this.settings = new LyricsSettings(this, settings)
this.addSettingTab(this.settings)

Expand All @@ -25,6 +25,6 @@ export default class LyricsPlugin extends Plugin {
new LyricsMarkdownRender(this, source, element, context),
)
},
)
).sortOrder = -1000
}
}
Loading

0 comments on commit 99b2ea8

Please sign in to comment.