1 /*
2     ALAC plugin for deadbeef
3     Copyright (C) 2012-2013 Alexey Yakovenko <waker@users.sourceforge.net>
4     Uses the reverse engineered ALAC decoder (C) 2005 David Hammerton
5     All rights reserved.
6 
7     Permission is hereby granted, free of charge, to any person
8     obtaining a copy of this software and associated documentation
9     files (the "Software"), to deal in the Software without
10     restriction, including without limitation the rights to use,
11     copy, modify, merge, publish, distribute, sublicense, and/or
12     sell copies of the Software, and to permit persons to whom the
13     Software is furnished to do so, subject to the following conditions:
14 
15     The above copyright notice and this permission notice shall be
16     included in all copies or substantial portions of the Software.
17 
18     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20     OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22     HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23     WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24     FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25     OTHER DEALINGS IN THE SOFTWARE.
26  */
27 #include "../../deadbeef.h"
28 #ifdef HAVE_CONFIG_H
29 #include "../../config.h"
30 #endif
31 #include <stdlib.h>
32 #include <string.h>
33 #include <math.h>
34 #include "mp4ff.h"
35 #include "demux.h"
36 #include "decomp.h"
37 #include "stream.h"
38 
39 #define min(x,y) ((x)<(y)?(x):(y))
40 #define max(x,y) ((x)>(y)?(x):(y))
41 
42 //#define trace(...) { fprintf(stderr, __VA_ARGS__); }
43 #define trace(fmt,...)
44 
45 static DB_decoder_t plugin;
46 DB_functions_t *deadbeef;
47 
48 #ifdef WORDS_BIGENDIAN
49 int host_bigendian = 1;
50 #else
51 int host_bigendian = 0;
52 #endif
53 
54 #define BUFFER_SIZE (1024*24)
55 #define IN_BUFFER_SIZE (1024*80)
56 
57 typedef struct {
58     DB_fileinfo_t info;
59     DB_FILE *file;
60     demux_res_t demux_res;
61     stream_t *stream;
62     alac_file *alac;
63     int junk;
64     char out_buffer[BUFFER_SIZE];
65     int out_remaining;
66     int skipsamples;
67     int currentsample;
68     int startsample;
69     int endsample;
70     int current_frame;
71     int64_t dataoffs;
72 } alacplug_info_t;
73 
74 // allocate codec control structure
75 static DB_fileinfo_t *
alacplug_open(uint32_t hints)76 alacplug_open (uint32_t hints) {
77     DB_fileinfo_t *_info = malloc (sizeof (alacplug_info_t));
78     alacplug_info_t *info = (alacplug_info_t *)_info;
79     memset (info, 0, sizeof (alacplug_info_t));
80     return _info;
81 }
82 
83 static uint32_t
alacplug_fs_read(void * user_data,void * buffer,uint32_t length)84 alacplug_fs_read (void *user_data, void *buffer, uint32_t length) {
85     alacplug_info_t *info = user_data;
86     return deadbeef->fread (buffer, 1, length, info->file);
87 }
88 
89 static uint32_t
alacplug_fs_seek(void * user_data,uint64_t position)90 alacplug_fs_seek (void *user_data, uint64_t position) {
91     alacplug_info_t *info = user_data;
92     return deadbeef->fseek (info->file, position+info->junk, SEEK_SET);
93 }
94 
95 static int
get_sample_info(demux_res_t * demux_res,uint32_t samplenum,uint32_t * sample_duration,uint32_t * sample_byte_size)96 get_sample_info(demux_res_t *demux_res, uint32_t samplenum,
97                            uint32_t *sample_duration,
98                            uint32_t *sample_byte_size)
99 {
100     unsigned int duration_index_accum = 0;
101     unsigned int duration_cur_index = 0;
102 
103     if (samplenum >= demux_res->num_sample_byte_sizes)
104     {
105         fprintf(stderr, "sample %i does not exist\n", samplenum);
106         return 0;
107     }
108 
109     if (!demux_res->num_time_to_samples)
110     {
111         fprintf(stderr, "no time to samples\n");
112         return 0;
113     }
114     while ((demux_res->time_to_sample[duration_cur_index].sample_count + duration_index_accum)
115             <= samplenum)
116     {
117         duration_index_accum += demux_res->time_to_sample[duration_cur_index].sample_count;
118         duration_cur_index++;
119         if (duration_cur_index >= demux_res->num_time_to_samples)
120         {
121             fprintf(stderr, "sample %i does not have a duration\n", samplenum);
122             return 0;
123         }
124     }
125 
126     *sample_duration = demux_res->time_to_sample[duration_cur_index].sample_duration;
127     *sample_byte_size = demux_res->sample_byte_size[samplenum];
128 
129     return 1;
130 }
131 
132 static int
alacplug_get_totalsamples(demux_res_t * demux_res)133 alacplug_get_totalsamples (demux_res_t *demux_res) {
134     int totalsamples = 0;
135     for (int i = 0; i < demux_res->num_sample_byte_sizes; i++)
136     {
137         unsigned int thissample_duration = 0;
138         unsigned int thissample_bytesize = 0;
139 
140         get_sample_info(demux_res, i, &thissample_duration,
141                 &thissample_bytesize);
142 
143         totalsamples += thissample_duration;
144     }
145     return totalsamples;
146 }
147 
148 static int
alacplug_init(DB_fileinfo_t * _info,DB_playItem_t * it)149 alacplug_init (DB_fileinfo_t *_info, DB_playItem_t *it) {
150     alacplug_info_t *info = (alacplug_info_t *)_info;
151 
152     deadbeef->pl_lock ();
153     info->file = deadbeef->fopen (deadbeef->pl_find_meta (it, ":URI"));
154     deadbeef->pl_unlock ();
155     if (!info->file) {
156         return -1;
157     }
158 
159     info->stream = stream_create_file (info->file, 1, info->junk);
160 
161     if (!qtmovie_read(info->stream, &info->demux_res)) {
162         if (!info->demux_res.format_read || info->demux_res.format != MAKEFOURCC('a','l','a','c')) {
163             return -1;
164         }
165     }
166     info->dataoffs = deadbeef->ftell (info->file);
167 
168     info->alac = create_alac(info->demux_res.sample_size, info->demux_res.num_channels);
169     alac_set_info(info->alac, info->demux_res.codecdata);
170     info->demux_res.sample_rate = alac_get_samplerate (info->alac);
171     info->demux_res.sample_size = alac_get_bitspersample (info->alac);
172 
173     int totalsamples = alacplug_get_totalsamples (&info->demux_res);
174     if (!info->file->vfs->is_streaming ()) {
175         if (it->endsample > 0) {
176             info->startsample = it->startsample;
177             info->endsample = it->endsample;
178             plugin.seek_sample (_info, 0);
179         }
180         else {
181             info->startsample = 0;
182             info->endsample = totalsamples-1;
183         }
184     }
185 
186     _info->plugin = &plugin;
187     _info->fmt.bps = info->demux_res.sample_size;
188     _info->fmt.channels = info->demux_res.num_channels;
189     _info->fmt.samplerate = info->demux_res.sample_rate;
190     for (int i = 0; i < _info->fmt.channels; i++) {
191         _info->fmt.channelmask |= 1 << i;
192     }
193 
194     return 0;
195 }
196 
197 static void
alacplug_free(DB_fileinfo_t * _info)198 alacplug_free (DB_fileinfo_t *_info) {
199     alacplug_info_t *info = (alacplug_info_t *)_info;
200     if (info) {
201         if (info->file) {
202             deadbeef->fclose (info->file);
203         }
204         if (info->stream) {
205             stream_destroy (info->stream);
206         }
207         qtmovie_free_demux (&info->demux_res);
208         if (info->alac) {
209             alac_file_free (info->alac);
210         }
211         free (info);
212     }
213 }
214 
215 static int
alacplug_read(DB_fileinfo_t * _info,char * bytes,int size)216 alacplug_read (DB_fileinfo_t *_info, char *bytes, int size) {
217     alacplug_info_t *info = (alacplug_info_t *)_info;
218     int samplesize = _info->fmt.channels * _info->fmt.bps / 8;
219     if (!info->file->vfs->is_streaming ()) {
220         if (info->currentsample + size / samplesize > info->endsample) {
221             size = (info->endsample - info->currentsample + 1) * samplesize;
222             if (size <= 0) {
223                 trace ("alacplug_read: eof (current=%d, total=%d)\n", info->currentsample, info->endsample);
224                 return 0;
225             }
226         }
227     }
228     int initsize = size;
229     while (size > 0) {
230         // handle seeking
231         if (info->skipsamples > 0 && info->out_remaining > 0) {
232             int skip = min (info->out_remaining, info->skipsamples);
233             if (skip < info->out_remaining) {
234                 memmove (info->out_buffer, info->out_buffer + skip * samplesize, (info->out_remaining - skip) * samplesize);
235             }
236             info->out_remaining -= skip;
237             info->skipsamples -= skip;
238         }
239         if (info->out_remaining > 0) {
240             int n = size / samplesize;
241             n = min (info->out_remaining, n);
242 
243             char *src = info->out_buffer;
244             memcpy (bytes, src, n * samplesize);
245             bytes += n * samplesize;
246             src += n * samplesize;
247             size -= n * samplesize;
248 
249             if (n == info->out_remaining) {
250                 info->out_remaining = 0;
251             }
252             else {
253                 memmove (info->out_buffer, src, (info->out_remaining - n) * samplesize);
254                 info->out_remaining -= n;
255             }
256             continue;
257         }
258 
259         // decode next frame
260         if (info->current_frame == info->demux_res.num_sample_byte_sizes) {
261             break; // end of file
262         }
263 
264         uint32_t sample_duration;
265         uint32_t sample_byte_size;
266 
267         int outputBytes;
268 
269         /* just get one sample for now */
270         if (!get_sample_info(&info->demux_res, info->current_frame,
271                              &sample_duration, &sample_byte_size))
272         {
273             fprintf(stderr, "alac: sample failed\n");
274             break;
275         }
276 
277         if (IN_BUFFER_SIZE < sample_byte_size)
278         {
279             fprintf(stderr, "alac: buffer too small! (is %i want %i)\n",
280                     IN_BUFFER_SIZE,
281                     sample_byte_size);
282             break;
283         }
284 
285         char buffer[IN_BUFFER_SIZE];
286         stream_read(info->stream, sample_byte_size, buffer);
287 
288         outputBytes = BUFFER_SIZE;
289         decode_frame(info->alac, buffer, info->out_buffer, &outputBytes);
290         info->current_frame++;
291 
292         info->out_remaining += outputBytes / samplesize;
293     }
294 
295     info->currentsample += (initsize-size) / samplesize;
296     return initsize-size;
297 }
298 
299 static int
alacplug_seek_sample(DB_fileinfo_t * _info,int sample)300 alacplug_seek_sample (DB_fileinfo_t *_info, int sample) {
301     alacplug_info_t *info = (alacplug_info_t *)_info;
302 
303     sample += info->startsample;
304 
305     int totalsamples = 0;
306     int64_t seekpos = 0;
307     int i;
308     for (i = 0; i < info->demux_res.num_sample_byte_sizes; i++)
309     {
310         unsigned int thissample_duration = 0;
311         unsigned int thissample_bytesize = 0;
312 
313         get_sample_info(&info->demux_res, i, &thissample_duration,
314                 &thissample_bytesize);
315 
316         if (totalsamples + thissample_duration > sample) {
317             info->skipsamples = sample - totalsamples;
318             break;
319         }
320         totalsamples += thissample_duration;
321         seekpos += info->demux_res.sample_byte_size[i];
322     }
323 
324     if (i == info->demux_res.num_sample_byte_sizes) {
325         return -1;
326     }
327 
328 
329     deadbeef->fseek(info->file, info->dataoffs + seekpos, SEEK_SET);
330 
331     info->current_frame = i;
332     info->out_remaining = 0;
333     info->currentsample = sample;
334     _info->readpos = (float)(info->currentsample - info->startsample) / _info->fmt.samplerate;
335     return 0;
336 }
337 
338 static int
alacplug_seek(DB_fileinfo_t * _info,float t)339 alacplug_seek (DB_fileinfo_t *_info, float t) {
340     return alacplug_seek_sample (_info, t * _info->fmt.samplerate);
341 }
342 
343 static const char *metainfo[] = {
344     "artist", "artist",
345     "title", "title",
346     "album", "album",
347     "track", "track",
348     "date", "year",
349     "genre", "genre",
350     "comment", "comment",
351     "performer", "performer",
352     "album_artist", "band",
353     "writer", "composer",
354     "vendor", "vendor",
355     "disc", "disc",
356     "compilation", "compilation",
357     "totaldiscs", "numdiscs",
358     "copyright", "copyright",
359     "totaltracks", "numtracks",
360     "tool", "tool",
361     "MusicBrainz Track Id", "musicbrainz_trackid",
362     NULL
363 };
364 
365 
366 /* find a metadata item by name */
367 /* returns 0 if item found, 1 if no such item */
368 int32_t mp4ff_meta_find_by_name(const mp4ff_t *f, const char *item, char **value);
369 
370 
371 void
alacplug_load_tags(DB_playItem_t * it,mp4ff_t * mp4)372 alacplug_load_tags (DB_playItem_t *it, mp4ff_t *mp4) {
373     char *s = NULL;
374     int got_itunes_tags = 0;
375 
376     int n = mp4ff_meta_get_num_items (mp4);
377     for (int t = 0; t < n; t++)  {
378         char *key = NULL;
379         char *value = NULL;
380         int res = mp4ff_meta_get_by_index(mp4, t, &key, &value);
381         if (key && value) {
382             got_itunes_tags = 1;
383             if (strcasecmp (key, "cover")) {
384                 if (!strcasecmp (key, "replaygain_track_gain")) {
385                     deadbeef->pl_set_item_replaygain (it, DDB_REPLAYGAIN_TRACKGAIN, atof (value));
386                 }
387                 else if (!strcasecmp (key, "replaygain_album_gain")) {
388                     deadbeef->pl_set_item_replaygain (it, DDB_REPLAYGAIN_ALBUMGAIN, atof (value));
389                 }
390                 else if (!strcasecmp (key, "replaygain_track_peak")) {
391                     deadbeef->pl_set_item_replaygain (it, DDB_REPLAYGAIN_TRACKPEAK, atof (value));
392                 }
393                 else if (!strcasecmp (key, "replaygain_album_peak")) {
394                     deadbeef->pl_set_item_replaygain (it, DDB_REPLAYGAIN_ALBUMPEAK, atof (value));
395                 }
396                 else {
397                     int i;
398                     for (i = 0; metainfo[i]; i += 2) {
399                         if (!strcasecmp (metainfo[i], key)) {
400                             deadbeef->pl_add_meta (it, metainfo[i+1], value);
401                             break;
402                         }
403                     }
404                     if (!metainfo[i]) {
405                         deadbeef->pl_add_meta (it, key, value);
406                     }
407                 }
408             }
409         }
410         if (key) {
411             free (key);
412         }
413         if (value) {
414             free (value);
415         }
416     }
417 
418     if (got_itunes_tags) {
419         uint32_t f = deadbeef->pl_get_item_flags (it);
420         f |= DDB_TAG_ITUNES;
421         deadbeef->pl_set_item_flags (it, f);
422     }
423 }
424 
425 int
alacplug_read_metadata(DB_playItem_t * it)426 alacplug_read_metadata (DB_playItem_t *it) {
427     deadbeef->pl_lock ();
428     DB_FILE *fp = deadbeef->fopen (deadbeef->pl_find_meta (it, ":URI"));
429     deadbeef->pl_unlock ();
430     if (!fp) {
431         return -1;
432     }
433 
434     if (fp->vfs->is_streaming ()) {
435         deadbeef->fclose (fp);
436         return -1;
437     }
438 
439     alacplug_info_t inf;
440     memset (&inf, 0, sizeof (inf));
441     inf.file = fp;
442     inf.junk = deadbeef->junk_get_leading_size (fp);
443     if (inf.junk >= 0) {
444         deadbeef->fseek (inf.file, inf.junk, SEEK_SET);
445     }
446     else {
447         inf.junk = 0;
448     }
449 
450     mp4ff_callback_t cb = {
451         .read = alacplug_fs_read,
452         .write = NULL,
453         .seek = alacplug_fs_seek,
454         .truncate = NULL,
455         .user_data = &inf
456     };
457 
458     deadbeef->pl_delete_all_meta (it);
459 
460     mp4ff_t *mp4 = mp4ff_open_read (&cb);
461     if (mp4) {
462         alacplug_load_tags (it, mp4);
463         mp4ff_close (mp4);
464     }
465     /*int apeerr = */deadbeef->junk_apev2_read (it, fp);
466     /*int v2err = */deadbeef->junk_id3v2_read (it, fp);
467     /*int v1err = */deadbeef->junk_id3v1_read (it, fp);
468     deadbeef->fclose (fp);
469     return 0;
470 }
471 
472 static DB_playItem_t *
alacplug_insert(ddb_playlist_t * plt,DB_playItem_t * after,const char * fname)473 alacplug_insert (ddb_playlist_t *plt, DB_playItem_t *after, const char *fname) {
474     trace ("adding %s\n", fname);
475     mp4ff_t *mp4 = NULL;
476     DB_playItem_t *it = NULL;
477     demux_res_t demux_res;
478     memset (&demux_res, 0, sizeof (demux_res));
479     stream_t *stream;
480     DB_FILE *fp = deadbeef->fopen (fname);
481     if (!fp) {
482         trace ("not found\n");
483         return NULL;
484     }
485     alacplug_info_t info = {0};
486     info.file = fp;
487     info.junk = deadbeef->junk_get_leading_size (fp);
488     if (info.junk >= 0) {
489         trace ("junk: %d\n", info.junk);
490         deadbeef->fseek (fp, info.junk, SEEK_SET);
491     }
492     else {
493         info.junk = 0;
494     }
495 
496     float duration = -1;
497 
498     stream = stream_create_file (fp, 1, info.junk);
499     if (!stream) {
500         trace ("alac: stream_create_file failed\n");
501         goto error;
502     }
503 
504     if (!qtmovie_read(stream, &demux_res)) {
505         if (!demux_res.format_read || demux_res.format != MAKEFOURCC('a','l','a','c')) {
506             trace ("alac track not found in the file %s, expected atom %X got %X\n", fname, MAKEFOURCC('a','l','a','c'), demux_res.format);
507             goto error;
508         }
509     }
510 
511     alac_file *alac = create_alac(demux_res.sample_size, demux_res.num_channels);
512     alac_set_info(alac, demux_res.codecdata);
513     demux_res.sample_rate = alac_get_samplerate (alac);
514     demux_res.sample_size = alac_get_bitspersample (alac);
515     alac_file_free (alac);
516 
517     it = deadbeef->pl_item_alloc_init (fname, plugin.plugin.id);
518     deadbeef->pl_add_meta (it, ":FILETYPE", "ALAC");
519 
520     int totalsamples = alacplug_get_totalsamples (&demux_res);
521     duration = totalsamples / (float)demux_res.sample_rate;
522 
523     deadbeef->plt_set_item_duration (plt, it, duration);
524 
525     // read tags
526     mp4ff_callback_t cb = {
527         .read = alacplug_fs_read,
528         .write = NULL,
529         .seek = alacplug_fs_seek,
530         .truncate = NULL,
531         .user_data = &info
532     };
533     deadbeef->fseek (fp, info.junk, SEEK_SET);
534     mp4 = mp4ff_open_read (&cb);
535     if (mp4) {
536         alacplug_load_tags (it, mp4);
537     }
538 
539     int apeerr = deadbeef->junk_apev2_read (it, fp);
540     int v2err = deadbeef->junk_id3v2_read (it, fp);
541     int v1err = deadbeef->junk_id3v1_read (it, fp);
542 
543     int64_t fsize = deadbeef->fgetlength (fp);
544 
545     deadbeef->fclose (fp);
546     fp = NULL;
547     stream_destroy (stream);
548     stream = NULL;
549     if (mp4) {
550         mp4ff_close (mp4);
551         mp4 = NULL;
552     }
553     int samplerate = demux_res.sample_rate;
554     int bps = demux_res.sample_size;
555     int channels = demux_res.num_channels;
556 
557     qtmovie_free_demux (&demux_res);
558 
559     trace ("duration %f\n", duration);
560     if (duration > 0) {
561         char s[100];
562         snprintf (s, sizeof (s), "%lld", fsize);
563         deadbeef->pl_add_meta (it, ":FILE_SIZE", s);
564         snprintf (s, sizeof (s), "%d", bps);
565         deadbeef->pl_add_meta (it, ":BPS", s);
566         snprintf (s, sizeof (s), "%d", channels);
567         deadbeef->pl_add_meta (it, ":CHANNELS", s);
568         snprintf (s, sizeof (s), "%d", samplerate);
569         deadbeef->pl_add_meta (it, ":SAMPLERATE", s);
570         int br = (int)roundf(fsize / duration * 8 / 1000);
571         snprintf (s, sizeof (s), "%d", br);
572         deadbeef->pl_add_meta (it, ":BITRATE", s);
573         // embedded cue
574         deadbeef->pl_lock ();
575         const char *cuesheet = deadbeef->pl_find_meta (it, "cuesheet");
576         DB_playItem_t *cue = NULL;
577 
578         if (cuesheet) {
579             cue = deadbeef->plt_insert_cue_from_buffer (plt, after, it, cuesheet, strlen (cuesheet), totalsamples, samplerate);
580             if (cue) {
581                 deadbeef->pl_item_unref (it);
582                 deadbeef->pl_item_unref (cue);
583                 deadbeef->pl_unlock ();
584                 return cue;
585             }
586         }
587         deadbeef->pl_unlock ();
588 
589         cue  = deadbeef->plt_insert_cue (plt, after, it, totalsamples, samplerate);
590         if (cue) {
591             deadbeef->pl_item_unref (it);
592             deadbeef->pl_item_unref (cue);
593             return cue;
594         }
595     }
596 
597     trace ("success\n");
598 success:
599     after = deadbeef->plt_insert_item (plt, after, it);
600     deadbeef->pl_item_unref (it);
601 error:
602     if (fp) {
603         deadbeef->fclose (fp);
604     }
605     if (mp4) {
606         mp4ff_close (mp4);
607     }
608     qtmovie_free_demux (&demux_res);
609     return it;
610 }
611 
612 static const char * exts[] = { "mp4", "m4a", NULL };
613 
614 // define plugin interface
615 static DB_decoder_t plugin = {
616     .plugin.api_vmajor = 1,
617     .plugin.api_vminor = 0,
618     .plugin.version_major = 1,
619     .plugin.version_minor = 0,
620     .plugin.type = DB_PLUGIN_DECODER,
621     .plugin.id = "alac",
622     .plugin.name = "ALAC player",
623     .plugin.descr = "plays alac files from MP4 and M4A files",
624     .plugin.copyright =
625         "ALAC plugin for deadbeef\n"
626         "Copyright (C) 2012-2013 Alexey Yakovenko <waker@users.sourceforge.net>\n"
627         "Uses the reverse engineered ALAC decoder (C) 2005 David Hammerton\n"
628         "All rights reserved.\n"
629         "\n"
630         "Permission is hereby granted, free of charge, to any person\n"
631         "obtaining a copy of this software and associated documentation\n"
632         "files (the \"Software\"), to deal in the Software without\n"
633         "restriction, including without limitation the rights to use,\n"
634         "copy, modify, merge, publish, distribute, sublicense, and/or\n"
635         "sell copies of the Software, and to permit persons to whom the\n"
636         "Software is furnished to do so, subject to the following conditions:\n"
637         "\n"
638         "The above copyright notice and this permission notice shall be\n"
639         "included in all copies or substantial portions of the Software.\n"
640         "\n"
641         "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n"
642         "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n"
643         "OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n"
644         "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n"
645         "HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n"
646         "WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n"
647         "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n"
648         "OTHER DEALINGS IN THE SOFTWARE.\n"
649     ,
650     .plugin.website = "http://deadbeef.sf.net",
651     .open = alacplug_open,
652     .init = alacplug_init,
653     .free = alacplug_free,
654     .read = alacplug_read,
655     .seek = alacplug_seek,
656     .seek_sample = alacplug_seek_sample,
657     .insert = alacplug_insert,
658     .read_metadata = alacplug_read_metadata,
659     .exts = exts,
660 };
661 
662 DB_plugin_t *
alac_load(DB_functions_t * api)663 alac_load (DB_functions_t *api) {
664     deadbeef = api;
665     return DB_PLUGIN (&plugin);
666 }
667