La cola de conversión sigue a los clips cuando se mueven, y «Comparar» en vertical
Los trabajos de la cola apuntaban al clip por su ruta y nadie los avisaba al moverlo: en Línea Gene el plan llevó «Material Sony» a 00_Grabacion y 138 conversiones fallaron, los 11 verificados no se podían reemplazar y volver a escanear no los reenganchaba. Ahora se mueven con el clip (plan, Deshacer, renombrar), el convertido acompaña a su original, al escanear se reenganchan por nombre y peso, antes de convertir se comprueba que el clip esté, y reemplazar junta antes el convertido con su original. El comparador toma la forma del clip y la lupa ya no estira el recorte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Este commit está contenido en:
@@ -444,6 +444,13 @@
|
||||
.cmp .marco img{width:100%;height:100%;object-fit:contain;display:block;cursor:crosshair}
|
||||
.cmp .lupa{position:absolute;width:220px;height:220px;border:2px solid var(--acc);border-radius:8px;background-repeat:no-repeat;background-color:#000;pointer-events:none;display:none;box-shadow:0 4px 14px rgba(0,0,0,.3)}
|
||||
.cmp .marco.con .lupa{display:block}
|
||||
/* en vertical (móvil, cámara girada): los dos cuadros a lo alto, uno junto al otro, y la nota y lo demás en una
|
||||
columna a su derecha. Antes el marco era siempre apaisado y el clip quedaba como una tira en medio */
|
||||
.cmpcuerpo.vert{display:grid;grid-template-columns:auto minmax(260px,1fr);gap:22px;align-items:start}
|
||||
.cmpcuerpo.vert .cmp{grid-template-columns:auto auto;justify-content:start}
|
||||
.cmpcuerpo.vert .cmp .lado{min-width:auto}
|
||||
.cmpcuerpo.vert .cmpt{margin-top:26px}
|
||||
.cmpcuerpo.vert .cmpnota{grid-template-columns:1fr}
|
||||
.cmpnota{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin-top:12px}
|
||||
.cmpnota>div{border:1px solid var(--line);border-radius:8px;padding:10px 12px;font-size:12.5px;display:flex;flex-direction:column;gap:4px}
|
||||
.cmpnota b.n{font-size:26px;color:var(--ok)} .cmpnota b.n.baja{color:var(--warn)}
|
||||
@@ -2002,7 +2009,7 @@ async function cargarCola(){
|
||||
if (corre) { const a = eta && eta.actual && eta.actual.id === t.id ? eta.actual : null; sub = `<span class="acc">convirtiendo${a && a.dur ? ` · ${tiempoLargo(a.dur - a.resto)} de ${tiempoLargo(a.dur)} · quedan ${tiempoLargo(a.resto_seg)}` : ''}${E && E.mensaje ? ' · ' + esc(E.mensaje) : ''}</span> · ${motorT}`; }
|
||||
else if (t && t.estado === 'error') sub = `<span class="bad" title="${esc(t.log || '')}">${esc(((t.log || '').trim().split('\n').pop() || 'falló sin decir por qué').slice(0, 320))}</span>`;
|
||||
else if (t && t.estado === 'descartado') sub = `<span class="mut">${esc(t.log || '')}</span>`;
|
||||
else if (t && t.estado === 'pendiente' && (t.log || '').startsWith('se interrumpió')) sub = `<span class="mut">${esc(t.log)}</span>`;
|
||||
else if (t && t.estado === 'pendiente' && t.log) sub = `<span class="mut">${esc(t.log)}</span>`;
|
||||
else if (t && ['listo', 'verificado', 'reemplazado'].includes(t.estado)) sub = `${t.nota ? `nota de calidad <b>${t.nota} / 100</b> · ` : ''}${esc((t.salida || '').split(/[\\/]/).pop())} · ${motorT}`;
|
||||
else if (c && c.excluido) sub += ' · ' + esc(c.excluido);
|
||||
if (conCarpeta) sub = `<span class="carp">${esc(carpeta(f) || '(raíz)')}</span> · ` + sub;
|
||||
@@ -2141,17 +2148,44 @@ function comparar(id){
|
||||
const lado = (cual, tit, det) => `<div class="lado"><div style="display:flex;gap:8px;align-items:baseline"><b>${tit}</b><span class="mut" style="font-size:12px">${det}</span></div><div class="marco" data-lado="${cual}"><img src="${src(cual)}" alt="${tit}: cuadro en el segundo ${tAct}"><div class="lupa"></div></div></div>`;
|
||||
const notaHtml = () => t.nota ? `<b class="n ${t.nota < 93 ? 'baja' : ''}">${t.nota} <span style="font-size:13px;color:var(--mut);font-weight:500">/ 100</span></b><span class="mut">VMAF sobre unos 10 s del medio del clip · el cuadro peor, ${t.nota_min}. De 93 arriba no se nota a simple vista.</span>` : `<button class="p" id="cmpMedir">Medir la calidad</button><span class="mut">compara unos 10 s del medio del clip con el original; tarda entre medio minuto y dos.</span>`;
|
||||
const fm = (ESTADO.formatos || {})[t.formato || 'h265_10bit'] || {};
|
||||
modal(`Comparar: ${nom}`, `<div class="cmp">${lado('original', 'Original', `${gb(t.bytes_in)}${t.motor === 'braw' ? ' · BRAW revelado con la misma curva' : ''}`)}${lado('convertido', 'Convertido', `${t.bytes_out ? gb(t.bytes_out) + ' · ' : ''}${esc((fm.nombre || '').split(' · ')[0])}`)}</div>
|
||||
<div class="cmpt"><span>cuadro en el segundo</span>${puntos.map(p => `<button data-t="${p}" class="${p === tAct ? 'on' : ''}">${p}</button>`).join('')}<span style="margin-left:8px">pasa el ratón por la imagen: la lupa amplía el mismo recorte en los dos</span></div>
|
||||
<div class="cmpnota"><div><span class="cabsec" style="margin:0">Nota de calidad</span><div id="cmpNota" style="display:flex;flex-direction:column;gap:4px">${notaHtml()}</div></div><div><span class="cabsec" style="margin:0">Se conserva</span><span>${durC ? tiempoLargo(durC) + ' · ' : ''}${t.fps ? t.fps + ' fps · ' : ''}código de tiempo · audio · etiquetas de la cámara · ${fm.nombre && fm.nombre.includes('10') ? '10 bits de color' : 'el color del original'}</span></div><div>${t.estado === 'verificado' && !(t.formato || '').startsWith('prores') ? `<button class="ok" id="cmpReemplazar">Reemplazar este original</button><span class="mut">va a _Para borrar/originales; se deshace desde Historial</span>` : t.estado === 'reemplazado' ? '<span class="mut">El original ya se reemplazó: está en _Para borrar/originales.</span>' : '<span class="mut">Cuando esté verificado se podrá reemplazar desde aquí.</span>'}</div></div>`, null);
|
||||
modal(`Comparar: ${nom}`, `<div class="cmpcuerpo" id="cmpCuerpo"><div class="cmp">${lado('original', 'Original', `${gb(t.bytes_in)}${t.motor === 'braw' ? ' · BRAW revelado con la misma curva' : ''}`)}${lado('convertido', 'Convertido', `${t.bytes_out ? gb(t.bytes_out) + ' · ' : ''}${esc((fm.nombre || '').split(' · ')[0])}`)}</div>
|
||||
<div class="cmpinfo"><div class="cmpt"><span>cuadro en el segundo</span>${puntos.map(p => `<button data-t="${p}" class="${p === tAct ? 'on' : ''}">${p}</button>`).join('')}<span style="margin-left:8px">pasa el ratón por la imagen: la lupa amplía el mismo recorte en los dos</span></div>
|
||||
<div class="cmpnota"><div><span class="cabsec" style="margin:0">Nota de calidad</span><div id="cmpNota" style="display:flex;flex-direction:column;gap:4px">${notaHtml()}</div></div><div><span class="cabsec" style="margin:0">Se conserva</span><span>${durC ? tiempoLargo(durC) + ' · ' : ''}${t.fps ? t.fps + ' fps · ' : ''}código de tiempo · audio · etiquetas de la cámara · ${fm.nombre && fm.nombre.includes('10') ? '10 bits de color' : 'el color del original'}</span></div><div>${t.estado === 'verificado' && !(t.formato || '').startsWith('prores') ? `<button class="ok" id="cmpReemplazar">Reemplazar este original</button><span class="mut">va a _Para borrar/originales; se deshace desde Historial</span>` : t.estado === 'reemplazado' ? '<span class="mut">El original ya se reemplazó: está en _Para borrar/originales.</span>' : '<span class="mut">Cuando esté verificado se podrá reemplazar desde aquí.</span>'}</div></div></div></div>`, null);
|
||||
$('#modal').classList.add('ancho');
|
||||
const marcos = $$('#modalCuerpo .marco');
|
||||
const lupa = (ev) => {
|
||||
const m = ev.currentTarget, img = m.querySelector('img'), r = img.getBoundingClientRect(), Z = 3;
|
||||
const rx = Math.max(0, Math.min(1, (ev.clientX - r.left) / r.width)), ry = Math.max(0, Math.min(1, (ev.clientY - r.top) / r.height));
|
||||
marcos.forEach(mm => { const im = mm.querySelector('img'), rr = im.getBoundingClientRect(), l = mm.querySelector('.lupa'); mm.classList.add('con'); l.style.backgroundImage = `url("${im.src}")`; l.style.backgroundSize = `${rr.width * Z}px ${rr.height * Z}px`; l.style.backgroundPosition = `${-(rx * rr.width * Z - 110)}px ${-(ry * rr.height * Z - 110)}px`; l.style.left = `${Math.max(0, Math.min(rr.width - 220, rx * rr.width - 110))}px`; l.style.top = `${Math.max(0, Math.min(rr.height - 220, ry * rr.height - 110))}px`; });
|
||||
const marcos = $$('#modalCuerpo .marco'), cuerpo = $('#cmpCuerpo');
|
||||
// El marco toma la forma del clip. En vertical, los dos cuadros tan altos como quepan con la columna de la
|
||||
// nota al lado; en apaisado, como siempre, a todo el ancho.
|
||||
const ajustar = () => {
|
||||
const im = marcos.map(m => m.querySelector('img')).find(x => x && x.naturalWidth);
|
||||
if (!im || !document.body.contains(cuerpo)) return;
|
||||
const ar = im.naturalWidth / im.naturalHeight, vert = ar < 0.95;
|
||||
cuerpo.classList.toggle('vert', vert);
|
||||
marcos.forEach(m => { m.style.aspectRatio = `${im.naturalWidth} / ${im.naturalHeight}`; m.style.width = m.style.height = ''; });
|
||||
if (vert) {
|
||||
const cabe = cuerpo.clientWidth - 280 - 22 - 14;
|
||||
const h = Math.max(220, Math.min(window.innerHeight * 0.68, 780, cabe / (2 * ar)));
|
||||
marcos.forEach(m => { m.style.height = `${Math.round(h)}px`; m.style.width = `${Math.round(h * ar)}px`; });
|
||||
}
|
||||
};
|
||||
marcos.forEach(m => { m.onmousemove = lupa; m.onmouseleave = () => marcos.forEach(mm => mm.classList.remove('con')); const im = m.querySelector('img'); im.onerror = () => { m.innerHTML = `<span style="padding:12px;text-align:center">no se pudo sacar el cuadro ${m.dataset.lado === 'original' ? 'del original' : 'del convertido'}</span>`; }; });
|
||||
const alCambiar = () => { if (!document.body.contains(cuerpo)) return window.removeEventListener('resize', alCambiar); ajustar(); };
|
||||
window.addEventListener('resize', alCambiar);
|
||||
// dónde está el cuadro dentro de la imagen (sin las franjas, si las hubiera): la lupa amplía ESO, sin estirarlo
|
||||
const caja = im => { const r = im.getBoundingClientRect(), nw = im.naturalWidth || r.width, nh = im.naturalHeight || r.height, s = Math.min(r.width / nw, r.height / nh), w = nw * s, h = nh * s; return {left: r.left + (r.width - w) / 2, top: r.top + (r.height - h) / 2, width: w, height: h}; };
|
||||
const lupa = (ev) => {
|
||||
const img = ev.currentTarget.querySelector('img'); if (!img) return;
|
||||
const r = caja(img), Z = 3;
|
||||
const rx = Math.max(0, Math.min(1, (ev.clientX - r.left) / r.width)), ry = Math.max(0, Math.min(1, (ev.clientY - r.top) / r.height));
|
||||
marcos.forEach(mm => {
|
||||
const im = mm.querySelector('img'), l = mm.querySelector('.lupa'); if (!im || !l) return;
|
||||
const rr = caja(im), mr = mm.getBoundingClientRect(), L = Math.round(Math.max(120, Math.min(220, Math.min(rr.width, rr.height) * 0.5)));
|
||||
mm.classList.add('con'); l.style.width = l.style.height = `${L}px`;
|
||||
l.style.backgroundImage = `url("${im.src}")`; l.style.backgroundSize = `${rr.width * Z}px ${rr.height * Z}px`;
|
||||
l.style.backgroundPosition = `${-(rx * rr.width * Z - L / 2)}px ${-(ry * rr.height * Z - L / 2)}px`;
|
||||
l.style.left = `${rr.left - mr.left + Math.max(0, Math.min(rr.width - L, rx * rr.width - L / 2))}px`;
|
||||
l.style.top = `${rr.top - mr.top + Math.max(0, Math.min(rr.height - L, ry * rr.height - L / 2))}px`;
|
||||
});
|
||||
};
|
||||
marcos.forEach(m => { m.onmousemove = lupa; m.onmouseleave = () => marcos.forEach(mm => mm.classList.remove('con')); const im = m.querySelector('img'); im.addEventListener('load', ajustar); if (im.complete && im.naturalWidth) ajustar(); im.onerror = () => { m.innerHTML = `<span style="padding:12px;text-align:center">no se pudo sacar el cuadro ${m.dataset.lado === 'original' ? 'del original' : 'del convertido'}</span>`; }; });
|
||||
$$('#modalCuerpo [data-t]').forEach(b => b.onclick = () => { tAct = +b.dataset.t; $$('#modalCuerpo [data-t]').forEach(x => x.classList.toggle('on', x === b)); marcos.forEach(m => { const im = m.querySelector('img'); if (im) im.src = src(m.dataset.lado); }); });
|
||||
const bm = $('#cmpMedir'); if (bm) bm.onclick = () => accion(bm, async () => { $('#cmpNota').innerHTML = '<span class="spin"></span> midiendo…'; try { const r = await api('/api/optimizar/calidad', {id}); t.nota = r.vmaf; t.nota_min = r.minimo; $('#cmpNota').innerHTML = notaHtml(); } catch (e) { $('#cmpNota').innerHTML = `<span class="bad">${esc(e.message)}</span>`; } });
|
||||
const br = $('#cmpReemplazar'); if (br) br.onclick = () => accion(br, async () => { const r = await api('/api/optimizar/reemplazar', {ids: [id]}); const x = r.resultados[0] || {}; aviso(x.ok ? `Reemplazado: ${x.motivo}` : `No se reemplazó: ${x.motivo}`, x.ok ? 'ok' : 'bad', x.ok ? {texto: 'Ver historial', ir: () => irA('historial')} : null); $('#modal').classList.remove('on'); cargarCola(); });
|
||||
|
||||
Referencia en una nueva incidencia
Block a user