Skip to content

Commit

Permalink
Add FFMETADATA1 export, with chapter and stop code information.
Browse files Browse the repository at this point in the history
You can feed the output of this into ffmpeg, to produce (for example)
Matroska output with chapter markers. Stop codes are just listed as
comments at the moment.

This is a straight translation of my decode-vbi Python program.
  • Loading branch information
atsampson committed Jan 4, 2020
1 parent 80d9be1 commit cd4fb00
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 1 deletion.
3 changes: 2 additions & 1 deletion scripts/test-decode
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,12 @@ def run_ld_process_vbi(args):
def run_ld_export_metadata(args):
"""Run ld-export-metadata."""

clean(args, ['.vits.csv', '.vbi.csv'])
clean(args, ['.vits.csv', '.vbi.csv', '.ffmetadata'])

cmd = [src_dir + '/tools/ld-export-metadata/ld-export-metadata']
cmd += ['--vits-csv', args.output + '.vits.csv']
cmd += ['--vbi-csv', args.output + '.vbi.csv']
cmd += ['--ffmetadata', args.output + '.ffmetadata']
cmd += [args.output + '.tbc.json']
run_command(cmd)

Expand Down
147 changes: 147 additions & 0 deletions tools/ld-export-metadata/ffmetadata.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/************************************************************************
ffmetadata.cpp
ld-export-metadata - Export JSON metadata into other formats
Copyright (C) 2019-2020 Adam Sampson
This file is part of ld-decode-tools.
ld-export-metadata 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 <http://www.gnu.org/licenses/>.
************************************************************************/

#include "ffmetadata.h"

#include "vbidecoder.h"

#include <QtGlobal>
#include <QFile>
#include <QTextStream>
#include <set>
#include <vector>

using std::set;
using std::vector;

struct ChapterChange {
qint32 field;
qint32 chapter;
};

bool writeFfmetadata(LdDecodeMetaData &metaData, const QString &fileName)
{
const auto videoParameters = metaData.getVideoParameters();

// We'll give positions using 0-based field indexes directly, rather than
// using the times encoded in the VBI, because we might be working with a
// capture of only part of a disc. Select the appropriate timebase to make
// this work.
const QString timeBase = videoParameters.isSourcePal ? "1/50" : "1001/60000";

// Scan through the fields in the input, collecting VBI information
vector<ChapterChange> chapterChanges;
set<qint32> stopCodes;
qint32 chapter = -1;
qint32 firstFieldIndex = 0;
VbiDecoder vbiDecoder;

for (qint32 fieldIndex = 0; fieldIndex < videoParameters.numberOfSequentialFields; fieldIndex++) {
// Get the (1-based) field
const auto field = metaData.getField(fieldIndex + 1);

// Codes may be in either field; we want the index of the first
if (field.isFirstField) {
firstFieldIndex = fieldIndex;
}

// Decode this field's VBI
const auto vbi = vbiDecoder.decode(field.vbi.vbiData[0], field.vbi.vbiData[1], field.vbi.vbiData[2]);

if (vbi.chNo != -1 && vbi.chNo != chapter) {
// Chapter change
chapter = vbi.chNo;
chapterChanges.emplace_back(ChapterChange {firstFieldIndex, chapter});
}

if (vbi.picStop) {
// Stop code
stopCodes.insert(firstFieldIndex);
}
}

// Add a dummy change at the end of the input, so we can get the length of
// the last chapter
chapterChanges.emplace_back(ChapterChange {videoParameters.numberOfSequentialFields, -1});

// Because chapter markers have no error detection, a corrupt marker will
// result in a spurious chapter change. Remove suspiciously short chapters.
// XXX This could be smarter for sequences like 1 1 1 1 *2 2 3* 2 2 2 2
vector<ChapterChange> cleanChanges;
for (qint32 i = 0; i < static_cast<qint32>(chapterChanges.size() - 1); i++) {
const auto &change = chapterChanges[i];
const auto &nextChange = chapterChanges[i + 1];

if ((nextChange.field - change.field) < 10) {
// Chapters should be at least 30 tracks (= 60 or more fields) long. So
// this is too short -- drop it.
} else if ((!cleanChanges.empty()) && (change.chapter == cleanChanges.back().chapter)) {
// Change to the same chapter - drop
} else {
// Keep
cleanChanges.emplace_back(change);
}
}

// Keep the dummy change
cleanChanges.emplace_back(chapterChanges.back());

// Open the output file
QFile file(fileName);
if (!file.open(QFile::WriteOnly | QFile::Text)) {
qDebug("writeFfmetadata: Could not open file for output");
return false;
}
QTextStream stream(&file);
stream.setCodec("UTF-8");

// Write the header
stream << ";FFMETADATA1\n";

// Write the chapter changes, skipping the dummy one at the end
for (qint32 i = 0; i < static_cast<qint32>(cleanChanges.size() - 1); i++) {
const auto &change = cleanChanges[i];
const auto &nextChange = cleanChanges[i + 1];

stream << "\n";
stream << "[CHAPTER]\n";
stream << "TIMEBASE=" << timeBase << "\n";
stream << "START=" << change.field << "\n";
stream << "END=" << (nextChange.field - 1) << "\n";
stream << "title=" << QString("Chapter %1").arg(change.chapter) << "\n";
}

if (!stopCodes.empty()) {
// Write the stop codes, as comments
// XXX Is there a way to represent these properly?
stream << "\n";
for (qint32 field : stopCodes) {
stream << "; Stop code at " << field << "\n";
}
}

// Done!
file.close();
return true;
}
43 changes: 43 additions & 0 deletions tools/ld-export-metadata/ffmetadata.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/************************************************************************
ffmetadata.h
ld-export-metadata - Export JSON metadata into other formats
Copyright (C) 2020 Adam Sampson
This file is part of ld-decode-tools.
ld-export-metadata 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 <http://www.gnu.org/licenses/>.
************************************************************************/

