You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1040 lines
44 KiB

/*****************************************************************************
* ConvertAndSave.m: MacOS X interface module
*****************************************************************************
* Copyright (C) 2012 Felix Paul Kühne
* $Id$
*
* Authors: Felix Paul Kühne <fkuehne -at- videolan -dot- org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#import "ConvertAndSave.h"
#import "intf.h"
#import "playlist.h"
#import "misc.h"
#import "SharedDialogs.h"
#import <vlc_common.h>
#import <vlc_url.h>
/* mini doc:
* the used NSMatrix includes a bunch of cells referenced most easily by tags. There you go: */
#define MPEGTS 0
#define WEBM 1
#define OGG 2
#define MP4 3
#define MPEGPS 4
#define MJPEG 5
#define WAV 6
#define FLV 7
#define MPEG1 8
#define MKV 9
#define RAW 10
#define AVI 11
#define ASF 12
/* 13-15 are present, but not set */
@interface VLCConvertAndSave()
{
NSArray *_videoCodecs;
NSArray *_audioCodecs;
NSArray *_subsCodecs;
BOOL b_streaming;
}
- (void)updateDropView;
- (void)updateOKButton;
- (void)resetCustomizationSheetBasedOnProfile:(NSString *)profileString;
- (void)selectCellByEncapsulationFormat:(NSString *)format;
- (NSString *)currentEncapsulationFormatAsFileExtension:(BOOL)b_extension;
- (NSString *)composedOptions;
- (void)updateCurrentProfile;
- (void)storeProfilesOnDisk;
- (void)recreateProfilePopup;
@end
@implementation VLCConvertAndSave
#pragma mark -
#pragma mark Initialization
+ (void)initialize
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
/* We are using the same format as the Qt4 intf here:
* Container(string), transcode video(bool), transcode audio(bool),
* use subtitles(bool), video codec(string), video bitrate(integer),
* scale(float), fps(float), width(integer, height(integer),
* audio codec(string), audio bitrate(integer), channels(integer),
* samplerate(integer), subtitle codec(string), subtitle overlay(bool) */
NSArray * defaultProfiles = [[NSArray alloc] initWithObjects:
@"mp4;1;1;0;h264;0;0;0;0;0;mpga;128;2;44100;0;1",
@"webm;1;1;0;VP80;2000;0;0;0;0;vorb;128;2;44100;0;1",
@"ts;1;1;0;h264;800;1;0;0;0;mpga;128;2;44100;0;0",
@"ts;1;1;0;drac;800;1;0;0;0;mpga;128;2;44100;0;0",
@"ogg;1;1;0;theo;800;1;0;0;0;vorb;128;2;44100;0;0",
@"ogg;1;1;0;theo;800;1;0;0;0;flac;128;2;44100;0;0",
@"ts;1;1;0;mp2v;800;1;0;0;0;mpga;128;2;44100;0;0",
@"asf;1;1;0;WMV2;800;1;0;0;0;wma2;128;2;44100;0;0",
@"asf;1;1;0;DIV3;800;1;0;0;0;mp3;128;2;44100;0;0",
@"ogg;0;1;0;none;800;1;0;0;0;vorb;128;2;44100;none;0",
@"raw;0;1;0;none;800;1;0;0;0;mp3;128;2;44100;none;0",
@"mp4;0;1;0;none;800;1;0;0;0;mpga;128;2;44100;none;0",
@"raw;0;1;0;none;800;1;0;0;0;flac;128;2;44100;none;0",
@"wav;0;1;0;none;800;1;0;0;0;s16l;128;2;44100;none;0", nil];
NSArray * defaultProfileNames = [[NSArray alloc] initWithObjects:
@"Video - H.264 + MP3 (MP4)",
@"Video - VP80 + Vorbis (Webm)",
@"Video - H.264 + MP3 (TS)",
@"Video - Dirac + MP3 (TS)",
@"Video - Theora + Vorbis (OGG)",
@"Video - Theora + Flac (OGG)",
@"Video - MPEG-2 + MPGA (TS)",
@"Video - WMV + WMA (ASF)",
@"Video - DIV3 + MP3 (ASF)",
@"Audio - Vorbis (OGG)",
@"Audio - MP3",
@"Audio - MP3 (MP4)",
@"Audio - FLAC",
@"Audio - CD",
nil];
NSDictionary *appDefaults = [NSDictionary dictionaryWithObjectsAndKeys:defaultProfiles, @"CASProfiles", defaultProfileNames, @"CASProfileNames", nil];
[defaults registerDefaults:appDefaults];
}
- (id)init
{
self = [super initWithWindowNibName:@"ConvertAndSave"];
if (self) {
self.popupPanel = [[VLCPopupPanelController alloc] init];
self.textfieldPanel = [[VLCTextfieldPanelController alloc] init];
}
return self;
}
- (void)windowDidLoad
{
[self.window setTitle: _NS("Convert & Stream")];
[_okButton setTitle: _NS("Go!")];
[_dropLabel setStringValue: _NS("Drop media here")];
[_dropButton setTitle: _NS("Open media...")];
[_profileLabel setStringValue: _NS("Choose Profile")];
[_customizeButton setTitle: _NS("Customize...")];
[_destinationLabel setStringValue: _NS("Choose Destination")];
[_fileDestinationFileNameStub setStringValue: _NS("Choose an output location")];
[_fileDestinationFileName setHidden: YES];
[_fileDestinationBrowseButton setTitle:_NS("Browse...")];
[_streamDestinationButton setTitle:_NS("Setup Streaming...")];
[_streamDestinationURLLabel setStringValue:@"Select Streaming Method"];
[_destinationFileButton setTitle:_NS("Save as File")];
[_destinationStreamButton setTitle:_NS("Stream")];
[_destinationCancelBtn setHidden:YES];
[_customizeOkButton setTitle: _NS("Apply")];
[_customizeCancelButton setTitle: _NS("Cancel")];
[_customizeNewProfileButton setTitle: _NS("Save as new Profile...")];
[[_customizeTabView tabViewItemAtIndex:0] setLabel: _NS("Encapsulation")];
[[_customizeTabView tabViewItemAtIndex:1] setLabel: _NS("Video codec")];
[[_customizeTabView tabViewItemAtIndex:2] setLabel: _NS("Audio codec")];
[[_customizeTabView tabViewItemAtIndex:3] setLabel: _NS("Subtitles")];
[_customizeTabView selectTabViewItemAtIndex: 0];
[_customizeVidCheckbox setTitle: _NS("Video")];
[_customizeVidKeepCheckbox setTitle: _NS("Keep original video track")];
[_customizeVidCodecLabel setStringValue: _NS("Codec")];
[_customizeVidBitrateLabel setStringValue: _NS("Bitrate")];
[_customizeVidFramerateLabel setStringValue: _NS("Frame rate")];
[_customizeVidResolutionBox setTitle: _NS("Resolution")];
[_customizeVidResLabel setStringValue: _NS("You just need to fill one of the three following parameters, VLC will autodetect the other using the original aspect ratio")];
[_customizeVidWidthLabel setStringValue: _NS("Width")];
[_customizeVidHeightLabel setStringValue: _NS("Height")];
[_customizeVidScaleLabel setStringValue: _NS("Scale")];
[_customizeAudCheckbox setTitle: _NS("Audio")];
[_customizeAudKeepCheckbox setTitle: _NS("Keep original audio track")];
[_customizeAudCodecLabel setStringValue: _NS("Codec")];
[_customizeAudBitrateLabel setStringValue: _NS("Bitrate")];
[_customizeAudChannelsLabel setStringValue: _NS("Channels")];
[_customizeAudSamplerateLabel setStringValue: _NS("Samplerate")];
[_customizeSubsCheckbox setTitle: _NS("Subtitles")];
[_customizeSubsOverlayCheckbox setTitle: _NS("Overlay subtitles on the video")];
[_streamOkButton setTitle: _NS("Apply")];
[_streamCancelButton setTitle: _NS("Cancel")];
[_streamDestinationLabel setStringValue:_NS("Stream Destination")];
[_streamAnnouncementLabel setStringValue:_NS("Stream Announcement")];
[_streamTypeLabel setStringValue:_NS("Type")];
[_streamAddressLabel setStringValue:_NS("Address")];
[_streamTTLLabel setStringValue:_NS("TTL")];
[_streamTTLStepper setEnabled:NO];
[_streamPortLabel setStringValue:_NS("Port")];
[_streamSAPCheckbox setStringValue:_NS("SAP Announcement")];
[[_streamSDPMatrix cellWithTag:0] setTitle:_NS("None")];
[[_streamSDPMatrix cellWithTag:1] setTitle:_NS("HTTP Announcement")];
[[_streamSDPMatrix cellWithTag:2] setTitle:_NS("RTSP Announcement")];
[[_streamSDPMatrix cellWithTag:3] setTitle:_NS("Export SDP as file")];
[_streamSAPCheckbox setState:NSOffState];
[_streamSDPMatrix setEnabled:NO];
/* there is no way to hide single cells, so replace the existing ones with empty cells.. */
id blankCell = [[NSCell alloc] init];
[blankCell setEnabled:NO];
[_customizeEncapMatrix putCell:blankCell atRow:3 column:1];
[_customizeEncapMatrix putCell:blankCell atRow:3 column:2];
[_customizeEncapMatrix putCell:blankCell atRow:3 column:3];
/* fetch profiles from defaults */
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[self setProfileValueList: [defaults arrayForKey:@"CASProfiles"]];
[self setProfileNames: [defaults arrayForKey:@"CASProfileNames"]];
[self recreateProfilePopup];
_videoCodecs = [[NSArray alloc] initWithObjects:
[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],
[NSArray arrayWithObjects:@"mpgv", @"mp2v", @"mp4v", @"DIV1", @"DIV2", @"DIV3", @"H263", @"h264", @"VP80", @"WMV1", @"WMV2", @"MJPG", @"theo", @"drac", nil],
nil];
_audioCodecs = [[NSArray alloc] initWithObjects:
[NSArray arrayWithObjects:@"MPEG Audio", @"MP3", @"MPEG 4 Audio (AAC)", @"A52/AC-3", @"Vorbis", @"Flac", @"Speex", @"WAV", @"WMA2", nil],
[NSArray arrayWithObjects:@"mpga", @"mp3", @"mp4a", @"a52", @"vorb", @"flac", @"spx", @"s16l", @"wma2", nil],
nil];
_subsCodecs = [[NSArray alloc] initWithObjects:
[NSArray arrayWithObjects:@"DVB subtitle", @"T.140", nil],
[NSArray arrayWithObjects:@"dvbs", @"t140", nil],
nil];
[_customizeVidCodecPopup removeAllItems];
[_customizeVidScalePopup removeAllItems];
[_customizeAudCodecPopup removeAllItems];
[_customizeAudSampleratePopup removeAllItems];
[_customizeSubsPopup removeAllItems];
[_customizeVidCodecPopup addItemsWithTitles:[_videoCodecs firstObject]];
[_customizeAudCodecPopup addItemsWithTitles:[_audioCodecs firstObject]];
[_customizeSubsPopup addItemsWithTitles:[_subsCodecs firstObject]];
[_customizeAudSampleratePopup addItemWithTitle:@"8000"];
[_customizeAudSampleratePopup addItemWithTitle:@"11025"];
[_customizeAudSampleratePopup addItemWithTitle:@"22050"];
[_customizeAudSampleratePopup addItemWithTitle:@"44100"];
[_customizeAudSampleratePopup addItemWithTitle:@"48000"];
[_customizeVidScalePopup addItemWithTitle:@"1"];
[_customizeVidScalePopup addItemWithTitle:@"0.25"];
[_customizeVidScalePopup addItemWithTitle:@"0.5"];
[_customizeVidScalePopup addItemWithTitle:@"0.75"];
[_customizeVidScalePopup addItemWithTitle:@"1.25"];
[_customizeVidScalePopup addItemWithTitle:@"1.5"];
[_customizeVidScalePopup addItemWithTitle:@"1.75"];
[_customizeVidScalePopup addItemWithTitle:@"2"];
[_okButton setEnabled: NO];
// setup drop view
[_dropBox enablePlaylistItems];
[_dropBox setDropHandler: self];
[self resetCustomizationSheetBasedOnProfile:[self.profileValueList firstObject]];
}
# pragma mark -
# pragma mark User Interaction - main window
- (IBAction)finalizePanel:(id)sender
{
if (b_streaming) {
if ([[[_streamTypePopup selectedItem] title] isEqualToString:@"HTTP"]) {
NSString *muxformat = [self.currentProfile firstObject];
if ([muxformat isEqualToString:@"wav"] || [muxformat isEqualToString:@"mov"] || [muxformat isEqualToString:@"mp4"] || [muxformat isEqualToString:@"mkv"]) {
NSBeginInformationalAlertSheet(_NS("Invalid container format for HTTP streaming"), _NS("OK"), @"", @"", self.window,
nil, nil, nil, nil,
_NS("Media encapsulated as %@ cannot be streamed through the HTTP protocol for technical reasons."),
[[self currentEncapsulationFormatAsFileExtension:YES] uppercaseString]);
return;
}
}
}
playlist_t * p_playlist = pl_Get(VLCIntf);
input_item_t *p_input = input_item_New([_MRL UTF8String], [[_dropinMediaLabel stringValue] UTF8String]);
if (!p_input)
return;
input_item_AddOption(p_input, [[self composedOptions] UTF8String], VLC_INPUT_OPTION_TRUSTED);
if (b_streaming)
input_item_AddOption(p_input, [[NSString stringWithFormat:@"ttl=%@", [_streamTTLField stringValue]] UTF8String], VLC_INPUT_OPTION_TRUSTED);
int returnValue;
returnValue = playlist_AddInput(p_playlist, p_input, PLAYLIST_STOP, PLAYLIST_END, true, pl_Unlocked);
if (returnValue == VLC_SUCCESS) {
/* let's "play" */
PL_LOCK;
playlist_item_t *p_item = playlist_ItemGetByInput(p_playlist, p_input);
playlist_Control(p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, NULL,
p_item);
PL_UNLOCK;
}
else
msg_Err(VLCIntf, "CAS: playlist add input failed :(");
/* we're done with this input */
vlc_gc_decref(p_input);
[self.window performClose:sender];
}
- (IBAction)openMedia:(id)sender
{
/* preliminary implementation until the open panel is cleaned up */
NSOpenPanel * openPanel = [NSOpenPanel openPanel];
[openPanel setCanChooseDirectories:NO];
[openPanel setResolvesAliases:YES];
[openPanel setAllowsMultipleSelection:NO];
[openPanel beginSheetModalForWindow:self.window completionHandler:^(NSInteger returnCode) {
if (returnCode == NSOKButton)
{
[self setMRL: toNSStr(vlc_path2uri([[[openPanel URL] path] UTF8String], NULL))];
[self updateOKButton];
[self updateDropView];
}
}];
}
- (IBAction)switchProfile:(id)sender
{
NSUInteger index = [_profilePopup indexOfSelectedItem];
// last index is "custom"
if (index <= ([self.profileValueList count] - 1))
[self resetCustomizationSheetBasedOnProfile:[self.profileValueList objectAtIndex:index]];
}
- (IBAction)deleteProfileAction:(id)sender
{
/* show panel */
[_popupPanel setTitleString:_NS("Remove a profile")];
[_popupPanel setSubTitleString:_NS("Select the profile you would like to remove:")];
[_popupPanel setOkButtonString:_NS("Remove")];
[_popupPanel setCancelButtonString:_NS("Cancel")];
[_popupPanel setPopupButtonContent:self.profileNames];
__weak typeof(self) _self = self;
[_popupPanel runModalForWindow:self.window completionHandler:^(NSInteger returnCode, NSInteger selectedIndex) {
if (returnCode != NSOKButton)
return;
/* remove requested profile from the arrays */
NSMutableArray * workArray = [[NSMutableArray alloc] initWithArray:_self.profileNames];
[workArray removeObjectAtIndex:selectedIndex];
[_self setProfileNames:[[NSArray alloc] initWithArray:workArray]];
workArray = [[NSMutableArray alloc] initWithArray:_self.profileValueList];
[workArray removeObjectAtIndex:selectedIndex];
[_self setProfileValueList:[[NSArray alloc] initWithArray:workArray]];
/* update UI */
[_self recreateProfilePopup];
/* update internals */
[_self switchProfile:_self];
[_self storeProfilesOnDisk];
}];
}
- (IBAction)iWantAFile:(id)sender
{
NSRect boxFrame = [_destinationBox frame];
NSRect subViewFrame = [_fileDestinationView frame];
subViewFrame.origin.x = (boxFrame.size.width - subViewFrame.size.width) / 2;
subViewFrame.origin.y = ((boxFrame.size.height - subViewFrame.size.height) / 2) - 15.;
[_fileDestinationView setFrame: subViewFrame];
[[_destinationFileButton animator] setHidden: YES];
[[_destinationStreamButton animator] setHidden: YES];
[_destinationBox performSelector:@selector(addSubview:) withObject:_fileDestinationView afterDelay:0.2];
[[_destinationCancelBtn animator] setHidden:NO];
b_streaming = NO;
[_okButton setTitle:_NS("Save")];
}
- (IBAction)iWantAStream:(id)sender
{
NSRect boxFrame = [_destinationBox frame];
NSRect subViewFrame = [_streamDestinationView frame];
subViewFrame.origin.x = (boxFrame.size.width - subViewFrame.size.width) / 2;
subViewFrame.origin.y = ((boxFrame.size.height - subViewFrame.size.height) / 2) - 15.;
[_streamDestinationView setFrame: subViewFrame];
[[_destinationFileButton animator] setHidden: YES];
[[_destinationStreamButton animator] setHidden: YES];
[_destinationBox performSelector:@selector(addSubview:) withObject:_streamDestinationView afterDelay:0.2];
[[_destinationCancelBtn animator] setHidden:NO];
b_streaming = YES;
[_okButton setTitle:_NS("Stream")];
}
- (IBAction)cancelDestination:(id)sender
{
if ([_streamDestinationView superview] != nil)
[_streamDestinationView removeFromSuperview];
if ([_fileDestinationView superview] != nil)
[_fileDestinationView removeFromSuperview];
[_destinationCancelBtn setHidden:YES];
[[_destinationFileButton animator] setHidden: NO];
[[_destinationStreamButton animator] setHidden: NO];
b_streaming = NO;
}
- (IBAction)browseFileDestination:(id)sender
{
NSSavePanel * saveFilePanel = [NSSavePanel savePanel];
[saveFilePanel setCanSelectHiddenExtension: YES];
[saveFilePanel setCanCreateDirectories: YES];
if ([[_customizeEncapMatrix selectedCell] tag] != RAW) // there is no clever guess for this
[saveFilePanel setAllowedFileTypes:[NSArray arrayWithObject:[self currentEncapsulationFormatAsFileExtension:YES]]];
[saveFilePanel beginSheetModalForWindow:self.window completionHandler:^(NSInteger returnCode) {
if (returnCode == NSOKButton) {
[self setOutputDestination:[[saveFilePanel URL] path]];
[_fileDestinationFileName setStringValue: [[NSFileManager defaultManager] displayNameAtPath:_outputDestination]];
[[_fileDestinationFileNameStub animator] setHidden: YES];
[[_fileDestinationFileName animator] setHidden: NO];
} else {
[self setOutputDestination:@""];
[[_fileDestinationFileName animator] setHidden: YES];
[[_fileDestinationFileNameStub animator] setHidden: NO];
}
[self updateOKButton];
}];
}
#pragma mark -
#pragma mark User interaction - customization panel
- (IBAction)customizeProfile:(id)sender
{
[NSApp beginSheet:_customizePanel modalForWindow:self.window modalDelegate:self didEndSelector:NULL contextInfo:nil];
}
- (IBAction)closeCustomizationSheet:(id)sender
{
[_customizePanel orderOut:sender];
[NSApp endSheet: _customizePanel];
if (sender == _customizeOkButton)
[self updateCurrentProfile];
}
- (IBAction)videoSettingsChanged:(id)sender
{
bool enableSettings = [_customizeVidCheckbox state] == NSOnState && [_customizeVidKeepCheckbox state] == NSOffState;
[_customizeVidSettingsBox enableSubviews:enableSettings];
[_customizeVidKeepCheckbox setEnabled:[_customizeVidCheckbox state] == NSOnState];
}
- (IBAction)audioSettingsChanged:(id)sender
{
bool enableSettings = [_customizeAudCheckbox state] == NSOnState && [_customizeAudKeepCheckbox state] == NSOffState;
[_customizeAudSettingsBox enableSubviews:enableSettings];
[_customizeAudKeepCheckbox setEnabled:[_customizeAudCheckbox state] == NSOnState];
}
- (IBAction)subSettingsChanged:(id)sender
{
bool enableSettings = [_customizeSubsCheckbox state] == NSOnState;
[_customizeSubsOverlayCheckbox setEnabled:enableSettings];
[_customizeSubsPopup setEnabled:enableSettings];
}
- (IBAction)newProfileAction:(id)sender
{
/* show panel */
[_textfieldPanel setTitleString: _NS("Save as new profile")];
[_textfieldPanel setSubTitleString: _NS("Enter a name for the new profile:")];
[_textfieldPanel setCancelButtonString: _NS("Cancel")];
[_textfieldPanel setOkButtonString: _NS("Save")];
__weak typeof(self) _self = self;
[_textfieldPanel runModalForWindow:_customizePanel completionHandler:^(NSInteger returnCode, NSString *resultingText) {
if (returnCode != NSOKButton || [resultingText length] == 0)
return;
/* prepare current data */
[_self updateCurrentProfile];
/* add profile to arrays */
NSMutableArray * workArray = [[NSMutableArray alloc] initWithArray:self.profileNames];
[workArray addObject:resultingText];
[_self setProfileNames:[[NSArray alloc] initWithArray:workArray]];
workArray = [[NSMutableArray alloc] initWithArray:self.profileValueList];
[workArray addObject:[self.currentProfile componentsJoinedByString:@";"]];
[_self setProfileValueList:[[NSArray alloc] initWithArray:workArray]];
/* update UI */
[_self recreateProfilePopup];
[_profilePopup selectItemWithTitle:resultingText];
/* update internals */
[_self switchProfile:self];
[_self storeProfilesOnDisk];
}];
}
#pragma mark -
#pragma mark User interaction - stream panel
- (IBAction)showStreamPanel:(id)sender
{
[NSApp beginSheet:_streamPanel modalForWindow:self.window modalDelegate:self didEndSelector:NULL contextInfo:nil];
}
- (IBAction)closeStreamPanel:(id)sender
{
[_streamPanel orderOut:sender];
[NSApp endSheet: _streamPanel];
if (sender == _streamCancelButton)
return;
/* provide a summary of the user selections */
NSMutableString * labelContent = [[NSMutableString alloc] initWithFormat:_NS("%@ stream to %@:%@"), [_streamTypePopup titleOfSelectedItem], [_streamAddressField stringValue], [_streamPortField stringValue]];
if ([_streamTypePopup indexOfSelectedItem] > 1)
[labelContent appendFormat:@" (\"%@\")", [_streamChannelField stringValue]];
[_streamDestinationURLLabel setStringValue:labelContent];
/* catch obvious errors */
if (![[_streamAddressField stringValue] length] > 0) {
NSBeginInformationalAlertSheet(_NS("No Address given"),
_NS("OK"), @"", @"", _streamPanel, nil, nil, nil, nil,
@"%@", _NS("In order to stream, a valid destination address is required."));
return;
}
if ([_streamSAPCheckbox state] && ![[_streamChannelField stringValue] length] > 0) {
NSBeginInformationalAlertSheet(_NS("No Channel Name given"),
_NS("OK"), @"", @"", _streamPanel, nil, nil, nil, nil,
@"%@", _NS("SAP stream announcement is enabled. However, no channel name is provided."));
return;
}
if ([_streamSDPMatrix isEnabled] && [_streamSDPMatrix selectedCell] != [_streamSDPMatrix cellWithTag:0] && ![[_streamSDPField stringValue] length] > 0) {
NSBeginInformationalAlertSheet(_NS("No SDP URL given"),
_NS("OK"), @"", @"", _streamPanel, nil, nil, nil, nil,
@"%@", _NS("A SDP export is requested, but no URL is provided."));
return;
}
/* store destination for further reference and update UI */
[self setOutputDestination: [_streamAddressField stringValue]];
[self updateOKButton];
}
- (IBAction)streamTypeToggle:(id)sender
{
NSUInteger index = [_streamTypePopup indexOfSelectedItem];
if (index <= 1) { // HTTP, MMSH
[_streamTTLField setEnabled:NO];
[_streamTTLStepper setEnabled:NO];
[_streamSAPCheckbox setEnabled:NO];
[_streamSDPMatrix setEnabled:NO];
} else if (index == 2) { // RTP
[_streamTTLField setEnabled:YES];
[_streamTTLStepper setEnabled:YES];
[_streamSAPCheckbox setEnabled:YES];
[_streamSDPMatrix setEnabled:YES];
} else { // UDP
[_streamTTLField setEnabled:YES];
[_streamTTLStepper setEnabled:YES];
[_streamSAPCheckbox setEnabled:YES];
[_streamSDPMatrix setEnabled:NO];
}
[self streamAnnouncementToggle:sender];
}
- (IBAction)streamAnnouncementToggle:(id)sender
{
[_streamChannelField setEnabled:[_streamSAPCheckbox state] && [_streamSAPCheckbox isEnabled]];
[_streamSDPField setEnabled:[_streamSDPMatrix isEnabled] && ([_streamSDPMatrix selectedCell] != [_streamSDPMatrix cellWithTag:0])];
if ([[_streamSDPMatrix selectedCell] tag] == 3)
[_streamSDPFileBrowseButton setEnabled: YES];
else
[_streamSDPFileBrowseButton setEnabled: NO];
}
- (IBAction)sdpFileLocationSelector:(id)sender
{
NSSavePanel * saveFilePanel = [NSSavePanel savePanel];
[saveFilePanel setCanSelectHiddenExtension: YES];
[saveFilePanel setCanCreateDirectories: YES];
[saveFilePanel setAllowedFileTypes:[NSArray arrayWithObject:@"sdp"]];
[saveFilePanel beginSheetModalForWindow:_streamPanel completionHandler:^(NSInteger returnCode) {
if (returnCode == NSOKButton)
[_streamSDPField setStringValue:[[saveFilePanel URL] path]];
}];
}
#pragma mark -
#pragma mark User interaction - misc
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
NSPasteboard *paste = [sender draggingPasteboard];
NSArray *types = [NSArray arrayWithObjects:NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil];
NSString *desired_type = [paste availableTypeFromArray: types];
NSData *carried_data = [paste dataForType: desired_type];
if (carried_data) {
if ([desired_type isEqualToString:NSFilenamesPboardType]) {
NSArray *values = [[paste propertyListForType: NSFilenamesPboardType] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
if ([values count] > 0) {
[self setMRL: toNSStr(vlc_path2uri([[values firstObject] UTF8String], NULL))];
[self updateOKButton];
[self updateDropView];
return YES;
}
} else if ([desired_type isEqualToString:@"VLCPlaylistItemPboardType"]) {
NSArray * array = [[[VLCMain sharedInstance] playlist] draggedItems];
NSUInteger count = [array count];
if (count > 0) {
playlist_t * p_playlist = pl_Get(VLCIntf);
playlist_item_t * p_item = NULL;
PL_LOCK;
/* let's look for the first proper input item */
for (NSUInteger x = 0; x < count; x++) {
p_item = [[array objectAtIndex:x] pointerValue];
if (p_item) {
if (p_item->p_input) {
if (p_item->p_input->psz_uri != nil) {
[self setMRL: toNSStr(p_item->p_input->psz_uri)];
[self updateDropView];
[self updateOKButton];
PL_UNLOCK;
return YES;
}
}
}
}
PL_UNLOCK;
}
}
}
return NO;
}
# pragma mark -
# pragma mark Private Functionality
- (void)updateDropView
{
if ([_MRL length] > 0) {
NSString * path = [[NSURL URLWithString:_MRL] path];
[_dropinMediaLabel setStringValue: [[NSFileManager defaultManager] displayNameAtPath: path]];
NSImage * image = [[NSWorkspace sharedWorkspace] iconForFile: path];
[image setSize:NSMakeSize(128,128)];
[_dropinIcon setImage: image];
if (![_dropinView superview]) {
NSRect boxFrame = [_dropBox frame];
NSRect subViewFrame = [_dropinView frame];
subViewFrame.origin.x = (boxFrame.size.width - subViewFrame.size.width) / 2;
subViewFrame.origin.y = (boxFrame.size.height - subViewFrame.size.height) / 2;
[_dropinView setFrame: subViewFrame];
[[_dropImage animator] setHidden: YES];
[_dropBox performSelector:@selector(addSubview:) withObject:_dropinView afterDelay:0.6];
}
} else {
[_dropinView removeFromSuperview];
[[_dropImage animator] setHidden: NO];
}
}
- (void)updateOKButton
{
if ([_outputDestination length] > 0 && [_MRL length] > 0)
[_okButton setEnabled: YES];
else
[_okButton setEnabled: NO];
}
- (void)resetCustomizationSheetBasedOnProfile:(NSString *)profileString
{
/* Container(string), transcode video(bool), transcode audio(bool),
* use subtitles(bool), video codec(string), video bitrate(integer),
* scale(float), fps(float), width(integer, height(integer),
* audio codec(string), audio bitrate(integer), channels(integer),
* samplerate(integer), subtitle codec(string), subtitle overlay(bool) */
NSArray * components = [profileString componentsSeparatedByString:@";"];
if ([components count] != 16) {
msg_Err(VLCIntf, "CAS: the requested profile '%s' is invalid", [profileString UTF8String]);
return;
}
[self selectCellByEncapsulationFormat:[components firstObject]];
[_customizeVidCheckbox setState:[[components objectAtIndex:1] intValue]];
[_customizeAudCheckbox setState:[[components objectAtIndex:2] intValue]];
[_customizeSubsCheckbox setState:[[components objectAtIndex:3] intValue]];
[self setVidBitrate:[[components objectAtIndex:5] intValue]];
[_customizeVidScalePopup selectItemWithTitle:[components objectAtIndex:6]];
[self setVidFramerate:[[components objectAtIndex:7] intValue]];
[_customizeVidWidthField setStringValue:[components objectAtIndex:8]];
[_customizeVidHeightField setStringValue:[components objectAtIndex:9]];
[self setAudBitrate:[[components objectAtIndex:11] intValue]];
[self setAudChannels:[[components objectAtIndex:12] intValue]];
[_customizeAudSampleratePopup selectItemWithTitle:[components objectAtIndex:13]];
[_customizeSubsOverlayCheckbox setState:[[components objectAtIndex:15] intValue]];
/* since there is no proper lookup mechanism in arrays, we need to implement a string specific one ourselves */
NSArray * tempArray = [_videoCodecs objectAtIndex:1];
NSUInteger count = [tempArray count];
NSString * searchString = [components objectAtIndex:4];
int videoKeep = [searchString isEqualToString:@"copy"];
[_customizeVidKeepCheckbox setState:videoKeep];
if ([searchString isEqualToString:@"none"] || [searchString isEqualToString:@"0"] || videoKeep) {
[_customizeVidCodecPopup selectItemAtIndex:-1];
} else {
for (NSUInteger x = 0; x < count; x++) {
if ([[tempArray objectAtIndex:x] isEqualToString: searchString]) {
[_customizeVidCodecPopup selectItemAtIndex:x];
break;
}
}
}
tempArray = [_audioCodecs objectAtIndex:1];
count = [tempArray count];
searchString = [components objectAtIndex:10];
int audioKeep = [searchString isEqualToString:@"copy"];
[_customizeAudKeepCheckbox setState:audioKeep];
if ([searchString isEqualToString:@"none"] || [searchString isEqualToString:@"0"] || audioKeep) {
[_customizeAudCodecPopup selectItemAtIndex:-1];
} else {
for (NSUInteger x = 0; x < count; x++) {
if ([[tempArray objectAtIndex:x] isEqualToString: searchString]) {
[_customizeAudCodecPopup selectItemAtIndex:x];
break;
}
}
}
tempArray = [_subsCodecs objectAtIndex:1];
count = [tempArray count];
searchString = [components objectAtIndex:14];
if ([searchString isEqualToString:@"none"] || [searchString isEqualToString:@"0"]) {
[_customizeSubsPopup selectItemAtIndex:-1];
} else {
for (NSUInteger x = 0; x < count; x++) {
if ([[tempArray objectAtIndex:x] isEqualToString: searchString]) {
[_customizeSubsPopup selectItemAtIndex:x];
break;
}
}
}
[self videoSettingsChanged:nil];
[self audioSettingsChanged:nil];
[self subSettingsChanged:nil];
[self setCurrentProfile: [[NSMutableArray alloc] initWithArray:[profileString componentsSeparatedByString:@";"]]];
}
- (void)selectCellByEncapsulationFormat:(NSString *)format
{
if ([format isEqualToString:@"ts"])
[_customizeEncapMatrix selectCellWithTag:MPEGTS];
else if ([format isEqualToString:@"webm"])
[_customizeEncapMatrix selectCellWithTag:WEBM];
else if ([format isEqualToString:@"ogg"])
[_customizeEncapMatrix selectCellWithTag:OGG];
else if ([format isEqualToString:@"ogm"])
[_customizeEncapMatrix selectCellWithTag:OGG];
else if ([format isEqualToString:@"mp4"])
[_customizeEncapMatrix selectCellWithTag:MP4];
else if ([format isEqualToString:@"mov"])
[_customizeEncapMatrix selectCellWithTag:MP4];
else if ([format isEqualToString:@"ps"])
[_customizeEncapMatrix selectCellWithTag:MPEGPS];
else if ([format isEqualToString:@"mpjpeg"])
[_customizeEncapMatrix selectCellWithTag:MJPEG];
else if ([format isEqualToString:@"wav"])
[_customizeEncapMatrix selectCellWithTag:WAV];
else if ([format isEqualToString:@"flv"])
[_customizeEncapMatrix selectCellWithTag:FLV];
else if ([format isEqualToString:@"mpeg1"])
[_customizeEncapMatrix selectCellWithTag:MPEG1];
else if ([format isEqualToString:@"mkv"])
[_customizeEncapMatrix selectCellWithTag:MKV];
else if ([format isEqualToString:@"raw"])
[_customizeEncapMatrix selectCellWithTag:RAW];
else if ([format isEqualToString:@"avi"])
[_customizeEncapMatrix selectCellWithTag:AVI];
else if ([format isEqualToString:@"asf"])
[_customizeEncapMatrix selectCellWithTag:ASF];
else if ([format isEqualToString:@"wmv"])
[_customizeEncapMatrix selectCellWithTag:ASF];
else
msg_Err(VLCIntf, "CAS: unknown encap format requested for customization");
}
- (NSString *)currentEncapsulationFormatAsFileExtension:(BOOL)b_extension
{
NSUInteger cellTag = (NSUInteger) [[_customizeEncapMatrix selectedCell] tag];
NSString * returnValue;
switch (cellTag) {
case MPEGTS:
returnValue = @"ts";
break;
case WEBM:
returnValue = @"webm";
break;
case OGG:
returnValue = @"ogg";
break;
case MP4:
{
if (b_extension)
returnValue = @"m4v";
else
returnValue = @"mp4";
break;
}
case MPEGPS:
{
if (b_extension)
returnValue = @"mpg";
else
returnValue = @"ps";
break;
}
case MJPEG:
returnValue = @"mjpeg";
break;
case WAV:
returnValue = @"wav";
break;
case FLV:
returnValue = @"flv";
break;
case MPEG1:
{
if (b_extension)
returnValue = @"mpg";
else
returnValue = @"mpeg1";
break;
}
case MKV:
returnValue = @"mkv";
break;
case RAW:
returnValue = @"raw";
break;
case AVI:
returnValue = @"avi";
break;
case ASF:
returnValue = @"asf";
break;
default:
returnValue = @"none";
break;
}
return returnValue;
}
- (NSString *)composedOptions
{
NSMutableString *composedOptions = [[NSMutableString alloc] initWithString:@":sout=#transcode{"];
BOOL haveVideo = YES;
if ([[self.currentProfile objectAtIndex:1] intValue]) {
// video is enabled
if (![[self.currentProfile objectAtIndex:4] isEqualToString:@"copy"]) {
[composedOptions appendFormat:@"vcodec=%@", [self.currentProfile objectAtIndex:4]];
if ([[self.currentProfile objectAtIndex:5] intValue] > 0) // bitrate
[composedOptions appendFormat:@",vb=%@", [self.currentProfile objectAtIndex:5]];
if ([[self.currentProfile objectAtIndex:6] floatValue] > 0.) // scale
[composedOptions appendFormat:@",scale=%@", [self.currentProfile objectAtIndex:6]];
if ([[self.currentProfile objectAtIndex:7] floatValue] > 0.) // fps
[composedOptions appendFormat:@",fps=%@", [self.currentProfile objectAtIndex:7]];
if ([[self.currentProfile objectAtIndex:8] intValue] > 0) // width
[composedOptions appendFormat:@",width=%@", [self.currentProfile objectAtIndex:8]];
if ([[self.currentProfile objectAtIndex:9] intValue] > 0) // height
[composedOptions appendFormat:@",height=%@", [self.currentProfile objectAtIndex:9]];
} else {
haveVideo = NO;
}
} else {
[composedOptions appendString:@"vcodec=none"];
}
BOOL haveAudio = YES;
if ([[self.currentProfile objectAtIndex:2] intValue]) {
// audio is enabled
if (![[self.currentProfile objectAtIndex:10] isEqualToString:@"copy"]) {
if(haveVideo)
[composedOptions appendString:@","];
[composedOptions appendFormat:@"acodec=%@", [self.currentProfile objectAtIndex:10]];
[composedOptions appendFormat:@",ab=%@", [self.currentProfile objectAtIndex:11]]; // bitrate
[composedOptions appendFormat:@",channels=%@", [self.currentProfile objectAtIndex:12]]; // channel number
[composedOptions appendFormat:@",samplerate=%@", [self.currentProfile objectAtIndex:13]]; // sample rate
} else {
haveAudio = NO;
}
} else {
if(haveVideo)
[composedOptions appendString:@","];
[composedOptions appendString:@"acodec=none"];
}
if ([self.currentProfile objectAtIndex:3]) {
if(haveVideo || haveAudio)
[composedOptions appendString:@","];
// subtitles enabled
[composedOptions appendFormat:@"scodec=%@", [self.currentProfile objectAtIndex:14]];
if ([[self.currentProfile objectAtIndex:15] intValue])
[composedOptions appendFormat:@",soverlay"];
}
if (!b_streaming) {
/* file transcoding */
// add muxer
[composedOptions appendFormat:@"}:standard{mux=%@", [self.currentProfile firstObject]];
// add output destination
[composedOptions appendFormat:@",access=file{no-overwrite},dst=%@}", _outputDestination];
} else {
/* streaming */
if ([[[_streamTypePopup selectedItem] title] isEqualToString:@"RTP"])
[composedOptions appendFormat:@":rtp{mux=ts,dst=%@,port=%@", _outputDestination, [_streamPortField stringValue]];
else if ([[[_streamTypePopup selectedItem] title] isEqualToString:@"UDP"])
[composedOptions appendFormat:@":standard{mux=ts,dst=%@,port=%@,access=udp", _outputDestination, [_streamPortField stringValue]];
else if ([[[_streamTypePopup selectedItem] title] isEqualToString:@"MMSH"])
[composedOptions appendFormat:@":standard{mux=asfh,dst=%@,port=%@,access=mmsh", _outputDestination, [_streamPortField stringValue]];
else
[composedOptions appendFormat:@":standard{mux=%@,dst=%@,port=%@,access=http", [self.currentProfile firstObject], [_streamPortField stringValue], _outputDestination];
if ([_streamSAPCheckbox state])
[composedOptions appendFormat:@",sap,name=\"%@\"", [_streamChannelField stringValue]];
if ([_streamSDPMatrix selectedCell] != [_streamSDPMatrix cellWithTag:0]) {
NSInteger tag = [[_streamSDPMatrix selectedCell] tag];
switch (tag) {
case 1:
[composedOptions appendFormat:@",sdp=\"http://%@\"", [_streamSDPField stringValue]];
break;
case 2:
[composedOptions appendFormat:@",sdp=\"rtsp://%@\"", [_streamSDPField stringValue]];
break;
case 3:
[composedOptions appendFormat:@",sdp=\"file://%s\"", vlc_path2uri([[_streamSDPField stringValue] UTF8String], NULL)];
default:
break;
}
}
[composedOptions appendString:@"} :sout-keep"];
}
return [NSString stringWithString:composedOptions];
}
- (void)updateCurrentProfile
{
[self.currentProfile removeAllObjects];
NSInteger i;
[self.currentProfile addObject: [self currentEncapsulationFormatAsFileExtension:NO]];
[self.currentProfile addObject: [NSString stringWithFormat:@"%li", [_customizeVidCheckbox state]]];
[self.currentProfile addObject: [NSString stringWithFormat:@"%li", [_customizeAudCheckbox state]]];
[self.currentProfile addObject: [NSString stringWithFormat:@"%li", [_customizeSubsCheckbox state]]];
NSString *videoCodec;
if([_customizeVidKeepCheckbox state] == NSOnState)
videoCodec = @"copy";
else {
i = [_customizeVidCodecPopup indexOfSelectedItem];
if (i >= 0)
videoCodec = [[_videoCodecs objectAtIndex:1] objectAtIndex:i];
else
videoCodec = @"none";
}
[self.currentProfile addObject: videoCodec];
[self.currentProfile addObject: [NSString stringWithFormat:@"%i", [self vidBitrate]]];
[self.currentProfile addObject: [NSString stringWithFormat:@"%i", [[[_customizeVidScalePopup selectedItem] title] intValue]]];
[self.currentProfile addObject: [NSString stringWithFormat:@"%i", [self vidFramerate]]];
[self.currentProfile addObject: [NSString stringWithFormat:@"%i", [_customizeVidWidthField intValue]]];
[self.currentProfile addObject: [NSString stringWithFormat:@"%i", [_customizeVidHeightField intValue]]];
NSString *audioCodec;
if([_customizeAudKeepCheckbox state] == NSOnState)
audioCodec = @"copy";
else {
i = [_customizeAudCodecPopup indexOfSelectedItem];
if (i >= 0)
audioCodec = [[_audioCodecs objectAtIndex:1] objectAtIndex:i];
else
audioCodec = @"none";
}
[self.currentProfile addObject: audioCodec];
[self.currentProfile addObject: [NSString stringWithFormat:@"%i", [self audBitrate]]];
[self.currentProfile addObject: [NSString stringWithFormat:@"%i", [self audChannels]]];
[self.currentProfile addObject: [[_customizeAudSampleratePopup selectedItem] title]];
i = [_customizeSubsPopup indexOfSelectedItem];
if (i >= 0)
[self.currentProfile addObject: [[_subsCodecs objectAtIndex:1] objectAtIndex:i]];
else
[self.currentProfile addObject: @"none"];
[self.currentProfile addObject: [NSString stringWithFormat:@"%li", [_customizeSubsOverlayCheckbox state]]];
}
- (void)storeProfilesOnDisk
{
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:_profileNames forKey:@"CASProfileNames"];
[defaults setObject:_profileValueList forKey:@"CASProfiles"];
[defaults synchronize];
}
- (void)recreateProfilePopup
{
[_profilePopup removeAllItems];
[_profilePopup addItemsWithTitles:self.profileNames];
[_profilePopup addItemWithTitle:_NS("Custom")];
[[_profilePopup menu] addItem:[NSMenuItem separatorItem]];
[_profilePopup addItemWithTitle:_NS("Organize Profiles...")];
[[_profilePopup lastItem] setTarget: self];
[[_profilePopup lastItem] setAction: @selector(deleteProfileAction:)];
}
- (IBAction)customizeSubsCheckbox:(id)sender {
}
@end