]> git.sesse.net Git - vlc/blob - modules/gui/macosx/ConvertAndSave.m
macosx: fixed random debug message crash
[vlc] / modules / gui / macosx / ConvertAndSave.m
1 /*****************************************************************************
2  * ConvertAndSave.h: MacOS X interface module
3  *****************************************************************************
4  * Copyright (C) 2012 Felix Paul Kühne
5  * $Id$
6  *
7  * Authors: Felix Paul Kühne <fkuehne -at- videolan -dot- org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 #import "ConvertAndSave.h"
25 #import "intf.h"
26 #import "playlist.h"
27 #import <vlc_common.h>
28 #import <vlc_url.h>
29
30 /* mini doc:
31  * the used NSMatrix includes a bunch of cells referenced most easily by tags. There you go: */
32 #define MPEGTS 0
33 #define WEBM 1
34 #define OGG 2
35 #define MP4 3
36 #define MPEGPS 4
37 #define MJPEG 5
38 #define WAV 6
39 #define FLV 7
40 #define MPEG1 8
41 #define MKV 9
42 #define RAW 10
43 #define AVI 11
44 #define ASF 12
45 /* 13-15 are present, but not set */
46
47 @implementation VLCConvertAndSave
48
49 @synthesize MRL=_MRL, outputDestination=_outputDestination, profileNames=_profileNames, profileValueList=_profileValueList, currentProfile=_currentProfile;
50
51 static VLCConvertAndSave *_o_sharedInstance = nil;
52
53 #pragma mark -
54 #pragma mark Initialization
55
56 + (void)initialize{
57     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
58
59     /* We are using the same format as the Qt4 intf here:
60      * Container(string), transcode video(bool), transcode audio(bool),
61      * use subtitles(bool), video codec(string), video bitrate(integer),
62      * scale(float), fps(float), width(integer, height(integer),
63      * audio codec(string), audio bitrate(integer), channels(integer),
64      * samplerate(integer), subtitle codec(string), subtitle overlay(bool) */
65     NSArray * defaultProfiles = [[NSArray alloc] initWithObjects:
66                                  @"mp4;1;1;0;h264;0;0;0;0;0;mpga;128;2;44100;0;1",
67                                  @"webm;1;1;0;VP80;2000;0;0;0;0;vorb;128;2;44100;0;1",
68                                  @"ts;1;1;0;h264;800;1;0;0;0;mpga;128;2;44100;0;0",
69                                  @"ts;1;1;0;drac;800;1;0;0;0;mpga;128;2;44100;0;0",
70                                  @"ogg;1;1;0;theo;800;1;0;0;0;vorb;128;2;44100;0;0",
71                                  @"ogg;1;1;0;theo;800;1;0;0;0;flac;128;2;44100;0;0",
72                                  @"ts;1;1;0;mp2v;800;1;0;0;0;mpga;128;2;44100;0;0",
73                                  @"asf;1;1;0;WMV2;800;1;0;0;0;wma2;128;2;44100;0;0",
74                                  @"asf;1;1;0;DIV3;800;1;0;0;0;mp3;128;2;44100;0;0",
75                                  @"ogg;1;1;0;none;800;1;0;0;0;vorb;128;2;44100;none;0",
76                                  @"raw;1;1;0;none;800;1;0;0;0;mp3;128;2;44100;none;0",
77                                  @"mp4;1;1;0;none;800;1;0;0;0;mpga;128;2;44100;none;0",
78                                  @"raw;1;1;0;none;800;1;0;0;0;flac;128;2;44100;none;0",
79                                  @"wav;1;1;0;none;800;1;0;0;0;s16l;128;2;44100;none;0", nil];
80
81     NSArray * defaultProfileNames = [[NSArray alloc] initWithObjects:
82                                      @"Video - H.264 + MP3 (MP4)",
83                                      @"Video - VP80 + Vorbis (Webm)",
84                                      @"Video - H.264 + MP3 (TS)",
85                                      @"Video - Dirac + MP3 (TS)",
86                                      @"Video - Theora + Vorbis (OGG)",
87                                      @"Video - Theora + Flac (OGG)",
88                                      @"Video - MPEG-2 + MPGA (TS)",
89                                      @"Video - WMV + WMA (ASF)",
90                                      @"Video - DIV3 + MP3 (ASF)",
91                                      @"Audio - Vorbis (OGG)",
92                                      @"Audio - MP3",
93                                      @"Audio - MP3 (MP4)",
94                                      @"Audio - FLAC",
95                                      @"Audio - CD",
96                                      nil];
97
98     NSDictionary *appDefaults = [NSDictionary dictionaryWithObjectsAndKeys:defaultProfiles, @"CASProfiles", defaultProfileNames, @"CASProfileNames", nil];
99
100     [defaults registerDefaults:appDefaults];
101     [defaultProfiles release];
102 }
103
104 + (VLCConvertAndSave *)sharedInstance
105 {
106     return _o_sharedInstance ? _o_sharedInstance : [[self alloc] init];
107 }
108
109 - (id)init
110 {
111     if (_o_sharedInstance) {
112         [self dealloc];
113     } else {
114         _o_sharedInstance = [super init];
115     }
116
117     return _o_sharedInstance;
118 }
119
120 - (void)dealloc
121 {
122     if (_currentProfile)
123         [_currentProfile release];
124
125     [_profileNames release];
126     [_profileValueList release];
127     [_videoCodecs release];
128     [_audioCodecs release];
129     [_subsCodecs release];
130
131     [super dealloc];
132 }
133
134 - (void)awakeFromNib
135 {
136     [_window setTitle: _NS("Convert & Save")];
137     [_ok_btn setTitle: _NS("Save")];
138     [_drop_lbl setStringValue: _NS("Drop media here")];
139     [_drop_btn setTitle: _NS("Open media...")];
140     [_profile_lbl setStringValue: _NS("Choose Profile")];
141     [_profile_btn setTitle: _NS("Customize")];
142     [_destination_lbl setStringValue: _NS("Choose Destination")];
143     [_destination_filename_stub_lbl setStringValue: _NS("Choose an output location")];
144     [_destination_filename_lbl setHidden: YES];
145     [_destination_browse_btn setTitle:_NS("Browse...")];
146     [_destination_stream_btn setTitle:_NS("Setup Streaming...")];
147     [_destination_stream_lbl setStringValue:@"Select Streaming Method"];
148     [_destination_itwantafile_btn setTitle:_NS("Save as File")];
149     [_destination_itwantastream_btn setTitle:_NS("Stream")];
150     [_destination_cancel_btn setHidden:YES];
151     [_customize_ok_btn setTitle: _NS("Apply")];
152     [_customize_cancel_btn setTitle: _NS("Cancel")];
153     [[_customize_tabview tabViewItemAtIndex:0] setLabel: _NS("Encapsulation")];
154     [[_customize_tabview tabViewItemAtIndex:1] setLabel: _NS("Video codec")];
155     [[_customize_tabview tabViewItemAtIndex:2] setLabel: _NS("Audio codec")];
156     [[_customize_tabview tabViewItemAtIndex:3] setLabel: _NS("Subtitles")];
157     [_customize_tabview selectTabViewItemAtIndex: 0];
158     [_customize_vid_ckb setTitle: _NS("Video")];
159     [_customize_vid_keep_ckb setTitle: _NS("Keep original video track")];
160     [_customize_vid_codec_lbl setStringValue: _NS("Codec")];
161     [_customize_vid_bitrate_lbl setStringValue: _NS("Bitrate")];
162     [_customize_vid_framerate_lbl setStringValue: _NS("Frame Rate")];
163     [_customize_vid_res_box setTitle: _NS("Resolution")];
164     [_customize_vid_res_lbl setStringValue: _NS("You just need to fill one of the three following parameters, VLC will autodetect the other using the original aspect ratio")];
165     [_customize_vid_width_lbl setStringValue: _NS("Width")];
166     [_customize_vid_height_lbl setStringValue: _NS("Height")];
167     [_customize_vid_scale_lbl setStringValue: _NS("Scale")];
168     [_customize_aud_ckb setTitle: _NS("Audio")];
169     [_customize_aud_keep_ckb setTitle: _NS("Keep original audio track")];
170     [_customize_aud_codec_lbl setStringValue: _NS("Codec")];
171     [_customize_aud_bitrate_lbl setStringValue: _NS("Bitrate")];
172     [_customize_aud_channels_lbl setStringValue: _NS("Channels")];
173     [_customize_aud_samplerate_lbl setStringValue: _NS("Sample Rate")];
174     [_customize_subs_ckb setTitle: _NS("Subtitles")];
175     [_customize_subs_overlay_ckb setTitle: _NS("Overlay subtitles on the video")];
176     [_stream_ok_btn setTitle:_NS("Close")];
177     [_stream_destination_lbl setStringValue:_NS("Stream Destination")];
178     [_stream_announcement_lbl setStringValue:_NS("Stream Announcement")];
179     [_stream_type_lbl setStringValue:_NS("Type")];
180     [_stream_address_lbl setStringValue:_NS("Address")];
181     [_stream_ttl_lbl setStringValue:_NS("TTL")];
182     [_stream_ttl_fld setEnabled:NO];
183     [_stream_ttl_stepper setEnabled:NO];
184     [_stream_port_lbl setStringValue:_NS("Port")];
185     [_stream_sap_ckb setStringValue:_NS("SAP Announcement")];
186     [_stream_http_ckb setStringValue:_NS("HTTP Announcement")];
187     [_stream_rtsp_ckb setStringValue:_NS("RTSP Announcement")];
188     [_stream_sdp_ckb setStringValue:_NS("Export SDP as file")];
189     [_stream_sap_ckb setState:NSOffState];
190     [_stream_http_ckb setState:NSOffState];
191     [_stream_rtsp_ckb setState:NSOffState];
192     [_stream_sdp_ckb setState:NSOffState];
193
194     /* there is no way to hide single cells, so replace the existing ones with empty cells.. */
195     id blankCell = [[[NSCell alloc] init] autorelease];
196     [blankCell setEnabled:NO];
197     [_customize_encap_matrix putCell:blankCell atRow:3 column:1];
198     [_customize_encap_matrix putCell:blankCell atRow:3 column:2];
199     [_customize_encap_matrix putCell:blankCell atRow:3 column:3];
200
201     /* fetch profiles from defaults */
202     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
203     _profileValueList = [[defaults arrayForKey:@"CASProfiles"] retain];
204     _profileNames = [[defaults arrayForKey:@"CASProfileNames"] retain];
205
206     [_profile_pop removeAllItems];
207     [_profile_pop addItemsWithTitles:_profileNames];
208     [_profile_pop addItemWithTitle:_NS("Custom")];
209
210     _videoCodecs = [[NSArray alloc] initWithObjects:
211                     [NSArray arrayWithObjects:@"MPEG-1", @"MPEG-2", @"MPEG-4", @"DIVX 1", @"DIVX 2", @"DIVX 3", @"H.263", @"H.264", @"VP8", @"WMV1", @"WMV2", @"M-JPEG", @"Theora", @"Dirac", nil],
212                     [NSArray arrayWithObjects:@"mpgv", @"mp2v", @"mp4v", @"DIV1", @"DIV2", @"DIV3", @"H263", @"h264", @"VP80", @"WMV1", @"WMV2", @"MJPG", @"theo", @"drac", nil],
213                     nil];
214     _audioCodecs = [[NSArray alloc] initWithObjects:
215                     [NSArray arrayWithObjects:@"MPEG Audio", @"MP3", @"MPEG 4 Audio (AAC)", @"A52/AC-3", @"Vorbis", @"Flac", @"Speex", @"WAV", @"WMA2", nil],
216                     [NSArray arrayWithObjects:@"mpga", @"mp3", @"mp4a", @"a52", @"vorb", @"flac", @"spx", @"s16l", @"wma2", nil],
217                     nil];
218     _subsCodecs = [[NSArray alloc] initWithObjects:
219                    [NSArray arrayWithObjects:@"DVB subtitle", @"T.140", nil],
220                    [NSArray arrayWithObjects:@"dvbs", @"t140", nil],
221                    nil];
222
223     [_customize_vid_codec_pop removeAllItems];
224     [_customize_vid_scale_pop removeAllItems];
225     [_customize_aud_codec_pop removeAllItems];
226     [_customize_aud_samplerate_pop removeAllItems];
227     [_customize_subs_pop removeAllItems];
228
229     [_customize_vid_codec_pop addItemsWithTitles:[_videoCodecs objectAtIndex:0]];
230     [_customize_aud_codec_pop addItemsWithTitles:[_audioCodecs objectAtIndex:0]];
231     [_customize_subs_pop addItemsWithTitles:[_subsCodecs objectAtIndex:0]];
232
233     [_customize_aud_samplerate_pop addItemWithTitle:@"8000"];
234     [_customize_aud_samplerate_pop addItemWithTitle:@"11025"];
235     [_customize_aud_samplerate_pop addItemWithTitle:@"22050"];
236     [_customize_aud_samplerate_pop addItemWithTitle:@"44100"];
237     [_customize_aud_samplerate_pop addItemWithTitle:@"48000"];
238
239     [_customize_vid_scale_pop addItemWithTitle:@"1"];
240     [_customize_vid_scale_pop addItemWithTitle:@"0.25"];
241     [_customize_vid_scale_pop addItemWithTitle:@"0.5"];
242     [_customize_vid_scale_pop addItemWithTitle:@"0.75"];
243     [_customize_vid_scale_pop addItemWithTitle:@"1.25"];
244     [_customize_vid_scale_pop addItemWithTitle:@"1.5"];
245     [_customize_vid_scale_pop addItemWithTitle:@"1.75"];
246     [_customize_vid_scale_pop addItemWithTitle:@"2"];
247
248     [_ok_btn setEnabled: NO];
249
250     [self resetCustomizationSheetBasedOnProfile:[_profileValueList objectAtIndex:0]];
251 }
252
253 # pragma mark -
254 # pragma mark Code to Communicate with other objects
255
256 - (void)toggleWindow
257 {
258     [_window makeKeyAndOrderFront: nil];
259 }
260
261 # pragma mark -
262 # pragma mark User Interaction
263
264 - (IBAction)finalizePanel:(id)sender
265 {
266     playlist_t * p_playlist = pl_Get(VLCIntf);
267
268     input_item_t *p_input = input_item_New([_MRL UTF8String], [[_dropin_media_lbl stringValue] UTF8String]);
269     if (!p_input)
270         return;
271
272     input_item_AddOption(p_input, [[self composedOptions] UTF8String], VLC_INPUT_OPTION_TRUSTED);
273
274     int returnValue;
275     returnValue = playlist_AddInput( p_playlist, p_input, PLAYLIST_STOP, PLAYLIST_END, true, pl_Unlocked );
276
277     if (returnValue == VLC_SUCCESS) {
278         /* let's "play" */
279         PL_LOCK;
280         playlist_item_t *p_item = playlist_ItemGetByInput( p_playlist, p_input );
281         playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, NULL,
282                          p_item );
283         PL_UNLOCK;
284     }
285     else
286         msg_Err( VLCIntf, "CAS: playlist add input failed :(");
287
288     /* we're done with this input */
289     vlc_gc_decref( p_input );
290
291     [_window performClose:sender];
292 }
293
294 - (IBAction)openMedia:(id)sender
295 {
296     /* preliminary implementation until the open panel is cleaned up */
297     NSOpenPanel * openPanel = [NSOpenPanel openPanel];
298     [openPanel setCanChooseDirectories:NO];
299     [openPanel setResolvesAliases:YES];
300     [openPanel setAllowsMultipleSelection:NO];
301     [openPanel beginSheetForDirectory:nil file:nil types:nil modalForWindow: _window modalDelegate:self didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:) contextInfo:nil];
302 }
303
304 - (void)openPanelDidEnd:(NSOpenPanel *)panel returnCode:(int)returnCode  contextInfo:(void  *)contextInfo
305 {
306     if (returnCode == NSOKButton)
307     {
308         [self setMRL: [NSString stringWithUTF8String:vlc_path2uri([[[panel URL] path] UTF8String], NULL)]];
309         [self updateOKButton];
310         [self updateDropView];
311     }
312 }
313
314 - (IBAction)switchProfile:(id)sender
315 {
316     NSUInteger index = [_profile_pop indexOfSelectedItem];
317     if (index < ([_profileValueList count] - 1))
318         [self resetCustomizationSheetBasedOnProfile:[_profileValueList objectAtIndex:index]];
319 }
320
321 - (IBAction)customizeProfile:(id)sender
322 {
323     [NSApp beginSheet:_customize_panel modalForWindow:_window modalDelegate:self didEndSelector:NULL contextInfo:nil];
324 }
325
326 - (IBAction)closeCustomizationSheet:(id)sender
327 {
328     [_customize_panel orderOut:sender];
329     [NSApp endSheet: _customize_panel];
330
331     /* update current profile based upon the sheet's values */
332     /* Container(string), transcode video(bool), transcode audio(bool),
333      * use subtitles(bool), video codec(string), video bitrate(integer),
334      * scale(float), fps(float), width(integer, height(integer),
335      * audio codec(string), audio bitrate(integer), channels(integer),
336      * samplerate(integer), subtitle codec(string), subtitle overlay(bool) */
337
338     if (sender == _customize_ok_btn && [_currentProfile count] == 16) {
339         NSInteger i;
340         [_currentProfile replaceObjectAtIndex:0 withObject:[self currentEncapsulationFormatAsFileExtension:NO]];
341         [_currentProfile replaceObjectAtIndex:1 withObject:[NSString stringWithFormat:@"%li", [_customize_vid_ckb state]]];
342         [_currentProfile replaceObjectAtIndex:2 withObject:[NSString stringWithFormat:@"%li", [_customize_aud_ckb state]]];
343         [_currentProfile replaceObjectAtIndex:3 withObject:[NSString stringWithFormat:@"%li", [_customize_subs_ckb state]]];
344         i = [_customize_vid_codec_pop indexOfSelectedItem];
345         if (i >= 0)
346             [_currentProfile replaceObjectAtIndex:4 withObject:[_videoCodecs objectAtIndex:i]];
347         else
348             [_currentProfile replaceObjectAtIndex:4 withObject:@"none"];
349         [_currentProfile replaceObjectAtIndex:5 withObject:[_customize_vid_bitrate_fld stringValue]];
350         [_currentProfile replaceObjectAtIndex:6 withObject:[[_customize_vid_scale_pop selectedItem] title]];
351         [_currentProfile replaceObjectAtIndex:7 withObject:[_customize_vid_framerate_fld stringValue]];
352         [_currentProfile replaceObjectAtIndex:8 withObject:[_customize_vid_width_fld stringValue]];
353         [_currentProfile replaceObjectAtIndex:9 withObject:[_customize_vid_height_fld stringValue]];
354         i = [_customize_aud_codec_pop indexOfSelectedItem];
355         if (i >= 0)
356             [_currentProfile replaceObjectAtIndex:10 withObject:[_audioCodecs objectAtIndex:i]];
357         else
358             [_currentProfile replaceObjectAtIndex:10 withObject:@"none"];
359         [_currentProfile replaceObjectAtIndex:11 withObject:[_customize_aud_bitrate_fld stringValue]];
360         [_currentProfile replaceObjectAtIndex:12 withObject:[_customize_aud_channels_fld stringValue]];
361         [_currentProfile replaceObjectAtIndex:13 withObject:[[_customize_aud_samplerate_pop selectedItem] title]];
362         i = [_customize_subs_pop indexOfSelectedItem];
363         if (i >= 0)
364             [_currentProfile replaceObjectAtIndex:14 withObject:[_subsCodecs objectAtIndex:i]];
365         else
366             [_currentProfile replaceObjectAtIndex:14 withObject:@"none"];
367         [_currentProfile replaceObjectAtIndex:15 withObject:[NSString stringWithFormat:@"%li", [_customize_subs_overlay_ckb state]]];
368     }
369 }
370
371 - (IBAction)iWantAFile:(id)sender
372 {
373     NSRect boxFrame = [_destination_box frame];
374     NSRect subViewFrame = [_destination_itwantafile_view frame];
375     subViewFrame.origin.x = (boxFrame.size.width - subViewFrame.size.width) / 2;
376     subViewFrame.origin.y = ((boxFrame.size.height - subViewFrame.size.height) / 2) - 15.;
377     [_destination_itwantafile_view setFrame: subViewFrame];
378     [[_destination_itwantafile_btn animator] setHidden: YES];
379     [[_destination_itwantastream_btn animator] setHidden: YES];
380     [_destination_box performSelector:@selector(addSubview:) withObject:_destination_itwantafile_view afterDelay:0.2];
381     [[_destination_cancel_btn animator] setHidden:NO];
382     b_streaming = NO;
383     [_ok_btn setTitle:_NS("Save")];
384 }
385
386 - (IBAction)iWantAStream:(id)sender
387 {
388     NSRect boxFrame = [_destination_box frame];
389     NSRect subViewFrame = [_destination_itwantastream_view frame];
390     subViewFrame.origin.x = (boxFrame.size.width - subViewFrame.size.width) / 2;
391     subViewFrame.origin.y = ((boxFrame.size.height - subViewFrame.size.height) / 2) - 15.;
392     [_destination_itwantastream_view setFrame: subViewFrame];
393     [[_destination_itwantafile_btn animator] setHidden: YES];
394     [[_destination_itwantastream_btn animator] setHidden: YES];
395     [_destination_box performSelector:@selector(addSubview:) withObject:_destination_itwantastream_view afterDelay:0.2];
396     [[_destination_cancel_btn animator] setHidden:NO];
397     b_streaming = YES;
398     [_ok_btn setTitle:_NS("Stream")];
399 }
400
401 - (IBAction)cancelDestination:(id)sender
402 {
403     if ([_destination_itwantastream_view superview] != nil)
404         [_destination_itwantastream_view removeFromSuperview];
405     if ([_destination_itwantafile_view superview] != nil)
406         [_destination_itwantafile_view removeFromSuperview];
407
408     [_destination_cancel_btn setHidden:YES];
409     [[_destination_itwantafile_btn animator] setHidden: NO];
410     [[_destination_itwantastream_btn animator] setHidden: NO];
411     b_streaming = NO;
412 }
413
414 - (IBAction)browseFileDestination:(id)sender
415 {
416     NSSavePanel * saveFilePanel = [NSSavePanel savePanel];
417     [saveFilePanel setCanSelectHiddenExtension: YES];
418     [saveFilePanel setCanCreateDirectories: YES];
419     if ([[_customize_encap_matrix selectedCell] tag] != RAW) // there is no clever guess for this
420         [saveFilePanel setAllowedFileTypes:[NSArray arrayWithObject:[self currentEncapsulationFormatAsFileExtension:YES]]];
421     [saveFilePanel beginSheetForDirectory:nil file:nil modalForWindow:_window modalDelegate:self didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:) contextInfo:nil];
422 }
423
424 - (void)savePanelDidEnd:(NSSavePanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
425 {
426     if (returnCode == NSOKButton) {
427         [self setOutputDestination:[[sheet URL] path]];
428         [_destination_filename_lbl setStringValue: [[NSFileManager defaultManager] displayNameAtPath:_outputDestination]];
429         [[_destination_filename_stub_lbl animator] setHidden: YES];
430         [[_destination_filename_lbl animator] setHidden: NO];
431     } else {
432         [self setOutputDestination:@""];
433         [[_destination_filename_lbl animator] setHidden: YES];
434         [[_destination_filename_stub_lbl animator] setHidden: NO];
435     }
436     [self updateOKButton];
437 }
438
439 - (IBAction)showStreamPanel:(id)sender
440 {
441     [NSApp beginSheet:_stream_panel modalForWindow:_window modalDelegate:self didEndSelector:NULL contextInfo:nil];
442 }
443
444 - (IBAction)closeStreamPanel:(id)sender
445 {
446     NSMutableString * labelContent = [[NSMutableString alloc] initWithFormat:@"%@ @ %@:%@", [_stream_type_pop titleOfSelectedItem], [_stream_address_fld stringValue], [_stream_port_fld stringValue]];
447
448     if ([_stream_type_pop indexOfSelectedItem] > 1) {
449         [labelContent appendFormat:@" TTL:%@", [_stream_ttl_fld stringValue]];
450         if ([_stream_sap_ckb state] || [_stream_rtsp_ckb state] || [_stream_http_ckb state] || [_stream_sdp_ckb state])
451             [labelContent appendFormat:@" — %@:\"%@\" (", _NS("Channel"), [_stream_channel_fld stringValue]];
452
453         BOOL b_gotSomething;
454         if (_stream_sap_ckb) {
455             [labelContent appendString:@"SAP"];
456             b_gotSomething = YES;
457         }
458         if (_stream_rtsp_ckb) {
459             if (b_gotSomething)
460                 [labelContent appendString:@", "];
461             [labelContent appendString:@"RTSP"];
462             b_gotSomething = YES;
463         }
464         if (_stream_http_ckb) {
465             if (b_gotSomething)
466                 [labelContent appendString:@", "];
467             [labelContent appendString:@"HTTP"];
468             b_gotSomething = YES;
469         }
470         if ([_stream_sdp_ckb state]) {
471             if (b_gotSomething)
472                 [labelContent appendString:@", "];
473             [labelContent appendString:@"SDP"];
474         }
475
476         if ([_stream_sap_ckb state] || [_stream_rtsp_ckb state] || [_stream_http_ckb state] || [_stream_sdp_ckb state])
477             [labelContent appendString:@")"];
478     }
479     [_destination_stream_lbl setStringValue:labelContent];
480     [labelContent release];
481
482     [_stream_panel orderOut:sender];
483     [NSApp endSheet: _stream_panel];
484 }
485
486 - (IBAction)streamTypeToggle:(id)sender
487 {
488     NSUInteger index = [_stream_type_pop indexOfSelectedItem];
489     if (index <= 1) { // HTTP, MMSH
490         [_stream_ttl_fld setEnabled:NO];
491         [_stream_ttl_stepper setEnabled:NO];
492         [_stream_sap_ckb setEnabled:NO];
493         [_stream_rtsp_ckb setEnabled:NO];
494         [_stream_http_ckb setEnabled:NO];
495         [_stream_sdp_ckb setEnabled:NO];
496     } else if (index == 2) { // RTP
497         [_stream_ttl_fld setEnabled:YES];
498         [_stream_ttl_stepper setEnabled:YES];
499         [_stream_sap_ckb setEnabled:YES];
500         [_stream_rtsp_ckb setEnabled:YES];
501         [_stream_http_ckb setEnabled:YES];
502         [_stream_sdp_ckb setEnabled:YES];
503         [_stream_channel_fld setEnabled:YES];
504         [_stream_sdp_fld setEnabled:[_stream_sdp_ckb state]];
505     } else { // UDP
506         [_stream_ttl_fld setEnabled:YES];
507         [_stream_ttl_stepper setEnabled:YES];
508         [_stream_sap_ckb setEnabled:YES];
509         [_stream_rtsp_ckb setEnabled:NO];
510         [_stream_http_ckb setEnabled:NO];
511         [_stream_sdp_ckb setEnabled:NO];
512         [_stream_channel_fld setEnabled:YES];
513     }
514 }
515
516 - (IBAction)streamAnnouncementToggle:(id)sender
517 {
518     [_stream_sdp_fld setEnabled:[_stream_sdp_ckb state]];
519 }
520
521 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
522 {
523     NSPasteboard *paste = [sender draggingPasteboard];
524     NSArray *types = [NSArray arrayWithObjects: NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil];
525     NSString *desired_type = [paste availableTypeFromArray: types];
526     NSData *carried_data = [paste dataForType: desired_type];
527
528     if( carried_data ) {
529         if( [desired_type isEqualToString:NSFilenamesPboardType] ) {
530             NSArray *values = [[paste propertyListForType: NSFilenamesPboardType] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
531
532             if ([values count] > 0) {
533                 [self setMRL: [NSString stringWithUTF8String:vlc_path2uri([[values objectAtIndex:0] UTF8String], NULL)]];
534                 [self updateOKButton];
535                 [self updateDropView];
536                 return YES;
537             }
538         } else if( [desired_type isEqualToString:@"VLCPlaylistItemPboardType"] ) {
539             NSArray * array = [[[VLCMain sharedInstance] playlist] draggedItems];
540             NSUInteger count = [array count];
541             if (count > 0) {
542                 playlist_t * p_playlist = pl_Get( VLCIntf );
543                 playlist_item_t * p_item = NULL;
544
545                 PL_LOCK;
546                 /* let's look for the first proper input item */
547                 for (NSUInteger x = 0; x < count; x++) {
548                     p_item = [[array objectAtIndex:x] pointerValue];
549                     if (p_item) {
550                         if (p_item->p_input) {
551                             if (p_item->p_input->psz_uri != nil) {
552                                 [self setMRL: [NSString stringWithFormat:@"%s", p_item->p_input->psz_uri]];
553                                 [self updateDropView];
554                                 [self updateOKButton];
555
556                                 PL_UNLOCK;
557
558                                 return YES;
559                             }
560                         }
561                     }
562                 }
563                 PL_UNLOCK;
564             }
565         }
566     }
567     return NO;
568 }
569
570 # pragma mark -
571 # pragma mark Private Functionality
572 - (void)updateDropView
573 {
574     if ([_MRL length] > 0) {
575         NSString * path = [[NSURL URLWithString:_MRL] path];
576         [_dropin_media_lbl setStringValue: [[NSFileManager defaultManager] displayNameAtPath: path]];
577         NSImage * image = [[NSWorkspace sharedWorkspace] iconForFile: path];
578         [image setSize:NSMakeSize(128,128)];
579         [_dropin_icon_view setImage: image];
580
581         if (![_dropin_view superview]) {
582             NSRect boxFrame = [_drop_box frame];
583             NSRect subViewFrame = [_dropin_view frame];
584             subViewFrame.origin.x = (boxFrame.size.width - subViewFrame.size.width) / 2;
585             subViewFrame.origin.y = (boxFrame.size.height - subViewFrame.size.height) / 2;
586             [_dropin_view setFrame: subViewFrame];
587             [[_drop_image_view animator] setHidden: YES];
588             [_drop_box performSelector:@selector(addSubview:) withObject:_dropin_view afterDelay:0.6];
589         }
590     } else {
591         [_dropin_view removeFromSuperview];
592         [[_drop_image_view animator] setHidden: NO];
593     }
594 }
595
596 - (void)updateOKButton
597 {
598     if ([_outputDestination length] > 0 && [_MRL length] > 0)
599         [_ok_btn setEnabled: YES];
600     else
601         [_ok_btn setEnabled: NO];
602 }
603
604 - (void)resetCustomizationSheetBasedOnProfile:(NSString *)profileString
605 {
606     /* Container(string), transcode video(bool), transcode audio(bool),
607     * use subtitles(bool), video codec(string), video bitrate(integer),
608     * scale(float), fps(float), width(integer, height(integer),
609     * audio codec(string), audio bitrate(integer), channels(integer),
610     * samplerate(integer), subtitle codec(string), subtitle overlay(bool) */
611
612     NSArray * components = [profileString componentsSeparatedByString:@";"];
613     if ([components count] != 16) {
614         msg_Err(VLCIntf, "CAS: the requested profile '%s' is invalid", [profileString UTF8String]);
615         return;
616     }
617
618     [self selectCellByEncapsulationFormat:[components objectAtIndex:0]];
619     [_customize_vid_ckb setState:[[components objectAtIndex:1] intValue]];
620     [_customize_aud_ckb setState:[[components objectAtIndex:2] intValue]];
621     [_customize_subs_ckb setState:[[components objectAtIndex:3] intValue]];
622     [_customize_vid_bitrate_fld setStringValue:[components objectAtIndex:5]];
623     [_customize_vid_scale_pop selectItemWithTitle:[components objectAtIndex:6]];
624     [_customize_vid_framerate_fld setStringValue:[components objectAtIndex:7]];
625     [_customize_vid_width_fld setStringValue:[components objectAtIndex:8]];
626     [_customize_vid_height_fld setStringValue:[components objectAtIndex:9]];
627     [_customize_aud_bitrate_fld setStringValue:[components objectAtIndex:11]];
628     [_customize_aud_channels_fld setStringValue:[components objectAtIndex:12]];
629     [_customize_aud_samplerate_pop selectItemWithTitle:[components objectAtIndex:13]];
630     [_customize_subs_overlay_ckb setState:[[components objectAtIndex:15] intValue]];
631
632     /* since there is no proper lookup mechanism in arrays, we need to implement a string specific one ourselves */
633     NSArray * tempArray = [_videoCodecs objectAtIndex:1];
634     NSUInteger count = [tempArray count];
635     NSString * searchString = [components objectAtIndex:4];
636     if ([searchString isEqualToString:@"none"] || [searchString isEqualToString:@"0"]) {
637         [_customize_vid_codec_pop selectItemAtIndex:-1];
638     } else {
639         for (NSUInteger x = 0; x < count; x++) {
640             if ([[tempArray objectAtIndex:x] isEqualToString: searchString]) {
641                 [_customize_vid_codec_pop selectItemAtIndex:x];
642                 break;
643             }
644         }
645     }
646
647     tempArray = [_audioCodecs objectAtIndex:1];
648     count = [tempArray count];
649     searchString = [components objectAtIndex:10];
650     if ([searchString isEqualToString:@"none"] || [searchString isEqualToString:@"0"]) {
651         [_customize_aud_codec_pop selectItemAtIndex:-1];
652     } else {
653         for (NSUInteger x = 0; x < count; x++) {
654             if ([[tempArray objectAtIndex:x] isEqualToString: searchString]) {
655                 [_customize_aud_codec_pop selectItemAtIndex:x];
656                 break;
657             }
658         }
659     }
660
661     tempArray = [_subsCodecs objectAtIndex:1];
662     count = [tempArray count];
663     searchString = [components objectAtIndex:14];
664     if ([searchString isEqualToString:@"none"] || [searchString isEqualToString:@"0"]) {
665         [_customize_subs_pop selectItemAtIndex:-1];
666     } else {
667         for (NSUInteger x = 0; x < count; x++) {
668             if ([[tempArray objectAtIndex:x] isEqualToString: searchString]) {
669                 [_customize_subs_pop selectItemAtIndex:x];
670                 break;
671             }
672         }
673     }
674
675     if (_currentProfile)
676         [_currentProfile release];
677     _currentProfile = [[NSMutableArray alloc] initWithArray: [profileString componentsSeparatedByString:@";"]];
678 }
679
680 - (void)selectCellByEncapsulationFormat:(NSString *)format
681 {
682     if ([format isEqualToString:@"ts"])
683         [_customize_encap_matrix selectCellWithTag:MPEGTS];
684     else if([format isEqualToString:@"webm"])
685         [_customize_encap_matrix selectCellWithTag:WEBM];
686     else if([format isEqualToString:@"ogg"])
687         [_customize_encap_matrix selectCellWithTag:OGG];
688     else if([format isEqualToString:@"ogm"])
689         [_customize_encap_matrix selectCellWithTag:OGG];
690     else if([format isEqualToString:@"mp4"])
691         [_customize_encap_matrix selectCellWithTag:MP4];
692     else if([format isEqualToString:@"mov"])
693         [_customize_encap_matrix selectCellWithTag:MP4];
694     else if([format isEqualToString:@"ps"])
695         [_customize_encap_matrix selectCellWithTag:MPEGPS];
696     else if([format isEqualToString:@"mpjpeg"])
697         [_customize_encap_matrix selectCellWithTag:MJPEG];
698     else if([format isEqualToString:@"wav"])
699         [_customize_encap_matrix selectCellWithTag:WAV];
700     else if([format isEqualToString:@"flv"])
701         [_customize_encap_matrix selectCellWithTag:FLV];
702     else if([format isEqualToString:@"mpeg1"])
703         [_customize_encap_matrix selectCellWithTag:MPEG1];
704     else if([format isEqualToString:@"mkv"])
705         [_customize_encap_matrix selectCellWithTag:MKV];
706     else if([format isEqualToString:@"raw"])
707         [_customize_encap_matrix selectCellWithTag:RAW];
708     else if([format isEqualToString:@"avi"])
709         [_customize_encap_matrix selectCellWithTag:AVI];
710     else if([format isEqualToString:@"asf"])
711         [_customize_encap_matrix selectCellWithTag:ASF];
712     else if([format isEqualToString:@"wmv"])
713         [_customize_encap_matrix selectCellWithTag:ASF];
714     else
715         msg_Err(VLCIntf, "CAS: unknown encap format requested for customization");
716 }
717
718 - (NSString *)currentEncapsulationFormatAsFileExtension:(BOOL)b_extension
719 {
720     NSUInteger cellTag = [[_customize_encap_matrix selectedCell] tag];
721     NSString * returnValue;
722     switch (cellTag) {
723         case MPEGTS:
724             returnValue = @"ts";
725             break;
726         case WEBM:
727             returnValue = @"webm";
728             break;
729         case OGG:
730             returnValue = @"ogg";
731             break;
732         case MP4:
733         {
734             if (b_extension)
735                 returnValue = @"m4v";
736             else
737                 returnValue = @"mp4";
738             break;
739         }
740         case MPEGPS:
741         {
742             if (b_extension)
743                 returnValue = @"mpg";
744             else
745                 returnValue = @"ps";
746             break;
747         }
748         case MJPEG:
749             returnValue = @"mjpeg";
750             break;
751         case WAV:
752             returnValue = @"wav";
753             break;
754         case FLV:
755             returnValue = @"flv";
756             break;
757         case MPEG1:
758         {
759             if (b_extension)
760                 returnValue = @"mpg";
761             else
762                 returnValue = @"mpeg1";
763             break;
764         }
765         case MKV:
766             returnValue = @"mkv";
767             break;
768         case RAW:
769             returnValue = @"raw";
770             break;
771         case AVI:
772             returnValue = @"avi";
773             break;
774         case ASF:
775             returnValue = @"asf";
776             break;
777
778         default:
779             returnValue = @"none";
780             break;
781     }
782
783     return returnValue;
784 }
785
786 - (NSString *)composedOptions
787 {
788     NSMutableString *composedOptions = [[NSMutableString alloc] initWithString:@":sout=#transcode{"];
789     if ([[_currentProfile objectAtIndex:1] intValue]) {
790         // video is enabled
791         [composedOptions appendFormat:@"vcodec=%@", [_currentProfile objectAtIndex:4]];
792         if (![[_currentProfile objectAtIndex:4] isEqualToString:@"none"]) {
793             if ([[_currentProfile objectAtIndex:5] intValue] > 0) // bitrate
794                 [composedOptions appendFormat:@",vb=%@", [_currentProfile objectAtIndex:5]];
795             if ([[_currentProfile objectAtIndex:6] floatValue] > 0.) // scale
796                 [composedOptions appendFormat:@",scale=%@", [_currentProfile objectAtIndex:6]];
797             if ([[_currentProfile objectAtIndex:7] floatValue] > 0.) // fps
798                 [composedOptions appendFormat:@",fps=%@", [_currentProfile objectAtIndex:7]];
799             if ([[_currentProfile objectAtIndex:8] intValue] > 0) // width
800                 [composedOptions appendFormat:@",width=%@", [_currentProfile objectAtIndex:8]];
801             if ([[_currentProfile objectAtIndex:9] intValue] > 0) // height
802                 [composedOptions appendFormat:@",height=%@", [_currentProfile objectAtIndex:9]];
803         }
804     }
805     if ([[_currentProfile objectAtIndex:2] intValue]) {
806         // audio is enabled
807
808         // add another comma in case video is enabled
809         if ([[_currentProfile objectAtIndex:1] intValue])
810             [composedOptions appendString:@","];
811
812         [composedOptions appendFormat:@"acodec=%@", [_currentProfile objectAtIndex:10]];
813         if (![[_currentProfile objectAtIndex:10] isEqualToString:@"none"]) {
814             [composedOptions appendFormat:@",ab=%@", [_currentProfile objectAtIndex:11]]; // bitrate
815             [composedOptions appendFormat:@",channels=%@", [_currentProfile objectAtIndex:12]]; // channel number
816             [composedOptions appendFormat:@",samplerate=%@", [_currentProfile objectAtIndex:13]]; // sample rate
817         }
818     }
819     if ([_currentProfile objectAtIndex:3]) {
820         // subtitles enabled
821         [composedOptions appendFormat:@",scodec=%@", [_currentProfile objectAtIndex:14]];
822         if ([[_currentProfile objectAtIndex:15] intValue])
823             [composedOptions appendFormat:@",soverlay"];
824     }
825
826     // add muxer
827     [composedOptions appendFormat:@"}:standard{mux=%@", [_currentProfile objectAtIndex:0]];
828
829     // add output destination (file only at this point)
830     [composedOptions appendFormat:@",dst=%@,access=file}", _outputDestination];
831
832     NSString * returnString = [NSString stringWithString:composedOptions];
833     [composedOptions release];
834
835     return returnString;
836 }
837
838 @end
839
840 # pragma mark -
841 # pragma mark Drag and drop handling
842
843 @implementation VLCDropEnabledBox
844
845 - (void)awakeFromNib
846 {
847     [self registerForDraggedTypes:[NSArray arrayWithObjects: NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
848 }
849
850 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
851 {
852     b_activeDragAndDrop = YES;
853     [self setNeedsDisplay:YES];
854
855     if (OSX_SNOW_LEOPARD || OSX_LION)
856         [[NSCursor dragCopyCursor] set];
857
858     if ((NSDragOperationGeneric & [sender draggingSourceOperationMask]) == NSDragOperationGeneric)
859         return NSDragOperationGeneric;
860
861     return NSDragOperationNone;
862 }
863
864 - (void)draggingEnded:(id < NSDraggingInfo >)sender
865 {
866     [[NSCursor arrowCursor] set];
867     b_activeDragAndDrop = NO;
868     [self setNeedsDisplay:YES];
869 }
870
871 - (void)draggingExited:(id < NSDraggingInfo >)sender
872 {
873     [[NSCursor arrowCursor] set];
874     b_activeDragAndDrop = NO;
875     [self setNeedsDisplay:YES];
876 }
877
878 - (void)drawRect:(NSRect)dirtyRect
879 {
880     if (b_activeDragAndDrop) {
881         [[NSColor colorWithCalibratedRed:(.154/.255) green:(.154/.255) blue:(.154/.255) alpha:1.] setFill];
882         NSRect frameRect = [[self contentView] bounds];
883         frameRect.origin.x += 10;
884         frameRect.origin.y += 10;
885         frameRect.size.width -= 17;
886         frameRect.size.height -= 17;
887         NSFrameRectWithWidthUsingOperation(frameRect, 4., NSCompositeHighlight);
888     }
889
890     [super drawRect:dirtyRect];
891 }
892
893 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
894 {
895     return YES;
896 }
897
898 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
899 {
900     return [[VLCConvertAndSave sharedInstance] performDragOperation: sender];
901 }
902
903 - (void)concludeDragOperation:(id <NSDraggingInfo>)sender
904 {
905     [self setNeedsDisplay:YES];
906 }
907
908 @end
909
910 @implementation VLCDropEnabledImageView
911
912 - (void)awakeFromNib
913 {
914     [self registerForDraggedTypes:[NSArray arrayWithObjects: NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
915 }
916
917 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
918 {
919     return [[[self superview] superview] draggingEntered:sender];
920 }
921
922 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
923 {
924     return YES;
925 }
926
927 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
928 {
929     return [[VLCConvertAndSave sharedInstance] performDragOperation: sender];
930 }
931
932 - (void)concludeDragOperation:(id <NSDraggingInfo>)sender
933 {
934     [self setNeedsDisplay:YES];
935 }
936
937 @end
938
939 @implementation VLCDropEnabledButton
940
941 - (void)awakeFromNib
942 {
943     [self registerForDraggedTypes:[NSArray arrayWithObjects: NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
944 }
945
946 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
947 {
948     return [[[self superview] superview] draggingEntered:sender];
949 }
950
951 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
952 {
953     return YES;
954 }
955
956 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
957 {
958     return [[VLCConvertAndSave sharedInstance] performDragOperation: sender];
959 }
960
961 - (void)concludeDragOperation:(id <NSDraggingInfo>)sender
962 {
963     [self setNeedsDisplay:YES];
964 }
965
966 @end