1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
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 ****************************************************************************/
43 #include "playlist_viewer.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];
57 i
=filetype_load_menu(menu
,sizeof(menu
)/sizeof(*menu
));
60 m
= menu_init( menu
, i
, NULL
, NULL
, NULL
, NULL
);
61 result
= menu_show(m
);
65 filetype_load_plugin(menu
[result
].desc
,selected_file
);
70 splash(HZ
*2, true, "No viewers found");
75 /* For playlist options */
76 struct playlist_args
{
81 static bool add_to_playlist(int position
, bool queue
)
83 bool new_playlist
= !(mpeg_status() & MPEG_STATUS_PLAY
);
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
)
94 if (global_settings
.recursive_dir_insert
!= RECURSE_ASK
)
95 recurse
= (bool)global_settings
.recursive_dir_insert
;
98 /* Ask if user wants to recurse directory */
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
));
113 int btn
= button_get(true);
120 /* ignore button releases */
121 if (!(btn
& BUTTON_REL
))
128 playlist_insert_directory(NULL
, selected_file
, position
, queue
,
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
138 if (global_settings
.playlist_shuffle
)
139 playlist_shuffle(current_tick
, -1);
141 status_set_playmode(STATUS_PLAY
);
143 onplay_result
= ONPLAY_START_PLAY
;
149 static bool view_playlist(void)
151 bool was_playing
= mpeg_status() & MPEG_STATUS_PLAY
;
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
;
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
;
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
;
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;
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;
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;
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;
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;
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;
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;
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
);
241 /* helper function to remove a non-empty directory */
242 static int remove_dir(char* dirname
, int len
)
246 int dirlen
= strlen(dirname
);
248 dir
= opendir(dirname
);
250 return -1; /* open error */
254 struct dirent
* entry
;
255 /* walk through the directory content */
256 entry
= readdir(dir
);
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 */
271 break; /* or better continue, delete what we can? */
274 { /* remove a file */
275 result
= remove(dirname
);
281 { /* remove the now empty directory */
282 dirname
[dirlen
] = '\0'; /* terminate to original length */
284 result
= rmdir(dirname
);
291 /* share code for file and directory deletion, saves space */
292 static bool delete_handler(bool is_dir
)
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
));
309 int btn
= button_get(true);
314 char pathname
[MAX_PATH
]; /* space to go deep */
315 strncpy(pathname
, selected_file
, sizeof pathname
);
316 res
= remove_dir(pathname
, sizeof(pathname
));
320 res
= remove(selected_file
);
324 onplay_result
= ONPLAY_RELOAD_DIR
;
326 lcd_puts(0,0,str(LANG_DELETED
));
327 lcd_puts_scroll(0,1,selected_file
);
335 /* ignore button releases */
336 if (!(btn
& BUTTON_REL
))
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)) {
365 lcd_puts(0,0,str(LANG_RENAME
));
366 lcd_puts(0,1,str(LANG_FAILED
));
371 onplay_result
= ONPLAY_RELOAD_DIR
;
377 static void xingupdate(int percent
)
381 snprintf(buf
, 32, "%d%%", percent
);
387 static int insert_data_in_file(char *fname
, int fpos
, char *buf
, int num_bytes
)
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
);
400 return 10*orig_fd
- 1;
403 fd
= creat(tmpname
, O_WRONLY
);
409 /* First, copy the initial portion (the ID3 tag) */
411 readlen
= read(orig_fd
, mp3buf
, fpos
);
415 return 10*readlen
- 3;
418 rc
= write(fd
, mp3buf
, readlen
);
426 /* Now insert the data into the file */
427 rc
= write(fd
, buf
, num_bytes
);
436 readlen
= read(orig_fd
, mp3buf
, mp3end
- mp3buf
);
440 return 10*readlen
- 7;
443 rc
= write(fd
, mp3buf
, readlen
);
449 } while(readlen
> 0);
454 /* Remove the old file */
460 /* Replace the old file with the new */
461 rc
= rename(tmpname
, fname
);
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
;
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 */
500 lcd_puts_scroll(0, 0, selected_file
);
505 rc
= mp3info(&entry
, selected_file
);
511 fd
= open(selected_file
, O_RDWR
);
517 flen
= lseek(fd
, 0, SEEK_END
);
521 num_frames
= count_mp3_frames(fd
, entry
.first_frame_offset
,
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
);
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 */
553 memset(mp3buf
, 0, unused_space
);
554 rc
= write(fd
, mp3buf
, unused_space
);
562 /* Then write the Xing header */
563 rc
= write(fd
, xingbuf
, framelen
);
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. */
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
);
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
);
615 DEBUGF("Not a VBR file\n");
616 splash(HZ
*2, true, str(LANG_VBRFIX_NOT_VBR
));
622 bool create_dir(void)
624 char dirname
[MAX_PATH
];
629 cwd
= getcwd(NULL
, 0);
630 memset(dirname
, 0, sizeof dirname
);
632 snprintf(dirname
, sizeof dirname
, "%s/",
635 pathlen
= strlen(dirname
);
636 rc
= kbd_input(dirname
+ pathlen
, (sizeof dirname
)-pathlen
);
640 rc
= mkdir(dirname
, 0);
642 splash(HZ
, true, "%s %s", str(LANG_CREATE_DIR
), str(LANG_FAILED
));
644 onplay_result
= ONPLAY_RELOAD_DIR
;
650 int onplay(char* file
, int attr
)
652 struct menu_item items
[6]; /* increase this if you add entries! */
655 onplay_result
= ONPLAY_OK
;
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
;
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
;
680 items
[i
].desc
= str(LANG_RENAME
);
681 items
[i
].voice_id
= LANG_RENAME
;
682 items
[i
].function
= rename_file
;
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
;
694 items
[i
].desc
= str(LANG_DELETE_DIR
);
695 items
[i
].voice_id
= LANG_DELETE_DIR
;
696 items
[i
].function
= delete_dir
;
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
;
709 items
[i
].desc
= str(LANG_CREATE_DIR
);
710 items
[i
].voice_id
= LANG_CREATE_DIR
;
711 items
[i
].function
= create_dir
;
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
);
718 items
[result
].function();
721 return onplay_result
;