diff --git a/tools/ld-analyse/blacksnranalysisdialog.cpp b/tools/ld-analyse/blacksnranalysisdialog.cpp index 24cd6c47d..1a2e8035c 100644 --- a/tools/ld-analyse/blacksnranalysisdialog.cpp +++ b/tools/ld-analyse/blacksnranalysisdialog.cpp @@ -54,7 +54,7 @@ BlackSnrAnalysisDialog::BlackSnrAnalysisDialog(QWidget *parent) : numberOfFrames = 0; // Connect to scale changed slot - connect(((QObject*)plot->axisWidget(QwtPlot::xBottom)) , SIGNAL(scaleDivChanged () ), this, SLOT(scaleDivChangedSlot () )); + connect(plot->axisWidget(QwtPlot::xBottom), &QwtScaleWidget::scaleDivChanged, this, &BlackSnrAnalysisDialog::scaleDivChangedSlot); } BlackSnrAnalysisDialog::~BlackSnrAnalysisDialog() diff --git a/tools/ld-analyse/chromadecoderconfigdialog.ui b/tools/ld-analyse/chromadecoderconfigdialog.ui index b44bfd702..736e92b56 100644 --- a/tools/ld-analyse/chromadecoderconfigdialog.ui +++ b/tools/ld-analyse/chromadecoderconfigdialog.ui @@ -7,13 +7,16 @@ 0 0 460 - 514 + 637 Chroma Decoder Configuration + + QLayout::SetFixedSize + @@ -23,6 +26,18 @@ + + + 0 + 0 + + + + + 400 + 0 + + 200 @@ -92,6 +107,19 @@ + + + + Qt::Vertical + + + + 20 + 15 + + + + @@ -147,7 +175,7 @@ 20 - 40 + 15 @@ -167,7 +195,7 @@ 20 - 40 + 15 @@ -207,7 +235,7 @@ 20 - 40 + 15 @@ -234,7 +262,7 @@ 20 - 40 + 0 @@ -291,7 +319,7 @@ 20 - 40 + 15 @@ -339,7 +367,7 @@ 20 - 40 + 15 @@ -376,7 +404,7 @@ 20 - 40 + 0 @@ -385,25 +413,12 @@ - - - - Qt::Vertical - - - - 20 - 40 - - - - - + diff --git a/tools/ld-analyse/configuration.cpp b/tools/ld-analyse/configuration.cpp index 014f88c5f..fb4e46fff 100644 --- a/tools/ld-analyse/configuration.cpp +++ b/tools/ld-analyse/configuration.cpp @@ -78,6 +78,7 @@ void Configuration::writeConfiguration(void) configuration->setValue("blackSnrAnalysisDialogGeometry", settings.windows.blackSnrAnalysisDialogGeometry); configuration->setValue("whiteSnrAnalysisDialogGeometry", settings.windows.whiteSnrAnalysisDialogGeometry); configuration->setValue("closedCaptionDialogGeometry", settings.windows.closedCaptionDialogGeometry); + configuration->setValue("videoParametersDialogGeometry", settings.windows.videoParametersDialogGeometry); configuration->setValue("chromaDecoderConfigDialogGeometry", settings.windows.chromaDecoderConfigDialogGeometry); configuration->endGroup(); @@ -111,6 +112,7 @@ void Configuration::readConfiguration(void) settings.windows.blackSnrAnalysisDialogGeometry = configuration->value("blackSnrAnalysisDialogGeometry").toByteArray(); settings.windows.whiteSnrAnalysisDialogGeometry = configuration->value("whiteSnrAnalysisDialogGeometry").toByteArray(); settings.windows.closedCaptionDialogGeometry = configuration->value("closedCaptionDialogGeometry").toByteArray(); + settings.windows.videoParametersDialogGeometry = configuration->value("videoParametersDialogGeometry").toByteArray(); settings.windows.chromaDecoderConfigDialogGeometry = configuration->value("chromaDecoderConfigDialogGeometry").toByteArray(); configuration->endGroup(); } @@ -135,6 +137,7 @@ void Configuration::setDefault(void) settings.windows.blackSnrAnalysisDialogGeometry = QByteArray(); settings.windows.whiteSnrAnalysisDialogGeometry = QByteArray(); settings.windows.closedCaptionDialogGeometry = QByteArray(); + settings.windows.videoParametersDialogGeometry = QByteArray(); settings.windows.chromaDecoderConfigDialogGeometry = QByteArray(); // Write the configuration @@ -265,6 +268,16 @@ QByteArray Configuration::getClosedCaptionDialogGeometry(void) return settings.windows.closedCaptionDialogGeometry; } +void Configuration::setVideoParametersDialogGeometry(QByteArray videoParametersDialogGeometry) +{ + settings.windows.videoParametersDialogGeometry = videoParametersDialogGeometry; +} + +QByteArray Configuration::getVideoParametersDialogGeometry(void) +{ + return settings.windows.videoParametersDialogGeometry; +} + void Configuration::setChromaDecoderConfigDialogGeometry(QByteArray chromaDecoderConfigDialogGeometry) { settings.windows.chromaDecoderConfigDialogGeometry = chromaDecoderConfigDialogGeometry; diff --git a/tools/ld-analyse/configuration.h b/tools/ld-analyse/configuration.h index ca87e3f92..9bb0f1d67 100644 --- a/tools/ld-analyse/configuration.h +++ b/tools/ld-analyse/configuration.h @@ -70,6 +70,8 @@ class Configuration : public QObject QByteArray getWhiteSnrAnalysisDialogGeometry(void); void setClosedCaptionDialogGeometry(QByteArray closedCaptionDialogGeometry); QByteArray getClosedCaptionDialogGeometry(void); + void setVideoParametersDialogGeometry(QByteArray videoParametersConfigDialogGeometry); + QByteArray getVideoParametersDialogGeometry(void); void setChromaDecoderConfigDialogGeometry(QByteArray chromaDecoderConfigDialogGeometry); QByteArray getChromaDecoderConfigDialogGeometry(void); @@ -98,6 +100,7 @@ public slots: QByteArray blackSnrAnalysisDialogGeometry; QByteArray whiteSnrAnalysisDialogGeometry; QByteArray closedCaptionDialogGeometry; + QByteArray videoParametersDialogGeometry; QByteArray chromaDecoderConfigDialogGeometry; }; diff --git a/tools/ld-analyse/dropoutanalysisdialog.cpp b/tools/ld-analyse/dropoutanalysisdialog.cpp index f24a4c699..bea73042e 100644 --- a/tools/ld-analyse/dropoutanalysisdialog.cpp +++ b/tools/ld-analyse/dropoutanalysisdialog.cpp @@ -52,7 +52,7 @@ DropoutAnalysisDialog::DropoutAnalysisDialog(QWidget *parent) : numberOfFrames = 0; // Connect to scale changed slot - connect(((QObject*)plot->axisWidget(QwtPlot::xBottom)) , SIGNAL(scaleDivChanged() ), this, SLOT(scaleDivChangedSlot() )); + connect(plot->axisWidget(QwtPlot::xBottom), &QwtScaleWidget::scaleDivChanged, this, &DropoutAnalysisDialog::scaleDivChangedSlot); } DropoutAnalysisDialog::~DropoutAnalysisDialog() diff --git a/tools/ld-analyse/ld-analyse.pro b/tools/ld-analyse/ld-analyse.pro index 1b0a6410a..9c23cd54e 100644 --- a/tools/ld-analyse/ld-analyse.pro +++ b/tools/ld-analyse/ld-analyse.pro @@ -33,6 +33,7 @@ SOURCES += \ oscilloscopedialog.cpp \ vectorscopedialog.cpp \ aboutdialog.cpp \ + videoparametersdialog.cpp \ chromadecoderconfigdialog.cpp \ tbcsource.cpp \ vbidialog.cpp \ @@ -65,6 +66,7 @@ HEADERS += \ oscilloscopedialog.h \ vectorscopedialog.h \ aboutdialog.h \ + videoparametersdialog.h \ chromadecoderconfigdialog.h \ tbcsource.h \ vbidialog.h \ @@ -99,6 +101,7 @@ FORMS += \ oscilloscopedialog.ui \ vectorscopedialog.ui \ aboutdialog.ui \ + videoparametersdialog.ui \ chromadecoderconfigdialog.ui \ vbidialog.ui \ dropoutanalysisdialog.ui \ diff --git a/tools/ld-analyse/mainwindow.cpp b/tools/ld-analyse/mainwindow.cpp index 4ac2a7f65..8a2c777eb 100644 --- a/tools/ld-analyse/mainwindow.cpp +++ b/tools/ld-analyse/mainwindow.cpp @@ -4,6 +4,7 @@ ld-analyse - TBC output analysis Copyright (C) 2018-2022 Simon Inns + Copyright (C) 2022 Adam Sampson This file is part of ld-decode-tools. @@ -25,21 +26,6 @@ #include "mainwindow.h" #include "ui_mainwindow.h" -namespace { -namespace Aspect { - constexpr quint8 SAR_11 = 0; - constexpr char const* SAR_11_S = "SAR 1:1"; - constexpr quint8 DAR_43 = 1; - constexpr char const* DAR_43_S ="DAR 4:3"; - constexpr int DAR_43_ADJ_525 = -150; // NTSC - constexpr int DAR_43_ADJ_625 = -196; // PAL - constexpr quint8 DAR_169 = 2; - constexpr char const* DAR_169_S ="DAR 16:9"; - constexpr int DAR_169_ADJ_525 = 122; - constexpr int DAR_169_ADJ_625 = 103; -} -} - MainWindow::MainWindow(QString inputFilenameParam, QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) @@ -57,6 +43,7 @@ MainWindow::MainWindow(QString inputFilenameParam, QWidget *parent) : whiteSnrAnalysisDialog = new WhiteSnrAnalysisDialog(this); busyDialog = new BusyDialog(this); closedCaptionDialog = new ClosedCaptionsDialog(this); + videoParametersDialog = new VideoParametersDialog(this); chromaDecoderConfigDialog = new ChromaDecoderConfigDialog(this); // Add a status bar to show the state of the source video file @@ -68,24 +55,27 @@ MainWindow::MainWindow(QString inputFilenameParam, QWidget *parent) : // Set the initial frame number currentFrameNumber = 1; - // Set the initial aspect - aspectRatio = Aspect::SAR_11; - if (tbcSource.getIsWidescreen()) aspectRatio = Aspect::DAR_169; - // Connect to the scan line changed signal from the oscilloscope dialogue connect(oscilloscopeDialog, &OscilloscopeDialog::scopeCoordsChanged, this, &MainWindow::scopeCoordsChangedSignalHandler); lastScopeLine = 1; lastScopeDot = 1; + // Make shift-clicking on the oscilloscope change the black/white level + connect(oscilloscopeDialog, &OscilloscopeDialog::scopeLevelSelect, videoParametersDialog, &VideoParametersDialog::levelSelected); + // Connect to the changed signal from the vectorscope dialogue connect(vectorscopeDialog, &VectorscopeDialog::scopeChanged, this, &MainWindow::vectorscopeChangedSignalHandler); + // Connect to the video parameters changed signal + connect(videoParametersDialog, &VideoParametersDialog::videoParametersChanged, this, &MainWindow::videoParametersChangedSignalHandler); + // Connect to the chroma decoder configuration changed signal connect(chromaDecoderConfigDialog, &ChromaDecoderConfigDialog::chromaDecoderConfigChanged, this, &MainWindow::chromaDecoderConfigChangedSignalHandler); - // Connect to the TbcSource signals (busy loading and finished loading) - connect(&tbcSource, &TbcSource::busyLoading, this, &MainWindow::on_busyLoading); + // Connect to the TbcSource signals (busy and finished loading) + connect(&tbcSource, &TbcSource::busy, this, &MainWindow::on_busy); connect(&tbcSource, &TbcSource::finishedLoading, this, &MainWindow::on_finishedLoading); + connect(&tbcSource, &TbcSource::finishedSaving, this, &MainWindow::on_finishedSaving); // Load the window geometry and settings from the configuration restoreGeometry(configuration.getMainWindowGeometry()); @@ -98,6 +88,7 @@ MainWindow::MainWindow(QString inputFilenameParam, QWidget *parent) : blackSnrAnalysisDialog->restoreGeometry(configuration.getBlackSnrAnalysisDialogGeometry()); whiteSnrAnalysisDialog->restoreGeometry(configuration.getWhiteSnrAnalysisDialogGeometry()); closedCaptionDialog->restoreGeometry(configuration.getClosedCaptionDialogGeometry()); + videoParametersDialog->restoreGeometry(configuration.getVideoParametersDialogGeometry()); chromaDecoderConfigDialog->restoreGeometry(configuration.getChromaDecoderConfigDialogGeometry()); // Store the current button palette for the show dropouts button @@ -128,6 +119,7 @@ MainWindow::~MainWindow() configuration.setBlackSnrAnalysisDialogGeometry(blackSnrAnalysisDialog->saveGeometry()); configuration.setWhiteSnrAnalysisDialogGeometry(whiteSnrAnalysisDialog->saveGeometry()); configuration.setClosedCaptionDialogGeometry(closedCaptionDialog->saveGeometry()); + configuration.setVideoParametersDialogGeometry(videoParametersDialog->saveGeometry()); configuration.setChromaDecoderConfigDialogGeometry(chromaDecoderConfigDialog->saveGeometry()); configuration.writeConfiguration(); @@ -141,17 +133,54 @@ MainWindow::~MainWindow() // Update GUI methods for when TBC source files are loaded and unloaded ----------------------------------------------- +// Enable or disable all the GUI controls +void MainWindow::setGuiEnabled(bool enabled) +{ + // Enable the frame controls + ui->frameNumberSpinBox->setEnabled(enabled); + ui->previousPushButton->setEnabled(enabled); + ui->nextPushButton->setEnabled(enabled); + ui->startFramePushButton->setEnabled(enabled); + ui->endFramePushButton->setEnabled(enabled); + ui->frameHorizontalSlider->setEnabled(enabled); + ui->mediaControl_frame->setEnabled(enabled); + + // Enable menu options + ui->actionLine_scope->setEnabled(enabled); + ui->actionVectorscope->setEnabled(enabled); + ui->actionVBI->setEnabled(enabled); + ui->actionNTSC->setEnabled(enabled); + ui->actionVideo_metadata->setEnabled(enabled); + ui->actionVITS_Metrics->setEnabled(enabled); + ui->actionZoom_In->setEnabled(enabled); + ui->actionZoom_Out->setEnabled(enabled); + ui->actionZoom_1x->setEnabled(enabled); + ui->actionZoom_2x->setEnabled(enabled); + ui->actionZoom_3x->setEnabled(enabled); + ui->actionDropout_analysis->setEnabled(enabled); + ui->actionVisible_Dropout_analysis->setEnabled(enabled); + ui->actionSNR_analysis->setEnabled(enabled); // Black SNR + ui->actionWhite_SNR_analysis->setEnabled(enabled); + ui->actionSave_frame_as_PNG->setEnabled(enabled); + ui->actionClosed_Captions->setEnabled(enabled); + ui->actionVideo_parameters->setEnabled(enabled); + ui->actionChroma_decoder_configuration->setEnabled(enabled); + ui->actionReload_TBC->setEnabled(enabled); + + // "Save JSON" should be disabled by default + ui->actionSave_JSON->setEnabled(false); + + // Set zoom button states + ui->zoomInPushButton->setEnabled(enabled); + ui->zoomOutPushButton->setEnabled(enabled); + ui->originalSizePushButton->setEnabled(enabled); +} + // Method to update the GUI when a file is loaded void MainWindow::updateGuiLoaded() { - // Enable the frame controls - ui->frameNumberSpinBox->setEnabled(true); - ui->previousPushButton->setEnabled(true); - ui->nextPushButton->setEnabled(true); - ui->startFramePushButton->setEnabled(true); - ui->endFramePushButton->setEnabled(true); - ui->frameHorizontalSlider->setEnabled(true); - ui->mediaControl_frame->setEnabled(true); + // Enable the GUI controls + setGuiEnabled(true); // Update the current frame number currentFrameNumber = 1; @@ -171,39 +200,15 @@ void MainWindow::updateGuiLoaded() ui->nextPushButton->setAutoRepeatDelay(500); ui->nextPushButton->setAutoRepeatInterval(1); - // Enable menu options - ui->actionLine_scope->setEnabled(true); - ui->actionVectorscope->setEnabled(true); - ui->actionVBI->setEnabled(true); - ui->actionNTSC->setEnabled(true); - ui->actionVideo_metadata->setEnabled(true); - ui->actionVITS_Metrics->setEnabled(true); - ui->actionZoom_In->setEnabled(true); - ui->actionZoom_Out->setEnabled(true); - ui->actionZoom_1x->setEnabled(true); - ui->actionZoom_2x->setEnabled(true); - ui->actionZoom_3x->setEnabled(true); - ui->actionDropout_analysis->setEnabled(true); - ui->actionVisible_Dropout_analysis->setEnabled(true); - ui->actionSNR_analysis->setEnabled(true); // Black SNR - ui->actionWhite_SNR_analysis->setEnabled(true); - ui->actionSave_frame_as_PNG->setEnabled(true); - ui->actionClosed_Captions->setEnabled(true); - ui->actionChroma_decoder_configuration->setEnabled(true); - ui->actionReload_TBC->setEnabled(true); - // Set option button states ui->videoPushButton->setText(tr("Source")); ui->dropoutsPushButton->setText(tr("Dropouts Off")); - ui->aspectPushButton->setText(tr(Aspect::SAR_11_S)); + displayAspectRatio = false; + updateAspectPushButton(); updateSourcesPushButton(); ui->fieldOrderPushButton->setText(tr("Normal Field-order")); - // Set zoom button states - ui->zoomInPushButton->setEnabled(true); - ui->zoomOutPushButton->setEnabled(true); - ui->originalSizePushButton->setEnabled(true); - + // Zoom button options ui->zoomInPushButton->setAutoRepeat(true); ui->zoomInPushButton->setAutoRepeatDelay(500); ui->zoomInPushButton->setAutoRepeatInterval(100); @@ -219,35 +224,28 @@ void MainWindow::updateGuiLoaded() statusText += " sequential frames available"; sourceVideoStatus.setText(statusText); - // Reset the aspect setting - aspectRatio = Aspect::SAR_11; - if (tbcSource.getIsWidescreen()) { - aspectRatio = Aspect::DAR_169; - ui->aspectPushButton->setText(tr(Aspect::DAR_169_S)); - } - // Load and show the current frame showFrame(); + // Update the video parameters dialogue + videoParametersDialog->setVideoParameters(tbcSource.getVideoParameters()); + // Update the chroma decoder configuration dialogue chromaDecoderConfigDialog->setConfiguration(tbcSource.getSystem(), tbcSource.getPalConfiguration(), tbcSource.getNtscConfiguration(), tbcSource.getOutputConfiguration()); // Ensure the busy dialogue is hidden busyDialog->hide(); + + // Disable "Save JSON", now we've loaded the metadata into the GUI + ui->actionSave_JSON->setEnabled(false); } // Method to update the GUI when a file is unloaded void MainWindow::updateGuiUnloaded() { - // Disable the frame controls - ui->frameNumberSpinBox->setEnabled(false); - ui->previousPushButton->setEnabled(false); - ui->nextPushButton->setEnabled(false); - ui->startFramePushButton->setEnabled(false); - ui->endFramePushButton->setEnabled(false); - ui->frameHorizontalSlider->setEnabled(false); - ui->mediaControl_frame->setEnabled(false); + // Disable the GUI controls + setGuiEnabled(false); // Update the current frame number currentFrameNumber = 1; @@ -263,40 +261,14 @@ void MainWindow::updateGuiUnloaded() sourceVideoStatus.setText(tr("No source video file loaded")); fieldNumberStatus.setText(tr(" - Fields: ./.")); - // Disable menu options - ui->actionLine_scope->setEnabled(false); - ui->actionVectorscope->setEnabled(false); - ui->actionVBI->setEnabled(false); - ui->actionNTSC->setEnabled(false); - ui->actionVideo_metadata->setEnabled(false); - ui->actionVITS_Metrics->setEnabled(false); - ui->actionZoom_In->setEnabled(false); - ui->actionZoom_Out->setEnabled(false); - ui->actionZoom_1x->setEnabled(false); - ui->actionZoom_2x->setEnabled(false); - ui->actionZoom_3x->setEnabled(false); - ui->actionDropout_analysis->setEnabled(false); - ui->actionVisible_Dropout_analysis->setEnabled(false); - ui->actionSNR_analysis->setEnabled(false); // Black SNR - ui->actionWhite_SNR_analysis->setEnabled(false); - ui->actionSave_frame_as_PNG->setEnabled(false); - ui->actionClosed_Captions->setEnabled(false); - ui->actionChroma_decoder_configuration->setEnabled(false); - ui->actionReload_TBC->setEnabled(false); - // Set option button states ui->videoPushButton->setText(tr("Source")); ui->dropoutsPushButton->setText(tr("Dropouts Off")); - aspectRatio = Aspect::SAR_11; - ui->aspectPushButton->setText(tr(Aspect::SAR_11_S));; + displayAspectRatio = false; + updateAspectPushButton(); updateSourcesPushButton(); ui->fieldOrderPushButton->setText(tr("Normal Field-order")); - // Set zoom button states - ui->zoomInPushButton->setEnabled(false); - ui->zoomOutPushButton->setEnabled(false); - ui->originalSizePushButton->setEnabled(false); - // Hide the displayed frame hideFrame(); @@ -306,9 +278,22 @@ void MainWindow::updateGuiUnloaded() dropoutAnalysisDialog->hide(); // Hide configuration dialogues + videoParametersDialog->hide(); chromaDecoderConfigDialog->hide(); } +// Update the aspect ratio button +void MainWindow::updateAspectPushButton() +{ + if (!displayAspectRatio) { + ui->aspectPushButton->setText(tr("SAR 1:1")); + } else if (tbcSource.getIsWidescreen()) { + ui->aspectPushButton->setText(tr("DAR 16:9")); + } else { + ui->aspectPushButton->setText(tr("DAR 4:3")); + } +} + // Update the source selection button void MainWindow::updateSourcesPushButton() { @@ -363,6 +348,24 @@ void MainWindow::showFrame() ui->frameViewerLabel->clear(); ui->frameViewerLabel->setScaledContents(false); ui->frameViewerLabel->setAlignment(Qt::AlignCenter); + + // Update the frame views + updateFrame(); + + // Update the closed caption dialog + if (tbcSource.getSystem() == NTSC) { + closedCaptionDialog->addData(currentFrameNumber, tbcSource.getCcData0(), tbcSource.getCcData1()); + } + // QT Bug workaround for some macOS versions + #if defined(Q_OS_MACOS) + repaint(); + #endif +} + +// Redraw all the GUI elements that depend on the decoded frame +void MainWindow::updateFrame() +{ + // Update the main frame viewer updateFrameViewer(); // If the scope dialogues are open, update them @@ -372,15 +375,22 @@ void MainWindow::showFrame() if (vectorscopeDialog->isVisible()) { updateVectorscopeDialogue(); } +} - // Update the closed caption dialog - if (tbcSource.getSystem() == NTSC) { - closedCaptionDialog->addData(currentFrameNumber, tbcSource.getCcData0(), tbcSource.getCcData1()); +// Return the width adjustment for the current aspect mode +qint32 MainWindow::getAspectAdjustment() { + // Using source aspect ratio? No adjustment + if (!displayAspectRatio) return 0; + + if (tbcSource.getSystem() == PAL) { + // 625 lines + if (tbcSource.getIsWidescreen()) return 103; // 16:9 + else return -196; // 4:3 + } else { + // 525 lines + if (tbcSource.getIsWidescreen()) return 122; // 16:9 + else return -150; // 4:3 } - // QT Bug workaround for some macOS versions - #if defined(Q_OS_MACOS) - repaint(); - #endif } // Redraw the frame viewer (for example, when scaleFactor has been changed) @@ -404,17 +414,8 @@ void MainWindow::updateFrameViewer() QPixmap pixmap = QPixmap::fromImage(frameImage); - // Get the pixmap width and height (and apply scaling and aspect ratio adjustment if required) - qint32 adjustment = 0; - if (aspectRatio == Aspect::DAR_43) { - if (tbcSource.getSystem() == PAL) adjustment = Aspect::DAR_43_ADJ_625; // 625-line 4:3 - else adjustment = Aspect::DAR_43_ADJ_525; // 525-line 4:3 - } - - if (aspectRatio == Aspect::DAR_169) { - if (tbcSource.getSystem() == PAL) adjustment = Aspect::DAR_169_ADJ_625; // 625-line 16:9 - else adjustment = Aspect::DAR_169_ADJ_525; // 525-line 16:9 - } + // Get the aspect ratio adjustment if required + qint32 adjustment = getAspectAdjustment(); // Scale and apply the pixmap (only if it's valid) if (!pixmap.isNull()) { @@ -510,6 +511,14 @@ void MainWindow::on_actionReload_TBC_triggered() } } +// Start saving the modified JSON metadata +void MainWindow::on_actionSave_JSON_triggered() +{ + tbcSource.saveSourceJson(); + + // Saving continues in the background... +} + // Display the scan line oscilloscope view void MainWindow::on_actionLine_scope_triggered() { @@ -587,8 +596,10 @@ void MainWindow::on_actionSave_frame_as_PNG_triggered() if (!tbcSource.getChromaDecoder()) filenameSuggestion += tr("source_"); else filenameSuggestion += tr("chroma_"); - if (aspectRatio == Aspect::DAR_43) filenameSuggestion += tr("ar43_"); - if (aspectRatio == Aspect::DAR_169) filenameSuggestion += tr("ar169_"); + if (displayAspectRatio) { + if (tbcSource.getIsWidescreen()) filenameSuggestion += tr("ar169_"); + else filenameSuggestion += tr("ar43_"); + } filenameSuggestion += QString::number(currentFrameNumber) + tr(".png"); @@ -605,25 +616,9 @@ void MainWindow::on_actionSave_frame_as_PNG_triggered() // Generate QImage for the current frame QImage imageToSave = tbcSource.getFrameImage(); - // Change to 4:3 aspect ratio? - if (aspectRatio == Aspect::DAR_43) { - // Scale to 4:3 aspect - qint32 adjustment = 0; - if (tbcSource.getSystem() == PAL) adjustment = Aspect::DAR_43_ADJ_625; // 625-line 4:3 - else adjustment = Aspect::DAR_43_ADJ_525; // 525-line 4:3 - - imageToSave = imageToSave.scaled((imageToSave.size().width() + adjustment), - (imageToSave.size().height()), - Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - } - - // Change to 16:9 aspect ratio? - if (aspectRatio == Aspect::DAR_169) { - // Scale to 16:9 aspect - qint32 adjustment = 0; - if (tbcSource.getSystem() == PAL) adjustment = Aspect::DAR_169_ADJ_625; // 625-line 16:9 - else adjustment = Aspect::DAR_169_ADJ_525; // 525-line 16:9 - + // Get the aspect ratio adjustment, and scale the image if needed + qint32 adjustment = getAspectAdjustment(); + if (adjustment != 0) { imageToSave = imageToSave.scaled((imageToSave.size().width() + adjustment), (imageToSave.size().height()), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); @@ -635,7 +630,6 @@ void MainWindow::on_actionSave_frame_as_PNG_triggered() QMessageBox messageBox; messageBox.warning(this, "Warning","Could not save a PNG using the specified filename!"); - messageBox.setFixedSize(500, 200); } // Update the configuration for the PNG directory @@ -684,6 +678,12 @@ void MainWindow::on_actionClosed_Captions_triggered() closedCaptionDialog->show(); } +// Show video parameters dialogue +void MainWindow::on_actionVideo_parameters_triggered() +{ + videoParametersDialog->show(); +} + // Show chroma decoder configuration void MainWindow::on_actionChroma_decoder_configuration_triggered() { @@ -777,6 +777,18 @@ void MainWindow::on_videoPushButton_clicked() showFrame(); } +// Aspect ratio button clicked +void MainWindow::on_aspectPushButton_clicked() +{ + displayAspectRatio = !displayAspectRatio; + + // Update the button text + updateAspectPushButton(); + + // Update the frame viewer (the scopes don't depend on this) + updateFrameViewer(); +} + // Show/hide dropouts button clicked void MainWindow::on_dropoutsPushButton_clicked() { @@ -882,23 +894,6 @@ void MainWindow::on_mouseModePushButton_clicked() updateFrameViewer(); } -// Aspect ratio button clicked -void MainWindow::on_aspectPushButton_clicked() -{ - aspectRatio += 1; - - if (aspectRatio > 2) aspectRatio = 0; - - if (aspectRatio == Aspect::SAR_11) ui->aspectPushButton->setText(tr(Aspect::SAR_11_S)); - else if (aspectRatio == Aspect::DAR_43) ui->aspectPushButton->setText(tr(Aspect::DAR_43_S)); - else if (aspectRatio == Aspect::DAR_169) ui->aspectPushButton->setText(tr(Aspect::DAR_169_S)); - - qDebug() << "Aspect ratio: " << aspectRatio << " text " << ui->aspectPushButton->text(); - - // Show the current frame (why isn't this option passed?) - showFrame(); -} - // Miscellaneous handler methods -------------------------------------------------------------------------------------- // Handler called when another class changes the currenly selected scan line @@ -1018,6 +1013,22 @@ void MainWindow::mouseScanLineSelect(qint32 oX, qint32 oY) } } +// Handle parameters changed signal from the video parameters dialogue +void MainWindow::videoParametersChangedSignalHandler(const LdDecodeMetaData::VideoParameters &videoParameters) +{ + // Update the VideoParameters in the source + tbcSource.setVideoParameters(videoParameters); + + // Enable the "Save JSON" action, since the metadata has been modified + ui->actionSave_JSON->setEnabled(true); + + // Update the aspect button's label + updateAspectPushButton(); + + // Update the frame views + updateFrame(); +} + // Handle configuration changed signal from the chroma decoder configuration dialogue void MainWindow::chromaDecoderConfigChangedSignalHandler() { @@ -1026,24 +1037,16 @@ void MainWindow::chromaDecoderConfigChangedSignalHandler() chromaDecoderConfigDialog->getNtscConfiguration(), chromaDecoderConfigDialog->getOutputConfiguration()); - // Update the frame viewer - updateFrameViewer(); - - // If the scope windows are open, update them - if (oscilloscopeDialog->isVisible()) { - updateOscilloscopeDialogue(); - } - if (vectorscopeDialog->isVisible()) { - updateVectorscopeDialogue(); - } + // Update the frame views + updateFrame(); } // TbcSource class signal handlers ------------------------------------------------------------------------------------ -// Signal handler for busyLoading signal from TbcSource class -void MainWindow::on_busyLoading(QString infoMessage) +// Signal handler for busy signal from TbcSource class +void MainWindow::on_busy(QString infoMessage) { - qDebug() << "MainWindow::on_busyLoading(): Got signal with message" << infoMessage; + qDebug() << "MainWindow::on_busy(): Got signal with message" << infoMessage; // Set the busy message and centre the dialog in the parent window busyDialog->setMessage(infoMessage); busyDialog->move(this->geometry().center() - busyDialog->rect().center()); @@ -1058,7 +1061,7 @@ void MainWindow::on_busyLoading(QString infoMessage) } // Signal handler for finishedLoading signal from TbcSource class -void MainWindow::on_finishedLoading() +void MainWindow::on_finishedLoading(bool success) { qDebug() << "MainWindow::on_finishedLoading(): Called"; @@ -1066,7 +1069,7 @@ void MainWindow::on_finishedLoading() busyDialog->hide(); // Ensure source loaded ok - if (tbcSource.getIsSourceLoaded()) { + if (success) { // Generate the graph data dropoutAnalysisDialog->startUpdate(tbcSource.getNumberOfFrames()); visibleDropoutAnalysisDialog->startUpdate(tbcSource.getNumberOfFrames()); @@ -1107,14 +1110,30 @@ void MainWindow::on_finishedLoading() // Show the error to the user QMessageBox messageBox; - messageBox.warning(this, "Error", tbcSource.getLastLoadError()); - messageBox.setFixedSize(500, 200); + messageBox.warning(this, "Error", tbcSource.getLastIOError()); } // Enable the main window this->setEnabled(true); } +// Signal handler for finishedSaving signal from TbcSource class +void MainWindow::on_finishedSaving(bool success) +{ + qDebug() << "MainWindow::on_finishedSaving(): Called"; + // Hide the busy dialogue + busyDialog->hide(); + if (success) { + // Disable the "Save JSON" action until the metadata is modified again + ui->actionSave_JSON->setEnabled(false); + } else { + // Show the error to the user + QMessageBox messageBox; + messageBox.warning(this, "Error", tbcSource.getLastIOError()); + } + // Enable the main window + this->setEnabled(true); +} diff --git a/tools/ld-analyse/mainwindow.h b/tools/ld-analyse/mainwindow.h index 80a9c2f13..0c54e688c 100644 --- a/tools/ld-analyse/mainwindow.h +++ b/tools/ld-analyse/mainwindow.h @@ -44,6 +44,7 @@ #include "whitesnranalysisdialog.h" #include "busydialog.h" #include "closedcaptionsdialog.h" +#include "videoparametersdialog.h" #include "chromadecoderconfigdialog.h" #include "configuration.h" #include "tbcsource.h" @@ -65,6 +66,7 @@ private slots: void on_actionExit_triggered(); void on_actionOpen_TBC_file_triggered(); void on_actionReload_TBC_triggered(); + void on_actionSave_JSON_triggered(); void on_actionLine_scope_triggered(); void on_actionVectorscope_triggered(); void on_actionAbout_ld_analyse_triggered(); @@ -80,6 +82,7 @@ private slots: void on_actionZoom_2x_triggered(); void on_actionZoom_3x_triggered(); void on_actionClosed_Captions_triggered(); + void on_actionVideo_parameters_triggered(); void on_actionChroma_decoder_configuration_triggered(); // Media control frame handlers @@ -90,6 +93,7 @@ private slots: void on_frameNumberSpinBox_editingFinished(); void on_frameHorizontalSlider_valueChanged(int value); void on_videoPushButton_clicked(); + void on_aspectPushButton_clicked(); void on_dropoutsPushButton_clicked(); void on_sourcesPushButton_clicked(); void on_fieldOrderPushButton_clicked(); @@ -97,18 +101,19 @@ private slots: void on_zoomOutPushButton_clicked(); void on_originalSizePushButton_clicked(); void on_mouseModePushButton_clicked(); - void on_aspectPushButton_clicked(); // Miscellaneous handlers void scopeCoordsChangedSignalHandler(qint32 xCoord, qint32 yCoord); void vectorscopeChangedSignalHandler(); void mousePressEvent(QMouseEvent *event); void mouseMoveEvent(QMouseEvent *event); + void videoParametersChangedSignalHandler(const LdDecodeMetaData::VideoParameters &videoParameters); void chromaDecoderConfigChangedSignalHandler(); // Tbc Source signal handlers - void on_busyLoading(QString infoMessage); - void on_finishedLoading(); + void on_busy(QString infoMessage); + void on_finishedLoading(bool success); + void on_finishedSaving(bool success); private: Ui::MainWindow *ui; @@ -124,6 +129,7 @@ private slots: WhiteSnrAnalysisDialog* whiteSnrAnalysisDialog; BusyDialog* busyDialog; ClosedCaptionsDialog *closedCaptionDialog; + VideoParametersDialog *videoParametersDialog; ChromaDecoderConfigDialog *chromaDecoderConfigDialog; // Class globals @@ -131,7 +137,7 @@ private slots: QLabel sourceVideoStatus; QLabel fieldNumberStatus; TbcSource tbcSource; - quint8 aspectRatio; + bool displayAspectRatio; qint32 lastScopeLine; qint32 lastScopeDot; qint32 currentFrameNumber; @@ -140,12 +146,16 @@ private slots: QString lastFilename; // Update GUI methods + void setGuiEnabled(bool enabled); void updateGuiLoaded(); void updateGuiUnloaded(); + void updateAspectPushButton(); void updateSourcesPushButton(); // Frame display methods void showFrame(); + void updateFrame(); + qint32 getAspectAdjustment(); void updateFrameViewer(); void hideFrame(); diff --git a/tools/ld-analyse/mainwindow.ui b/tools/ld-analyse/mainwindow.ui index cc640b7f0..28bb7b575 100755 --- a/tools/ld-analyse/mainwindow.ui +++ b/tools/ld-analyse/mainwindow.ui @@ -459,6 +459,7 @@ + @@ -477,6 +478,7 @@ + @@ -639,10 +641,21 @@ Closed Captions... + + + Video parameters... + + + Ctrl+P + + Chroma decoder configuration... + + Ctrl+K + @@ -652,6 +665,11 @@ Ctrl+A + + + Save JSON + + White SNR analysis... diff --git a/tools/ld-analyse/oscilloscopedialog.cpp b/tools/ld-analyse/oscilloscopedialog.cpp index 09e866e7e..b1a91d73b 100644 --- a/tools/ld-analyse/oscilloscopedialog.cpp +++ b/tools/ld-analyse/oscilloscopedialog.cpp @@ -306,7 +306,12 @@ void OscilloscopeDialog::mousePressEvent(QMouseEvent *event) oX + 1 <= ui->scopeLabel->width() && oY <= ui->scopeLabel->height()) { - mousePictureDotSelect(oX); + // Is shift held down? + if (event->modifiers().testFlag(Qt::ShiftModifier)) { + mouseLevelSelect(oY); + } else { + mousePictureDotSelect(oX); + } event->accept(); } } @@ -314,23 +319,20 @@ void OscilloscopeDialog::mousePressEvent(QMouseEvent *event) // Mouse drag event handler void OscilloscopeDialog::mouseMoveEvent(QMouseEvent *event) { - // Get the mouse position relative to our scene - QPoint origin = ui->scopeLabel->mapFromGlobal(QCursor::pos()); - - // Check that the mouse click is within bounds of the current picture - qint32 oX = origin.x(); - qint32 oY = origin.y(); + // Handle this the same way as a click + mousePressEvent(event); +} - if (oX + 1 >= 0 && - oY >= 0 && - oX + 1 <= ui->scopeLabel->width() && - oY <= ui->scopeLabel->height()) { +// Handle a click on the scope with shift held down, to select a level +void OscilloscopeDialog::mouseLevelSelect(qint32 oY) +{ + qreal unscaledYR = (65536.0 / static_cast(ui->scopeLabel->height())) * static_cast(oY); - mousePictureDotSelect(oX); - event->accept(); - } + qint32 level = qBound(0, 65535 - static_cast(unscaledYR), 65535); + emit scopeLevelSelect(level); } +// Handle a click on the scope without shift held down, to select a sample void OscilloscopeDialog::mousePictureDotSelect(qint32 oX) { qreal unscaledXR = (static_cast(scopeWidth) / diff --git a/tools/ld-analyse/oscilloscopedialog.h b/tools/ld-analyse/oscilloscopedialog.h index ec63c5c43..1ab7df2b8 100644 --- a/tools/ld-analyse/oscilloscopedialog.h +++ b/tools/ld-analyse/oscilloscopedialog.h @@ -51,6 +51,7 @@ class OscilloscopeDialog : public QDialog signals: void scopeCoordsChanged(qint32 xCoord, qint32 yCoord); + void scopeLevelSelect(qint32 value); private slots: void on_previousPushButton_clicked(); @@ -74,6 +75,7 @@ private slots: qint32 lastScopeY; QImage getFieldLineTraceImage(TbcSource::ScanLineData scanLineData, qint32 pictureDot); + void mouseLevelSelect(qint32 oY); void mousePictureDotSelect(qint32 oX); }; diff --git a/tools/ld-analyse/tbcsource.cpp b/tools/ld-analyse/tbcsource.cpp index a3bd58919..d289d65ac 100644 --- a/tools/ld-analyse/tbcsource.cpp +++ b/tools/ld-analyse/tbcsource.cpp @@ -49,11 +49,12 @@ void TbcSource::loadSource(QString sourceFilename) // Set the current file name QFileInfo inFileInfo(sourceFilename); currentSourceFilename = inFileInfo.fileName(); - qDebug() << "TbcSource::startBackgroundLoad(): Opening TBC source file:" << currentSourceFilename; + qDebug() << "TbcSource::loadSource(): Opening TBC source file:" << currentSourceFilename; // Set up and fire-off background loading thread qDebug() << "TbcSource::loadSource(): Setting up background loader thread"; - connect(&watcher, SIGNAL(finished()), this, SLOT(finishBackgroundLoad())); + disconnect(&watcher, &QFutureWatcher::finished, nullptr, nullptr); + connect(&watcher, &QFutureWatcher::finished, this, &TbcSource::finishBackgroundLoad); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) future = QtConcurrent::run(this, &TbcSource::startBackgroundLoad, sourceFilename); #else @@ -70,6 +71,21 @@ void TbcSource::unloadSource() resetState(); } +// Start saving the JSON file for the current source +void TbcSource::saveSourceJson() +{ + // Start a background saving thread + qDebug() << "TbcSource::saveSourceJson(): Starting background save thread"; + disconnect(&watcher, &QFutureWatcher::finished, nullptr, nullptr); + connect(&watcher, &QFutureWatcher::finished, this, &TbcSource::finishBackgroundSave); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + future = QtConcurrent::run(this, &TbcSource::startBackgroundSave, currentJsonFilename); +#else + future = QtConcurrent::run(&TbcSource::startBackgroundSave, this, currentJsonFilename); +#endif + watcher.setFuture(future); +} + // Method returns true is a TBC source is loaded bool TbcSource::getIsSourceLoaded() { @@ -84,12 +100,10 @@ QString TbcSource::getCurrentSourceFilename() return currentSourceFilename; } -// If loadSource failed, return a description of the last error -QString TbcSource::getLastLoadError() +// Return a description of the last IO error +QString TbcSource::getLastIOError() { - if (sourceReady) return QString(); - - return lastLoadError; + return lastIOError; } // Method to set the highlight dropouts mode (true = dropouts highlighted) @@ -343,6 +357,18 @@ const LdDecodeMetaData::VideoParameters &TbcSource::getVideoParameters() return ldDecodeMetaData.getVideoParameters(); } +// Update the VideoParameters for the current source +void TbcSource::setVideoParameters(const LdDecodeMetaData::VideoParameters &videoParameters) +{ + invalidateFrameCache(); + + // Update the metadata + ldDecodeMetaData.setVideoParameters(videoParameters); + + // Reconfigure the chroma decoder + configureChromaDecoder(); +} + // Get scan line data from the frame TbcSource::ScanLineData TbcSource::getScanLineData(qint32 scanLine) { @@ -458,17 +484,7 @@ void TbcSource::setChromaConfiguration(const PalColour::Configuration &_palConfi ntscConfiguration = _ntscConfiguration; outputConfiguration = _outputConfiguration; - // Configure the chroma decoder - LdDecodeMetaData::VideoParameters videoParameters = ldDecodeMetaData.getVideoParameters(); - if (videoParameters.system == PAL || videoParameters.system == PAL_M) { - palColour.updateConfiguration(videoParameters, palConfiguration); - } else { - ntscColour.updateConfiguration(videoParameters, ntscConfiguration); - } - - // Configure the OutputWriter. - // Because we have padding disabled, this won't change the VideoParameters. - outputWriter.updateConfiguration(videoParameters, outputConfiguration); + configureChromaDecoder(); } const PalColour::Configuration &TbcSource::getPalConfiguration() @@ -560,6 +576,22 @@ void TbcSource::invalidateFrameCache() frameCacheValid = false; } +// Configure the chroma decoder for its settings and the VideoParameters +void TbcSource::configureChromaDecoder() +{ + // Configure the chroma decoder + LdDecodeMetaData::VideoParameters videoParameters = ldDecodeMetaData.getVideoParameters(); + if (videoParameters.system == PAL || videoParameters.system == PAL_M) { + palColour.updateConfiguration(videoParameters, palConfiguration); + } else { + ntscColour.updateConfiguration(videoParameters, ntscConfiguration); + } + + // Configure the OutputWriter. + // Because we have padding disabled, this won't change the VideoParameters. + outputWriter.updateConfiguration(videoParameters, outputConfiguration); +} + // Ensure the SourceFields for the current frame are loaded void TbcSource::loadInputFields() { @@ -853,11 +885,11 @@ void TbcSource::generateData() } } -void TbcSource::startBackgroundLoad(QString sourceFilename) +bool TbcSource::startBackgroundLoad(QString sourceFilename) { // Open the TBC metadata file qDebug() << "TbcSource::startBackgroundLoad(): Processing JSON metadata..."; - emit busyLoading("Processing JSON metadata..."); + emit busy("Processing JSON metadata..."); QString jsonFileName = sourceFilename + ".json"; @@ -884,8 +916,8 @@ void TbcSource::startBackgroundLoad(QString sourceFilename) currentSourceFilename.clear(); // Show an error to the user and give up - lastLoadError = "Could not load source TBC JSON metadata file"; - return; + lastIOError = "Could not load source TBC JSON metadata file"; + return false; } // Get the video parameters from the metadata @@ -893,15 +925,15 @@ void TbcSource::startBackgroundLoad(QString sourceFilename) // Open the new source video qDebug() << "TbcSource::startBackgroundLoad(): Loading TBC file..."; - emit busyLoading("Loading TBC file..."); + emit busy("Loading TBC file..."); if (!sourceVideo.open(sourceFilename, videoParameters.fieldWidth * videoParameters.fieldHeight)) { // Open failed qWarning() << "Open TBC file failed for filename" << sourceFilename; currentSourceFilename.clear(); // Show an error to the user and give up - lastLoadError = "Could not open source TBC data file"; - return; + lastIOError = "Could not open source TBC data file"; + return false; } // Is there a separate _chroma.tbc file? @@ -911,7 +943,7 @@ void TbcSource::startBackgroundLoad(QString sourceFilename) if (QFileInfo::exists(chromaSourceFilename)) { // Yes! Open it. qDebug() << "TbcSource::startBackgroundLoad(): Loading chroma TBC file..."; - emit busyLoading("Loading chroma TBC file..."); + emit busy("Loading chroma TBC file..."); if (!chromaSourceVideo.open(chromaSourceFilename, videoParameters.fieldWidth * videoParameters.fieldHeight)) { // Open failed qWarning() << "Open chroma TBC file failed for filename" << chromaSourceFilename; @@ -919,8 +951,8 @@ void TbcSource::startBackgroundLoad(QString sourceFilename) sourceVideo.close(); // Show an error to the user and give up - lastLoadError = "Could not open source chroma TBC data file"; - return; + lastIOError = "Could not open source chroma TBC data file"; + return false; } sourceMode = isChromaTbc ? CHROMA_SOURCE : BOTH_SOURCES; @@ -929,6 +961,7 @@ void TbcSource::startBackgroundLoad(QString sourceFilename) // Both the video and metadata files are now open sourceReady = true; currentSourceFilename = sourceFilename; + currentJsonFilename = jsonFileName; // Configure the chroma decoder if (videoParameters.system == PAL || videoParameters.system == PAL_M) { @@ -942,12 +975,66 @@ void TbcSource::startBackgroundLoad(QString sourceFilename) } // Analyse the metadata - emit busyLoading("Generating graph data and chapter map..."); + emit busy("Generating graph data and chapter map..."); generateData(); + + return true; } void TbcSource::finishBackgroundLoad() { // Send a finished loading message to the main window - emit finishedLoading(); + emit finishedLoading(future.result()); +} + +bool TbcSource::startBackgroundSave(QString jsonFilename) +{ + qDebug() << "TbcSource::startBackgroundSave(): Saving to" << jsonFilename; + emit busy("Saving JSON metadata..."); + + // The general idea here is that decoding takes a long time -- so we want + // to be careful not to destroy the user's only copy of their JSON file if + // something goes wrong! + + // Write the metadata out to a new temporary file + QString newJsonFilename = jsonFilename + ".new"; + if (!ldDecodeMetaData.write(newJsonFilename)) { + // Writing failed + lastIOError = "Could not write to new JSON file"; + return false; + } + + // If there isn't already a .bup backup file, rename the existing file to that name + // (matching the behaviour of ld-process-vbi) + QString backupFilename = jsonFilename + ".bup"; + if (!QFile::exists(backupFilename)) { + if (!QFile::rename(jsonFilename, jsonFilename + ".bup")) { + // Renaming failed + lastIOError = "Could not rename existing JSON file to backup"; + return false; + } + } else { + // There is a backup, so it's safe to remove the existing file + if (!QFile::remove(jsonFilename)) { + // Deleting failed + lastIOError = "Could not remove existing JSON file"; + return false; + } + } + + // Rename the new file to the target name + if (!QFile::rename(newJsonFilename, jsonFilename)) { + // Renaming failed + lastIOError = "Could not rename new JSON file to target name"; + return false; + } + + qDebug() << "TbcSource::startBackgroundSave(): Save complete"; + return true; +} + +void TbcSource::finishBackgroundSave() +{ + // Send a finished saving message to the main window + emit finishedSaving(future.result()); } diff --git a/tools/ld-analyse/tbcsource.h b/tools/ld-analyse/tbcsource.h index 916a3120f..cf1dd723c 100644 --- a/tools/ld-analyse/tbcsource.h +++ b/tools/ld-analyse/tbcsource.h @@ -69,8 +69,9 @@ class TbcSource : public QObject void loadSource(QString inputFileName); void unloadSource(); bool getIsSourceLoaded(); + void saveSourceJson(); QString getCurrentSourceFilename(); - QString getLastLoadError(); + QString getLastIOError(); void setHighlightDropouts(bool _state); void setChromaDecoder(bool _state); @@ -109,8 +110,11 @@ class TbcSource : public QObject qint32 getGraphDataSize(); bool getIsDropoutPresent(); - const ComponentFrame &getComponentFrame(); + const LdDecodeMetaData::VideoParameters &getVideoParameters(); + void setVideoParameters(const LdDecodeMetaData::VideoParameters &videoParameters); + + const ComponentFrame &getComponentFrame(); ScanLineData getScanLineData(qint32 scanLine); qint32 getFirstFieldNumber(); @@ -129,11 +133,13 @@ class TbcSource : public QObject qint32 startOfChapter(qint32 currentFrameNumber); signals: - void busyLoading(QString information); - void finishedLoading(); + void busy(QString information); + void finishedLoading(bool success); + void finishedSaving(bool success); private slots: void finishBackgroundLoad(); + void finishBackgroundSave(); private: bool sourceReady; @@ -155,7 +161,8 @@ private slots: SourceMode sourceMode; LdDecodeMetaData ldDecodeMetaData; QString currentSourceFilename; - QString lastLoadError; + QString currentJsonFilename; + QString lastIOError; // Chroma decoder objects PalColour palColour; @@ -166,8 +173,8 @@ private slots: VbiDecoder vbiDecoder; // Background loader globals - QFutureWatcher watcher; - QFuture future; + QFutureWatcher watcher; + QFuture future; // Metadata for the loaded frame qint32 firstFieldNumber, secondFieldNumber; @@ -198,11 +205,13 @@ private slots: void resetState(); void invalidateFrameCache(); + void configureChromaDecoder(); void loadInputFields(); void decodeFrame(); QImage generateQImage(); void generateData(); - void startBackgroundLoad(QString sourceFilename); + bool startBackgroundLoad(QString sourceFilename); + bool startBackgroundSave(QString jsonFilename); }; #endif // TBCSOURCE_H diff --git a/tools/ld-analyse/vbidialog.ui b/tools/ld-analyse/vbidialog.ui index 4f9573d73..514e83374 100644 --- a/tools/ld-analyse/vbidialog.ui +++ b/tools/ld-analyse/vbidialog.ui @@ -25,741 +25,538 @@ Decoded VBI - - - - 10 - 10 - 141 - 21 - - - - Disc type: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 10 - 110 - 141 - 21 - - - - Lead in: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 10 - 130 - 141 - 21 - - - - Lead out: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 10 - 150 - 141 - 21 - - - - User code: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 10 - 30 - 141 - 21 - - - - Picture number: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 10 - 170 - 141 - 21 - - - - Picture stop code: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 10 - 50 - 141 - 21 - - - - CLV time code: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 160 - 10 - 161 - 21 - - - - TextLabel - - - - - - 160 - 30 - 161 - 21 - - - - TextLabel - - - - - - 160 - 50 - 161 - 21 - - - - TextLabel - - - - - - 160 - 110 - 161 - 21 - - - - TextLabel - - - - - - 160 - 130 - 161 - 21 - - - - TextLabel - - - - - - 160 - 150 - 161 - 21 - - - - TextLabel - - - - - - 160 - 170 - 161 - 21 - - - - TextLabel - - - - - - 10 - 70 - 141 - 21 - - - - Chapter number: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 160 - 70 - 161 - 21 - - - - TextLabel - - - - - - 10 - 230 - 341 - 231 - - - - 1 - - - - Original - - - - - 160 - 170 - 171 - 21 - - - - TextLabel - - - - - - 160 - 10 - 171 - 21 - - - - TextLabel - - - - - - 160 - 90 - 171 - 21 - - - - TextLabel - - - - - - 160 - 30 - 171 - 21 - - - - TextLabel - - - - - - 10 - 110 - 141 - 21 - - - - FM/FM Multiplex: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 10 - 70 - 141 - 21 - - - - Teletext: + + + QLayout::SetFixedSize + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + TextLabel + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + CLV time code: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Disc type: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + TextLabel + + + + + + + Picture stop code: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + Lead in: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Picture number: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Chapter number: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + User code: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Lead out: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 20 + 15 + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 20 + 15 + + + + + + + + + 0 + 0 + + + + + 400 + 0 + + + + Programme Status: - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 10 - 50 - 141 - 21 - - - - Disc side: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 160 - 70 - 171 - 21 - - - - TextLabel - - - - - - 10 - 30 - 141 - 21 - - - - Disc size: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 160 - 150 - 171 - 21 - - - - TextLabel - - - - - - 10 - 10 - 141 - 21 - - - - CX: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 10 - 90 - 141 - 21 - - - - Programme dump: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 160 - 110 - 171 - 21 - - - - TextLabel - - - - - - 10 - 130 - 141 - 21 - - - - Digital: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 160 - 130 - 171 - 21 - - - - TextLabel - - - - - - 160 - 50 - 171 - 21 - - - - TextLabel - - - - - - 10 - 150 - 141 - 21 - - - - Sound mode: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 10 - 170 - 141 - 21 - - - - Parity correct: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - Amendment 2 - - - - - 10 - 130 - 141 - 21 - - - - Sound mode: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 160 - 90 - 171 - 21 - - - - TextLabel - - - - - - 160 - 130 - 171 - 21 - - - - TextLabel - - - - - - 160 - 110 - 171 - 21 - - - - TextLabel - - - - - - 160 - 10 - 171 - 21 - - - - TextLabel - - - - - - 160 - 30 - 171 - 21 - - - - TextLabel - - - - - - 10 - 90 - 141 - 21 - - - - Copy allowed: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 10 - 10 - 141 - 21 - - - - CX: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 160 - 50 - 171 - 21 - - - - TextLabel - - - - - - 10 - 50 - 141 - 21 - - - - Disc side: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 160 - 70 - 171 - 21 - - - - TextLabel - - - - - - 10 - 70 - 141 - 21 - - - - Teletext: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 10 - 30 - 141 - 21 - - - - Disc size: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 10 - 110 - 141 - 21 - - - - Standard Video: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 10 - 210 - 221 - 21 - - - - Programme Status: - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + 0 + + + + Original + + + + + + CX: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Teletext: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Sound mode: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + TextLabel + + + + + + + FM/FM Multiplex: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + Disc side: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + Parity correct: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + Programme dump: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Digital: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Disc size: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + Amendment 2 + + + + + + CX: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + TextLabel + + + + + + + Disc size: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + TextLabel + + + + + + + Disc side: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + TextLabel + + + + + + + Teletext: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + TextLabel + + + + + + + Copy allowed: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + TextLabel + + + + + + + Standard Video: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + TextLabel + + + + + + + Sound mode: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + TextLabel + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + diff --git a/tools/ld-analyse/videoparametersdialog.cpp b/tools/ld-analyse/videoparametersdialog.cpp new file mode 100644 index 000000000..3a9065ac0 --- /dev/null +++ b/tools/ld-analyse/videoparametersdialog.cpp @@ -0,0 +1,130 @@ +/************************************************************************ + + videoparametersdialog.cpp + + ld-analyse - TBC output analysis + Copyright (C) 2022 Adam Sampson + + This file is part of ld-decode-tools. + + ld-analyse is free software: you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +************************************************************************/ + +#include "videoparametersdialog.h" +#include "ui_videoparametersdialog.h" + +VideoParametersDialog::VideoParametersDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::VideoParametersDialog) +{ + ui->setupUi(this); + setWindowFlags(Qt::Window); + + // Update the dialogue + updateDialog(); +} + +VideoParametersDialog::~VideoParametersDialog() +{ + delete ui; +} + +void VideoParametersDialog::setVideoParameters(const LdDecodeMetaData::VideoParameters &_videoParameters) +{ + videoParameters = _videoParameters; + + // Transfer values to the dialogue + ui->blackLevelSpinBox->setValue(videoParameters.black16bIre); + ui->whiteLevelSpinBox->setValue(videoParameters.white16bIre); + if (videoParameters.isWidescreen) ui->aspectRatio169RadioButton->setChecked(true); + else ui->aspectRatio43RadioButton->setChecked(true); + + // Update the dialogue + updateDialog(); +} + +void VideoParametersDialog::updateDialog() +{ + // Adjust the black level reset buttons depending on whether the system is NTSC + if (videoParameters.system == NTSC) { + ui->blackLevelResetButton->setText("Reset NTSC"); + ui->blackLevelAltResetButton->setText("Reset NTSC-J"); + ui->blackLevelAltResetButton->show(); + } else { + ui->blackLevelResetButton->setText("Reset"); + ui->blackLevelAltResetButton->hide(); + } +} + +// Public slots + +// Set either black or white level, depending on which half of the range the value is in +void VideoParametersDialog::levelSelected(qint32 level) +{ + if (level < 0x8000) { + ui->blackLevelSpinBox->setValue(level); + } else { + ui->whiteLevelSpinBox->setValue(level); + } +} + +// Private slots + +void VideoParametersDialog::on_blackLevelSpinBox_valueChanged(int value) +{ + videoParameters.black16bIre = value; + updateDialog(); + emit videoParametersChanged(videoParameters); +} + +void VideoParametersDialog::on_whiteLevelSpinBox_valueChanged(int value) +{ + videoParameters.white16bIre = value; + updateDialog(); + emit videoParametersChanged(videoParameters); +} + +// The reset black and white levels come from EBU Tech 3280 p6 (PAL) and SMPTE +// 244M p2 (NTSC), and match what ld-decode uses by default. + +void VideoParametersDialog::on_blackLevelResetButton_clicked() +{ + if (videoParameters.system == NTSC) { + ui->blackLevelSpinBox->setValue(0x3C00 + 0x0A80); // including setup + } else { + ui->blackLevelSpinBox->setValue(0x4000); + } +} + +void VideoParametersDialog::on_blackLevelAltResetButton_clicked() +{ + ui->blackLevelSpinBox->setValue(0x3C00); +} + +void VideoParametersDialog::on_whiteLevelResetButton_clicked() +{ + if (videoParameters.system == NTSC) { + ui->whiteLevelSpinBox->setValue(0xC800); + } else { + ui->whiteLevelSpinBox->setValue(0xD300); + } +} + +void VideoParametersDialog::on_aspectRatioButtonGroup_buttonClicked(QAbstractButton *button) +{ + videoParameters.isWidescreen = (button == ui->aspectRatio169RadioButton); + updateDialog(); + emit videoParametersChanged(videoParameters); +} diff --git a/tools/ld-analyse/videoparametersdialog.h b/tools/ld-analyse/videoparametersdialog.h new file mode 100644 index 000000000..498ca9647 --- /dev/null +++ b/tools/ld-analyse/videoparametersdialog.h @@ -0,0 +1,70 @@ +/************************************************************************ + + videoparametersdialog.h + + ld-analyse - TBC output analysis + Copyright (C) 2022 Adam Sampson + + This file is part of ld-decode-tools. + + ld-analyse is free software: you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +************************************************************************/ + +#ifndef VIDEOPARAMETERSDIALOG_H +#define VIDEOPARAMETERSDIALOG_H + +#include +#include + +#include "lddecodemetadata.h" + +namespace Ui { +class VideoParametersDialog; +} + +class VideoParametersDialog : public QDialog +{ + Q_OBJECT + +public: + explicit VideoParametersDialog(QWidget *parent = nullptr); + ~VideoParametersDialog(); + + void setVideoParameters(const LdDecodeMetaData::VideoParameters &videoParameters); + +signals: + void videoParametersChanged(const LdDecodeMetaData::VideoParameters &videoParameters); + +public slots: + void levelSelected(qint32 level); + +private slots: + void on_blackLevelSpinBox_valueChanged(int value); + void on_whiteLevelSpinBox_valueChanged(int value); + + void on_blackLevelResetButton_clicked(); + void on_blackLevelAltResetButton_clicked(); + void on_whiteLevelResetButton_clicked(); + + void on_aspectRatioButtonGroup_buttonClicked(QAbstractButton *button); + +private: + Ui::VideoParametersDialog *ui; + LdDecodeMetaData::VideoParameters videoParameters; + + void updateDialog(); +}; + +#endif // VIDEOPARAMETERSDIALOG_H diff --git a/tools/ld-analyse/videoparametersdialog.ui b/tools/ld-analyse/videoparametersdialog.ui new file mode 100644 index 000000000..b9069128a --- /dev/null +++ b/tools/ld-analyse/videoparametersdialog.ui @@ -0,0 +1,234 @@ + + + VideoParametersDialog + + + + 0 + 0 + 452 + 177 + + + + Video Parameters + + + + QLayout::SetFixedSize + + + 12 + + + + + + 100 + 0 + + + + Reset NTSC + + + + + + + Black level: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 70 + 30 + + + + 0x + + + 32768 + + + 65535 + + + 256 + + + 54016 + + + 16 + + + + + + + Display aspect ratio: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 70 + 30 + + + + 0x + + + 32767 + + + 256 + + + 16384 + + + 16 + + + + + + + + 100 + 0 + + + + Reset + + + + + + + + 100 + 0 + + + + Reset NTSC-J + + + + + + + White level: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 15 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 4:3 + + + true + + + aspectRatioButtonGroup + + + + + + + 16:9 + + + aspectRatioButtonGroup + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + + + + + + + diff --git a/tools/ld-analyse/visibledropoutanalysisdialog.cpp b/tools/ld-analyse/visibledropoutanalysisdialog.cpp index 19628c1c2..168f31403 100644 --- a/tools/ld-analyse/visibledropoutanalysisdialog.cpp +++ b/tools/ld-analyse/visibledropoutanalysisdialog.cpp @@ -52,7 +52,7 @@ VisibleDropOutAnalysisDialog::VisibleDropOutAnalysisDialog(QWidget *parent) : numberOfFrames = 0; // Connect to scale changed slot - connect(((QObject*)plot->axisWidget(QwtPlot::xBottom)) , SIGNAL(scaleDivChanged() ), this, SLOT(scaleDivChangedSlot() )); + connect(plot->axisWidget(QwtPlot::xBottom), &QwtScaleWidget::scaleDivChanged, this, &VisibleDropOutAnalysisDialog::scaleDivChangedSlot); } VisibleDropOutAnalysisDialog::~VisibleDropOutAnalysisDialog() diff --git a/tools/ld-analyse/whitesnranalysisdialog.cpp b/tools/ld-analyse/whitesnranalysisdialog.cpp index 0135a7e3c..4d740a9a6 100644 --- a/tools/ld-analyse/whitesnranalysisdialog.cpp +++ b/tools/ld-analyse/whitesnranalysisdialog.cpp @@ -54,7 +54,7 @@ WhiteSnrAnalysisDialog::WhiteSnrAnalysisDialog(QWidget *parent) : numberOfFrames = 0; // Connect to scale changed slot - connect(((QObject*)plot->axisWidget(QwtPlot::xBottom)) , SIGNAL(scaleDivChanged () ), this, SLOT(scaleDivChangedSlot () )); + connect(plot->axisWidget(QwtPlot::xBottom), &QwtScaleWidget::scaleDivChanged, this, &WhiteSnrAnalysisDialog::scaleDivChangedSlot); } WhiteSnrAnalysisDialog::~WhiteSnrAnalysisDialog()