1 /*
2 Copyright (c) 2008-2009, The Musepack Development Team
3 All rights reserved.
4
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20 #include <mpc/mpcdec.h>
21 #include "../libmpcdec/internal.h"
22 #include "../libmpcenc/libmpcenc.h"
23 #include "iniparser.h"
24
25 #include <sys/stat.h>
26
27 #include <cuetools/cuefile.h>
28
29 // tags.c
30 void Init_Tags ( void );
31 int FinalizeTags ( FILE* fp, unsigned int Version, unsigned int flags );
32 int addtag ( const char* key, size_t keylen, const char* value,
33 size_t valuelen, int converttoutf8, int flags );
34 #define TAG_NO_HEADER 1
35 #define TAG_NO_FOOTER 2
36 #define TAG_NO_PREAMBLE 4
37 #define TAG_VERSION 2000
38
39 #ifdef _MSC_VER
40 # include <io.h>
41 # define atoll _atoi64
42 # define ftruncate chsize
43 # define strcasecmp stricmp
44 #endif
45
46 #define MPCCHAP_MAJOR 0
47 #define MPCCHAP_MINOR 9
48 #define MPCCHAP_BUILD 0
49
50 #define _cat(a,b,c) #a"."#b"."#c
51 #define cat(a,b,c) _cat(a,b,c)
52 #define MPCCHAP_VERSION cat(MPCCHAP_MAJOR,MPCCHAP_MINOR,MPCCHAP_BUILD)
53
54 const char About[] = "%s - Musepack (MPC) sv8 chapter editor v" MPCCHAP_VERSION " (C) 2008-2009 MDT\nBuilt " __DATE__ " " __TIME__ "\n";
55
56
57 static const int Ptis[] = { PTI_TITLE, PTI_PERFORMER, PTI_SONGWRITER, PTI_COMPOSER,
58 PTI_ARRANGER, PTI_MESSAGE, PTI_GENRE};
59 static char const * const APE_keys[] = {"Title", "Artist", "Songwriter", "Composer",
60 "Arranger", "Comment", "Genre"};
61
usage(const char * exename)62 static void usage(const char *exename)
63 {
64 fprintf(stderr,
65 "Usage: %s <infile.mpc> <chapterfile.ini / cuefile>\n"
66 " if chapterfile.ini exists, chapter tags in infile.mpc will be\n"
67 " replaced by those from chapterfile.ini, else chapters will be\n"
68 " dumped to chapterfile.ini\n"
69 " chapterfile.ini is something like :\n"
70 " [chapter_start_sample]\n"
71 " SomeKey=Some Value\n"
72 " SomeOtherKey=Some Other Value\n"
73 " [other_chapter_start]\n"
74 " YouKnowWhatKey=I think you start to understand ...\n"
75 , exename);
76 }
77
add_chaps_ini(char * mpc_file,char * chap_file,mpc_demux * demux,mpc_streaminfo * si)78 mpc_status add_chaps_ini(char * mpc_file, char * chap_file, mpc_demux * demux, mpc_streaminfo * si)
79 {
80 struct stat stbuf;
81 FILE * in_file;
82 int chap_pos, end_pos, chap_size, i, nchap;
83 char * tmp_buff;
84 dictionary * dict;
85
86 chap_pos = (demux->chap_pos >> 3) + si->header_position;
87 end_pos = mpc_demux_pos(demux) >> 3;
88 chap_size = end_pos - chap_pos;
89
90 stat(mpc_file, &stbuf);
91 tmp_buff = malloc(stbuf.st_size - chap_pos - chap_size);
92 in_file = fopen( mpc_file, "r+b" );
93 fseek(in_file, chap_pos + chap_size, SEEK_SET);
94 fread(tmp_buff, 1, stbuf.st_size - chap_pos - chap_size, in_file);
95 fseek(in_file, chap_pos, SEEK_SET);
96
97 dict = iniparser_load(chap_file);
98
99 nchap = iniparser_getnsec(dict);
100 for (i = 0; i < nchap; i++) {
101 int j, nitem, ntags = 0, tag_len = 0, offset_size;
102 mpc_uint16_t gain = 0, peak = 0;
103 char * chap_sec = iniparser_getsecname(dict, i), block_header[12] = "CT", sample_offset[10];
104 mpc_int64_t chap_pos = atoll(chap_sec);
105
106 if (chap_pos > si->samples - si->beg_silence)
107 fprintf(stderr, "warning : chapter %i starts @ %lli samples after the end of the stream (%lli)\n",
108 i + 1, chap_pos, si->samples - si->beg_silence);
109
110 Init_Tags();
111
112 nitem = iniparser_getnkey(dict, i);
113 for (j = 0; j < nitem; j++) {
114 char * item_key, * item_value;
115 int key_len, item_len;
116 item_key = iniparser_getkeyname(dict, i, j, & item_value);
117 if (strcmp(item_key, "gain") == 0)
118 gain = atoi(item_value);
119 else if (strcmp(item_key, "peak") == 0)
120 peak = atoi(item_value);
121 else {
122 key_len = strlen(item_key);
123 item_len = strlen(item_value);
124 addtag(item_key, key_len, item_value, item_len, 0, 0);
125 tag_len += key_len + item_len;
126 ntags++;
127 }
128 }
129 if (ntags > 0) tag_len += 24 + ntags * 9;
130
131 offset_size = encodeSize(chap_pos, sample_offset, MPC_FALSE);
132 tag_len = encodeSize(tag_len + 4 + offset_size + 2, block_header + 2, MPC_TRUE);
133 fwrite(block_header, 1, tag_len + 2, in_file);
134 fwrite(sample_offset, 1, offset_size, in_file);
135 sample_offset[0] = gain >> 8;
136 sample_offset[1] = gain & 0xFF;
137 sample_offset[2] = peak >> 8;
138 sample_offset[3] = peak & 0xFF;
139 fwrite(sample_offset, 1, 4, in_file);
140 FinalizeTags(in_file, TAG_VERSION, TAG_NO_FOOTER | TAG_NO_PREAMBLE);
141 }
142
143 fwrite(tmp_buff, 1, stbuf.st_size - chap_pos - chap_size, in_file);
144 ftruncate(fileno(in_file), ftell(in_file));
145
146 fclose(in_file);
147 free(tmp_buff);
148 iniparser_freedict(dict);
149
150 return MPC_STATUS_OK;
151 }
152
add_chaps_cue(char * mpc_file,char * chap_file,mpc_demux * demux,mpc_streaminfo * si)153 mpc_status add_chaps_cue(char * mpc_file, char * chap_file, mpc_demux * demux, mpc_streaminfo * si)
154 {
155 Cd *cd = 0;
156 int nchap, format = UNKNOWN;
157 struct stat stbuf;
158 FILE * in_file;
159 int chap_pos, end_pos, chap_size, i;
160 char * tmp_buff;
161
162 if (0 == (cd = cf_parse(chap_file, &format))) {
163 fprintf(stderr, "%s: input file error\n", chap_file);
164 return !MPC_STATUS_OK;
165 }
166
167 chap_pos = (demux->chap_pos >> 3) + si->header_position;
168 end_pos = mpc_demux_pos(demux) >> 3;
169 chap_size = end_pos - chap_pos;
170
171 stat(mpc_file, &stbuf);
172 tmp_buff = malloc(stbuf.st_size - chap_pos - chap_size);
173 in_file = fopen( mpc_file, "r+b" );
174 fseek(in_file, chap_pos + chap_size, SEEK_SET);
175 fread(tmp_buff, 1, stbuf.st_size - chap_pos - chap_size, in_file);
176 fseek(in_file, chap_pos, SEEK_SET);
177
178 nchap = cd_get_ntrack(cd);
179 for (i = 1; i <= nchap; i++) {
180 char track_buf[16], block_header[12] = "CT", sample_offset[10];
181 int j, nitem = 0, tag_len = 0, key_len, item_len, offset_size;
182 Track * track;
183 Cdtext *cdtext;
184 mpc_int64_t chap_pos;
185
186 track = cd_get_track (cd, i);
187 cdtext = track_get_cdtext(track);
188
189 // position du chapitre
190 chap_pos = (mpc_int64_t) si->sample_freq * track_get_start (track) / 75;
191
192 if (chap_pos > si->samples - si->beg_silence)
193 fprintf(stderr, "warning : chapter %i starts @ %lli samples after the end of the stream (%lli)\n",
194 i, chap_pos, si->samples - si->beg_silence);
195
196 Init_Tags();
197
198 sprintf(track_buf, "%i/%i", i, nchap);
199 key_len = 5;
200 item_len = strlen(track_buf);
201 addtag("Track", key_len, track_buf, item_len, 0, 0);
202 tag_len += key_len + item_len;
203 nitem++;
204
205 for (j = 0; j < (sizeof(Ptis) / sizeof(*Ptis)); j++) {
206 char const * item_key = APE_keys[j], * item_value;
207 item_value = cdtext_get (Ptis[j], cdtext);
208 if (item_value != 0) {
209 key_len = strlen(item_key);
210 item_len = strlen(item_value);
211 addtag(item_key, key_len, item_value, item_len, 0, 0);
212 tag_len += key_len + item_len;
213 nitem++;
214 }
215 }
216
217 tag_len += 24 + nitem * 9;
218 offset_size = encodeSize(chap_pos, sample_offset, MPC_FALSE);
219 tag_len = encodeSize(tag_len + 4 + offset_size + 2, block_header + 2, MPC_TRUE);
220 fwrite(block_header, 1, tag_len + 2, in_file);
221 fwrite(sample_offset, 1, offset_size, in_file);
222 fwrite("\0\0\0\0", 1, 4, in_file); // put unknow chapter gain / peak
223 FinalizeTags(in_file, TAG_VERSION, TAG_NO_FOOTER | TAG_NO_PREAMBLE);
224 }
225
226 fwrite(tmp_buff, 1, stbuf.st_size - chap_pos - chap_size, in_file);
227 ftruncate(fileno(in_file), ftell(in_file));
228
229 fclose(in_file);
230 free(tmp_buff);
231
232 return MPC_STATUS_OK;
233 }
234
dump_chaps(mpc_demux * demux,char * chap_file,int chap_nb)235 mpc_status dump_chaps(mpc_demux * demux, char * chap_file, int chap_nb)
236 {
237 int i;
238 FILE * out_file;
239 mpc_chap_info const * chap;
240
241 if (chap_nb <= 0)
242 return MPC_STATUS_OK;
243
244 out_file = fopen(chap_file, "wb");
245 if (out_file == 0)
246 return !MPC_STATUS_OK;
247
248 for (i = 0; i < chap_nb; i++) {
249 chap = mpc_demux_chap(demux, i);
250 fprintf(out_file, "[%lli]\ngain=%i\npeak=%i\n", chap->sample, chap->gain, chap->peak);
251 if (chap->tag_size > 0) {
252 int item_count, j;
253 char const * tag = chap->tag;
254 item_count = tag[8] | (tag[9] << 8) | (tag[10] << 16) | (tag[11] << 24);
255 tag += 24;
256 for( j = 0; j < item_count; j++){
257 int key_len = strlen(tag + 8);
258 int value_len = tag[0] | (tag[1] << 8) | (tag[2] << 16) | (tag[3] << 24);
259 fprintf(out_file, "%s=\"%.*s\"\n", tag + 8, value_len, tag + 9 + key_len);
260 tag += 9 + key_len + value_len;
261 }
262 }
263 fprintf(out_file, "\n");
264 }
265
266 fclose(out_file);
267
268 return MPC_STATUS_OK;
269 }
270
main(int argc,char ** argv)271 int main(int argc, char **argv)
272 {
273 mpc_reader reader;
274 mpc_demux* demux;
275 mpc_streaminfo si;
276 char * mpc_file, * chap_file;
277 mpc_status err;
278 FILE * test_file;
279 int chap_nb;
280
281 fprintf(stderr, About, argv[0]);
282
283 if (argc != 3)
284 usage(argv[0]);
285
286 mpc_file = argv[1];
287 chap_file = argv[2];
288
289 err = mpc_reader_init_stdio(&reader, mpc_file);
290 if(err < 0) return !MPC_STATUS_OK;
291
292 demux = mpc_demux_init(&reader);
293 if(!demux) return !MPC_STATUS_OK;
294 mpc_demux_get_info(demux, &si);
295
296 if (si.stream_version < 8) {
297 fprintf(stderr, "this file cannot be edited, please convert it first to sv8 using mpc2sv8\n");
298 exit(!MPC_STATUS_OK);
299 }
300
301 chap_nb = mpc_demux_chap_nb(demux);
302
303 test_file = fopen(chap_file, "rb" );
304 if (test_file == 0) {
305 err = dump_chaps(demux, chap_file, chap_nb);
306 } else {
307 int len;
308 fclose(test_file);
309 len = strlen(chap_file);
310 if (strcasecmp(chap_file + len - 4, ".cue") == 0 || strcasecmp(chap_file + len - 4, ".toc") == 0)
311 err = add_chaps_cue(mpc_file, chap_file, demux, &si);
312 else if (strcasecmp(chap_file + len - 4, ".ini") == 0)
313 err = add_chaps_ini(mpc_file, chap_file, demux, &si);
314 else
315 err = !MPC_STATUS_OK;
316 }
317
318 mpc_demux_exit(demux);
319 mpc_reader_exit_stdio(&reader);
320 return err;
321 }
322