Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/VLC/modules/gui/macosx/panels/   (VLC Media Plyer ©)  Datei vom 23.9.2026 mit Größe 29 kB image not shown  

Quelle  VLCInformationWindowController.m  Sprache: unbekannt

 
Spracherkennung für: .m vermutete Sprache: Masm {Masm[66] Cobol[40] Lisp[33]} [Methode: maximale Elemente, drei Dimensionen]

/*****************************************************************************
 * VLCInformationWindowController.m: Controller for the codec info panel
 *****************************************************************************
 * Copyright (C) 2002-2019 VLC authors and VideoLAN
 *
 * Authors: Benjamin Pracht <bigben at videolan dot org>
 *          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 "VLCInformationWindowController.h"

#import "extensions/NSImage+VLCAdditions.h"
#import "extensions/NSString+Helpers.h"
#import "extensions/NSTextField+VLCAdditions.h"

#import "library/VLCInputItem.h"
#import "library/VLCLibraryController.h"
#import "library/VLCLibraryImageCache.h"
#import "library/VLCLibraryModel.h"
#import "library/VLCLibraryRepresentedItem.h"

#import "main/VLCMain.h"

#import "playqueue/VLCPlayQueueController.h"
#import "playqueue/VLCPlayerController.h"

#import "views/VLCImageView.h"
#import "views/VLCSettingTextField.h"

#import "windows/video/VLCVideoOutputProvider.h"

#define PERFORM_ACTION_READWRITE_TEXTFIELDS(actionCallback) \
actionCallback(title);                                      \
actionCallback(artist);                                     \
actionCallback(album);                                      \
actionCallback(genre);                                      \
actionCallback(trackNumber);                                \
actionCallback(date);                                       \
actionCallback(actors);                                     \
actionCallback(director);                                   \
actionCallback(showName);                                   \
actionCallback(copyright);                                  \
actionCallback(publisher);                                  \
actionCallback(language);                                   \
actionCallback(contentDescription);

#define PERFORM_ACTION_ALL_TEXTFIELDS(actionCallback)   \
PERFORM_ACTION_READWRITE_TEXTFIELDS(actionCallback);    \
actionCallback(decodedMRL);                             \
actionCallback(trackTotal);                             \
actionCallback(season);                                 \
actionCallback(episode);                                \
actionCallback(nowPlaying);                             \
actionCallback(encodedBy);

#pragma mark - data storage object

@interface VLCCodecInformationTreeItem : NSObject

@property (readwrite) NSString *propertyName;
@property (readwrite) NSString *propertyValue;

@property (readwrite) NSArray *children;

@end

@implementation VLCCodecInformationTreeItem

- (NSString *)description
{
    return [NSString stringWithFormat:@"%@: name: %@ value: %@ children: %lu", NSStringFromClass([self class]), self.propertyName, self.propertyValue, self.children.count];
}

@end

#pragma mark - window controller

@interface VLCInformationWindowController () <NSOutlineViewDataSource, NSOutlineViewDelegate>
{
    VLCCodecInformationTreeItem *_rootCodecInformationItem;
    NSImage *_artwork;
    NSURL *_newArtworkURL;
    BOOL _statisticsEnabled;
}
@end

@implementation VLCInformationWindowController

- (id)init
{
    self = [super initWithWindowNibName:@"VLCInformationWindow"];
    return self;
}

- (void)dealloc
{
    [NSNotificationCenter.defaultCenter removeObserver:self];
}

- (void)awakeFromNib
{
    _statisticsEnabled = var_InheritBool(getIntf(), "stats");

    [self.window setExcludedFromWindowsMenu: YES];
    [self.window setCollectionBehavior: NSWindowCollectionBehaviorFullScreenAuxiliary];
    [self.window setInitialFirstResponder: _decodedMRLLabel];

    self.outlineView.dataSource = self;
    self.outlineView.delegate = self;
    self.outlineView.tableColumns.lastObject.resizingMask = NSTableColumnAutoresizingMask;

    NSNotificationCenter * const notificationCenter = NSNotificationCenter.defaultCenter;
    if (_mainMenuInstance && _statisticsEnabled) {
        [notificationCenter addObserver:self
                               selector:@selector(updateStatistics:)
                                   name:VLCPlayerStatisticsUpdated
                                 object:nil];
    }
    [notificationCenter addObserver:self
                           selector:@selector(updateCocoaWindowLevel:)
                               name:VLCWindowShouldUpdateLevel
                             object:nil];
    [notificationCenter addObserver:self
                           selector:@selector(mediaItemWasParsed:)
                               name:VLCInputItemParsingSucceeded
                             object:nil];

    [self initStrings];

    if (!_statisticsEnabled || !_mainMenuInstance) {
        if ([_segmentedView segmentCount] >= 3)
            [_segmentedView setSegmentCount: 2];
    } else {
        [self initMediaPanelStats];
    }

#define SET_METADATA_SETTING_FIELD_DELEGATE(field)  \
_##field##TextField.delegate = self

    PERFORM_ACTION_ALL_TEXTFIELDS(SET_METADATA_SETTING_FIELD_DELEGATE)

    [self updateRepresentation];
}

- (void)initStrings
{
    [self.window setTitle: _NS("Media Information")];

    [_decodedMRLLabel setStringValue: _NS("Location")];
    [_titleLabel setStringValue: NSTR(VLC_META_TITLE)];
    [_artistLabel setStringValue: NSTR(VLC_META_ARTIST)];
    [_saveMetaDataButton setStringValue: _NS("Save Metadata")];

    [_segmentedView setLabel:_NS("General") forSegment:0];
    [_segmentedView setLabel:_NS("Codec Details") forSegment:1];
    [_segmentedView setLabel:_NS("Statistics") forSegment:2];

    /* constants defined in vlc_meta.h */
    [_genreLabel setStringValue: NSTR(VLC_META_GENRE)];
    [_copyrightLabel setStringValue: NSTR(VLC_META_COPYRIGHT)];
    [_albumLabel setStringValue: NSTR(VLC_META_ALBUM)];
    [_trackNumberLabel setStringValue: NSTR(VLC_META_TRACK_NUMBER)];
    [_trackTotalLabel setStringValue: _NS("Track Total")];
    [_contentDescriptionLabel setStringValue: NSTR(VLC_META_DESCRIPTION)];
    [_dateLabel setStringValue: NSTR(VLC_META_DATE)];
    [_languageLabel setStringValue: NSTR(VLC_META_LANGUAGE)];
    [_nowPlayingLabel setStringValue: NSTR(VLC_META_NOW_PLAYING)];
    [_publisherLabel setStringValue: NSTR(VLC_META_PUBLISHER)];
    [_encodedByLabel setStringValue: NSTR(VLC_META_ENCODED_BY)];
    [_showNameLabel setStringValue: NSTR(VLC_META_SHOW_NAME)];
    [_episodeLabel setStringValue: NSTR(VLC_META_EPISODE)];
    [_seasonLabel setStringValue: NSTR(VLC_META_SEASON)];
    [_actorsLabel setStringValue: NSTR(VLC_META_ACTORS)];
    [_directorLabel setStringValue: NSTR(VLC_META_DIRECTOR)];

    /* statistics */
    [_inputLabel setStringValue: _NS("Input")];
    [_inputReadBytesLabel setStringValue: _NS("Read at media")];
    [_inputBitrateLabel setStringValue: _NS("Input bitrate")];
    [_inputReadPacketsLabel setStringValue: _NS("Read packets")];
    [_demuxReadBytesLabel setStringValue: _NS("Demuxed")];
    [_demuxReadPacketsLabel setStringValue: _NS("Demuxed packets")];
    [_demuxBitrateLabel setStringValue: _NS("Stream bitrate")];
    [_demuxCorruptedLabel setStringValue: _NS("Corrupted")];
    [_demuxDiscontinuitiesLabel setStringValue: _NS("Discontinuities")];

    [_videoLabel setStringValue: _NS("Video")];
    [_videoDecodedLabel setStringValue: _NS("Decoded blocks")];
    [_displayedLabel setStringValue: _NS("Displayed frames")];
    [_lostFramesLabel setStringValue: _NS("Lost frames")];
    [_lateFramesLabel setStringValue: _NS("Late frames")];

    [_audioLabel setStringValue: _NS("Audio")];
    [_audioDecodedLabel setStringValue: _NS("Decoded blocks")];
    [_playedAudioBuffersLabel setStringValue: _NS("Played buffers")];
    [_lostAudioBuffersLabel setStringValue: _NS("Lost buffers")];
}

