Fixed frame counting optimization, resolving issue #17. Also added a Siemens Star focusing screen, a middle gray metering screen and a field guide screen for use with the filmout feature. TODO: actually change monitors when selected.
This commit is contained in:
parent
872b46d4a5
commit
f239f862e8
|
@ -739,7 +739,7 @@ button:focus {
|
||||||
background: black;
|
background: black;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
#video input[type=text],
|
#video input,
|
||||||
#video select {
|
#video select {
|
||||||
display: block;
|
display: block;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
@ -755,25 +755,25 @@ button:focus {
|
||||||
font-size: 21px;
|
font-size: 21px;
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
}
|
}
|
||||||
#video input[type=text] span,
|
#video input span,
|
||||||
#video select span {
|
#video select span {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 200;
|
font-weight: 200;
|
||||||
}
|
}
|
||||||
#video input[type=text]:active,
|
#video input:active,
|
||||||
#video select:active,
|
#video select:active,
|
||||||
#video input[type=text] .active,
|
#video input .active,
|
||||||
#video select .active {
|
#video select .active {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
color: #272b30;
|
color: #272b30;
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
#video input[type=text]:focus,
|
#video input:focus,
|
||||||
#video select:focus {
|
#video select:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
#video input[type=text].active,
|
#video input.active,
|
||||||
#video select.active {
|
#video select.active {
|
||||||
border-color: #DAE035;
|
border-color: #DAE035;
|
||||||
color: #DAE035;
|
color: #DAE035;
|
||||||
|
@ -805,6 +805,7 @@ button:focus {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
height: 360px;
|
height: 360px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
#filmout_monitor.on {
|
#filmout_monitor.on {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -815,14 +816,18 @@ button:focus {
|
||||||
height: 360px;
|
height: 360px;
|
||||||
}
|
}
|
||||||
#filmout {
|
#filmout {
|
||||||
height: 360px;
|
position: absolute;
|
||||||
min-height: 360px;
|
background-repeat: no-repeat;
|
||||||
width: auto;
|
background-size: contain;
|
||||||
margin: 0 auto;
|
background-position: center;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
display: block;
|
display: block;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
#filmout .on {
|
#filmout.on {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
#filmout_position_wrap {
|
#filmout_position_wrap {
|
||||||
|
|
100
app/display.html
100
app/display.html
|
@ -57,30 +57,75 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
img.style.backgroundImage = `url('${src}')`;
|
img.style.backgroundImage = `url('${src}')`;
|
||||||
//img.onload = () => {
|
});
|
||||||
//document.body.appendChild(img)
|
|
||||||
//return resolve(img)
|
|
||||||
//}
|
|
||||||
//img.onerror = reject
|
|
||||||
//img.src = src
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
async function onMeter () {
|
async function onMeter () {
|
||||||
|
console.log('meter')
|
||||||
const body = document.querySelector('body')
|
const body = document.querySelector('body')
|
||||||
if (!body.classList.contains('meter')) {
|
if (!body.classList.contains('meter')) {
|
||||||
body.classList.add('meter')
|
body.classList.add('meter')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function onField () {
|
async function onFocus () {
|
||||||
|
console.log('focus')
|
||||||
const can = document.getElementById('can')
|
const can = document.getElementById('can')
|
||||||
const ctx = can.getContext('2d')
|
const dpr = window.devicePixelRatio || 1
|
||||||
|
let ctx;
|
||||||
if (!can.classList.contains('show')) {
|
if (!can.classList.contains('show')) {
|
||||||
can.classList.add('show')
|
can.classList.add('show')
|
||||||
}
|
}
|
||||||
can.width = window.innerWidth
|
can.width = window.innerWidth * dpr
|
||||||
can.height = window.innerHeight
|
can.height = window.innerHeight * dpr
|
||||||
|
|
||||||
can.style.width = `${window.innerWidth}px`
|
can.style.width = `${window.innerWidth}px`
|
||||||
can.style.height = `${window.innerHeight}px`
|
can.style.height = `${window.innerHeight}px`
|
||||||
|
|
||||||
|
ctx = can.getContext('2d')
|
||||||
|
ctx.scale(dpr, dpr)
|
||||||
|
|
||||||
|
try{
|
||||||
|
await drawFocus(can, ctx)
|
||||||
|
} catch (err) {
|
||||||
|
alert(JSON.stringify(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function drawFocus (can, ctx) {
|
||||||
|
const count = 20
|
||||||
|
const half = Math.round(count / 2)
|
||||||
|
const dpr = window.devicePixelRatio || 1
|
||||||
|
const w = can.width / dpr
|
||||||
|
const h = can.height / dpr
|
||||||
|
const opp = (Math.tan(360 / count) * ((h / 2) + h - w) / 1.5)
|
||||||
|
|
||||||
|
console.log(opp)
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.moveTo(w / 2, h / 2)
|
||||||
|
ctx.lineTo((w / 2) + opp, h - w)
|
||||||
|
ctx.lineTo((w / 2) - opp, h - w)
|
||||||
|
ctx.fill()
|
||||||
|
ctx.translate(w / 2, h / 2);
|
||||||
|
ctx.rotate((360 / count) * Math.PI / 180)
|
||||||
|
ctx.translate(- w / 2, -h / 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function onField () {
|
||||||
|
console.log('field guide')
|
||||||
|
const can = document.getElementById('can')
|
||||||
|
const dpr = window.devicePixelRatio || 1
|
||||||
|
let ctx;
|
||||||
|
if (!can.classList.contains('show')) {
|
||||||
|
can.classList.add('show')
|
||||||
|
}
|
||||||
|
can.width = window.innerWidth * dpr
|
||||||
|
can.height = window.innerHeight * dpr
|
||||||
|
|
||||||
|
can.style.width = `${window.innerWidth}px`
|
||||||
|
can.style.height = `${window.innerHeight}px`
|
||||||
|
|
||||||
|
ctx = can.getContext('2d')
|
||||||
|
ctx.scale(dpr, dpr)
|
||||||
|
|
||||||
try{
|
try{
|
||||||
await drawField(can, ctx)
|
await drawField(can, ctx)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -91,10 +136,13 @@
|
||||||
async function drawField (can, ctx) {
|
async function drawField (can, ctx) {
|
||||||
const count = 20
|
const count = 20
|
||||||
const half = Math.round(count / 2)
|
const half = Math.round(count / 2)
|
||||||
const w = can.width
|
const dpr = window.devicePixelRatio || 1
|
||||||
const h = can.height
|
const w = can.width / dpr
|
||||||
|
const h = can.height / dpr
|
||||||
const wsec = w / count
|
const wsec = w / count
|
||||||
const hsec= h / count
|
const hsec = h / count
|
||||||
|
const spacer = 12
|
||||||
|
const fontSize = 18;
|
||||||
|
|
||||||
ctx.moveTo(w / 2, 0)
|
ctx.moveTo(w / 2, 0)
|
||||||
ctx.lineTo(w / 2, h)
|
ctx.lineTo(w / 2, h)
|
||||||
|
@ -111,34 +159,21 @@
|
||||||
ctx.lineTo(w - (wsec * i), hsec * i)
|
ctx.lineTo(w - (wsec * i), hsec * i)
|
||||||
ctx.stroke()
|
ctx.stroke()
|
||||||
}
|
}
|
||||||
ctx.font = '30px Arial'
|
ctx.font = `${fontSize}px Arial`
|
||||||
for (let i = 0; i < half; i++) {
|
for (let i = 0; i < half; i++) {
|
||||||
ctx.fillText(`${(half - i)}`, (wsec * i) + 15, (h / 2) - 15)
|
ctx.fillText(`${(half - i)}`, (wsec * i) + spacer, (h / 2) - spacer)
|
||||||
ctx.fillText(`${(half - i)}`, (w - (wsec * i)) - 25, (h / 2) - 15)
|
//ctx.fillText(`${(half - i)}`, (w - (wsec * i)) - spacer, (h / 2) - spacer)
|
||||||
ctx.fillText(`${(half - i)}`, (w / 2) + 15, (hsec * i) + 25 )
|
ctx.fillText(`${(half - i)}`, (w / 2) + spacer, (hsec * i) + spacer + (fontSize / 2) )
|
||||||
ctx.fillText(`${(half - i)}`, (w / 2) + 15, (h - (hsec * i)) - 15)
|
//ctx.fillText(`${(half - i)}`, (w / 2) + spacer, (h - (hsec * i)) - spacer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function onDigital (event, arg) {
|
async function onDigital (event, arg) {
|
||||||
console.log('called')
|
|
||||||
if (arg.src) {
|
if (arg.src) {
|
||||||
try {
|
try {
|
||||||
await setImage(arg.src)
|
await setImage(arg.src)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
}
|
}
|
||||||
} else if (arg.meter) {
|
|
||||||
try {
|
|
||||||
await setMeter()
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
} else if (arg.grid) {
|
|
||||||
try {
|
|
||||||
await setGrid()
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return event.returnValue = true
|
return event.returnValue = true
|
||||||
}
|
}
|
||||||
|
@ -160,6 +195,7 @@
|
||||||
ipcRenderer.on('display', onDigital)
|
ipcRenderer.on('display', onDigital)
|
||||||
ipcRenderer.on('field', onField)
|
ipcRenderer.on('field', onField)
|
||||||
ipcRenderer.on('meter', onMeter)
|
ipcRenderer.on('meter', onMeter)
|
||||||
|
ipcRenderer.on('focus', onFocus)
|
||||||
document.onkeydown = onEscape
|
document.onkeydown = onEscape
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -353,7 +353,7 @@
|
||||||
|
|
||||||
<div id="filmout_preview_wrap">
|
<div id="filmout_preview_wrap">
|
||||||
<div id="filmout_monitor">
|
<div id="filmout_monitor">
|
||||||
<img src="#" id="filmout">
|
<div id="filmout"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="filmout_stats">
|
<div id="filmout_stats">
|
||||||
|
@ -372,17 +372,17 @@
|
||||||
</div>
|
</div>
|
||||||
<div id="filmout_position_wrap">
|
<div id="filmout_position_wrap">
|
||||||
<div>
|
<div>
|
||||||
<button id="filmout_rewind" title="Rewind 1 Frame"><</button>
|
<button id="filmout_rewind" title="Rewind 1 Frame" onclick="filmout.rewind();"><</button>
|
||||||
<input id="filmout_position" class="count" type="text" value="00000"/>
|
<input id="filmout_position" class="count" type="number" value="00000" onchange="gui.counterFormat(this, this.value);" />
|
||||||
<button id="filmout_advance" title="Advance 1 Frame">></button>
|
<button id="filmout_advance" title="Advance 1 Frame" onclick="filmout.advance();">></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="filmout_functions">
|
<div id="filmout_functions">
|
||||||
<div>
|
<div>
|
||||||
<button id="filmout_preview">PREVIEW</button>
|
<button id="filmout_preview">PREVIEW</button>
|
||||||
<button id="filmout_meter">METER</button>
|
<button id="filmout_meter" onclick="filmout.meter();">METER</button>
|
||||||
<button id="filmout_focus">FOCUS</button>
|
<button id="filmout_focus" onclick="filmout.focus();">FOCUS</button>
|
||||||
<button id="filmout_field">FIELD GUIDE</button>
|
<button id="filmout_field" onclick="filmout.field();">FIELD GUIDE</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#video{
|
#video{
|
||||||
input[type=text],select{
|
input,select{
|
||||||
.button();
|
.button();
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 6px 12px;
|
padding: 6px 12px;
|
||||||
|
@ -41,6 +41,7 @@
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
height: 360px;
|
height: 360px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
position: relative;
|
||||||
&.on{
|
&.on{
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
@ -51,13 +52,17 @@
|
||||||
height: 360px;
|
height: 360px;
|
||||||
}
|
}
|
||||||
#filmout {
|
#filmout {
|
||||||
height: 360px;
|
position: absolute;
|
||||||
min-height: 360px;
|
background-repeat:no-repeat;
|
||||||
width: auto;
|
background-size: contain;
|
||||||
margin: 0 auto;
|
background-position: center;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
display: block;
|
display: block;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
& .on {
|
&.on {
|
||||||
opacity: 1.0;
|
opacity: 1.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,7 @@ class WebView {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
this.showing = true;
|
this.showing = true;
|
||||||
await delay_1.delay(100);
|
await delay_1.delay(200);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
async focus() {
|
async focus() {
|
||||||
|
@ -68,6 +68,7 @@ class WebView {
|
||||||
console.warn(`Cannot show focus screen because window does not exist`);
|
console.warn(`Cannot show focus screen because window does not exist`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
await delay_1.delay(500);
|
||||||
try {
|
try {
|
||||||
this.digitalWindow.webContents.send('focus', { focus: true });
|
this.digitalWindow.webContents.send('focus', { focus: true });
|
||||||
}
|
}
|
||||||
|
@ -80,7 +81,7 @@ class WebView {
|
||||||
console.warn(`Cannot show field guide because window does not exist`);
|
console.warn(`Cannot show field guide because window does not exist`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
//aspect ratio
|
await delay_1.delay(500);
|
||||||
try {
|
try {
|
||||||
this.digitalWindow.webContents.send('field', { field: true });
|
this.digitalWindow.webContents.send('field', { field: true });
|
||||||
}
|
}
|
||||||
|
@ -93,6 +94,7 @@ class WebView {
|
||||||
console.warn(`Cannot show meter screen because window does not exist`);
|
console.warn(`Cannot show meter screen because window does not exist`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
await delay_1.delay(500);
|
||||||
try {
|
try {
|
||||||
this.digitalWindow.webContents.send('meter', { meter: true });
|
this.digitalWindow.webContents.send('meter', { meter: true });
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -41,6 +41,8 @@ class FilmOut {
|
||||||
this.ipc.on('field', this.field.bind(this));
|
this.ipc.on('field', this.field.bind(this));
|
||||||
this.ipc.on('meter', this.meter.bind(this));
|
this.ipc.on('meter', this.meter.bind(this));
|
||||||
this.ipc.on('filmout_close', this.close.bind(this));
|
this.ipc.on('filmout_close', this.close.bind(this));
|
||||||
|
//preview
|
||||||
|
this.ipc.on('preview_frame', this.previewFrame.bind(this));
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -127,6 +129,21 @@ class FilmOut {
|
||||||
this.state.enabled = true;
|
this.state.enabled = true;
|
||||||
return await this.ui.send(this.id, { valid: true, state: JSON.stringify(this.state) });
|
return await this.ui.send(this.id, { valid: true, state: JSON.stringify(this.state) });
|
||||||
}
|
}
|
||||||
|
async previewFrame(evt, arg) {
|
||||||
|
const state = JSON.parse(JSON.stringify(this.state));
|
||||||
|
let path;
|
||||||
|
state.frame = arg.frame;
|
||||||
|
try {
|
||||||
|
path = await this.ffmpeg.frame(state, { color: [255, 255, 255] });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
this.ui.send('preview_frame', { path, frame: arg.frame });
|
||||||
|
}
|
||||||
|
async preview(evt, arg) {
|
||||||
|
}
|
||||||
async focus(evt, arg) {
|
async focus(evt, arg) {
|
||||||
try {
|
try {
|
||||||
await this.display.open();
|
await this.display.open();
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -35,6 +35,9 @@ class FilmOut {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.id = 'filmout';
|
this.id = 'filmout';
|
||||||
this.displays = [];
|
this.displays = [];
|
||||||
|
this.state = {
|
||||||
|
frame: 0
|
||||||
|
};
|
||||||
}
|
}
|
||||||
init() {
|
init() {
|
||||||
this.listen();
|
this.listen();
|
||||||
|
@ -42,6 +45,7 @@ class FilmOut {
|
||||||
listen() {
|
listen() {
|
||||||
ipcRenderer.on(this.id, this.onFilmout.bind(this));
|
ipcRenderer.on(this.id, this.onFilmout.bind(this));
|
||||||
ipcRenderer.on('system', this.onSystem.bind(this));
|
ipcRenderer.on('system', this.onSystem.bind(this));
|
||||||
|
ipcRenderer.on('preview_frame', this.onFrame.bind(this));
|
||||||
}
|
}
|
||||||
onSystem(evt, args) {
|
onSystem(evt, args) {
|
||||||
let option;
|
let option;
|
||||||
|
@ -58,6 +62,7 @@ class FilmOut {
|
||||||
if (args.displays.length > 1) {
|
if (args.displays.length > 1) {
|
||||||
$('#filmout_displays').on('change', this.onChange.bind(this));
|
$('#filmout_displays').on('change', this.onChange.bind(this));
|
||||||
}
|
}
|
||||||
|
$('#filmout_position').on('change', this.previewFrame.bind(this));
|
||||||
}
|
}
|
||||||
onChange() {
|
onChange() {
|
||||||
const val = $('#filmout_displays').val();
|
const val = $('#filmout_displays').val();
|
||||||
|
@ -87,8 +92,9 @@ class FilmOut {
|
||||||
elem.width(w);
|
elem.width(w);
|
||||||
}
|
}
|
||||||
elem.addClass('on');
|
elem.addClass('on');
|
||||||
$('#filmout_stats_monitor_size').text(`${display.width}x${display.height}`);
|
$('#filmout_stats_monitor_size').text(`${display.width} x ${display.height}`);
|
||||||
$('#filmout_stats_monitor_aspect').text(`${aspect}`);
|
$('#filmout_stats_monitor_aspect').text(`${aspect}`);
|
||||||
|
$('#filmout_stats_monitor_scale').text(`${parseFloat(display.scale).toFixed(1)} scale factor`);
|
||||||
console.dir(display);
|
console.dir(display);
|
||||||
}
|
}
|
||||||
selectFile() {
|
selectFile() {
|
||||||
|
@ -156,8 +162,8 @@ class FilmOut {
|
||||||
if (args.valid && args.valid === true) {
|
if (args.valid && args.valid === true) {
|
||||||
//success state
|
//success state
|
||||||
state = JSON.parse(args.state);
|
state = JSON.parse(args.state);
|
||||||
console.dir(args);
|
//console.dir(args)
|
||||||
console.dir(state);
|
//console.dir(state)
|
||||||
$('#digital').addClass('active');
|
$('#digital').addClass('active');
|
||||||
$('#projector_type_digital').prop('checked', 'checked');
|
$('#projector_type_digital').prop('checked', 'checked');
|
||||||
gui.notify('DEVICES', `Using video ${state.fileName}`);
|
gui.notify('DEVICES', `Using video ${state.fileName}`);
|
||||||
|
@ -169,17 +175,61 @@ class FilmOut {
|
||||||
if (light.disabled) {
|
if (light.disabled) {
|
||||||
light.enable();
|
light.enable();
|
||||||
}
|
}
|
||||||
|
this.state.frame = 0;
|
||||||
|
this.state.frames = state.frames;
|
||||||
|
this.state.width = state.info.width;
|
||||||
|
this.state.height = state.info.height;
|
||||||
|
this.state.name = state.fileName;
|
||||||
$('#seq_loop').val(`${state.frames - 1}`).trigger('change');
|
$('#seq_loop').val(`${state.frames - 1}`).trigger('change');
|
||||||
$('#filmout_stats_video_name').text(state.fileName);
|
$('#filmout_stats_video_name').text(state.fileName);
|
||||||
$('#filmout_stats_video_size').text(`${state.info.width}x${state.info.height}`);
|
$('#filmout_stats_video_size').text(`${state.info.width} x ${state.info.height}`);
|
||||||
$('#filmout_stats_video_frames').text(`${state.frames} frames`);
|
$('#filmout_stats_video_frames').text(`${state.frames} frames`);
|
||||||
gui.updateState();
|
gui.updateState();
|
||||||
|
this.previewFrame();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$('#projector_type_digital').prop('checked', 'checked');
|
$('#projector_type_digital').prop('checked', 'checked');
|
||||||
$('#digital').removeClass('active');
|
$('#digital').removeClass('active');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
previewFrame() {
|
||||||
|
const frameStr = $('#filmout_position').val();
|
||||||
|
const frame = parseInt(frameStr, 10);
|
||||||
|
this.state.frame = frame;
|
||||||
|
ipcRenderer.send('preview_frame', { frame });
|
||||||
|
}
|
||||||
|
onFrame(evt, args) {
|
||||||
|
const elem = $('#filmout');
|
||||||
|
elem[0].style.backgroundImage = `url('${args.path}')`;
|
||||||
|
elem.addClass('on');
|
||||||
|
}
|
||||||
|
advance() {
|
||||||
|
this.state.frame++;
|
||||||
|
if (this.state.frame >= this.state.frames) {
|
||||||
|
this.state.frame = 0;
|
||||||
|
}
|
||||||
|
$('#filmout_position').val(this.state.frame).trigger('change');
|
||||||
|
}
|
||||||
|
rewind() {
|
||||||
|
this.state.frame--;
|
||||||
|
if (this.state.frame < 0) {
|
||||||
|
this.state.frame = this.state.frames - 1;
|
||||||
|
}
|
||||||
|
$('#filmout_position').val(this.state.frame).trigger('change');
|
||||||
|
}
|
||||||
|
preview(evt, arg) {
|
||||||
|
}
|
||||||
|
focus() {
|
||||||
|
ipcRenderer.send('focus', { focus: true });
|
||||||
|
}
|
||||||
|
field() {
|
||||||
|
ipcRenderer.send('field', { field: true });
|
||||||
|
}
|
||||||
|
meter() {
|
||||||
|
ipcRenderer.send('meter', { meter: true });
|
||||||
|
}
|
||||||
|
close(evt, arg) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
filmout = new FilmOut();
|
filmout = new FilmOut();
|
||||||
module.exports = filmout;
|
module.exports = filmout;
|
||||||
|
|
|
@ -50,6 +50,9 @@ let filmout : FilmOut;
|
||||||
class FilmOut {
|
class FilmOut {
|
||||||
private id : string = 'filmout';
|
private id : string = 'filmout';
|
||||||
private displays : any[] = [];
|
private displays : any[] = [];
|
||||||
|
private state : any = {
|
||||||
|
frame : 0
|
||||||
|
}
|
||||||
constructor () {
|
constructor () {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -59,9 +62,11 @@ class FilmOut {
|
||||||
listen () {
|
listen () {
|
||||||
ipcRenderer.on(this.id, this.onFilmout.bind(this));
|
ipcRenderer.on(this.id, this.onFilmout.bind(this));
|
||||||
ipcRenderer.on('system', this.onSystem.bind(this));
|
ipcRenderer.on('system', this.onSystem.bind(this));
|
||||||
|
ipcRenderer.on('preview_frame', this.onFrame.bind(this));
|
||||||
}
|
}
|
||||||
onSystem (evt : Event, args : any) {
|
onSystem (evt : Event, args : any) {
|
||||||
let option : any;
|
let option : any;
|
||||||
|
|
||||||
for (let display of args.displays) {
|
for (let display of args.displays) {
|
||||||
this.displays.push(display);
|
this.displays.push(display);
|
||||||
option = $('<option>');
|
option = $('<option>');
|
||||||
|
@ -76,6 +81,7 @@ class FilmOut {
|
||||||
if (args.displays.length > 1) {
|
if (args.displays.length > 1) {
|
||||||
$('#filmout_displays').on('change', this.onChange.bind(this));
|
$('#filmout_displays').on('change', this.onChange.bind(this));
|
||||||
}
|
}
|
||||||
|
$('#filmout_position').on('change', this.previewFrame.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange () {
|
onChange () {
|
||||||
|
@ -107,8 +113,9 @@ class FilmOut {
|
||||||
}
|
}
|
||||||
|
|
||||||
elem.addClass('on');
|
elem.addClass('on');
|
||||||
$('#filmout_stats_monitor_size').text(`${display.width}x${display.height}`);
|
$('#filmout_stats_monitor_size').text(`${display.width} x ${display.height}`);
|
||||||
$('#filmout_stats_monitor_aspect').text(`${aspect}`);
|
$('#filmout_stats_monitor_aspect').text(`${aspect}`);
|
||||||
|
$('#filmout_stats_monitor_scale').text(`${parseFloat(display.scale).toFixed(1)} scale factor`);
|
||||||
console.dir(display);
|
console.dir(display);
|
||||||
}
|
}
|
||||||
selectFile () {
|
selectFile () {
|
||||||
|
@ -170,13 +177,15 @@ class FilmOut {
|
||||||
onFilmout (evt : any, args : any) {
|
onFilmout (evt : any, args : any) {
|
||||||
let state : any;
|
let state : any;
|
||||||
let color : number[] = [255, 255, 255];
|
let color : number[] = [255, 255, 255];
|
||||||
|
|
||||||
gui.spinner(false);
|
gui.spinner(false);
|
||||||
gui.overlay(false);
|
gui.overlay(false);
|
||||||
|
|
||||||
if (args.valid && args.valid === true) {
|
if (args.valid && args.valid === true) {
|
||||||
//success state
|
//success state
|
||||||
state = JSON.parse(args.state);
|
state = JSON.parse(args.state);
|
||||||
console.dir(args)
|
//console.dir(args)
|
||||||
console.dir(state)
|
//console.dir(state)
|
||||||
$('#digital').addClass('active');
|
$('#digital').addClass('active');
|
||||||
$('#projector_type_digital').prop('checked', 'checked');
|
$('#projector_type_digital').prop('checked', 'checked');
|
||||||
gui.notify('DEVICES', `Using video ${state.fileName}`);
|
gui.notify('DEVICES', `Using video ${state.fileName}`);
|
||||||
|
@ -192,17 +201,65 @@ class FilmOut {
|
||||||
light.enable();
|
light.enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.state.frame = 0;
|
||||||
|
this.state.frames = state.frames;
|
||||||
|
this.state.width = state.info.width;
|
||||||
|
this.state.height = state.info.height;
|
||||||
|
this.state.name = state.fileName;
|
||||||
|
|
||||||
$('#seq_loop').val(`${state.frames - 1}`).trigger('change');
|
$('#seq_loop').val(`${state.frames - 1}`).trigger('change');
|
||||||
$('#filmout_stats_video_name').text(state.fileName);
|
$('#filmout_stats_video_name').text(state.fileName);
|
||||||
$('#filmout_stats_video_size').text(`${state.info.width}x${state.info.height}`);
|
$('#filmout_stats_video_size').text(`${state.info.width} x ${state.info.height}`);
|
||||||
$('#filmout_stats_video_frames').text(`${state.frames} frames`);
|
$('#filmout_stats_video_frames').text(`${state.frames} frames`);
|
||||||
|
|
||||||
gui.updateState();
|
gui.updateState();
|
||||||
|
this.previewFrame();
|
||||||
} else {
|
} else {
|
||||||
$('#projector_type_digital').prop('checked', 'checked');
|
$('#projector_type_digital').prop('checked', 'checked');
|
||||||
$('#digital').removeClass('active');
|
$('#digital').removeClass('active');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
previewFrame () {
|
||||||
|
const frameStr : string = $('#filmout_position').val() as string;
|
||||||
|
const frame : number = parseInt(frameStr, 10);
|
||||||
|
this.state.frame = frame;
|
||||||
|
ipcRenderer.send('preview_frame', { frame });
|
||||||
|
}
|
||||||
|
onFrame (evt : any, args : any) {
|
||||||
|
const elem : any = $('#filmout');
|
||||||
|
elem[0].style.backgroundImage = `url('${args.path}')`;
|
||||||
|
elem.addClass('on');
|
||||||
|
}
|
||||||
|
advance () {
|
||||||
|
this.state.frame++;
|
||||||
|
if (this.state.frame >= this.state.frames) {
|
||||||
|
this.state.frame = 0;
|
||||||
|
}
|
||||||
|
$('#filmout_position').val(this.state.frame).trigger('change');
|
||||||
|
}
|
||||||
|
rewind () {
|
||||||
|
this.state.frame--;
|
||||||
|
if (this.state.frame < 0) {
|
||||||
|
this.state.frame = this.state.frames - 1;
|
||||||
|
}
|
||||||
|
$('#filmout_position').val(this.state.frame).trigger('change');
|
||||||
|
}
|
||||||
|
|
||||||
|
preview (evt : any, arg : any) {
|
||||||
|
|
||||||
|
}
|
||||||
|
focus () {
|
||||||
|
ipcRenderer.send('focus', { focus : true });
|
||||||
|
}
|
||||||
|
field () {
|
||||||
|
ipcRenderer.send('field', { field : true });
|
||||||
|
}
|
||||||
|
meter () {
|
||||||
|
ipcRenderer.send('meter', { meter : true });
|
||||||
|
}
|
||||||
|
close (evt : any, arg : any) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
filmout = new FilmOut();
|
filmout = new FilmOut();
|
||||||
|
|
|
@ -60,7 +60,7 @@ class WebView {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
this.showing = true;
|
this.showing = true;
|
||||||
await delay_1.delay(100);
|
await delay_1.delay(200);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
async focus() {
|
async focus() {
|
||||||
|
@ -68,6 +68,7 @@ class WebView {
|
||||||
console.warn(`Cannot show focus screen because window does not exist`);
|
console.warn(`Cannot show focus screen because window does not exist`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
await delay_1.delay(500);
|
||||||
try {
|
try {
|
||||||
this.digitalWindow.webContents.send('focus', { focus: true });
|
this.digitalWindow.webContents.send('focus', { focus: true });
|
||||||
}
|
}
|
||||||
|
@ -80,7 +81,7 @@ class WebView {
|
||||||
console.warn(`Cannot show field guide because window does not exist`);
|
console.warn(`Cannot show field guide because window does not exist`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
//aspect ratio
|
await delay_1.delay(500);
|
||||||
try {
|
try {
|
||||||
this.digitalWindow.webContents.send('field', { field: true });
|
this.digitalWindow.webContents.send('field', { field: true });
|
||||||
}
|
}
|
||||||
|
@ -93,6 +94,7 @@ class WebView {
|
||||||
console.warn(`Cannot show meter screen because window does not exist`);
|
console.warn(`Cannot show meter screen because window does not exist`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
await delay_1.delay(500);
|
||||||
try {
|
try {
|
||||||
this.digitalWindow.webContents.send('meter', { meter: true });
|
this.digitalWindow.webContents.send('meter', { meter: true });
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -41,6 +41,8 @@ class FilmOut {
|
||||||
this.ipc.on('field', this.field.bind(this));
|
this.ipc.on('field', this.field.bind(this));
|
||||||
this.ipc.on('meter', this.meter.bind(this));
|
this.ipc.on('meter', this.meter.bind(this));
|
||||||
this.ipc.on('filmout_close', this.close.bind(this));
|
this.ipc.on('filmout_close', this.close.bind(this));
|
||||||
|
//preview
|
||||||
|
this.ipc.on('preview_frame', this.previewFrame.bind(this));
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -127,6 +129,21 @@ class FilmOut {
|
||||||
this.state.enabled = true;
|
this.state.enabled = true;
|
||||||
return await this.ui.send(this.id, { valid: true, state: JSON.stringify(this.state) });
|
return await this.ui.send(this.id, { valid: true, state: JSON.stringify(this.state) });
|
||||||
}
|
}
|
||||||
|
async previewFrame(evt, arg) {
|
||||||
|
const state = JSON.parse(JSON.stringify(this.state));
|
||||||
|
let path;
|
||||||
|
state.frame = arg.frame;
|
||||||
|
try {
|
||||||
|
path = await this.ffmpeg.frame(state, { color: [255, 255, 255] });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
this.ui.send('preview_frame', { path, frame: arg.frame });
|
||||||
|
}
|
||||||
|
async preview(evt, arg) {
|
||||||
|
}
|
||||||
async focus(evt, arg) {
|
async focus(evt, arg) {
|
||||||
try {
|
try {
|
||||||
await this.display.open();
|
await this.display.open();
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -60,7 +60,7 @@ class WebView {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
this.showing = true;
|
this.showing = true;
|
||||||
await delay_1.delay(100);
|
await delay_1.delay(200);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
async focus() {
|
async focus() {
|
||||||
|
@ -68,6 +68,7 @@ class WebView {
|
||||||
console.warn(`Cannot show focus screen because window does not exist`);
|
console.warn(`Cannot show focus screen because window does not exist`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
await delay_1.delay(500);
|
||||||
try {
|
try {
|
||||||
this.digitalWindow.webContents.send('focus', { focus: true });
|
this.digitalWindow.webContents.send('focus', { focus: true });
|
||||||
}
|
}
|
||||||
|
@ -80,7 +81,7 @@ class WebView {
|
||||||
console.warn(`Cannot show field guide because window does not exist`);
|
console.warn(`Cannot show field guide because window does not exist`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
//aspect ratio
|
await delay_1.delay(500);
|
||||||
try {
|
try {
|
||||||
this.digitalWindow.webContents.send('field', { field: true });
|
this.digitalWindow.webContents.send('field', { field: true });
|
||||||
}
|
}
|
||||||
|
@ -93,6 +94,7 @@ class WebView {
|
||||||
console.warn(`Cannot show meter screen because window does not exist`);
|
console.warn(`Cannot show meter screen because window does not exist`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
await delay_1.delay(500);
|
||||||
try {
|
try {
|
||||||
this.digitalWindow.webContents.send('meter', { meter: true });
|
this.digitalWindow.webContents.send('meter', { meter: true });
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -41,6 +41,8 @@ class FilmOut {
|
||||||
this.ipc.on('field', this.field.bind(this));
|
this.ipc.on('field', this.field.bind(this));
|
||||||
this.ipc.on('meter', this.meter.bind(this));
|
this.ipc.on('meter', this.meter.bind(this));
|
||||||
this.ipc.on('filmout_close', this.close.bind(this));
|
this.ipc.on('filmout_close', this.close.bind(this));
|
||||||
|
//preview
|
||||||
|
this.ipc.on('preview_frame', this.previewFrame.bind(this));
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -127,6 +129,21 @@ class FilmOut {
|
||||||
this.state.enabled = true;
|
this.state.enabled = true;
|
||||||
return await this.ui.send(this.id, { valid: true, state: JSON.stringify(this.state) });
|
return await this.ui.send(this.id, { valid: true, state: JSON.stringify(this.state) });
|
||||||
}
|
}
|
||||||
|
async previewFrame(evt, arg) {
|
||||||
|
const state = JSON.parse(JSON.stringify(this.state));
|
||||||
|
let path;
|
||||||
|
state.frame = arg.frame;
|
||||||
|
try {
|
||||||
|
path = await this.ffmpeg.frame(state, { color: [255, 255, 255] });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
this.ui.send('preview_frame', { path, frame: arg.frame });
|
||||||
|
}
|
||||||
|
async preview(evt, arg) {
|
||||||
|
}
|
||||||
async focus(evt, arg) {
|
async focus(evt, arg) {
|
||||||
try {
|
try {
|
||||||
await this.display.open();
|
await this.display.open();
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,15 @@
|
||||||
|
ffprobe: Query the container
|
||||||
|
ffprobe -v error -select_streams v:0 -show_entries stream=nb_frames -of default=nokey=1:noprint_wrappers=1 input.mp4
|
||||||
|
This is a fast method.
|
||||||
|
Not all formats (such as Matroska) will report the number of frames resulting in the output of N/A. See the other methods listed below.
|
||||||
|
ffprobe: Count the number of frames
|
||||||
|
ffprobe -v error -count_frames -select_streams v:0 -show_entries stream=nb_read_frames -of default=nokey=1:noprint_wrappers=1 input.mkv
|
||||||
|
This is a slow method.
|
||||||
|
Add the -skip_frame nokey option to only count key frames.
|
||||||
|
ffmpeg: Count the number of frames
|
||||||
|
If you do not have ffprobe you can use ffmpeg instead:
|
||||||
|
|
||||||
|
ffmpeg -i input.mkv -map 0:v:0 -c copy -f null -
|
||||||
|
This is a somewhat fast method.
|
||||||
|
Refer to frame= near the end of the console output.
|
||||||
|
Add the -discard nokey input option (before -i) to only count key frames.
|
|
@ -65,7 +65,7 @@ class WebView {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
this.showing = true;
|
this.showing = true;
|
||||||
await delay(100);
|
await delay(200);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
async focus () {
|
async focus () {
|
||||||
|
@ -73,6 +73,7 @@ class WebView {
|
||||||
console.warn(`Cannot show focus screen because window does not exist`);
|
console.warn(`Cannot show focus screen because window does not exist`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
await delay(500);
|
||||||
try {
|
try {
|
||||||
this.digitalWindow.webContents.send('focus', { focus : true });
|
this.digitalWindow.webContents.send('focus', { focus : true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -84,7 +85,7 @@ class WebView {
|
||||||
console.warn(`Cannot show field guide because window does not exist`);
|
console.warn(`Cannot show field guide because window does not exist`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
//aspect ratio
|
await delay(500);
|
||||||
try {
|
try {
|
||||||
this.digitalWindow.webContents.send('field', { field : true });
|
this.digitalWindow.webContents.send('field', { field : true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -96,6 +97,7 @@ class WebView {
|
||||||
console.warn(`Cannot show meter screen because window does not exist`);
|
console.warn(`Cannot show meter screen because window does not exist`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
await delay(500);
|
||||||
try {
|
try {
|
||||||
this.digitalWindow.webContents.send('meter', { meter : true });
|
this.digitalWindow.webContents.send('meter', { meter : true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -49,6 +49,8 @@ class FilmOut {
|
||||||
this.ipc.on('field', this.field.bind(this));
|
this.ipc.on('field', this.field.bind(this));
|
||||||
this.ipc.on('meter', this.meter.bind(this));
|
this.ipc.on('meter', this.meter.bind(this));
|
||||||
this.ipc.on('filmout_close', this.close.bind(this));
|
this.ipc.on('filmout_close', this.close.bind(this));
|
||||||
|
//preview
|
||||||
|
this.ipc.on('preview_frame', this.previewFrame.bind(this));
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -134,6 +136,23 @@ class FilmOut {
|
||||||
this.state.enabled = true;
|
this.state.enabled = true;
|
||||||
return await this.ui.send(this.id, { valid : true, state : JSON.stringify(this.state) });
|
return await this.ui.send(this.id, { valid : true, state : JSON.stringify(this.state) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async previewFrame (evt : any, arg : any) {
|
||||||
|
const state : any = JSON.parse(JSON.stringify(this.state));
|
||||||
|
let path : string;
|
||||||
|
state.frame = arg.frame;
|
||||||
|
try {
|
||||||
|
path = await this.ffmpeg.frame(state, { color : [255, 255, 255] });
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
this.ui.send('preview_frame', { path, frame : arg.frame })
|
||||||
|
}
|
||||||
|
|
||||||
|
async preview (evt : any, arg : any) {
|
||||||
|
|
||||||
|
}
|
||||||
async focus (evt : any, arg : any) {
|
async focus (evt : any, arg : any) {
|
||||||
try {
|
try {
|
||||||
await this.display.open();
|
await this.display.open();
|
||||||
|
|
Loading…
Reference in New Issue