1 // this is a decoder plugin skeleton
2 // use to create new decoder plugins
3 
4 #include <stdlib.h>
5 #include <string.h>
6 #include <deadbeef/deadbeef.h>
7 
8 #define trace(...) { fprintf(stderr, __VA_ARGS__); }
9 
10 static DB_decoder_t plugin;
11 static DB_functions_t *deadbeef;
12 
13 typedef struct {
14     DB_fileinfo_t info;
15     int startsample;
16     int endsample;
17     int currentsample;
18 } example_info_t;
19 
20 static const char * exts[] = { "example", NULL }; // e.g. mp3
21 
22 // allocate codec control structure
23 static DB_fileinfo_t *
example_open(uint32_t hints)24 example_open (uint32_t hints) {
25     DB_fileinfo_t *_info = malloc (sizeof (example_info_t));
26     example_info_t *info = (example_info_t *)_info;
27     memset (info, 0, sizeof (example_info_t));
28     return _info;
29 }
30 
31 // prepare to decode the track, fill in mandatory plugin fields
32 // return -1 on failure
33 static int
example_init(DB_fileinfo_t * _info,DB_playItem_t * it)34 example_init (DB_fileinfo_t *_info, DB_playItem_t *it) {
35     example_info_t *info = (example_info_t *)_info;
36 
37     // take this parameters from your input file
38     // we set constants for clarity sake
39     _info->fmt.bps = 16;
40     _info->fmt.channels = 2;
41     _info->fmt.samplerate  = 44100;
42     for (int i = 0; i < _info->fmt.channels; i++) {
43         _info->fmt.channelmask |= 1 << i;
44     }
45     _info->readpos = 0;
46     _info->plugin = &plugin;
47 
48     if (it->endsample > 0) {
49         info->startsample = it->startsample;
50         info->endsample = it->endsample;
51         plugin.seek_sample (_info, 0);
52     }
53     else {
54         info->startsample = 0;
55         int TOTALSAMPLES = 1000; // calculate from file
56         info->endsample = TOTALSAMPLES-1;
57     }
58     return 0;
59 }
60 
61 // free everything allocated in _init
62 static void
example_free(DB_fileinfo_t * _info)63 example_free (DB_fileinfo_t *_info) {
64     example_info_t *info = (example_info_t *)_info;
65     if (info) {
66         free (info);
67     }
68 }
69 
70 
71 // try decode `size' bytes
72 // return number of decoded bytes
73 // or 0 on EOF/error
74 static int
example_read(DB_fileinfo_t * _info,char * bytes,int size)75 example_read (DB_fileinfo_t *_info, char *bytes, int size) {
76     example_info_t *info = (example_info_t *)_info;
77     info->currentsample += size / (_info->fmt.channels * _info->fmt.bps/8);
78     return size;
79 }
80 
81 // seek to specified sample (frame)
82 // return 0 on success
83 // return -1 on failure
84 static int
example_seek_sample(DB_fileinfo_t * _info,int sample)85 example_seek_sample (DB_fileinfo_t *_info, int sample) {
86     example_info_t *info = (example_info_t *)_info;
87 
88     info->currentsample = sample + info->startsample;
89     _info->readpos = (float)sample / _info->fmt.samplerate;
90     return 0;
91 }
92 
93 // seek to specified time in seconds
94 // return 0 on success
95 // return -1 on failure
96 static int
example_seek(DB_fileinfo_t * _info,float time)97 example_seek (DB_fileinfo_t *_info, float time) {
98     return example_seek_sample (_info, time * _info->fmt.samplerate);
99 }
100 
101 // read information from the track
102 // load/process cuesheet if exists
103 // insert track into playlist
104 // return track pointer on success
105 // return NULL on failure
106 
107 static DB_playItem_t *
example_insert(ddb_playlist_t * plt,DB_playItem_t * after,const char * fname)108 example_insert (ddb_playlist_t *plt, DB_playItem_t *after, const char *fname) {
109     // open file
110     DB_FILE *fp = deadbeef->fopen (fname);
111     if (!fp) {
112         trace ("example: failed to fopen %s\n", fname);
113         return NULL;
114     }
115 
116     // decoder_* functions are imaginary -- you should replace them with real
117     // decoder library calls
118     decoder_info_t *di = decoder_open ();
119     if (!di) {
120         trace ("example: failed to init decoder\n");
121         return NULL;
122     }
123     // read track info/tags
124     track_info_t ti;
125     if (decoder_read_info (&ti) < 0) {
126         trace ("example: failed to read info\n");
127         decoder_free (di);
128         return NULL;
129     }
130 
131     // replace "example" with your file type (e.g. MP3, WAV, etc)
132     const char *ft = "example";
133 
134     // no cuesheet, prepare track for addition
135     DB_playItem_t *it = deadbeef->pl_item_alloc_init (fname, plugin.plugin.id);
136 
137     deadbeef->pl_replace_meta (it, ":FILETYPE", ft);
138     deadbeef->plt_set_item_duration (plt, it, (float)ti.total_num_samples/ti.samplerate);
139 
140     // now we should have track duration, and can try loading cuesheet
141     // 1st try embedded cuesheet
142     if (ti.embeddedcuesheet[0]) {
143         DB_playItem_t *cue = deadbeef->plt_insert_cue_from_buffer (plt, after, it, ti.embeddedcuesheet, strlen (ti.embeddedcuesheet), ti.total_num_samples, ti.samplerate);
144         if (cue) {
145             deadbeef->pl_item_unref (it);
146             deadbeef->pl_item_unref (cue);
147             // cuesheet loaded
148             decoder_free (di);
149             return cue;
150         }
151     }
152 
153     // embedded cuesheet not found, try external one
154     DB_playItem_t *cue = deadbeef->plt_insert_cue (plt, after, it, ti.total_num_samples, ti.samplerate);
155     if (cue) {
156         // cuesheet loaded
157         deadbeef->pl_item_unref (it);
158         deadbeef->pl_item_unref (cue);
159         decoder_free (di);
160         return cue;
161     }
162 
163 
164     // add metainfo
165     if (!strlen (ti.title)) {
166         // title is empty, this call will set track title to filename without extension
167         deadbeef->pl_add_meta (it, "title", NULL);
168     }
169     else {
170         deadbeef->pl_add_meta (it, "title", ti.title);
171     }
172     deadbeef->pl_add_meta (it, "artist", ti.artist);
173     // ... etc ...
174 
175     // free decoder
176     decoder_free (di);
177 
178     // now the track is ready, insert into playlist
179     after = deadbeef->plt_insert_item (plt, after, it);
180     deadbeef->pl_item_unref (it);
181     return after;
182 }
183 
184 static int
example_start(void)185 example_start (void) {
186     // do one-time plugin initialization here
187     // e.g. starting threads for background processing, subscribing to events, etc
188     // return 0 on success
189     // return -1 on failure
190     return 0;
191 }
192 
193 static int
example_stop(void)194 example_stop (void) {
195     // undo everything done in _start here
196     // return 0 on success
197     // return -1 on failure
198     return 0;
199 }
200 
201 // define plugin interface
202 static DB_decoder_t plugin = {
203     DB_PLUGIN_SET_API_VERSION
204     .plugin.version_major = 0,
205     .plugin.version_minor = 1,
206     .plugin.type = DB_PLUGIN_DECODER,
207     .plugin.name = "short plugin name",
208     .plugin.id = "example",
209     .plugin.descr = "plugin description",
210     .plugin.copyright = "copyright message - author(s), license, etc",
211     .plugin.start = example_start,
212     .plugin.stop = example_stop,
213     .open = example_open,
214     .init = example_init,
215     .free = example_free,
216     .read = example_read,
217     .seek = example_seek,
218     .seek_sample = example_seek_sample,
219     .insert = example_insert,
220     .exts = exts,
221 };
222 
223 DB_plugin_t *
example_load(DB_functions_t * api)224 example_load (DB_functions_t *api) {
225     deadbeef = api;
226     return DB_PLUGIN (&plugin);
227 }
228 
229