- (void)updateCocoaWindowLevel:(NSNotification *)aNotification
{
    NSInteger windowLevel = [aNotification.userInfo[VLCWindowLevelKey] integerValue];
    if ([self.window isVisible] && [self.window level] != windowLevel)
        [self.window setLevel: windowLevel];
}

- (IBAction)toggleWindow:(id)sender
{
    if ([self.window isKeyWindow])
        [self.window orderOut:sender];
    else {
        [self.window setLevel: VLCMain.sharedInstance.voutProvider.currentStatusWindowLevel];
        [self.window makeKeyAndOrderFront:sender];
    }
}

- (IBAction)copyMrl:(id)sender
{
    if (self.representedInputItems.count == 0) {
        return;
    } else if (self.representedInputItems.count == 1) {
        NSPasteboard * const pasteboard = NSPasteboard.generalPasteboard;
        [pasteboard clearContents];
        [pasteboard setString:self.representedInputItems.firstObject.MRL
                      forType:NSPasteboardTypeString];
    } else {
        NSMenu * const choiceMenu = [[NSMenu alloc] initWithTitle:_NS("Copy MRL")];
        for (VLCInputItem * const inputItem in self.representedInputItems) {
            NSMenuItem * const menuItem =
                [[NSMenuItem alloc] initWithTitle:inputItem.title
                                           action:@selector(copyMrlFromMenuItem:)
                                    keyEquivalent:@""];
            menuItem.representedObject = inputItem;
            [choiceMenu addItem:menuItem];
        }
        CGFloat senderHeight = 0;
        if ([sender isKindOfClass:NSView.class]) {
            senderHeight = ((NSView *)sender).frame.size.height;
        }
        [choiceMenu popUpMenuPositioningItem:nil
                                  atLocation:NSMakePoint(0, senderHeight)
                                      inView:sender];
    }
}

