Compare commits

..

8 Commits

Author SHA1 Message Date
Frédéric Mangano
5c1a7b3a99 Mention METADATA_BLOCK_PICTURE in the man page 2025-03-12 11:38:38 +09:00
Marián Konček
b70e65f0d4 Fix CI 2025-02-15 11:19:16 +09:00
Timon Giese
fc7e5e939e Fix typos and formatting in manpage 2025-01-10 22:21:57 +09:00
Marián Konček
e8b66a6207 Fix some sanitizer errors of misaligned pointers 2024-11-07 16:40:51 +09:00
Marián Konček
ba5c151b5d Add GitHub Action 2024-11-07 16:40:51 +09:00
Marián Konček
76afc0efd5 Fix string out-of-bounds access 2024-11-06 15:05:50 +09:00
Frédéric Mangano
a54bac8f55 Fix the warning on comparison of size_t and long 2024-11-01 10:30:12 +09:00
Frédéric Mangano
3293647e8f Wrap fclose to avoid compiler warnings 2024-11-01 10:20:49 +09:00
6 changed files with 83 additions and 33 deletions

30
.github/workflows/ci.yaml vendored Normal file
View File

@ -0,0 +1,30 @@
name: Continuous Integration
on:
push:
branches: [master]
pull_request:
branches: [master]
workflow_dispatch:
env:
LC_CTYPE: C.UTF-8
CMAKE_COLOR_DIAGNOSTICS: ON
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout git repository
uses: actions/checkout@v4
- name: Install dependencies
run: |
sudo apt install cmake g++ pkg-config libogg-dev ffmpeg libtest-harness-perl libtest-deep-perl liblist-moreutils-perl libtest-utf8-perl
- name: Build
env:
CXX: g++
CXXFLAGS: -D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS -D_GLIBCXX_DEBUG -O2 -flto=auto -g -Wall -Wextra -Werror=format-security -fstack-protector-strong -fstack-clash-protection -fcf-protection -fsanitize=address,undefined
LDFLAGS: -fsanitize=address,undefined
run: |
cmake -B target -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON
cmake --build target
- name: Test
run: |
cmake --build target --target check

View File

