Spaces:
Running
Running
Improve Space output layout and CJK chart fonts
Browse files- app.py +13 -5
- packages.txt +1 -0
- softchart/fonts.py +35 -0
- softchart/tja_image.py +8 -2
app.py
CHANGED
|
@@ -16,6 +16,7 @@ import numpy as np
|
|
| 16 |
import torch
|
| 17 |
|
| 18 |
from softchart.generate import generate_song, generate_song_slot, load_hf
|
|
|
|
| 19 |
from softchart.grid import debias_to_grid, fit_grid_fixed_bpm, fit_grid_piecewise
|
| 20 |
from softchart.hf import SoftChartPlanner
|
| 21 |
from softchart.rhythm import snap_chart
|
|
@@ -207,8 +208,15 @@ def write_tja(gen, bpm, title, course, level, wave, downbeats=None, grid_fit=Non
|
|
| 207 |
def render_audio_plan(mel, title, course, plan=None):
|
| 208 |
import matplotlib
|
| 209 |
matplotlib.use("Agg")
|
|
|
|
| 210 |
import matplotlib.pyplot as plt
|
| 211 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 212 |
dur = mel.shape[1] / FPS
|
| 213 |
fig = plt.figure(figsize=(13, 4.4 if plan else 3.2))
|
| 214 |
gs = fig.add_gridspec(2 if plan else 1, 1,
|
|
@@ -341,7 +349,7 @@ def generate(audio, course, level, bpm_override, auto_plan_on, use_beat, use_pla
|
|
| 341 |
grid_info = ""
|
| 342 |
info = (f"BPM {bpm:.1f} · {len(g['hits'])} notes · {len(g['spans'])} spans" + grid_info
|
| 343 |
+ (f" · plan: {sum(1 for b in plan if b[3]==1)} gaps, {sum(1 for b in plan if b[3]==2)} climax" if plan else ""))
|
| 344 |
-
return
|
| 345 |
|
| 346 |
|
| 347 |
with gr.Blocks(title="SoftChart — AI Taiko chart generator") as demo:
|
|
@@ -366,12 +374,12 @@ with gr.Blocks(title="SoftChart — AI Taiko chart generator") as demo:
|
|
| 366 |
sampling = gr.Checkbox(label="Sampling (diverse) vs greedy (best)", value=False)
|
| 367 |
btn = gr.Button("Generate chart", variant="primary")
|
| 368 |
with gr.Column():
|
| 369 |
-
out_img = gr.Image(label="Full-song mel + plan")
|
| 370 |
-
out_chart = gr.Image(label="TJA chart")
|
| 371 |
-
out_info = gr.Textbox(label="Result", interactive=False)
|
| 372 |
out_tja = gr.File(label="Download .tja")
|
|
|
|
|
|
|
|
|
|
| 373 |
btn.click(generate, [audio, course, level, bpm, auto_plan_on, use_beat, use_planner, sampling],
|
| 374 |
-
[
|
| 375 |
|
| 376 |
if __name__ == "__main__":
|
| 377 |
# show_api=False avoids a gradio_client schema-introspection bug
|
|
|
|
| 16 |
import torch
|
| 17 |
|
| 18 |
from softchart.generate import generate_song, generate_song_slot, load_hf
|
| 19 |
+
from softchart.fonts import cjk_font_path
|
| 20 |
from softchart.grid import debias_to_grid, fit_grid_fixed_bpm, fit_grid_piecewise
|
| 21 |
from softchart.hf import SoftChartPlanner
|
| 22 |
from softchart.rhythm import snap_chart
|
|
|
|
| 208 |
def render_audio_plan(mel, title, course, plan=None):
|
| 209 |
import matplotlib
|
| 210 |
matplotlib.use("Agg")
|
| 211 |
+
from matplotlib import font_manager
|
| 212 |
import matplotlib.pyplot as plt
|
| 213 |
|
| 214 |
+
font_path = cjk_font_path()
|
| 215 |
+
if font_path is not None:
|
| 216 |
+
font_manager.fontManager.addfont(font_path)
|
| 217 |
+
matplotlib.rcParams["font.family"] = font_manager.FontProperties(fname=font_path).get_name()
|
| 218 |
+
matplotlib.rcParams["axes.unicode_minus"] = False
|
| 219 |
+
|
| 220 |
dur = mel.shape[1] / FPS
|
| 221 |
fig = plt.figure(figsize=(13, 4.4 if plan else 3.2))
|
| 222 |
gs = fig.add_gridspec(2 if plan else 1, 1,
|
|
|
|
| 349 |
grid_info = ""
|
| 350 |
info = (f"BPM {bpm:.1f} · {len(g['hits'])} notes · {len(g['spans'])} spans" + grid_info
|
| 351 |
+ (f" · plan: {sum(1 for b in plan if b[3]==1)} gaps, {sum(1 for b in plan if b[3]==2)} climax" if plan else ""))
|
| 352 |
+
return tja_path, info, tja_img, audio_plan_img
|
| 353 |
|
| 354 |
|
| 355 |
with gr.Blocks(title="SoftChart — AI Taiko chart generator") as demo:
|
|
|
|
| 374 |
sampling = gr.Checkbox(label="Sampling (diverse) vs greedy (best)", value=False)
|
| 375 |
btn = gr.Button("Generate chart", variant="primary")
|
| 376 |
with gr.Column():
|
|
|
|
|
|
|
|
|
|
| 377 |
out_tja = gr.File(label="Download .tja")
|
| 378 |
+
out_info = gr.Textbox(label="Result", interactive=False)
|
| 379 |
+
out_chart = gr.Image(label="TJA chart")
|
| 380 |
+
out_img = gr.Image(label="Full-song mel + plan")
|
| 381 |
btn.click(generate, [audio, course, level, bpm, auto_plan_on, use_beat, use_planner, sampling],
|
| 382 |
+
[out_tja, out_info, out_chart, out_img])
|
| 383 |
|
| 384 |
if __name__ == "__main__":
|
| 385 |
# show_api=False avoids a gradio_client schema-introspection bug
|
packages.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
fonts-noto-cjk
|
softchart/fonts.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Font helpers for generated preview images."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import os
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def cjk_font_path(bold: bool = False) -> str | None:
|
| 9 |
+
names = (
|
| 10 |
+
(
|
| 11 |
+
"/usr/share/fonts/opentype/noto/NotoSansCJK-Bold.ttc",
|
| 12 |
+
"/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
|
| 13 |
+
),
|
| 14 |
+
(
|
| 15 |
+
"/usr/share/fonts/opentype/noto/NotoSansCJKjp-Bold.otf",
|
| 16 |
+
"/usr/share/fonts/opentype/noto/NotoSansCJKjp-Regular.otf",
|
| 17 |
+
),
|
| 18 |
+
(
|
| 19 |
+
"/usr/share/fonts/truetype/noto/NotoSansCJK-Bold.ttc",
|
| 20 |
+
"/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc",
|
| 21 |
+
),
|
| 22 |
+
(
|
| 23 |
+
"/System/Library/Fonts/ヒラギノ角ゴシック W6.ttc",
|
| 24 |
+
"/System/Library/Fonts/ヒラギノ角ゴシック W3.ttc",
|
| 25 |
+
),
|
| 26 |
+
(
|
| 27 |
+
"/System/Library/Fonts/Supplemental/Arial Unicode.ttf",
|
| 28 |
+
"/System/Library/Fonts/Supplemental/Arial Unicode.ttf",
|
| 29 |
+
),
|
| 30 |
+
)
|
| 31 |
+
for bold_path, regular_path in names:
|
| 32 |
+
path = bold_path if bold else regular_path
|
| 33 |
+
if os.path.exists(path):
|
| 34 |
+
return path
|
| 35 |
+
return None
|
softchart/tja_image.py
CHANGED
|
@@ -16,6 +16,8 @@ from dataclasses import dataclass
|
|
| 16 |
|
| 17 |
from PIL import Image, ImageDraw, ImageFont
|
| 18 |
|
|
|
|
|
|
|
| 19 |
|
| 20 |
_NOTE_CHARS = set("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
| 21 |
_COURSE_NAME = {
|
|
@@ -35,7 +37,9 @@ class ParsedTJA:
|
|
| 35 |
|
| 36 |
|
| 37 |
def _font(size: int, bold: bool = False) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:
|
|
|
|
| 38 |
names = (
|
|
|
|
| 39 |
"DejaVuSans-Bold.ttf" if bold else "DejaVuSans.ttf",
|
| 40 |
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" if bold else
|
| 41 |
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
|
|
@@ -45,6 +49,8 @@ def _font(size: int, bold: bool = False) -> ImageFont.FreeTypeFont | ImageFont.I
|
|
| 45 |
"/System/Library/Fonts/Supplemental/Helvetica.ttf",
|
| 46 |
)
|
| 47 |
for name in names:
|
|
|
|
|
|
|
| 48 |
try:
|
| 49 |
return ImageFont.truetype(name, size=size)
|
| 50 |
except OSError:
|
|
@@ -186,14 +192,14 @@ def _draw_note(draw: ImageDraw.ImageDraw, x: int, y: int, symbol: str,
|
|
| 186 |
else:
|
| 187 |
fill = "#8d96a0"
|
| 188 |
|
| 189 |
-
radius =
|
| 190 |
if symbol == "8":
|
| 191 |
draw.rectangle((x - 5, y - 12, x + 5, y + 12), fill=fill)
|
| 192 |
return
|
| 193 |
|
| 194 |
draw.ellipse((x - radius, y - radius, x + radius, y + radius),
|
| 195 |
fill=fill, outline="#202020", width=2)
|
| 196 |
-
draw.ellipse((x - radius +
|
| 197 |
fill="#ffffff")
|
| 198 |
label = "B" if symbol in {"3", "4", "6", "A", "B"} else ("R" if symbol in {"5", "6"} else ("P" if symbol in {"7", "9"} else ""))
|
| 199 |
if label:
|
|
|
|
| 16 |
|
| 17 |
from PIL import Image, ImageDraw, ImageFont
|
| 18 |
|
| 19 |
+
from .fonts import cjk_font_path
|
| 20 |
+
|
| 21 |
|
| 22 |
_NOTE_CHARS = set("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
| 23 |
_COURSE_NAME = {
|
|
|
|
| 37 |
|
| 38 |
|
| 39 |
def _font(size: int, bold: bool = False) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:
|
| 40 |
+
cjk_path = cjk_font_path(bold=bold)
|
| 41 |
names = (
|
| 42 |
+
cjk_path,
|
| 43 |
"DejaVuSans-Bold.ttf" if bold else "DejaVuSans.ttf",
|
| 44 |
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" if bold else
|
| 45 |
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
|
|
|
|
| 49 |
"/System/Library/Fonts/Supplemental/Helvetica.ttf",
|
| 50 |
)
|
| 51 |
for name in names:
|
| 52 |
+
if name is None:
|
| 53 |
+
continue
|
| 54 |
try:
|
| 55 |
return ImageFont.truetype(name, size=size)
|
| 56 |
except OSError:
|
|
|
|
| 192 |
else:
|
| 193 |
fill = "#8d96a0"
|
| 194 |
|
| 195 |
+
radius = 14 if symbol in {"3", "4", "6", "A", "B"} else 10
|
| 196 |
if symbol == "8":
|
| 197 |
draw.rectangle((x - 5, y - 12, x + 5, y + 12), fill=fill)
|
| 198 |
return
|
| 199 |
|
| 200 |
draw.ellipse((x - radius, y - radius, x + radius, y + radius),
|
| 201 |
fill=fill, outline="#202020", width=2)
|
| 202 |
+
draw.ellipse((x - radius + 3, y - radius + 3, x - radius + 8, y - radius + 8),
|
| 203 |
fill="#ffffff")
|
| 204 |
label = "B" if symbol in {"3", "4", "6", "A", "B"} else ("R" if symbol in {"5", "6"} else ("P" if symbol in {"7", "9"} else ""))
|
| 205 |
if label:
|