- (void)copyMrlFromMenuItem:(id)sender
{
    NSParameterAssert(sender);
    NSParameterAssert([sender isKindOfClass:NSMenuItem.class]);
    NSMenuItem * const menuItem = (NSMenuItem *)sender;
    NSParameterAssert(menuItem.representedObject);
    NSParameterAssert([menuItem.representedObject isKindOfClass:VLCInputItem.class]);
    VLCInputItem * const inputItem = (VLCInputItem *)menuItem.representedObject;
    NSPasteboard * const pasteboard = NSPasteboard.generalPasteboard;
    [pasteboard clearContents];
    [pasteboard setString:inputItem.MRL forType:NSPasteboardTypeString];
}

- (void)initMediaPanelStats
{
    //Initializing Input Variables
    [_inputReadBytesTextField setStringValue: [NSString stringWithFormat:_NS("%.1f KiB"), (float)0]];
    [_inputReadPacketsTextField setIntValue: 0];
    [_inputBitrateTextField setStringValue: [NSString stringWithFormat:@"%6.0f kb/s", (float)0]];
    [_demuxReadBytesTextField setStringValue: [NSString stringWithFormat:_NS("%.1f KiB"), (float)0]];
    [_demuxReadPacketsTextField setIntValue: 0];
    [_demuxBitrateTextField setStringValue: [NSString stringWithFormat:@"%6.0f kb/s", (float)0]];
    [_demuxDiscontinuitiesTextField setIntValue: 0];
    [_demuxCorruptedTextField setIntValue: 0];

    //Initializing Video Variables
    [_videoDecodedTextField setIntValue:0];
    [_displayedTextField setIntValue:0];
    [_lateFramesTextField setIntValue:0];
    [_lostFramesTextField setIntValue:0];

    //Initializing Audio Variables
    [_audioDecodedTextField setIntValue:0];
    [_playedAudioBuffersTextField setIntValue: 0];
    [_lostAudioBuffersTextField setIntValue: 0];
}