@ -1,4 +1,4 @@
.TH opustags 1 "April 2024" "@PROJECT_NAME@ @PROJECT_VERSION@"
.TH opustags 1 "March 2025" "@PROJECT_NAME@ @PROJECT_VERSION@"
.SH NAME
opustags \- Ogg Opus tag editor
.SH SYNOPSIS
@ -43,13 +43,13 @@ to set new tags without being bothered by the old ones.
If you want to replace all the tags, you can use the \fB--set-all\fP option which will cause
\fBopustags\fP to read tags from standard input.
The format is the same as the one used for output: newline-separated \fIFIELD=Value\fP assignment.
All the previously existing tags as deleted.
All the previously existing tags are deleted.
.PP
The Opus format specifications requires that tags are encoded in UTF-8, so that's the only encoding
opustags supports. If your system encoding is different, the tags are automatically converted to and
from your system locale. When you edit an Opus file whose tags contains characters unsupported by
your system encoding, the original UTF-8 values will be preserved for the tags you don't explicitly
modify.
The Opus format specification requires that tags are encoded in UTF-8, so thats the only encoding
\fBopustags\fP supports. If your system encoding is different, the tags are automatically converted
to and from your system locale. When you edit an Opus file whose tags contain characters unsupported
by your system encoding, the original UTF-8 values will be preserved for the tags you dont
explicitly modify.
.SH OPTIONS
.TP
.B \-h, \-\-help
@ -67,7 +67,7 @@ setting \fB--output\fP to the same path as the input file and enabling \fB--over
This option conflicts with \fB--output\fP.
.TP
.B \-y, \-\-overwrite
By default, \fBopustags\fP refuses to overwrite an already-existent file.
By default, \fBopustags\fP refuses to overwrite an already-existing file.
Use \fB-y\fP to allow overwriting.
Note that this option is not needed when the output is a special file like \fI/dev/null\fP.
.TP
@ -79,10 +79,10 @@ In both cases, the field names are case-insensitive, and expected to be ASCII.
.B \-a, \-\-add \fIFIELD=VALUE\fP
Add a tag. Note that multiple tags with the same field name are perfectly acceptable, so you can add
multiple fields with the same name, and previously existing tags will also be preserved.
When the \fB--delete\fP is used with the same \fIFIELD\fP, only the older tags are deleted.
When \fB--delete\fP is used with the same \fIFIELD\fP, only the older tags are deleted.
.TP
.B \-s, \-\-set \fIFIELD=VALUE\fP
This option is provided for convenience. It delete all the fields of the same
This option is provided for convenience. It deletes all the fields of the same
type that may already exist, then adds it with the wanted value.
This is strictly equivalent to \fB--delete\fP \fIFIELD\fP \fB--add\fP
\fIFIELD=VALUE\fP. You can combine it with \fB--add\fP to add tags of the same
@ -93,33 +93,35 @@ added with \fB--add\fP.
Delete all the previously existing tags.
.TP
.B \-S, \-\-set-all
Sets the tags from scratch.
Set the tags from scratch.
All the original tags are deleted and new ones are read from standard input.
Each line must specify a \fIFIELD=VALUE\fP pair and be separated with line feeds.
Empty lines and lines starting with \fI#\fP are ignored.
Multiline tags must have their continuation lines prefixed by a single tab (in other words, every
Multi-line tags must have their continuation lines prefixed by a single tab (in other words, every
\fI\\n\fP must be replaced by \fI\\n\\t\fP).
.TP
.B \-e, \-\-edit
Edit tags interactively by spawning the program specified by the EDITOR
environment variable. The allowed format is the same as \fB--set-all\fP.
environment variable. The allowed format is the same as with \fB--set-all\fP.
If TERM and VISUAL are set, VISUAL takes precedence over EDITOR.
.TP
.B \-\-output-cover \fIFILE\fP
Save the cover art of the input Opus file to the specified location.
Extract the cover art from the \fBMETADATA_BLOCK_PICTURE\fP tag into the specified location.
If the input file does not contain any cover art, this option has no effect.
To allow overwriting the target location, specify \fB--overwrite\fP.
In the case of multiple pictures embedded in the Opus tags, only the first one is saved.
Note that the since the image format is not fixed, you should consider an extension-less file name
and rely on the magic number to deduce the type. opustags does not add or check the target files
extension.
Note that since the image format is not fixed, you should consider an extension-less file name and
rely on the magic number to deduce the type.
\fBopustags\fP does not add or check the target files extension.
You can specify \fB-\fP for standard output, in which case the regular output will be suppressed.
.TP
.B \-\-set-cover \fIFILE\fP
Replace or set the cover art to the specified picture.
Set the cover art by embedding the specified picture into the \fBMETADATA_BLOCK_PICTURE\fP tag,
replacing any existing values.
Specify \fB-\fP to read the picture from standard input.
In theory, an Opus file may contain multiple pictures with different roles, though in practice only
the front cover really matters. opustags can currently only handle one front cover and nothing else.
the front cover really matters.
\fBopustags\fP can currently only handle one front cover and nothing else.
.TP
.B \-\-vendor
Print the vendor string from the OpusTags packet and do nothing else. Standard tags operations are
@ -140,9 +142,10 @@ character set even though your system cannot display it.
When editing tags programmatically with line-based tools like grep or sed, tags containing newlines
are likely to corrupt the result because these tools wont interpret multi-line tags as a whole. To
make automatic processing easier, \fB-z\fP delimits tags by a null byte (ASCII NUL) instead of line
feeds. That same \fB-z\fP flag is also supported by GNU grep or GNU sed and, combined with opustags
-z, would make them process the input tag-by-tag instead of line-by-line, thus supporting multi-line
tags as well. This option also disables the TAB prefix for continuation lines after a line feed.
feeds. That same \fB-z\fP flag is also supported by GNU grep or GNU sed and, combined with
\fBopustags -z\fP, would make them process the input tag-by-tag instead of line-by-line, thus
supporting multi-line tags as well.
This option also disables the tab prefix for continuation lines after a line feed.
.SH EXAMPLES
.PP
List all the tags in file foo.opus:
@ -172,7 +175,7 @@ Use GNU grep to remove all the CHAPTER* tags, with -z to support multi-line tags
.SH CAVEATS
.PP
\fBopustags\fP currently has the following limitations:
.IP \[bu]
.IP \[bu] 2n
Multiplexed streams are not supported.
.IP \[bu]
Control characters inside tags are printed raw rather than being escaped.

View File

@ -56,7 +56,7 @@ std::u8string ot::encode_base64(ot::byte_string_view src)
ot::byte_string ot::decode_base64(std::u8string_view src)
{
// Remove the padding and rely on the string length instead.
while (src.back() == u8'=')
while (!src.empty() && src.back() == u8'=')
src.remove_suffix(1);
size_t olen = src.size() / 4 * 3; // Whole blocks;

View File

@ -55,7 +55,9 @@ ot::opus_tags ot::parse_tags(const ogg_packet& packet)
// Comment count
if (pos + 4 > size)
throw status {st::cut_comment_count, "Comment count did not fit the comment header"};
uint32_t count = le32toh(*((uint32_t*) (data + pos)));
uint32_t count;
memcpy(&count, data + pos, sizeof(count));
count = le32toh(count);
pos += 4;
// Comments' data
@ -63,7 +65,9 @@ ot::opus_tags ot::parse_tags(const ogg_packet& packet)
if (pos + 4 > size)
throw status {st::cut_comment_length,
"Comment length did not fit the comment header"};
uint32_t comment_length = le32toh(*((uint32_t*) (data + pos)));
uint32_t comment_length;
memcpy(&comment_length, data + pos, sizeof(comment_length));
comment_length = le32toh(comment_length);
if (pos + 4 + comment_length > size)
throw status {st::cut_comment_data,
"Comment string did not fit the comment header"};
@ -134,12 +138,16 @@ ot::picture::picture(ot::byte_string block)
size_t desc_offset = mime_offset + 4 + mime_size;
if (storage.size() < desc_offset + 4)
throw status { st::invalid_size, "missing description in picture block" };
uint32_t desc_size = be32toh(*reinterpret_cast<const uint32_t*>(&storage[desc_offset]));
uint32_t desc_size;
memcpy(&desc_size, &storage[desc_offset], sizeof(desc_size));
desc_size = be32toh(desc_size);
size_t pic_offset = desc_offset + 4 + desc_size + 16;
if (storage.size() < pic_offset + 4)
throw status { st::invalid_size, "missing picture data in picture block" };
uint32_t pic_size = be32toh(*reinterpret_cast<const uint32_t*>(&storage[pic_offset]));
uint32_t pic_size;
memcpy(&pic_size, &storage[pic_offset], sizeof(pic_size));
pic_size = be32toh(pic_size);
if (storage.size() != pic_offset + 4 + pic_size)
throw status { st::invalid_size, "invalid picture block size" };
@ -157,7 +165,8 @@ ot::byte_string ot::picture::serialize() const
*reinterpret_cast<uint32_t*>(&bytes[0]) = htobe32(3); // Picture type: front cover.
*reinterpret_cast<uint32_t*>(&bytes[mime_offset]) = htobe32(mime_type.size());
std::copy(mime_type.begin(), mime_type.end(), std::next(bytes.begin(), mime_offset + 4));
*reinterpret_cast<uint32_t*>(&bytes[pic_offset]) = htobe32(picture_data.size());
uint32_t picture_data_size = htobe32(picture_data.size());
memcpy(&bytes[pic_offset], &picture_data_size, sizeof(picture_data_size));
std::copy(picture_data.begin(), picture_data.end(), std::next(bytes.begin(), pic_offset + 4));
return bytes;
}

View File

@ -119,13 +119,16 @@ using byte_string_view = std::basic_string_view<uint8_t>;
* \{
*/
/** fclose wrapper for std::unique_ptrs deleter. */
void close_file(FILE*);
/**
* Smart auto-closing FILE* handle.
*
* It implictly converts from an already opened FILE*.
*/
struct file : std::unique_ptr<FILE, decltype(&fclose)> {
file(FILE* f = nullptr) : std::unique_ptr<FILE, decltype(&fclose)>(f, &fclose) {}
struct file : std::unique_ptr<FILE, decltype(&close_file)> {
file(FILE* f = nullptr) : std::unique_ptr<FILE, decltype(&close_file)>(f, &close_file) {}
};
/**

View File

@ -29,6 +29,11 @@ ot::byte_string_view operator""_bsv(const char* data, size_t size)
return ot::byte_string_view(reinterpret_cast<const uint8_t*>(data), size);
}
void ot::close_file(FILE* file)
{
fclose(file);
}
void ot::partial_file::open(const char* destination)
{
final_name = destination;
@ -122,7 +127,7 @@ ot::byte_string ot::slurp_binary_file(const char* filename)
byte_string content;
long file_size = get_file_size(f.get());
if (file_size == -1) {
if (file_size < 0) {
// Read the input stream block by block and resize the output byte string as needed.
uint8_t buffer[4096];
while (!feof(f.get())) {
@ -135,7 +140,7 @@ ot::byte_string ot::slurp_binary_file(const char* filename)
} else {
// Lucky! We know the file size, so lets slurp it at once.
content.resize(file_size);
if (fread(content.data(), 1, file_size, f.get()) < file_size)
if (fread(content.data(), 1, file_size, f.get()) < size_t(file_size))
throw status { st::standard_error,
"Could not read '"s + filename + "': " + strerror(errno) + "." };
}