#ifndef FFMETADATA_H
#define FFMETADATA_H

#include <QString>

#include "lddecodemetadata.h"

/*!
Write an FFMETADATA1 file containing navigation information.
This is FFmpeg's generic metadata format, and can be used to provide
metadata for chapter-supporting formats like Matroska.
Format description: <https://ffmpeg.org/ffmpeg-formats.html#Metadata-1>
Returns true on success, false on failure.
*/
bool writeFfmetadata(LdDecodeMetaData &metaData, const QString &fileName);

#endif
2 changes: 2 additions & 0 deletions tools/ld-export-metadata/ld-export-metadata.pro
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ DEFINES += QT_DEPRECATED_WARNINGS

SOURCES += \
csv.cpp \
ffmetadata.cpp \
main.cpp \
../library/tbc/lddecodemetadata.cpp \
../library/tbc/vbidecoder.cpp

HEADERS += \
csv.h \
ffmetadata.h \
../library/tbc/lddecodemetadata.h \
../library/tbc/vbidecoder.h

Expand Down
13 changes: 13 additions & 0 deletions tools/ld-export-metadata/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <QCommandLineParser>

#include "csv.h"
#include "ffmetadata.h"

#include "lddecodemetadata.h"

Expand Down Expand Up @@ -123,6 +124,11 @@ int main(int argc, char *argv[])
QCoreApplication::translate("main", "file"));
parser.addOption(writeVbiCsvOption);

QCommandLineOption writeFfmetadataOption("ffmetadata",
QCoreApplication::translate("main", "Write navigation information as FFMETADATA1"),
QCoreApplication::translate("main", "file"));
parser.addOption(writeFfmetadataOption);

// -- Positional arguments --

// Positional argument to specify input video file
Expand Down Expand Up @@ -167,6 +173,13 @@ int main(int argc, char *argv[])
return 1;
}
}
if (parser.isSet(writeFfmetadataOption)) {
const QString &fileName = parser.value(writeFfmetadataOption);
if (!writeFfmetadata(metaData, fileName)) {
qCritical() << "Failed to write output file:" << fileName;
return 1;
}
}

// Quit with success
return 0;
Expand Down

0 comments on commit cd4fb00

Please sign in to comment.