- (void)setRepresentedMediaLibraryItems:(NSArray<VLCLibraryRepresentedItem *> *)representedMediaLibraryItems
{
    NSMutableArray<VLCInputItem *> * const inputItems = NSMutableArray.array;
    for (VLCLibraryRepresentedItem * const representedItem in representedMediaLibraryItems) {
        NSArray<VLCMediaLibraryMediaItem *> * const mediaItems = representedItem.item.mediaItems;
        for (VLCMediaLibraryMediaItem * const mediaItem in mediaItems) {
            [inputItems addObject:mediaItem.inputItem];
        }
    }

    NSParameterAssert(inputItems.count > 0);
    NSArray<VLCInputItem *> * const nonMutableInputItems = inputItems.copy;
    _representedInputItems = nonMutableInputItems;

    NSMutableSet * const artworkMrlSet = NSMutableSet.set;

    __weak typeof(self) weakSelf = self;
    const dispatch_queue_t queue = dispatch_queue_create("vlc_infowindow_libraryitemimg_queue", 0);
    dispatch_async(queue, ^{
        NSMutableArray<NSImage *> * const artworkImages = NSMutableArray.array;
        dispatch_group_t group = dispatch_group_create();

        for (VLCLibraryRepresentedItem * const item in representedMediaLibraryItems) {
            NSArray<VLCMediaLibraryMediaItem *> * const mediaItems = item.item.mediaItems;

            for (VLCMediaLibraryMediaItem * const mediaItem in mediaItems) {
                @synchronized (artworkMrlSet) {
                    NSString * const itemArtworkMrl = mediaItem.smallArtworkMRL;
                    if ([artworkMrlSet containsObject:itemArtworkMrl] || itemArtworkMrl == nil) {
                        continue;
                    }
                    [artworkMrlSet addObject:itemArtworkMrl];
                }

                @synchronized (artworkImages) {
                    dispatch_group_enter(group);
                    [VLCLibraryImageCache thumbnailForLibraryItem:mediaItem
                                                   withCompletion:^(NSImage * const image) {
                        if (!weakSelf || nonMutableInputItems != weakSelf.representedInputItems) {
                            dispatch_group_leave(group);
                            return;
                        }
                        if (image) {
                            [artworkImages addObject:image];
                        }
                        dispatch_group_leave(group);
                    }];
                }
            }
        }

        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

        if (artworkImages.count == 0) {
            dispatch_async(dispatch_get_main_queue(), ^{
                _artwork = NSImage.VLCNoArtImage;
                [self updateRepresentation];
            });
            return;
        }

        // Without an image set the button's size can be {{0, 0}, {0, 0}}
        const CGFloat buttonHeight = self.artworkImageButton.frame.size.height;
        const NSSize artworkSize =
            buttonHeight == 0 ? NSMakeSize(256, 256) : NSMakeSize(buttonHeight, buttonHeight);
        NSArray<NSValue *> * const frames =
            [NSImage framesForCompositeImageSquareGridWithImages:artworkImages
                                                            size:artworkSize
                                                   gridItemCount:artworkImages.count];

        dispatch_async(dispatch_get_main_queue(), ^{
            _artwork = [NSImage compositeImageWithImages:artworkImages
                                                frames:frames
                                                    size:artworkSize];

            [self updateRepresentation];
        });
    });
}

- (void)setRepresentedInputItems:(NSArray<VLCInputItem *> *)representedInputItems
{
    if (representedInputItems == _representedInputItems) {
        return;
    }

    NSParameterAssert(representedInputItems.count > 0);
    _representedInputItems = representedInputItems;

    __weak typeof(self) weakSelf = self;
    const dispatch_queue_t queue = dispatch_queue_create("vlc_infowindow_inputitemimg_queue", 0);
    dispatch_async(queue, ^{
        NSMutableArray<NSImage *> * const artworkImages = NSMutableArray.array;
        dispatch_group_t group = dispatch_group_create();

        for (VLCInputItem * const item in representedInputItems) {
            @synchronized (artworkImages) {
                dispatch_group_enter(group);
                [VLCLibraryImageCache thumbnailForInputItem:item
                                             withCompletion:^(NSImage * const image) {
                    if (!weakSelf || representedInputItems != weakSelf.representedInputItems) {
                        dispatch_group_leave(group);
                        return;
                    }
                    if (image) {
                        [artworkImages addObject:image];
                    }
                    dispatch_group_leave(group);
                }];
            }
        }

        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

        if (artworkImages.count == 0) {
            dispatch_async(dispatch_get_main_queue(), ^{
                _artwork = NSImage.VLCNoArtImage;
                [self updateRepresentation];
            });
            return;
        }

        // Without an image set the button's size can be {{0, 0}, {0, 0}}
        const CGFloat buttonHeight = self.artworkImageButton.frame.size.height;
        const NSSize artworkSize =
            buttonHeight == 0 ? NSMakeSize(256, 256) : NSMakeSize(buttonHeight, buttonHeight);
        NSArray<NSValue *> * const frames =
            [NSImage framesForCompositeImageSquareGridWithImages:artworkImages
                                                            size:artworkSize
                                                   gridItemCount:artworkImages.count];

        dispatch_async(dispatch_get_main_queue(), ^{
            _artwork = [NSImage compositeImageWithImages:artworkImages
                                                frames:frames
                                                    size:artworkSize];
            [self updateRepresentation];
        });
    });
}

