Spaces:
Running
Running
Commit ·
6925b7f
1
Parent(s): a6b3d77
fixed share link
Browse files
app.py
CHANGED
|
@@ -183,11 +183,15 @@ def load_outputs ():
|
|
| 183 |
'''Read previously-generated .lyl files from the outputs dir into a
|
| 184 |
{label: text} dict, so past session outputs survive a server restart.'''
|
| 185 |
store = {}
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 191 |
return store
|
| 192 |
|
| 193 |
|
|
@@ -196,6 +200,19 @@ def load_library ():
|
|
| 196 |
return {**load_examples(), **load_outputs()}
|
| 197 |
|
| 198 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
# A managed style line in the new `--styles-in-comments` format: a leading `%<value>`
|
| 200 |
# comment carrying period / composer / instrumentation (one per line). `sync_prompt`
|
| 201 |
# regenerates these from the dropdowns; any other line the user typed is preserved.
|
|
@@ -312,11 +329,26 @@ def run_generation (prompt, measures, temperature, max_patches, seed, store, top
|
|
| 312 |
# finished: persist the document, refresh the file list, select the new entry
|
| 313 |
label = OUTPUT_PREFIX + time.strftime('%Y%m%d_%H%M%S') + ('_m%d' % meas if meas else '') + '_s%d' % int(seed)
|
| 314 |
store[label] = pretty
|
| 315 |
-
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
| 316 |
out_path = os.path.join(OUTPUT_DIR, label.replace(OUTPUT_PREFIX, '') + '.lyl')
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 320 |
# randomize the seed slider for the next run (the file above already used the
|
| 321 |
# seed this generation ran with, so naming is unaffected)
|
| 322 |
next_seed = random.randint(0, 2147483647)
|
|
@@ -505,7 +537,7 @@ function (value) {
|
|
| 505 |
# <textarea> + document.execCommand('copy'), which works from a user gesture.
|
| 506 |
_JS_SHARE = '''
|
| 507 |
function () {
|
| 508 |
-
const
|
| 509 |
const flash = (msg, ok) => {
|
| 510 |
const btn = document.getElementById('ls-share-btn');
|
| 511 |
if (!btn) return;
|
|
@@ -522,11 +554,26 @@ function () {
|
|
| 522 |
clearTimeout(hint._t);
|
| 523 |
hint._t = setTimeout(() => hint.classList.remove('show'), 2200);
|
| 524 |
};
|
| 525 |
-
//
|
| 526 |
-
|
| 527 |
-
|
| 528 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 529 |
}
|
|
|
|
|
|
|
|
|
|
| 530 |
const fallback = () => {
|
| 531 |
try {
|
| 532 |
const ta = document.createElement('textarea');
|
|
@@ -872,6 +919,10 @@ def build_ui ():
|
|
| 872 |
# ---- wiring ----
|
| 873 |
# mount the score player once the page (and LilyScore) is ready
|
| 874 |
demo.load(None, None, None, js=_JS_HELPERS)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 875 |
|
| 876 |
# style dropdowns -> keep the metadata-prompt text box in sync
|
| 877 |
for field in (composer, period, genre):
|
|
|
|
| 183 |
'''Read previously-generated .lyl files from the outputs dir into a
|
| 184 |
{label: text} dict, so past session outputs survive a server restart.'''
|
| 185 |
store = {}
|
| 186 |
+
exists = os.path.isdir(OUTPUT_DIR)
|
| 187 |
+
names = sorted(os.listdir(OUTPUT_DIR)) if exists else []
|
| 188 |
+
lyls = [n for n in names if n.endswith('.lyl')]
|
| 189 |
+
LOG.info('load_outputs: OUTPUT_DIR=%s exists=%s total_entries=%d lyl_files=%d',
|
| 190 |
+
OUTPUT_DIR, exists, len(names), len(lyls))
|
| 191 |
+
if exists:
|
| 192 |
+
for name in lyls:
|
| 193 |
+
with open(os.path.join(OUTPUT_DIR, name), encoding='utf-8') as f:
|
| 194 |
+
store[OUTPUT_PREFIX + name[:-4]] = f.read()
|
| 195 |
return store
|
| 196 |
|
| 197 |
|
|
|
|
| 200 |
return {**load_examples(), **load_outputs()}
|
| 201 |
|
| 202 |
|
| 203 |
+
def refresh_library ():
|
| 204 |
+
'''Re-read the library from disk and return updates for (store, file_list).
|
| 205 |
+
|
| 206 |
+
Wired to demo.load so EVERY new session / page refresh picks up outputs that
|
| 207 |
+
were generated after server boot. Without this, store=gr.State(examples) and the
|
| 208 |
+
Radio's choices are frozen at the boot-time snapshot, so a refresh drops any
|
| 209 |
+
freshly-generated score from the Score List even when it persisted to disk.'''
|
| 210 |
+
lib = load_library()
|
| 211 |
+
LOG.info('refresh_library: %d entries (%d examples + %d outputs)',
|
| 212 |
+
len(lib), len(load_examples()), len(load_outputs()))
|
| 213 |
+
return lib, gr.update(choices=list(lib.keys()))
|
| 214 |
+
|
| 215 |
+
|
| 216 |
# A managed style line in the new `--styles-in-comments` format: a leading `%<value>`
|
| 217 |
# comment carrying period / composer / instrumentation (one per line). `sync_prompt`
|
| 218 |
# regenerates these from the dropdowns; any other line the user typed is preserved.
|
|
|
|
| 329 |
# finished: persist the document, refresh the file list, select the new entry
|
| 330 |
label = OUTPUT_PREFIX + time.strftime('%Y%m%d_%H%M%S') + ('_m%d' % meas if meas else '') + '_s%d' % int(seed)
|
| 331 |
store[label] = pretty
|
|
|
|
| 332 |
out_path = os.path.join(OUTPUT_DIR, label.replace(OUTPUT_PREFIX, '') + '.lyl')
|
| 333 |
+
# Persist to disk so the output survives a page refresh / server restart. On some
|
| 334 |
+
# hosts (e.g. a ModelScope studio) the project dir may be read-only or the
|
| 335 |
+
# container's filesystem ephemeral, so a write can fail or silently not persist —
|
| 336 |
+
# log the outcome (and don't let a write error kill the final yield, which would
|
| 337 |
+
# leave the new entry out of the Score List even in-session).
|
| 338 |
+
saved = False
|
| 339 |
+
try:
|
| 340 |
+
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
| 341 |
+
with open(out_path, 'w', encoding='utf-8') as f:
|
| 342 |
+
f.write(pretty)
|
| 343 |
+
size = os.path.getsize(out_path)
|
| 344 |
+
saved = os.path.isfile(out_path)
|
| 345 |
+
LOG.info('generation done: %d chars -> %s (saved=%s, on_disk_size=%d, dir_writable=%s)',
|
| 346 |
+
len(pretty), out_path, saved, size, os.access(OUTPUT_DIR, os.W_OK))
|
| 347 |
+
# list what's actually on disk right after the write, to catch ephemeral-FS cases
|
| 348 |
+
on_disk = [n for n in os.listdir(OUTPUT_DIR) if n.endswith('.lyl')]
|
| 349 |
+
LOG.info('outputs dir now holds %d .lyl file(s): %s', len(on_disk), on_disk)
|
| 350 |
+
except Exception as e:
|
| 351 |
+
LOG.exception('FAILED to persist generated score to %s: %s', out_path, e)
|
| 352 |
# randomize the seed slider for the next run (the file above already used the
|
| 353 |
# seed this generation ran with, so naming is unaffected)
|
| 354 |
next_seed = random.randint(0, 2147483647)
|
|
|
|
| 537 |
# <textarea> + document.execCommand('copy'), which works from a user gesture.
|
| 538 |
_JS_SHARE = '''
|
| 539 |
function () {
|
| 540 |
+
const stripPrefix = (s) => (s || '').replace(/^\\s*[\\u{1F4C4}\\u2728]\\s*/u, '').trim();
|
| 541 |
const flash = (msg, ok) => {
|
| 542 |
const btn = document.getElementById('ls-share-btn');
|
| 543 |
if (!btn) return;
|
|
|
|
| 554 |
clearTimeout(hint._t);
|
| 555 |
hint._t = setTimeout(() => hint.classList.remove('show'), 2200);
|
| 556 |
};
|
| 557 |
+
// Determine the selected score. Prefer the live #score= hash, but fall back to the
|
| 558 |
+
// currently-checked Score List radio: when a fresh generation auto-selects its new
|
| 559 |
+
// file, that happens via Gradio setting the Radio value (NOT a user click), so the
|
| 560 |
+
// js-only select listener never fires and the hash is stale/empty. Reading the
|
| 561 |
+
// checked radio here covers both generation-selection and click-selection, and we
|
| 562 |
+
// (re)write the hash so the copied URL always deep-links to the right score.
|
| 563 |
+
let name = '';
|
| 564 |
+
const m = (location.hash || '').match(/(?:^#|&)score=([^&]*)/);
|
| 565 |
+
if (m) { try { name = decodeURIComponent(m[1]); } catch (e) { name = ''; } }
|
| 566 |
+
if (!name) {
|
| 567 |
+
const checked = document.querySelector('.score-list input:checked');
|
| 568 |
+
if (checked) {
|
| 569 |
+
const lab = checked.closest('label');
|
| 570 |
+
const span = lab && lab.querySelector('span');
|
| 571 |
+
name = stripPrefix(span ? span.textContent : (lab ? lab.textContent : ''));
|
| 572 |
+
}
|
| 573 |
}
|
| 574 |
+
if (!name) { flash('Select a score first', false); return []; }
|
| 575 |
+
try { history.replaceState(null, '', '#score=' + encodeURIComponent(name)); } catch (e) {}
|
| 576 |
+
const url = location.href;
|
| 577 |
const fallback = () => {
|
| 578 |
try {
|
| 579 |
const ta = document.createElement('textarea');
|
|
|
|
| 919 |
# ---- wiring ----
|
| 920 |
# mount the score player once the page (and LilyScore) is ready
|
| 921 |
demo.load(None, None, None, js=_JS_HELPERS)
|
| 922 |
+
# re-read the library from disk on every new session / refresh, so outputs
|
| 923 |
+
# generated after server boot still appear in the Score List (store + Radio
|
| 924 |
+
# are otherwise frozen at the boot-time snapshot — see refresh_library).
|
| 925 |
+
demo.load(refresh_library, None, outputs=[store, file_list])
|
| 926 |
|
| 927 |
# style dropdowns -> keep the metadata-prompt text box in sync
|
| 928 |
for field in (composer, period, genre):
|