-/*
- * Copyright (C) 2008 Andre Noll <maan@systemlinux.org>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
+/* Copyright (C) 2008 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
/** \file afh.c Paraslash's standalone audio format handler tool. */
-#include <dirent.h>
+#include <regex.h>
+#include <lopsub.h>
+#include "afh.lsg.h"
#include "para.h"
#include "string.h"
-#include "afh.cmdline.h"
#include "fd.h"
#include "afh.h"
#include "error.h"
+#include "version.h"
-static struct afh_args_info conf;
-/** The list of all status items */
-const char *status_item_list[] = {STATUS_ITEM_ARRAY};
+/** Array of error strings. */
+DEFINE_PARA_ERRLIST;
-INIT_AFH_ERRLISTS;
-INIT_STDERR_LOGGING(conf.loglevel_arg)
+static struct lls_parse_result *lpr;
-int main(int argc, char **argv)
+#define CMD_PTR (lls_cmd(0, afh_suite))
+#define OPT_RESULT(_name) (lls_opt_result(LSG_AFH_PARA_AFH_OPT_ ## _name, lpr))
+#define OPT_GIVEN(_name) (lls_opt_given(OPT_RESULT(_name)))
+#define OPT_STRING_VAL(_name) (lls_string_val(0, OPT_RESULT(_name)))
+#define OPT_UINT32_VAL(_name) (lls_uint32_val(0, OPT_RESULT(_name)))
+
+static int loglevel;
+INIT_STDERR_LOGGING(loglevel)
+
+static inline bool tag_needs_update(bool given, const char *tag,
+ const char *arg)
{
- int i, ret;
- void *audio_file_data;
- size_t audio_file_size;
- struct afh_info afhi;
+ return given && (!tag || strcmp(tag, arg) != 0);
+}
- afh_cmdline_parser(argc, argv, &conf);
- HANDLE_VERSION_FLAG("afh", conf);
- ret = -E_AFH_SYNTAX;
- if (conf.inputs_num != 1)
+static int rewrite_tags(const char *name, int input_fd, void *map,
+ size_t map_size, int audio_format_id, struct afh_info *afhi)
+{
+ struct taginfo *tags = &afhi->tags;
+ bool modified = false;
+ char *tmp_name;
+ const char *arg;
+ int output_fd = -1, ret;
+ struct stat sb;
+
+ arg = OPT_STRING_VAL(YEAR);
+ if (tag_needs_update(OPT_GIVEN(YEAR), tags->year, arg)) {
+ free(tags->year);
+ tags->year = para_strdup(arg);
+ modified = true;
+ }
+ arg = OPT_STRING_VAL(TITLE);
+ if (tag_needs_update(OPT_GIVEN(TITLE), tags->title, arg)) {
+ free(tags->title);
+ tags->title = para_strdup(arg);
+ modified = true;
+ }
+ arg = OPT_STRING_VAL(ARTIST);
+ if (tag_needs_update(OPT_GIVEN(ARTIST), tags->artist, arg)) {
+ free(tags->artist);
+ tags->artist = para_strdup(arg);
+ modified = true;
+ }
+ arg = OPT_STRING_VAL(ALBUM);
+ if (tag_needs_update(OPT_GIVEN(ALBUM), tags->album, arg)) {
+ free(tags->album);
+ tags->album = para_strdup(arg);
+ modified = true;
+ }
+ arg = OPT_STRING_VAL(COMMENT);
+ if (tag_needs_update(OPT_GIVEN(COMMENT), tags->comment, arg)) {
+ free(tags->comment);
+ tags->comment = para_strdup(arg);
+ modified = true;
+ }
+ if (!modified) {
+ PARA_WARNING_LOG("no modifications necessary\n");
+ return 0;
+ }
+ /*
+ * mkstmp() creates the temporary file with permissions 0600, but we
+ * like it to have the same permissions as the original file, so we
+ * have to get this information.
+ */
+ if (fstat(input_fd, &sb) < 0) {
+ ret = -ERRNO_TO_PARA_ERROR(errno);
+ PARA_ERROR_LOG("failed to fstat fd %d (%s)\n", input_fd, name);
+ return ret;
+ }
+ tmp_name = make_message("%s.XXXXXX", name);
+ ret = mkstemp(tmp_name);
+ if (ret < 0) {
+ ret = -ERRNO_TO_PARA_ERROR(errno);
+ PARA_ERROR_LOG("could not create temporary file\n");
goto out;
- afh_init();
- ret = mmap_full_file(conf.inputs[0], O_RDONLY, &audio_file_data,
- &audio_file_size, NULL);
+ }
+ output_fd = ret;
+ if (fchmod(output_fd, sb.st_mode) < 0) {
+ ret = -ERRNO_TO_PARA_ERROR(errno);
+ PARA_ERROR_LOG("failed to fchmod fd %d (%s)\n", output_fd,
+ tmp_name);
+ goto out;
+ }
+ ret = afh_rewrite_tags(audio_format_id, map, map_size, tags, output_fd,
+ tmp_name);
if (ret < 0)
goto out;
- ret = compute_afhi(conf.inputs[0], audio_file_data, audio_file_size, &afhi);
+ if (OPT_GIVEN(BACKUP)) {
+ char *backup_name = make_message("%s~", name);
+ ret = xrename(name, backup_name);
+ free(backup_name);
+ if (ret < 0)
+ goto out;
+ }
+ ret = xrename(tmp_name, name);
if (ret < 0)
goto out;
- printf("%s: %dkbit/s\n" /* bitrate */
- "%s: %s\n" /* format */
- "%s: %dHz\n" /* frequency */
- "%s: %d\n" /* channels */
- "%s: %lu\n" /* seconds total */
- "%s" /* tag info */
- "%s: %lu: %lu\n" /* chunk time */
- "%s: %lu\n", /* num chunks */
- status_item_list[SI_BITRATE], afhi.bitrate,
- status_item_list[SI_FORMAT], audio_format_name(ret),
- status_item_list[SI_FREQUENCY], afhi.frequency,
- status_item_list[SI_CHANNELS], afhi.channels,
- status_item_list[SI_SECONDS_TOTAL], afhi.seconds_total,
- afhi.info_string,
- status_item_list[SI_CHUNK_TIME], (long unsigned)afhi.chunk_tv.tv_sec,
- (long unsigned)afhi.chunk_tv.tv_usec,
- status_item_list[SI_NUM_CHUNKS], afhi.chunks_total
- );
- ret = 1;
- if (!conf.chunk_table_given)
+ if (OPT_GIVEN(PRESERVE)) {
+ struct timespec times[2]; /* [0]: atime, [1]: mtime */
+ times[0].tv_nsec = UTIME_OMIT;
+ times[1] = sb.st_mtim;
+ /*
+ * We might well have written a file of identical size. If we
+ * keep the mtime as well, we might fool backup applications
+ * like rsync which skip files whose size and mtime haven't
+ * changed. So we change the mtime slightly.
+ */
+ times[1].tv_sec++;
+ if (futimens(output_fd, times) < 0) {
+ ret = -ERRNO_TO_PARA_ERROR(errno);
+ goto out;
+ }
+ }
+out:
+ if (ret < 0 && output_fd >= 0)
+ unlink(tmp_name); /* ignore errors */
+ free(tmp_name);
+ if (output_fd >= 0)
+ close(output_fd);
+ return ret;
+}
+
+static void print_info(int audio_format_num, struct afh_info *afhi)
+{
+ char *msg;
+
+ afh_get_afhi_txt(audio_format_num, afhi, &msg);
+ printf("%s", msg);
+ free(msg);
+}
+
+static void print_chunk_table(struct afh_info *afhi, int audio_format_id,
+ const void *map, size_t mapsize)
+{
+ int i, ret;
+ void *ctx = NULL;
+
+ for (i = 0; i < afhi->chunks_total; i++) {
+ struct timeval tv;
+ long unsigned from, to;
+ const char *buf;
+ size_t len;
+ tv_scale(i, &afhi->chunk_tv, &tv);
+ from = tv2ms(&tv);
+ tv_scale(i + 1, &afhi->chunk_tv, &tv);
+ to = tv2ms(&tv);
+ ret = afh_get_chunk(i, afhi, audio_format_id, map, mapsize,
+ &buf, &len, &ctx);
+ if (ret < 0) {
+ PARA_ERROR_LOG("fatal: chunk %d: %s\n", i,
+ para_strerror(-ret));
+ return;
+ }
+ if (!OPT_GIVEN(PARSER_FRIENDLY))
+ printf("%d [%lu.%03lu - %lu.%03lu] ", i, from / 1000,
+ from % 1000, to / 1000, to % 1000);
+ printf("%td - %td", buf - (const char *)map,
+ buf + len - (const char *)map);
+ if (!OPT_GIVEN(PARSER_FRIENDLY))
+ printf(" (%zu)", len);
+ printf("\n");
+ }
+ afh_close(ctx, audio_format_id);
+}
+
+static void handle_help_flags(void)
+{
+ char *help;
+
+ if (OPT_GIVEN(DETAILED_HELP))
+ help = lls_long_help(CMD_PTR);
+ else if (OPT_GIVEN(HELP) || lls_num_inputs(lpr) == 0)
+ help = lls_short_help(CMD_PTR);
+ else
+ return;
+ printf("%s", help);
+ free(help);
+ printf("Supported audio formats\n %s\n", AUDIO_FORMAT_HANDLERS);
+ exit(EXIT_SUCCESS);
+}
+
+/**
+ * The main function of para_afh.
+ *
+ * \param argc Usual argument count.
+ * \param argv Usual argument vector.
+ *
+ * \return \p EXIT_FAILURE or \p EXIT_SUCCESS.
+ */
+int main(int argc, char **argv)
+{
+ int i, ret = 0, audio_format_num, fd;
+ void *audio_file_data;
+ size_t audio_file_size;
+ struct afh_info afhi;
+ char *errctx;
+
+ ret = lls(lls_parse(argc, argv, CMD_PTR, &lpr, &errctx));
+ if (ret < 0)
goto out;
- printf("chunk_table: ");
- for (i = 0; i <= afhi.chunks_total; i++)
- printf("%u ", afhi.chunk_table[i]);
- printf("\n");
- ret = 1;
+ loglevel = OPT_UINT32_VAL(LOGLEVEL);
+ version_handle_flag("afh", OPT_GIVEN(VERSION));
+ handle_help_flags();
+ afh_init();
+ for (i = 0; i < lls_num_inputs(lpr); i++) {
+ int ret2;
+ const char *path = lls_input(i, lpr);
+ ret = mmap_full_file(path, O_RDONLY, &audio_file_data,
+ &audio_file_size, &fd);
+ if (ret < 0) {
+ PARA_ERROR_LOG("failed to mmap \"%s\"\n", path);
+ goto out;
+ }
+ ret = compute_afhi(path, audio_file_data, audio_file_size,
+ fd, &afhi);
+ if (ret >= 0) {
+ audio_format_num = ret;
+ if (OPT_GIVEN(MODIFY)) {
+ ret = rewrite_tags(path, fd, audio_file_data,
+ audio_file_size, audio_format_num, &afhi);
+ } else {
+ printf("File %d: %s\n", i + 1, path);
+ print_info(audio_format_num, &afhi);
+ if (OPT_GIVEN(CHUNK_TABLE))
+ print_chunk_table(&afhi, audio_format_num,
+ audio_file_data, audio_file_size);
+ }
+ clear_afhi(&afhi);
+ }
+ close(fd);
+ ret2 = para_munmap(audio_file_data, audio_file_size);
+ if (ret2 < 0 && ret >= 0)
+ ret = ret2;
+ if (ret < 0)
+ break;
+ }
out:
+ if (errctx)
+ PARA_ERROR_LOG("%s\n", errctx);
if (ret < 0)
- PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+ PARA_EMERG_LOG("%s\n", para_strerror(-ret));
return ret < 0? EXIT_FAILURE : EXIT_SUCCESS;
}