- (void)mediaItemWasParsed:(NSNotification *)aNotification
{
    [self updateRepresentation];
}

- (void)updateStatistics:(NSNotification *)aNotification
{
    NSAssert(self.representedInputItems.count == 1, @"Should not be updating stats for many items");
    VLCPlayerController * const playerController =
        VLCMain.sharedInstance.playQueueController.playerController;
    VLCInputItem * const currentPlayingItem = playerController.currentMedia;
    VLCInputItem * const firstItem = self.representedInputItems.firstObject;
    
    if (self.mainMenuInstance && ![currentPlayingItem.MRL isEqualToString:firstItem.MRL]) {
        self.representedInputItems = @[currentPlayingItem];
    }

    NSAssert(_statisticsEnabled, @"Statistics should not be updated when they are disabled!");
    VLCInputStats * const inputStats = aNotification.userInfo[VLCPlayerInputStats];
    NSAssert(inputStats != nil, @"inputStats received for statistics update should not be nil!");

    /* input */
    [_inputReadBytesTextField setStringValue: [NSString stringWithFormat:
                                          @"%8.0f KiB", (float)(inputStats.inputReadBytes)/1024]];
    [_inputReadPacketsTextField setIntegerValue:inputStats.inputReadPackets];
    [_inputBitrateTextField setStringValue: [NSString stringWithFormat:
                                             @"%6.0f kb/s", (float)(inputStats.inputBitrate)*8000]];
    [_demuxReadBytesTextField setStringValue: [NSString stringWithFormat:
                                           @"%8.0f KiB", (float)(inputStats.demuxReadBytes)/1024]];
    [_demuxReadPacketsTextField setIntegerValue:inputStats.demuxReadPackets];
    [_demuxBitrateTextField setStringValue: [NSString stringWithFormat:
                                             @"%6.0f kb/s", (float)(inputStats.demuxBitrate)*8000]];
    [_demuxCorruptedTextField setIntegerValue:inputStats.demuxCorrupted];
    [_demuxDiscontinuitiesTextField setIntegerValue:inputStats.demuxDiscontinuity];

    /* Video */
    [_videoDecodedTextField setIntegerValue: inputStats.decodedVideo];
    [_displayedTextField setIntegerValue: inputStats.displayedPictures];
    [_lateFramesTextField setIntegerValue: inputStats.latePictures];
    [_lostFramesTextField setIntegerValue: inputStats.lostPictures];

    /* Audio */
    [_audioDecodedTextField setIntegerValue: inputStats.decodedAudio];
    [_playedAudioBuffersTextField setIntegerValue: inputStats.playedAudioBuffers];
    [_lostAudioBuffersTextField setIntegerValue: inputStats.lostAudioBuffers];
}

- (void)fillWindowWithInputItemData:(VLCInputItem *)inputItem
{
    NSParameterAssert(inputItem != nil);
    if (!inputItem.preparsed) {
        [inputItem parseInputItem];
    }

#define FILL_FIELD_FROM_INPUTITEM(field)                                                    \
_##field##TextField.originalStateString = inputItem.field == nil ? @"" : inputItem.field;

    PERFORM_ACTION_ALL_TEXTFIELDS(FILL_FIELD_FROM_INPUTITEM);

#undef FILL_FIELD_FROM_INPUTITEM

    _artworkImageButton.image = _artwork;

    if (!_mainMenuInstance) {
        self.window.title = inputItem.title;
    }
}

