/* DeaDBeeF - The Ultimate Music Player Copyright (C) 2009-2013 Alexey Yakovenko This program 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 2 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H # include "../../config.h" #endif #if HAVE_SYS_CDEFS_H #include #endif #if HAVE_SYS_SYSLIMITS_H #include #endif #include #include #include #include #include #include #include #include #include "converter.h" #include "../../deadbeef.h" #include "../../strdupa.h" #ifndef __linux__ #define O_LARGEFILE 0 #endif #define min(x,y) ((x)<(y)?(x):(y)) //#define trace(...) { fprintf(stderr, __VA_ARGS__); } #define trace(fmt,...) static ddb_converter_t plugin; static DB_functions_t *deadbeef; static ddb_encoder_preset_t *encoder_presets; static ddb_dsp_preset_t *dsp_presets; ddb_encoder_preset_t * encoder_preset_alloc (void) { ddb_encoder_preset_t *p = malloc (sizeof (ddb_encoder_preset_t)); if (!p) { fprintf (stderr, "failed to alloc ddb_encoder_preset_t\n"); return NULL; } memset (p, 0, sizeof (ddb_encoder_preset_t)); return p; } void encoder_preset_free (ddb_encoder_preset_t *p) { if (p) { if (p->title) { free (p->title); } if (p->ext) { free (p->ext); } if (p->encoder) { free (p->encoder); } free (p); } } ddb_encoder_preset_t * encoder_preset_load (const char *fname) { int err = 1; FILE *fp = fopen (fname, "rt"); if (!fp) { return NULL; } ddb_encoder_preset_t *p = encoder_preset_alloc (); char str[1024]; while (fgets (str, sizeof (str), fp)) { // chomp char *cr = str + strlen (str) - 1; while (*cr == '\n') { cr--; } cr++; *cr = 0; char *sp = strchr (str, ' '); if (!sp) { continue; } *sp = 0; char *item = sp + 1; if (!strcmp (str, "title")) { p->title = strdup (item); } else if (!strcmp (str, "ext")) { p->ext = strdup (item); } else if (!strcmp (str, "encoder")) { p->encoder = strdup (item); } else if (!strcmp (str, "method")) { p->method = atoi (item); } else if (!strcmp (str, "id3v2_version")) { p->id3v2_version = atoi (item); } else if (!strcmp (str, "tag_id3v2")) { p->tag_id3v2 = atoi (item); } else if (!strcmp (str, "tag_id3v1")) { p->tag_id3v1 = atoi (item); } else if (!strcmp (str, "tag_apev2")) { p->tag_apev2 = atoi (item); } else if (!strcmp (str, "tag_flac")) { p->tag_flac = atoi (item); } else if (!strcmp (str, "tag_oggvorbis")) { p->tag_oggvorbis = atoi (item); } } if (!p->title) { p->title = strdup ("Untitled"); } if (!p->ext) { p->ext = strdup (""); } if (!p->encoder) { p->encoder = strdup (""); } err = 0; if (err) { encoder_preset_free (p); p = NULL; } if (fp) { fclose (fp); } return p; } // @return -1 on path/write error, -2 if file already exists int encoder_preset_save (ddb_encoder_preset_t *p, int overwrite) { if (!p->title || !p->title[0]) { fprintf (stderr, "encoder_preset_save: empty title\n"); return -1; } const char *confdir = deadbeef->get_config_dir (); char path[PATH_MAX]; if (snprintf (path, sizeof (path), "%s/presets", confdir) < 0) { return -1; } mkdir (path, 0755); if (snprintf (path, sizeof (path), "%s/presets/encoders", confdir) < 0) { return -1; } mkdir (path, 0755); if (snprintf (path, sizeof (path), "%s/presets/encoders/%s.txt", confdir, p->title) < 0) { return -1; } if (!overwrite) { FILE *fp = fopen (path, "rb"); if (fp) { fclose (fp); return -2; } } FILE *fp = fopen (path, "w+b"); if (!fp) { return -1; } fprintf (fp, "title %s\n", p->title); fprintf (fp, "ext %s\n", p->ext); fprintf (fp, "encoder %s\n", p->encoder); fprintf (fp, "method %d\n", p->method); fprintf (fp, "id3v2_version %d\n", p->id3v2_version); fprintf (fp, "tag_id3v2 %d\n", p->tag_id3v2); fprintf (fp, "tag_id3v1 %d\n", p->tag_id3v1); fprintf (fp, "tag_apev2 %d\n", p->tag_apev2); fprintf (fp, "tag_flac %d\n", p->tag_flac); fprintf (fp, "tag_oggvorbis %d\n", p->tag_oggvorbis); fclose (fp); return 0; } void encoder_preset_copy (ddb_encoder_preset_t *to, ddb_encoder_preset_t *from) { to->title = strdup (from->title); to->ext = strdup (from->ext); to->encoder = strdup (from->encoder); to->method = from->method; to->tag_id3v2 = from->tag_id3v2; to->tag_id3v1 = from->tag_id3v1; to->tag_apev2 = from->tag_apev2; to->tag_flac = from->tag_flac; to->tag_oggvorbis = from->tag_oggvorbis; to->tag_mp3xing = from->tag_mp3xing; to->id3v2_version = from->id3v2_version; } ddb_encoder_preset_t * encoder_preset_get_list (void) { return encoder_presets; } ddb_encoder_preset_t * encoder_preset_get_for_idx (int idx) { ddb_encoder_preset_t *p = encoder_presets; while (p && idx--) { p = p->next; } return p; } void encoder_preset_append (ddb_encoder_preset_t *p) { // append ddb_encoder_preset_t *tail = encoder_presets; while (tail && tail->next) { tail = tail->next; } if (tail) { tail->next = p; } else { encoder_presets = p; } } void encoder_preset_remove (ddb_encoder_preset_t *p) { ddb_encoder_preset_t *prev = encoder_presets; while (prev && prev->next != p) { prev = prev->next; } if (prev) { prev->next = p->next; } else { encoder_presets = p->next; } } void encoder_preset_replace (ddb_encoder_preset_t *from, ddb_encoder_preset_t *to) { ddb_encoder_preset_t *prev = encoder_presets; while (prev && prev->next != from) { prev = prev->next; } if (prev) { prev->next = to; } else { encoder_presets = to; } to->next = from->next; } ddb_dsp_preset_t * dsp_preset_alloc (void) { ddb_dsp_preset_t *p = malloc (sizeof (ddb_dsp_preset_t)); if (!p) { fprintf (stderr, "failed to alloc ddb_dsp_preset_t\n"); return NULL; } memset (p, 0, sizeof (ddb_dsp_preset_t)); return p; } void dsp_preset_free (ddb_dsp_preset_t *p) { if (p) { if (p->title) { free (p->title); } deadbeef->dsp_preset_free (p->chain); free (p); } } void dsp_preset_copy (ddb_dsp_preset_t *to, ddb_dsp_preset_t *from) { to->title = strdup (from->title); ddb_dsp_context_t *tail = NULL; ddb_dsp_context_t *dsp = from->chain; while (dsp) { ddb_dsp_context_t *i = dsp->plugin->open (); if (dsp->plugin->num_params) { int n = dsp->plugin->num_params (); for (int j = 0; j < n; j++) { char s[1000] = ""; dsp->plugin->get_param (dsp, j, s, sizeof (s)); i->plugin->set_param (i, j, s); } } if (tail) { tail->next = i; tail = i; } else { to->chain = tail = i; } dsp = dsp->next; } } ddb_dsp_preset_t * dsp_preset_get_list (void) { return dsp_presets; } ddb_dsp_preset_t * dsp_preset_load (const char *fname) { ddb_dsp_preset_t *p = dsp_preset_alloc (); if (!p) { return NULL; } memset (p, 0, sizeof (ddb_dsp_preset_t)); const char *end = strrchr (fname, '.'); if (!end) { end = fname + strlen (fname); } const char *start = strrchr (fname, '/'); if (!start) { start = fname; } else { start++; } p->title = malloc (end-start+1); memcpy (p->title, start, end-start); p->title[end-start] = 0; int err = deadbeef->dsp_preset_load (fname, &p->chain); if (err != 0) { dsp_preset_free (p); return NULL; } return p; } int dsp_preset_save (ddb_dsp_preset_t *p, int overwrite) { if (!p->title || !p->title[0]) { fprintf (stderr, "dsp_preset_save: empty title\n"); return -1; } const char *confdir = deadbeef->get_config_dir (); char path[PATH_MAX]; if (snprintf (path, sizeof (path), "%s/presets", confdir) < 0) { return -1; } mkdir (path, 0755); if (snprintf (path, sizeof (path), "%s/presets/dsp", confdir) < 0) { return -1; } mkdir (path, 0755); if (snprintf (path, sizeof (path), "%s/presets/dsp/%s.txt", confdir, p->title) < 0) { return -1; } if (!overwrite) { FILE *fp = fopen (path, "rb"); if (fp) { fclose (fp); return -2; } } return deadbeef->dsp_preset_save (path, p->chain); } static int dirent_alphasort (const struct dirent **a, const struct dirent **b) { return strcmp ((*a)->d_name, (*b)->d_name); } int scandir_preset_filter (const struct dirent *ent) { char *ext = strrchr (ent->d_name, '.'); if (ext && !strcasecmp (ext, ".txt")) { return 1; } return 0; } static int copy_file (const char *in, const char *out) { int BUFFER_SIZE = 1000; FILE *fin = fopen (in, "rb"); if (!fin) { fprintf (stderr, "converter: failed to open file %s for reading\n", in); return -1; } FILE *fout = fopen (out, "w+b"); if (!fout) { fclose (fin); fprintf (stderr, "converter: failed to open file %s for writing\n", out); return -1; } char *buf = malloc (BUFFER_SIZE); if (!buf) { fprintf (stderr, "converter: failed to alloc %d bytes\n", BUFFER_SIZE); fclose (fin); fclose (fout); return -1; } fseek (fin, 0, SEEK_END); size_t sz = ftell (fin); rewind (fin); while (sz > 0) { int rs = min (sz, BUFFER_SIZE); if (fread (buf, rs, 1, fin) != 1) { fprintf (stderr, "converter: failed to read file %s\n", in); break; } if (fwrite (buf, rs, 1, fout) != 1) { fprintf (stderr, "converter: failed to write file %s\n", out); break; } sz -= rs; } free (buf); fclose (fin); fclose (fout); if (sz > 0) { unlink (out); } return 0; } int load_encoder_presets (void) { // check if we need to install presets char ppath[PATH_MAX]; char epath[PATH_MAX]; snprintf (ppath, sizeof (ppath), "%s/presets", deadbeef->get_config_dir ()); snprintf (epath, sizeof (epath), "%s/encoders", ppath); char path[PATH_MAX]; if (snprintf (path, sizeof (path), "%s/presets/encoders", deadbeef->get_config_dir ()) < 0) { return -1; } char syspath[PATH_MAX]; if (snprintf (syspath, sizeof (syspath), "%s/convpresets", deadbeef->get_plugin_dir ()) < 0) { return -1; } const char *preset_dirs[] = { syspath, path, NULL }; ddb_encoder_preset_t *tail = NULL; for (int di = 0; preset_dirs[di]; di++) { const char *path = preset_dirs[di]; struct dirent **namelist = NULL; int n = scandir (path, &namelist, scandir_preset_filter, dirent_alphasort); int i; for (i = 0; i < n; i++) { char s[PATH_MAX]; if (snprintf (s, sizeof (s), "%s/%s", path, namelist[i]->d_name) > 0){ ddb_encoder_preset_t *p = encoder_preset_load (s); if (p) { if (path == syspath) { // don't allow editing stock presets p->readonly = 1; } else { // check if the same RO preset exists for (ddb_encoder_preset_t *pr = encoder_presets; pr; pr = pr->next) { if (pr->readonly && !strcmp (pr->title, p->title)) { encoder_preset_free (p); p = NULL; break; } } if (!p) { // NOTE: we don't delete duplicate presets in $HOME // for compat with <=0.6.1 continue; } } if (tail) { tail->next = p; tail = p; } else { encoder_presets = tail = p; } } } free (namelist[i]); } free (namelist); } return 0; } void free_encoder_presets (void) { ddb_encoder_preset_t *p = encoder_presets; while (p) { ddb_encoder_preset_t *next = p->next; if (p->title) { free (p->title); } if (p->ext) { free (p->ext); } if (p->encoder) { free (p->encoder); } free (p); p = next; } encoder_presets = NULL; } int load_dsp_presets (void) { ddb_dsp_preset_t *tail = NULL; char path[PATH_MAX]; if (snprintf (path, sizeof (path), "%s/presets/dsp", deadbeef->get_config_dir ()) < 0) { return -1; } struct dirent **namelist = NULL; int n = scandir (path, &namelist, scandir_preset_filter, dirent_alphasort); int i; for (i = 0; i < n; i++) { char s[PATH_MAX]; if (snprintf (s, sizeof (s), "%s/%s", path, namelist[i]->d_name) > 0){ ddb_dsp_preset_t *p = dsp_preset_load (s); if (p) { if (tail) { tail->next = p; tail = p; } else { dsp_presets = tail = p; } } } free (namelist[i]); } free (namelist); return 0; } void free_dsp_presets (void) { ddb_dsp_preset_t *p = dsp_presets; while (p) { ddb_dsp_preset_t *next = p->next; if (p->title) { free (p->title); } if (p->chain) { deadbeef->dsp_preset_free (p->chain); } free (p); p = next; } dsp_presets = NULL; } ddb_dsp_preset_t * dsp_preset_get_for_idx (int idx) { ddb_dsp_preset_t *p = dsp_presets; while (p && idx--) { p = p->next; } return p; } void dsp_preset_append (ddb_dsp_preset_t *p) { // append ddb_dsp_preset_t *tail = dsp_presets; while (tail && tail->next) { tail = tail->next; } if (tail) { tail->next = p; } else { dsp_presets = p; } } void dsp_preset_remove (ddb_dsp_preset_t *p) { ddb_dsp_preset_t *prev = dsp_presets; while (prev && prev->next != p) { prev = prev->next; } if (prev) { prev->next = p->next; } else { dsp_presets = p->next; } } void dsp_preset_replace (ddb_dsp_preset_t *from, ddb_dsp_preset_t *to) { ddb_dsp_preset_t *prev = dsp_presets; while (prev && prev->next != from) { prev = prev->next; } if (prev) { prev->next = to; } else { dsp_presets = to; } to->next = from->next; } static void get_output_field (DB_playItem_t *it, const char *field, char *out, int sz) { int idx = deadbeef->pl_get_idx_of (it); char temp[PATH_MAX]; char fmt[strlen(field)+3]; snprintf (fmt, sizeof (fmt), "%%/%s", field); deadbeef->pl_format_title (it, idx, temp, sizeof (temp), -1, fmt); // escape special chars char invalid[] = "$\"`\\"; char *p = out; char *t = temp; int n = sz; while (*t && n > 1) { if (strchr (invalid, *t)) { *p++ = '\\'; n--; *p++ = *t; n--; } else { *p++ = *t; n--; } t++; } *p = 0; trace ("field '%s' expanded to '%s'\n", field, out); } void get_output_path (DB_playItem_t *it, const char *outfolder_user, const char *outfile, ddb_encoder_preset_t *encoder_preset, int preserve_folder_structure, const char *root_folder, int write_to_source_folder, char *out, int sz) { trace ("get_output_path: %s %s %s\n", outfolder_user, outfile, root_folder); deadbeef->pl_lock (); const char *uri = strdupa (deadbeef->pl_find_meta (it, ":URI")); deadbeef->pl_unlock (); char outfolder_preserve[2000]; if (preserve_folder_structure) { // generate new outfolder int rootlen = strlen (root_folder); const char *e = strrchr (uri, '/'); if (e) { const char *s = uri + rootlen; char subpath[e-s+1]; memcpy (subpath, s, e-s); subpath[e-s] = 0; snprintf (outfolder_preserve, sizeof (outfolder_preserve), "%s%s", outfolder_user[0] ? outfolder_user : getenv("HOME"), subpath); } } const char *outfolder; if (write_to_source_folder) { char *path = strdupa (uri); char *sep = strrchr (path, '/'); if (sep) { *sep = 0; } outfolder = path; } else { outfolder = preserve_folder_structure ? outfolder_preserve : outfolder_user; } int l; char fname[PATH_MAX]; int pathl = strlen(outfolder)*2+1; char path[pathl]; char *pattern = strdupa (outfile); // escape special chars char invalid[] = "$\"`\\"; char *p = path; const char *t = outfolder; while (*t && pathl > 1) { if (strchr (invalid, *t)) { *p++ = '\\'; pathl--; } *p++ = *t; pathl--; t++; } *p = 0; snprintf (out, sz, "%s/", path); // split path and create directories char *field = pattern; char *s = pattern; while (*s) { if ((*s == '/') || (*s == '\\')) { *s = '\0'; get_output_field (it, field, fname, sizeof(fname)); l = strlen (out); snprintf (out+l, sz-l, "%s/", fname); mkdir (out, 0755); field = s+1; } s++; } // last part of outfile is the filename get_output_field (it, field, fname, sizeof(fname)); l = strlen (out); snprintf (out+l, sz-l, "%s.%s", fname, encoder_preset->ext); trace ("converter output file is '%s'\n", out); } static void get_output_path_1_0 (DB_playItem_t *it, const char *outfolder, const char *outfile, ddb_encoder_preset_t *encoder_preset, char *out, int sz) { fprintf (stderr, "converter: warning: old version of \"get_output_path\" has been called, please update your plugins which depend on converter 1.1\n"); *out = 0; sz = 0; } static int check_dir (const char *dir, mode_t mode) { char *tmp = strdup (dir); char *slash = tmp; struct stat stat_buf; do { slash = strstr (slash+1, "/"); if (slash) *slash = 0; if (-1 == stat (tmp, &stat_buf)) { trace ("creating dir %s\n", tmp); if (0 != mkdir (tmp, mode)) { trace ("Failed to create %s\n", tmp); free (tmp); return 0; } } if (slash) *slash = '/'; } while (slash); free (tmp); return 1; } int convert (DB_playItem_t *it, const char *out, int output_bps, int output_is_float, ddb_encoder_preset_t *encoder_preset, ddb_dsp_preset_t *dsp_preset, int *abort) { char *buffer = NULL; char *dspbuffer = NULL; if (deadbeef->pl_get_item_duration (it) <= 0) { deadbeef->pl_lock (); fprintf (stderr, "converter: stream %s doesn't have finite length, skipped\n", deadbeef->pl_find_meta (it, ":URI")); deadbeef->pl_unlock (); return -1; } int err = -1; FILE *enc_pipe = NULL; int temp_file = -1; DB_decoder_t *dec = NULL; DB_fileinfo_t *fileinfo = NULL; char input_file_name[PATH_MAX] = ""; deadbeef->pl_lock (); dec = (DB_decoder_t *)deadbeef->plug_get_for_id (deadbeef->pl_find_meta (it, ":DECODER")); deadbeef->pl_unlock (); if (dec) { fileinfo = dec->open (0); if (fileinfo && dec->init (fileinfo, DB_PLAYITEM (it)) != 0) { deadbeef->pl_lock (); fprintf (stderr, "converter: failed to decode file %s\n", deadbeef->pl_find_meta (it, ":URI")); deadbeef->pl_unlock (); goto error; } if (fileinfo) { if (output_bps == -1) { output_bps = fileinfo->fmt.bps; output_is_float = fileinfo->fmt.is_float; } char *final_path = strdupa (out); char *sep = strrchr (final_path, '/'); if (sep) { *sep = 0; if (!check_dir (final_path, 0755)) { fprintf (stderr, "converter: failed to create output folder: %s\n", final_path); goto error; } } if (encoder_preset->method == DDB_ENCODER_METHOD_FILE) { const char *tmp = getenv ("TMPDIR"); if (!tmp) { tmp = "/tmp"; } snprintf (input_file_name, sizeof (input_file_name), "%s/ddbconvXXXXXX", tmp); char *res = mktemp (input_file_name); strcat (input_file_name, ".wav"); } else { strcpy (input_file_name, "-"); } char enc[2000]; memset (enc, 0, sizeof (enc)); // formatting: %o = outfile, %i = infile char *e = encoder_preset->encoder; char *o = enc; *o = 0; int len = sizeof (enc); while (e && *e) { if (len <= 0) { fprintf (stderr, "converter: failed to assemble encoder command line - buffer is not big enough, try to shorten your parameters. max allowed length is %u characters\n", (unsigned)sizeof (enc)); goto error; } if (e[0] == '%' && e[1]) { if (e[1] == 'o') { int l = snprintf (o, len, "\"%s\"", out); o += l; len -= l; } else if (e[1] == 'i') { int l = snprintf (o, len, "\"%s\"", input_file_name); o += l; len -= l; } else { strncpy (o, e, 2); o += 2; len -= 2; } e += 2; } else { *o++ = *e++; *o = 0; len--; } } fprintf (stderr, "converter: will encode using: %s\n", enc[0] ? enc : "internal RIFF WAVE writer"); mode_t wrmode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; if (!encoder_preset->encoder[0]) { // write to wave file temp_file = open (out, O_LARGEFILE | O_WRONLY | O_CREAT | O_TRUNC, wrmode); if (temp_file == -1) { fprintf (stderr, "converter: failed to open output wave file %s\n", out); goto error; } } else if (encoder_preset->method == DDB_ENCODER_METHOD_FILE) { temp_file = open (input_file_name, O_LARGEFILE | O_WRONLY | O_CREAT | O_TRUNC, wrmode); if (temp_file == -1) { fprintf (stderr, "converter: failed to open temp file %s\n", input_file_name); goto error; } } else { enc_pipe = popen (enc, "w"); if (!enc_pipe) { fprintf (stderr, "converter: failed to open encoder\n"); goto error; } } if (temp_file == -1 && enc_pipe) { temp_file = fileno (enc_pipe); } // write wave header char wavehdr_int[] = { 0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45, 0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x44, 0xac, 0x00, 0x00, 0x10, 0xb1, 0x02, 0x00, 0x04, 0x00, 0x10, 0x00, 0x64, 0x61, 0x74, 0x61 }; char wavehdr_float[] = { 0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45, 0x66, 0x6d, 0x74, 0x20, 0x28, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x02, 0x00, 0x40, 0x1f, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x08, 0x00, 0x20, 0x00, 0x16, 0x00, 0x20, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71, 0x66, 0x61, 0x63, 0x74, 0x04, 0x00, 0x00, 0x00, 0xc5, 0x5b, 0x00, 0x00, 0x64, 0x61, 0x74, 0x61 }; char *wavehdr = output_is_float ? wavehdr_float : wavehdr_int; int wavehdr_size = output_is_float ? sizeof (wavehdr_float) : sizeof (wavehdr_int); int header_written = 0; uint32_t outsize = 0; uint32_t outsr = fileinfo->fmt.samplerate; uint16_t outch = fileinfo->fmt.channels; int samplesize = fileinfo->fmt.channels * fileinfo->fmt.bps / 8; // block size int bs = 2000 * samplesize; // expected buffer size after worst-case dsp int dspsize = bs/samplesize*sizeof(float)*8*48; char *buffer = malloc (dspsize); // account for up to float32 7.1 resampled to 48x ratio char *dspbuffer = malloc (dspsize); int eof = 0; for (;;) { if (eof) { break; } if (abort && *abort) { break; } int sz = dec->read (fileinfo, buffer, bs); if (sz != bs) { eof = 1; } if (dsp_preset) { ddb_waveformat_t fmt; ddb_waveformat_t outfmt; memcpy (&fmt, &fileinfo->fmt, sizeof (fmt)); memcpy (&outfmt, &fileinfo->fmt, sizeof (fmt)); fmt.bps = 32; fmt.is_float = 1; deadbeef->pcm_convert (&fileinfo->fmt, buffer, &fmt, dspbuffer, sz); ddb_dsp_context_t *dsp = dsp_preset->chain; int frames = sz / samplesize; while (dsp) { frames = dsp->plugin->process (dsp, (float *)dspbuffer, frames, dspsize / (fmt.channels * 4), &fmt, NULL); if (frames <= 0) { break; } dsp = dsp->next; } if (frames <= 0) { fprintf (stderr, "converter: dsp error, please check you dsp preset\n"); goto error; } outsr = fmt.samplerate; outch = fmt.channels; outfmt.bps = output_bps; outfmt.is_float = output_is_float; outfmt.channels = outch; outfmt.samplerate = outsr; int n = deadbeef->pcm_convert (&fmt, dspbuffer, &outfmt, buffer, frames * sizeof (float) * fmt.channels); sz = n; } else if (fileinfo->fmt.bps != output_bps || fileinfo->fmt.is_float != output_is_float) { ddb_waveformat_t outfmt; memcpy (&outfmt, &fileinfo->fmt, sizeof (outfmt)); outfmt.bps = output_bps; outfmt.is_float = output_is_float; outfmt.channels = outch; outfmt.samplerate = outsr; int frames = sz / samplesize; int n = deadbeef->pcm_convert (&fileinfo->fmt, buffer, &outfmt, dspbuffer, frames * samplesize); memcpy (buffer, dspbuffer, n); sz = n; } outsize += sz; if (!header_written) { uint64_t size = (int64_t)(it->endsample-it->startsample) * outch * output_bps / 8; if (!size) { size = (double)deadbeef->pl_get_item_duration (it) * fileinfo->fmt.samplerate * outch * output_bps / 8; } if (outsr != fileinfo->fmt.samplerate) { uint64_t temp = size; temp *= outsr; temp /= fileinfo->fmt.samplerate; size = temp; } uint64_t chunksize; chunksize = size + 40; // for float, add 36 more if (output_is_float) { chunksize += 36; } uint32_t size32 = 0xffffffff; if (chunksize <= 0xffffffff) { size32 = chunksize; } memcpy (&wavehdr[4], &size32, 4); memcpy (&wavehdr[22], &outch, 2); memcpy (&wavehdr[24], &outsr, 4); uint16_t blockalign = outch * output_bps / 8; memcpy (&wavehdr[32], &blockalign, 2); memcpy (&wavehdr[34], &output_bps, 2); size32 = 0xffffffff; if (size <= 0xffffffff) { size32 = size; } if (wavehdr_size != write (temp_file, wavehdr, wavehdr_size)) { fprintf (stderr, "converter: wave header write error\n"); goto error; } if (encoder_preset->method == DDB_ENCODER_METHOD_PIPE) { size32 = 0; } if (write (temp_file, &size32, sizeof (size32)) != sizeof (size32)) { fprintf (stderr, "converter: wave header size write error\n"); goto error; } header_written = 1; } int64_t res = write (temp_file, buffer, sz); if (sz != res) { fprintf (stderr, "converter: write error (%"PRId64" bytes written out of %d)\n", res, sz); goto error; } } if (abort && *abort) { goto error; } if (temp_file != -1 && (!enc_pipe || temp_file != fileno (enc_pipe))) { lseek (temp_file, wavehdr_size, SEEK_SET); if (4 != write (temp_file, &outsize, 4)) { fprintf (stderr, "converter: data size write error\n"); goto error; } if (temp_file != -1 && (!enc_pipe || temp_file != fileno (enc_pipe))) { close (temp_file); temp_file = -1; } } if (encoder_preset->encoder[0] && encoder_preset->method == DDB_ENCODER_METHOD_FILE) { enc_pipe = popen (enc, "w"); } } } err = 0; error: if (buffer) { free (buffer); buffer = NULL; } if (dspbuffer) { free (dspbuffer); dspbuffer = NULL; } if (temp_file != -1 && (!enc_pipe || temp_file != fileno (enc_pipe))) { close (temp_file); temp_file = -1; } if (enc_pipe) { pclose (enc_pipe); enc_pipe = NULL; } if (dec && fileinfo) { dec->free (fileinfo); fileinfo = NULL; } if (abort && *abort && out[0]) { unlink (out); } if (input_file_name[0] && strcmp (input_file_name, "-")) { unlink (input_file_name); } if (err != 0) { return err; } // write junklib tags DB_playItem_t *out_it = NULL; if (encoder_preset->tag_id3v2 || encoder_preset->tag_id3v1 || encoder_preset->tag_apev2 || encoder_preset->tag_flac || encoder_preset->tag_oggvorbis) { out_it = deadbeef->pl_item_alloc (); deadbeef->pl_item_copy (out_it, it); char unesc_path[2000]; char invalid[] = "$\"`\\"; const char *p = out; char *o = unesc_path; while (*p) { if (*p == '\\') { p++; } *o++ = *p++; } *o = 0; deadbeef->pl_set_item_flags (out_it, 0); DB_metaInfo_t *m = deadbeef->pl_get_metadata_head (out_it); while (m) { DB_metaInfo_t *next = m->next; if (m->key[0] == ':' || m->key[0] == '!' || !strcasecmp (m->key, "cuesheet")) { deadbeef->pl_delete_metadata (out_it, m); } m = next; } deadbeef->pl_replace_meta (out_it, ":URI", unesc_path); } uint32_t tagflags = 0; if (encoder_preset->tag_id3v2) { tagflags |= JUNK_WRITE_ID3V2; } if (encoder_preset->tag_id3v1) { tagflags |= JUNK_WRITE_ID3V1; } if (encoder_preset->tag_apev2) { tagflags |= JUNK_WRITE_APEV2; } if (tagflags) { tagflags |= JUNK_STRIP_ID3V2 | JUNK_STRIP_APEV2 | JUNK_STRIP_ID3V1; deadbeef->junk_rewrite_tags (out_it, tagflags, encoder_preset->id3v2_version + 3, "iso8859-1"); } // write flac tags if (encoder_preset->tag_flac) { // find flac decoder plugin DB_decoder_t **plugs = deadbeef->plug_get_decoder_list (); DB_decoder_t *flac = NULL; for (int i = 0; plugs[i]; i++) { if (!strcmp (plugs[i]->plugin.id, "stdflac")) { flac = plugs[i]; break; } } if (!flac) { fprintf (stderr, "converter: flac plugin not found, cannot write flac metadata\n"); } else { if (0 != flac->write_metadata (out_it)) { fprintf (stderr, "converter: failed to write flac metadata, not a flac file?\n"); } } } // write vorbis tags if (encoder_preset->tag_oggvorbis) { // find flac decoder plugin DB_decoder_t **plugs = deadbeef->plug_get_decoder_list (); int res = -1; for (int i = 0; plugs[i]; i++) { if (!strcmp (plugs[i]->plugin.id, "stdogg") || !strcmp (plugs[i]->plugin.id, "stdopus")) { res = plugs[i]->write_metadata (out_it); if (!res) { break; } } } if (res) { fprintf (stderr, "converter: failed to write ogg metadata, not an ogg file?\n"); } } if (out_it) { deadbeef->pl_item_unref (out_it); } return err; } int convert_1_0 (DB_playItem_t *it, const char *outfolder, const char *outfile, int output_bps, int output_is_float, int preserve_folder_structure, const char *root_folder, ddb_encoder_preset_t *encoder_preset, ddb_dsp_preset_t *dsp_preset, int *abort) { fprintf (stderr, "converter: warning: old version of \"convert\" has been called, please update your plugins which depend on converter 1.1\n"); return -1; } int converter_cmd (int cmd, ...) { return -1; } int converter_start (void) { load_encoder_presets (); load_dsp_presets (); return 0; } int converter_stop (void) { free_encoder_presets (); free_dsp_presets (); return 0; } // define plugin interface static ddb_converter_t plugin = { .misc.plugin.api_vmajor = 1, .misc.plugin.api_vminor = 0, .misc.plugin.version_major = 1, .misc.plugin.version_minor = 3, .misc.plugin.type = DB_PLUGIN_MISC, .misc.plugin.name = "Converter", .misc.plugin.id = "converter", .misc.plugin.descr = "Converts any supported formats to other formats.\n" "Requires separate GUI plugin, e.g. Converter GTK UI\n", .misc.plugin.copyright = "Copyright (C) 2009-2013 Alexey Yakovenko \n" "\n" "This program is free software; you can redistribute it and/or\n" "modify it under the terms of the GNU General Public License\n" "as published by the Free Software Foundation; either version 2\n" "of the License, or (at your option) any later version.\n" "\n" "This program is distributed in the hope that it will be useful,\n" "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" "GNU General Public License for more details.\n" "\n" "You should have received a copy of the GNU General Public License\n" "along with this program; if not, write to the Free Software\n" "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n" , .misc.plugin.website = "http://deadbeef.sf.net", .misc.plugin.start = converter_start, .misc.plugin.stop = converter_stop, .misc.plugin.command = converter_cmd, .encoder_preset_alloc = encoder_preset_alloc, .encoder_preset_free = encoder_preset_free, .encoder_preset_load = encoder_preset_load, .encoder_preset_save = encoder_preset_save, .encoder_preset_copy = encoder_preset_copy, .encoder_preset_get_list = encoder_preset_get_list, .encoder_preset_get_for_idx = encoder_preset_get_for_idx, .encoder_preset_append = encoder_preset_append, .encoder_preset_remove = encoder_preset_remove, .encoder_preset_replace = encoder_preset_replace, .dsp_preset_alloc = dsp_preset_alloc, .dsp_preset_free = dsp_preset_free, .dsp_preset_load = dsp_preset_load, .dsp_preset_save = dsp_preset_save, .dsp_preset_copy = dsp_preset_copy, .dsp_preset_get_list = dsp_preset_get_list, .dsp_preset_get_for_idx = dsp_preset_get_for_idx, .dsp_preset_append = dsp_preset_append, .dsp_preset_remove = dsp_preset_remove, .dsp_preset_replace = dsp_preset_replace, .get_output_path_1_0 = get_output_path_1_0, .convert_1_0 = convert_1_0, // 1.1 entry points .load_encoder_presets = load_encoder_presets, .load_dsp_presets = load_dsp_presets, .free_encoder_presets = free_encoder_presets, .free_dsp_presets = free_dsp_presets, // 1.2 entry points .convert = convert, .get_output_path = get_output_path, }; DB_plugin_t * converter_load (DB_functions_t *api) { deadbeef = api; return DB_PLUGIN (&plugin); }