Plugin/file type association system. Patch #879411 by Henrik Backe
[kugel-rb.git] / apps / onplay.c
blobd864b3b2a915988498538445c3c7f341d8bb8a59
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2002 Björn Stenberg
12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement.
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
18 ****************************************************************************/
19 #include <stdio.h>
20 #include <string.h>
21 #include <stdlib.h>
22 #include <stdbool.h>
24 #include "debug.h"
25 #include "sprintf.h"
26 #include "lcd.h"
27 #include "dir.h"
28 #include "file.h"
29 #include "mpeg.h"
30 #include "menu.h"
31 #include "lang.h"
32 #include "playlist.h"
33 #include "button.h"
34 #include "kernel.h"
35 #include "keyboard.h"
36 #include "mp3data.h"
37 #include "id3.h"
38 #include "screens.h"
39 #include "tree.h"
40 #include "buffer.h"
41 #include "settings.h"
42 #include "status.h"
43 #include "playlist_viewer.h"
44 #include "talk.h"
45 #include "onplay.h"
46 #include "filetypes.h"
48 static char* selected_file = NULL;
49 static int selected_file_attr = 0;
50 static int onplay_result = ONPLAY_OK;
52 static bool list_viewers(void)
54 struct menu_item menu[8];
55 int m, i, result;
57 i=filetype_load_menu(menu,sizeof(menu)/sizeof(*menu));
58 if (i)
60 m = menu_init( menu, i, NULL, NULL, NULL, NULL );
61 result = menu_show(m);
62 if (result >= 0)
64 menu_exit(m);
65 filetype_load_plugin(menu[result].desc,selected_file);
68 else
70 splash(HZ*2, true, "No viewers found");
72 return false;
75 /* For playlist options */
76 struct playlist_args {
77 int position;
78 bool queue;
81 static bool add_to_playlist(int position, bool queue)
83 bool new_playlist = !(mpeg_status() & MPEG_STATUS_PLAY);
85 if (new_playlist)
86 playlist_create(NULL, NULL);
88 if ((selected_file_attr & TREE_ATTR_MASK) == TREE_ATTR_MPA)
89 playlist_insert_track(NULL, selected_file, position, queue);
90 else if (selected_file_attr & ATTR_DIRECTORY)
92 bool recurse = false;
94 if (global_settings.recursive_dir_insert != RECURSE_ASK)
95 recurse = (bool)global_settings.recursive_dir_insert;
96 else
98 /* Ask if user wants to recurse directory */
99 bool exit = false;
101 lcd_clear_display();
102 lcd_puts_scroll(0,0,str(LANG_RECURSE_DIRECTORY_QUESTION));
103 lcd_puts_scroll(0,1,selected_file);
105 #ifdef HAVE_LCD_BITMAP
106 lcd_puts(0,3,str(LANG_CONFIRM_WITH_PLAY_RECORDER));
107 lcd_puts(0,4,str(LANG_CANCEL_WITH_ANY_RECORDER));
108 #endif
110 lcd_update();
112 while (!exit) {
113 int btn = button_get(true);
114 switch (btn) {
115 case BUTTON_PLAY:
116 recurse = true;
117 exit = true;
118 break;
119 default:
120 /* ignore button releases */
121 if (!(btn & BUTTON_REL))
122 exit = true;
123 break;
128 playlist_insert_directory(NULL, selected_file, position, queue,
129 recurse);
131 else if ((selected_file_attr & TREE_ATTR_MASK) == TREE_ATTR_M3U)
132 playlist_insert_playlist(NULL, selected_file, position, queue);
134 if (new_playlist && (playlist_amount() > 0))
136 /* nothing is currently playing so begin playing what we just
137 inserted */
138 if (global_settings.playlist_shuffle)
139 playlist_shuffle(current_tick, -1);
140 playlist_start(0,0);
141 status_set_playmode(STATUS_PLAY);
142 status_draw(false);
143 onplay_result = ONPLAY_START_PLAY;
146 return false;
149 static bool view_playlist(void)
151 bool was_playing = mpeg_status() & MPEG_STATUS_PLAY;
152 bool result;
154 result = playlist_viewer_ex(selected_file);
156 if (!was_playing && (mpeg_status() & MPEG_STATUS_PLAY) &&
157 onplay_result == ONPLAY_OK)
158 /* playlist was started from viewer */
159 onplay_result = ONPLAY_START_PLAY;
161 return result;
164 /* Sub-menu for playlist options */
165 static bool playlist_options(void)
167 struct menu_item items[7];
168 struct playlist_args args[7]; /* increase these 2 if you add entries! */
169 int m, i=0, pstart=0, result;
170 bool ret = false;
172 if ((selected_file_attr & TREE_ATTR_MASK) == TREE_ATTR_M3U)
174 items[i].desc = str(LANG_VIEW);
175 items[i].voice_id = LANG_VIEW;
176 items[i].function = view_playlist;
177 i++;
178 pstart++;
181 if (mpeg_status() & MPEG_STATUS_PLAY)
183 items[i].desc = str(LANG_INSERT);
184 items[i].voice_id = LANG_INSERT;
185 args[i].position = PLAYLIST_INSERT;
186 args[i].queue = false;
187 i++;
189 items[i].desc = str(LANG_INSERT_FIRST);
190 items[i].voice_id = LANG_INSERT_FIRST;
191 args[i].position = PLAYLIST_INSERT_FIRST;
192 args[i].queue = false;
193 i++;
195 items[i].desc = str(LANG_INSERT_LAST);
196 items[i].voice_id = LANG_INSERT_LAST;
197 args[i].position = PLAYLIST_INSERT_LAST;
198 args[i].queue = false;
199 i++;
201 items[i].desc = str(LANG_QUEUE);
202 items[i].voice_id = LANG_QUEUE;
203 args[i].position = PLAYLIST_INSERT;
204 args[i].queue = true;
205 i++;
207 items[i].desc = str(LANG_QUEUE_FIRST);
208 items[i].voice_id = LANG_QUEUE_FIRST;
209 args[i].position = PLAYLIST_INSERT_FIRST;
210 args[i].queue = true;
211 i++;
213 items[i].desc = str(LANG_QUEUE_LAST);
214 items[i].voice_id = LANG_QUEUE_LAST;
215 args[i].position = PLAYLIST_INSERT_LAST;
216 args[i].queue = true;
217 i++;
219 else if (((selected_file_attr & TREE_ATTR_MASK) == TREE_ATTR_MPA) ||
220 (selected_file_attr & ATTR_DIRECTORY))
222 items[i].desc = str(LANG_INSERT);
223 items[i].voice_id = LANG_INSERT;
224 args[i].position = PLAYLIST_INSERT;
225 args[i].queue = false;
226 i++;
229 m = menu_init( items, i, NULL, NULL, NULL, NULL );
230 result = menu_show(m);
231 if (result >= 0 && result < pstart)
232 ret = items[result].function();
233 else if (result >= pstart)
234 ret = add_to_playlist(args[result].position, args[result].queue);
235 menu_exit(m);
237 return ret;
241 /* helper function to remove a non-empty directory */
242 static int remove_dir(char* dirname, int len)
244 int result = 0;
245 DIR* dir;
246 int dirlen = strlen(dirname);
248 dir = opendir(dirname);
249 if (!dir)
250 return -1; /* open error */
252 while(true)
254 struct dirent* entry;
255 /* walk through the directory content */
256 entry = readdir(dir);
257 if (!entry)
258 break;
260 /* append name to current directory */
261 snprintf(dirname+dirlen, len-dirlen, "/%s", entry->d_name);
263 if (entry->attribute & ATTR_DIRECTORY)
264 { /* remove a subdirectory */
265 if (!strcmp(entry->d_name, ".") ||
266 !strcmp(entry->d_name, ".."))
267 continue; /* skip these */
269 result = remove_dir(dirname, len); /* recursion */
270 if (result)
271 break; /* or better continue, delete what we can? */
273 else
274 { /* remove a file */
275 result = remove(dirname);
278 closedir(dir);
280 if (!result)
281 { /* remove the now empty directory */
282 dirname[dirlen] = '\0'; /* terminate to original length */
284 result = rmdir(dirname);
287 return result;
291 /* share code for file and directory deletion, saves space */
292 static bool delete_handler(bool is_dir)
294 bool exit = false;
295 int res;
297 lcd_clear_display();
298 lcd_puts(0,0,str(LANG_REALLY_DELETE));
299 lcd_puts_scroll(0,1,selected_file);
301 #ifdef HAVE_LCD_BITMAP
302 lcd_puts(0,3,str(LANG_CONFIRM_WITH_PLAY_RECORDER));
303 lcd_puts(0,4,str(LANG_CANCEL_WITH_ANY_RECORDER));
304 #endif
306 lcd_update();
308 while (!exit) {
309 int btn = button_get(true);
310 switch (btn) {
311 case BUTTON_PLAY:
312 if (is_dir)
314 char pathname[MAX_PATH]; /* space to go deep */
315 strncpy(pathname, selected_file, sizeof pathname);
316 res = remove_dir(pathname, sizeof(pathname));
318 else
320 res = remove(selected_file);
323 if (!res) {
324 onplay_result = ONPLAY_RELOAD_DIR;
325 lcd_clear_display();
326 lcd_puts(0,0,str(LANG_DELETED));
327 lcd_puts_scroll(0,1,selected_file);
328 lcd_update();
329 sleep(HZ);
330 exit = true;
332 break;
334 default:
335 /* ignore button releases */
336 if (!(btn & BUTTON_REL))
337 exit = true;
338 break;
341 return false;
345 static bool delete_file(void)
347 return delete_handler(false);
350 static bool delete_dir(void)
352 return delete_handler(true);
355 static bool rename_file(void)
357 char newname[MAX_PATH];
358 char* ptr = strrchr(selected_file, '/') + 1;
359 int pathlen = (ptr - selected_file);
360 strncpy(newname, selected_file, sizeof newname);
361 if (!kbd_input(newname + pathlen, (sizeof newname)-pathlen)) {
362 if (!strlen(selected_file+pathlen) ||
363 (rename(selected_file, newname) < 0)) {
364 lcd_clear_display();
365 lcd_puts(0,0,str(LANG_RENAME));
366 lcd_puts(0,1,str(LANG_FAILED));
367 lcd_update();
368 sleep(HZ*2);
370 else
371 onplay_result = ONPLAY_RELOAD_DIR;
374 return false;
377 static void xingupdate(int percent)
379 char buf[32];
381 snprintf(buf, 32, "%d%%", percent);
382 lcd_puts(0, 1, buf);
383 lcd_update();
387 static int insert_data_in_file(char *fname, int fpos, char *buf, int num_bytes)
389 int readlen;
390 int rc;
391 int orig_fd, fd;
392 char tmpname[MAX_PATH];
394 talk_buffer_steal(); /* we use the mp3 buffer, need to tell */
396 snprintf(tmpname, MAX_PATH, "%s.tmp", fname);
398 orig_fd = open(fname, O_RDONLY);
399 if(orig_fd < 0) {
400 return 10*orig_fd - 1;
403 fd = creat(tmpname, O_WRONLY);
404 if(fd < 0) {
405 close(orig_fd);
406 return 10*fd - 2;
409 /* First, copy the initial portion (the ID3 tag) */
410 if(fpos) {
411 readlen = read(orig_fd, mp3buf, fpos);
412 if(readlen < 0) {
413 close(fd);
414 close(orig_fd);
415 return 10*readlen - 3;
418 rc = write(fd, mp3buf, readlen);
419 if(rc < 0) {
420 close(fd);
421 close(orig_fd);
422 return 10*rc - 4;
426 /* Now insert the data into the file */
427 rc = write(fd, buf, num_bytes);
428 if(rc < 0) {
429 close(orig_fd);
430 close(fd);
431 return 10*rc - 5;
434 /* Copy the file */
435 do {
436 readlen = read(orig_fd, mp3buf, mp3end - mp3buf);
437 if(readlen < 0) {
438 close(fd);
439 close(orig_fd);
440 return 10*readlen - 7;
443 rc = write(fd, mp3buf, readlen);
444 if(rc < 0) {
445 close(fd);
446 close(orig_fd);
447 return 10*rc - 8;
449 } while(readlen > 0);
451 close(fd);
452 close(orig_fd);
454 /* Remove the old file */
455 rc = remove(fname);
456 if(rc < 0) {
457 return 10*rc - 9;
460 /* Replace the old file with the new */
461 rc = rename(tmpname, fname);
462 if(rc < 0) {
463 return 10*rc - 9;
466 return 0;
469 static void fileerror(int rc)
471 splash(HZ*2, true, "File error: %d", rc);
474 static const unsigned char empty_id3_header[] =
476 'I', 'D', '3', 0x04, 0x00, 0x00,
477 0x00, 0x00, 0x1f, 0x76 /* Size is 4096 minus 10 bytes for the header */
480 static bool vbr_fix(void)
482 unsigned char xingbuf[1500];
483 struct mp3entry entry;
484 int fd;
485 int rc;
486 int flen;
487 int num_frames;
488 int numbytes;
489 int framelen;
490 int unused_space;
492 if(mpeg_status()) {
493 splash(HZ*2, true, str(LANG_VBRFIX_STOP_PLAY));
494 return onplay_result;
497 talk_buffer_steal(); /* we use the mp3 buffer, need to tell */
499 lcd_clear_display();
500 lcd_puts_scroll(0, 0, selected_file);
501 lcd_update();
503 xingupdate(0);
505 rc = mp3info(&entry, selected_file);
506 if(rc < 0) {
507 fileerror(rc);
508 return true;
511 fd = open(selected_file, O_RDWR);
512 if(fd < 0) {
513 fileerror(fd);
514 return true;
517 flen = lseek(fd, 0, SEEK_END);
519 xingupdate(0);
521 num_frames = count_mp3_frames(fd, entry.first_frame_offset,
522 flen, xingupdate);
524 if(num_frames) {
525 /* Note: We don't need to pass a template header because it will be
526 taken from the mpeg stream */
527 framelen = create_xing_header(fd, entry.first_frame_offset,
528 flen, xingbuf, num_frames,
529 0, xingupdate, true);
531 /* Try to fit the Xing header first in the stream. Replace the existing
532 VBR header if there is one, else see if there is room between the
533 ID3 tag and the first MP3 frame. */
534 if(entry.first_frame_offset - entry.id3v2len >=
535 (unsigned int)framelen) {
536 DEBUGF("Using existing space between ID3 and first frame\n");
538 /* Seek to the beginning of the unused space */
539 rc = lseek(fd, entry.id3v2len, SEEK_SET);
540 if(rc < 0) {
541 close(fd);
542 fileerror(rc);
543 return true;
546 unused_space =
547 entry.first_frame_offset - entry.id3v2len - framelen;
549 /* Fill the unused space with 0's (using the MP3 buffer)
550 and write it to the file */
551 if(unused_space)
553 memset(mp3buf, 0, unused_space);
554 rc = write(fd, mp3buf, unused_space);
555 if(rc < 0) {
556 close(fd);
557 fileerror(rc);
558 return true;
562 /* Then write the Xing header */
563 rc = write(fd, xingbuf, framelen);
564 if(rc < 0) {
565 close(fd);
566 fileerror(rc);
567 return true;
570 close(fd);
571 } else {
572 /* If not, insert some space. If there is an ID3 tag in the
573 file we only insert just enough to squeeze the Xing header
574 in. If not, we insert an additional empty ID3 tag of 4K. */
576 close(fd);
578 /* Nasty trick alert! The insert_data_in_file() function
579 uses the MP3 buffer when copying the data. We assume
580 that the ID3 tag isn't longer than 1MB so the xing
581 buffer won't be overwritten. */
583 if(entry.first_frame_offset) {
584 DEBUGF("Inserting %d bytes\n", framelen);
585 numbytes = framelen;
586 } else {
587 DEBUGF("Inserting 4096+%d bytes\n", framelen);
588 numbytes = 4096 + framelen;
590 memset(mp3buf + 0x100000, 0, numbytes);
592 /* Insert the ID3 header */
593 memcpy(mp3buf + 0x100000, empty_id3_header,
594 sizeof(empty_id3_header));
597 /* Copy the Xing header */
598 memcpy(mp3buf + 0x100000 + numbytes - framelen, xingbuf, framelen);
600 rc = insert_data_in_file(selected_file,
601 entry.first_frame_offset,
602 mp3buf + 0x100000, numbytes);
604 if(rc < 0) {
605 fileerror(rc);
606 return true;
610 xingupdate(100);
612 else
614 /* Not a VBR file */
615 DEBUGF("Not a VBR file\n");
616 splash(HZ*2, true, str(LANG_VBRFIX_NOT_VBR));
619 return false;
622 bool create_dir(void)
624 char dirname[MAX_PATH];
625 char *cwd;
626 int rc;
627 int pathlen;
629 cwd = getcwd(NULL, 0);
630 memset(dirname, 0, sizeof dirname);
632 snprintf(dirname, sizeof dirname, "%s/",
633 cwd[1] ? cwd : "");
635 pathlen = strlen(dirname);
636 rc = kbd_input(dirname + pathlen, (sizeof dirname)-pathlen);
637 if(rc < 0)
638 return false;
640 rc = mkdir(dirname, 0);
641 if(rc < 0) {
642 splash(HZ, true, "%s %s", str(LANG_CREATE_DIR), str(LANG_FAILED));
643 } else {
644 onplay_result = ONPLAY_RELOAD_DIR;
647 return true;
650 int onplay(char* file, int attr)
652 struct menu_item items[6]; /* increase this if you add entries! */
653 int m, i=0, result;
655 onplay_result = ONPLAY_OK;
657 if(file)
659 selected_file = file;
660 selected_file_attr = attr;
662 if (!(attr & ATTR_DIRECTORY))
664 items[i].desc = str(LANG_ONPLAY_OPEN_WITH);
665 items[i].voice_id = LANG_ONPLAY_OPEN_WITH;
666 items[i].function = list_viewers;
667 i++;
670 if (((attr & TREE_ATTR_MASK) == TREE_ATTR_MPA) ||
671 (attr & ATTR_DIRECTORY) ||
672 ((attr & TREE_ATTR_MASK) == TREE_ATTR_M3U))
674 items[i].desc = str(LANG_PLAYINDICES_PLAYLIST);
675 items[i].voice_id = LANG_PLAYINDICES_PLAYLIST;
676 items[i].function = playlist_options;
677 i++;
680 items[i].desc = str(LANG_RENAME);
681 items[i].voice_id = LANG_RENAME;
682 items[i].function = rename_file;
683 i++;
685 if (!(attr & ATTR_DIRECTORY))
687 items[i].desc = str(LANG_DELETE);
688 items[i].voice_id = LANG_DELETE;
689 items[i].function = delete_file;
690 i++;
692 else
694 items[i].desc = str(LANG_DELETE_DIR);
695 items[i].voice_id = LANG_DELETE_DIR;
696 items[i].function = delete_dir;
697 i++;
700 if ((attr & TREE_ATTR_MASK) == TREE_ATTR_MPA)
702 items[i].desc = str(LANG_VBRFIX);
703 items[i].voice_id = LANG_VBRFIX;
704 items[i].function = vbr_fix;
705 i++;
709 items[i].desc = str(LANG_CREATE_DIR);
710 items[i].voice_id = LANG_CREATE_DIR;
711 items[i].function = create_dir;
712 i++;
714 /* DIY menu handling, since we want to exit after selection */
715 m = menu_init( items, i, NULL, NULL, NULL, NULL );
716 result = menu_show(m);
717 if (result >= 0)
718 items[result].function();
719 menu_exit(m);
721 return onplay_result;