- (void)fillWindowWithDictionaryData:(NSDictionary<NSString *, id> *)dict
{
    NSParameterAssert(dict != nil);

#define FILL_FIELD_FROM_DICT(field)                                                 \
{                                                                                   \
    NSString * const dictKey = [NSString stringWithUTF8String:#field];              \
    NSString * const fieldValue = [dict objectForKey:dictKey];                      \
                                                                                    \
    if ([fieldValue isEqualToString:VLCInputItemCommonDataDifferingFlagString]) {   \
        _##field##TextField.placeholderString = _NS("(Multiple values)");           \
        _##field##TextField.originalStateString = @"";                              \
    } else if (fieldValue != nil) {                                                 \
        _##field##TextField.placeholderString = @"";                                \
        _##field##TextField.originalStateString = fieldValue;                       \
    } else {                                                                        \
        _##field##TextField.placeholderString = @"";                                \
        _##field##TextField.originalStateString = @"";                              \
    }                                                                               \
}

    PERFORM_ACTION_ALL_TEXTFIELDS(FILL_FIELD_FROM_DICT);

#undef FILL_FIELD_FROM_DICT

    _artworkImageButton.image = _artwork;
}

- (void)updateRepresentation
{
    _saveMetaDataButton.enabled = NO;
    _newArtworkURL = nil;
    _artworkImageButton.image = _artwork;
    _artworkImageButton.alternateImage = _artwork;

    if (_representedInputItems.count == 0) {
        /* Erase */
#define CLEAR_TEXT(field) \
_##field##TextField.originalStateString = @"";

        PERFORM_ACTION_ALL_TEXTFIELDS(CLEAR_TEXT);

#undef CLEAR_TEXT
        _artworkImageButton.image = NSImage.VLCNoArtImage;
    } else if (_representedInputItems.count == 1) {
        [self fillWindowWithInputItemData:_representedInputItems.firstObject];
    } else if (_representedInputItems.count > 1) {
        NSDictionary * const commonItemsData = commonInputItemData(_representedInputItems);

        if ([commonItemsData objectForKey:@"inputItem"]) {
            self.representedInputItems = @[[commonItemsData objectForKey:@"inputItem"]];
        } else {
            [self fillWindowWithDictionaryData:commonItemsData];
        }
    }

    /* reload the codec details table */
    [self updateStreamsList];
}

- (void)updateStreamsForInputItems:(NSArray<VLCInputItem *> *)inputItems
{
    NSParameterAssert(inputItems != nil);

    for (VLCInputItem * const inputItem in inputItems) {
        input_item_t * const p_input = inputItem.vlcInputItem;
        vlc_mutex_lock(&p_input->lock);
        // build list of streams
        NSMutableArray * const streams = NSMutableArray.array;

        info_category_t *cat;
        vlc_list_foreach(cat, &p_input->categories, node) {
            if (info_category_IsHidden(cat)) {
                continue;
            }

            VLCCodecInformationTreeItem * const subItem = [[VLCCodecInformationTreeItem alloc] init];
            subItem.propertyName = toNSStr(cat->psz_name);

            // Build list of codec details
            NSMutableArray * const infos = NSMutableArray.array;

            info_t *info;
            info_foreach(info, &cat->infos) {
                VLCCodecInformationTreeItem * const infoItem = [[VLCCodecInformationTreeItem alloc] init];
                infoItem.propertyName = toNSStr(info->psz_name);
                infoItem.propertyValue = toNSStr(info->psz_value);
                [infos addObject:infoItem];
            }

            subItem.children = [infos copy];
            [streams addObject:subItem];
        }

        _rootCodecInformationItem.children = [streams copy];
        vlc_mutex_unlock(&p_input->lock);
    }
}

- (void)updateStreamsList
{
    _rootCodecInformationItem = [[VLCCodecInformationTreeItem alloc] init];

    if (_representedInputItems.count > 0) {
        [self updateStreamsForInputItems:_representedInputItems];
    }

    [self.outlineView reloadData];
    [self.outlineView expandItem:nil expandChildren:YES];
    [self.outlineView sizeLastColumnToFit];
}

- (void)controlTextDidChange:(NSNotification *)notification
{
    BOOL settingsChanged = NO;

#define CHECK_FIELD_SETTING_CHANGED(field)                              \
settingsChanged = settingsChanged || _##field##TextField.settingChanged;

    PERFORM_ACTION_ALL_TEXTFIELDS(CHECK_FIELD_SETTING_CHANGED);

#undef CHECK_FIELD_SETTING_CHANGED

    settingsChanged = settingsChanged || _newArtworkURL != nil;

    _saveMetaDataButton.enabled = settingsChanged;
}

- (void)reloadMediaLibraryFoldersForInputItems:(NSArray<VLCInputItem *> *)inputItems
{
    VLCLibraryController * const libraryController = VLCMain.sharedInstance.libraryController;
    [libraryController reloadMediaLibraryFoldersForInputItems:inputItems];
}

- (void)saveInputItemsMetadata:(NSArray<VLCInputItem *> *)inputItems
{
    NSParameterAssert(inputItems);

#define SET_INPUTITEM_PROP(field, prop)                          \
{                                                                \
    NSString * const value = _##field##TextField.stringValue;    \
    if (_##field##TextField.settingChanged) {                    \
        inputItem.prop = value;                                  \
    }                                                            \
}

#define SET_INPUTITEM_MATCHING_PROP(field)      \
SET_INPUTITEM_PROP(field, field)                \

    for (VLCInputItem * const inputItem in inputItems) {
        // We do not have a textfield for names; set the contents of the title field to the input
        // item's name
        SET_INPUTITEM_PROP(title, name);
        PERFORM_ACTION_READWRITE_TEXTFIELDS(SET_INPUTITEM_MATCHING_PROP);

        if (_newArtworkURL != nil) { // Artwork urls require their own handling
            inputItem.artworkURL = _newArtworkURL;
        }

        [inputItem writeMetadataToFile];
    }

#undef SET_INPUTITEM_MATCHING_PROP
#undef SET_INPUTITEM_PROP

    [self updateRepresentation];
    [self reloadMediaLibraryFoldersForInputItems:inputItems];
}

- (IBAction)saveMetaData:(id)sender
{
    if (_representedInputItems.count > 0) {
        [self saveInputItemsMetadata:_representedInputItems];
    } else {
        NSAlert *alert = [[NSAlert alloc] init];
        [alert setMessageText:_NS("Error while saving meta")];
        [alert setInformativeText:_NS("VLC was unable to save the meta data.")];
        [alert addButtonWithTitle:_NS("OK")];
        [alert runModal];
    }
}

- (IBAction)chooseArtwork:(id)sender
{
    NSOpenPanel * const panel = [NSOpenPanel openPanel];
    [panel setAllowedFileTypes:@[@"png", @"jpg", @"jpeg", @"gif", @"tiff", @"tif", @"bmp"]];
    [panel setAllowsMultipleSelection:NO];
    [panel setCanChooseDirectories:NO];
    [panel setCanChooseFiles:YES];
    [panel setCanCreateDirectories:NO];
    [panel setResolvesAliases:YES];
    [panel setAllowsOtherFileTypes:NO];
    [panel setPrompt:_NS("Choose")];
    [panel beginSheetModalForWindow:self.window completionHandler:^(const NSInteger result) {
        if (result != NSModalResponseOK) {
            return;
        }

        NSURL * const url = panel.URLs.firstObject;
        NSImage * const image = [[NSImage alloc] initWithContentsOfURL:url];
        if (!image) {
            return;
        }

        _artwork = image;
        _artworkImageButton.image = [[NSImage alloc] initWithContentsOfURL:url];

        _newArtworkURL = url;
        _saveMetaDataButton.enabled = YES;
    }];
}

@end

@implementation VLCInformationWindowController (NSOutlineViewDataSource)

- (NSInteger)outlineView:(NSOutlineView *)outlineView
  numberOfChildrenOfItem:(id)item
{
    return (item == nil) ? _rootCodecInformationItem.children.count : [item children].count;
}

- (BOOL)outlineView:(NSOutlineView *)outlineView
   isItemExpandable:(id)item
{
    return [item children].count > 0;
}

- (id)outlineView:(NSOutlineView *)outlineView
            child:(NSInteger)index
           ofItem:(id)item
{
    return (item == nil) ? _rootCodecInformationItem.children[index] : [item children][index];
}

- (id)outlineView:(NSOutlineView *)outlineView
objectValueForTableColumn:(NSTableColumn *)tableColumn
           byItem:(id)item
{
    if (!item)
        return @"";

    if ([[tableColumn identifier] isEqualToString:@"0"]) {
        return [item propertyName];
    } else {
        return [item propertyValue];
    }
}

@end

@implementation VLCInformationWindowController (NSOutlineViewDelegate)

- (NSView *)outlineView:(NSTableView *)outlineView
     viewForTableColumn:(nullable NSTableColumn *)tableColumn
                   item:(nonnull id)item
{
    NSTextField * const cellView = [NSTextField defaultLabelWithString:@""];
    cellView.objectValue =
        [self outlineView:self.outlineView objectValueForTableColumn:tableColumn byItem:item];
    return cellView;
}

@end

[Dauer der Verarbeitung: 0.5 Sekunden, vorverarbeitet 2026-10-24]