mirror of
https://github.com/fmang/opustags.git
synced 2025-03-13 08:00:07 +01:00
Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
5c1a7b3a99 | ||
|
b70e65f0d4 | ||
|
fc7e5e939e | ||
|
e8b66a6207 | ||
|
ba5c151b5d | ||
|
76afc0efd5 | ||
|
a54bac8f55 | ||
|
3293647e8f |
30
.github/workflows/ci.yaml
vendored
Normal file
30
.github/workflows/ci.yaml
vendored
Normal 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
|
49
opustags.1
49
opustags.1
@ -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 that’s 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 don’t
|
||||
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 file’s
|
||||
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 file’s 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 won’t 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.
|
||||
|
@ -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;
|
||||
|
19
src/opus.cc
19
src/opus.cc
@ -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;
|
||||
}
|
||||
|
@ -119,13 +119,16 @@ using byte_string_view = std::basic_string_view<uint8_t>;
|
||||
* \{
|
||||
*/
|
||||
|
||||
/** fclose wrapper for std::unique_ptr’s 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) {}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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 let’s 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) + "." };
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user