用C#修改Mp3文件属性

前段时间买了个mp3播放器,当将我电脑上的音乐传进去时发现我电脑上的mp3文件太杂乱无章了,便写了个工具将其自动按歌手,专辑等分类整理了一下。这里主要谈一下在写这个工具中的对用C#修改Mp3文件属性的一点心得

MP3及wma等大多是通过ID3 Tag标记标题,歌手,出版日期等歌曲信息的,目前ID3主要用的是ID3v1及ID3v2两种,目前大部分mp3播放器也都支持这两种格式。关于ID3文件的详细格式,可以在ID3.org上查询。

最开始我是写了一个简单的ID3 Tag的解析器,本身写个解析器并不是很难,但要命的是网上下载的很多文件并不是严格遵循ID3格式来写文件头的,不仅要处理大量的未知异常行为,一个不留神会造成对mp3文件的损害。于是我便放弃了自己写解析器的念头,想看一下网上有没有什么开源的ID3 Tag的解析器。在ID3的主页上看了一下,还真不少,各个语言的都有,光C#的就有如下几个:

另外,codeproject上还找到了一个Professional Tag Editor for MP3 (ID3) and WMA,提供了完整的界面,试了一下,功能也比较完善。  


有了这些开源的解析器后,还得处理以下几个问题:
  1. 并不是ID3Tag的属性的获取
    播放时间,比特率等并不是ID3Tag的信息,如何获取这些信息又是一个难题
  2. 下载的mp3文件的id3格式及播放器支持问题
    那些解析器之提供了基本的读取和修改功能,而真正要把修改正确应用到文件还需要一些额外的处理。
    如标题,歌手等在ID3v1和ID3v2中都存在,而网上下载的文件有的保护ID3v1,有的包含ID3v2,还有的啥都不包含;同时,有的播放器并不支持ID3v2。
  3. 这些开源解析器本身的bug
    开源软件的最大不足时缺乏足够的文档和测试,这些软件本身也还存在一些bug,如中文显示及一些异常的处理等,弄不好也很容易损坏mp3文件

这几个问题处理起来还是很头疼的,这时我发现windows本身提供了mp3文件属性修改的api,通过这些api可以更安全,快捷的修改MP3属性。这里是我的一个实现(需要WindowsAPICodePack)。

ContractedBlock.gif ExpandedBlockStart.gif Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.WindowsAPICodePack.Shell;
using Microsoft.WindowsAPICodePack.Shell.PropertySystem;
using System.Reflection;

namespace MediaCore
ExpandedBlockStart.gifContractedBlock.gif
{
    
public class MediaTags
ExpandedSubBlockStart.gifContractedSubBlock.gif    
{
ContractedSubBlock.gifExpandedSubBlockStart.gif        
Mp3文件属性#region Mp3文件属性
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// 标题
        
/// </summary>

        [MediaProperty("Title")]
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public string Title getset; }

ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// 子标题
        
/// </summary>

        [MediaProperty("Media.SubTitle")]
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public string SubTitle getset; }

ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// 星级
        
/// </summary>

        [MediaProperty("Rating")]
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public uint? Rating getset; }

ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// 备注
        
/// </summary>

        [MediaProperty("Comment")]
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public string Comment getset; }

ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// 艺术家
        
/// </summary>

        [MediaProperty("Author")]
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public string Author getset; }

ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// 唱片集
        
/// </summary>

        [MediaProperty("Music.AlbumTitle")]
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public string AlbumTitle getset; }

ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// 唱片集艺术家
        
/// </summary>

        [MediaProperty("Music.AlbumArtist")]
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public string AlbumArtist getset; }

ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// 年
        
/// </summary>

        [MediaProperty("Media.Year")]
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public uint? Year getset; }

ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// 流派
        
/// </summary>

        [MediaProperty("Music.Genre")]
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public string Genre getset; }

ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// #
        
/// </summary>

        [MediaProperty("Music.TrackNumber")]
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public uint? TrackNumber getset; }

ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// 播放时间
        
/// </summary>

        [MediaProperty("Media.Duration")]
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public string Duration getprivate set; }

ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
        
/// 比特率
        
/// </summary>

        [MediaProperty("Audio.EncodingBitrate")]
ExpandedSubBlockStart.gifContractedSubBlock.gif        
public string BitRate getprivate set; }
        
#endregion


        
public MediaTags(string mediaPath)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            
//var obj = ShellObject.FromParsingName(mp3Path);   //缩略图,只读
            
//obj.Thumbnail.Bitmap.Save(@"R:\2.jpg");

            Init(mediaPath);
        }


        
void Init(string mediaPath)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            
using (var obj = ShellObject.FromParsingName(mediaPath))
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                var mediaInfo 
= obj.Properties;
                
foreach (var properItem in this.GetType().GetProperties())
ExpandedSubBlockStart.gifContractedSubBlock.gif                
{
                    var mp3Att 
= properItem.GetCustomAttributes(typeof(MediaPropertyAttribute), false).FirstOrDefault();
                    var shellProper 
= mediaInfo.GetProperty("System." + mp3Att);
                    var value 
= shellProper == null ? null : shellProper.ValueAsObject;

                    
if (value == null)
ExpandedSubBlockStart.gifContractedSubBlock.gif                    
{
                        
continue;
                    }


                    
if (shellProper.ValueType == typeof(string[]))    //艺术家,流派等多值属性
ExpandedSubBlockStart.gifContractedSubBlock.gif
                    {
                        properItem.SetValue(
thisstring.Join(";", value as string[]), null);
                    }

                    
else if (properItem.PropertyType!=shellProper.ValueType)    //一些只读属性,类型不是string,但作为string输出,避免转换 如播放时间,比特率等
ExpandedSubBlockStart.gifContractedSubBlock.gif
                    {
                        properItem.SetValue(
this,value == null ? "" : shellProper.FormatForDisplay(PropertyDescriptionFormat.Default),null);
                    }

                    
else
ExpandedSubBlockStart.gifContractedSubBlock.gif                    
{
                        properItem.SetValue(
this, value, null);
                    }

                }

            }
    
        }


        
public void Commit(string mp3Path)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            var old 
= new MediaTags(mp3Path);

            
using (var obj = ShellObject.FromParsingName(mp3Path))
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                var mediaInfo 
= obj.Properties;
                
foreach (var proper in this.GetType().GetProperties())
ExpandedSubBlockStart.gifContractedSubBlock.gif                
{
                    var oldValue 
= proper.GetValue(old, null);
                    var newValue 
= proper.GetValue(thisnull);

                    
if (oldValue == null && newValue == null)
ExpandedSubBlockStart.gifContractedSubBlock.gif                    
{
                        
continue;
                    }


                    
if (oldValue == null || !oldValue.Equals(newValue))
ExpandedSubBlockStart.gifContractedSubBlock.gif                    
{
                        var mp3Att 
= proper.GetCustomAttributes(typeof(MediaPropertyAttribute), false).FirstOrDefault();
                        var shellProper 
= mediaInfo.GetProperty("System." + mp3Att);
                        Console.WriteLine(mp3Att);
                        SetPropertyValue(shellProper, newValue);
                    }

                }

            }

        }


ContractedSubBlock.gifExpandedSubBlockStart.gif        
SetPropertyValue#region SetPropertyValue
        
static void SetPropertyValue(IShellProperty prop, object value)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            
if (prop.ValueType == typeof(string[]))        //只读属性不会改变,故与实际类型不符的只有string[]这一种
ExpandedSubBlockStart.gifContractedSubBlock.gif
            {
ExpandedSubBlockStart.gifContractedSubBlock.gif                
string[] values = (value as string).Split(new char[] ';' }, StringSplitOptions.RemoveEmptyEntries);
                (prop 
as ShellProperty<string[]>).Value = values;
            }

            
if (prop.ValueType == typeof(string))
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                (prop 
as ShellProperty<string>).Value = value as string;
            }

            
else if (prop.ValueType == typeof(ushort?))
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                (prop 
as ShellProperty<ushort?>).Value = value as ushort?;
            }

            
else if (prop.ValueType == typeof(short?))
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                (prop 
as ShellProperty<short?>).Value = value as short?;
            }

            
else if (prop.ValueType == typeof(uint?))
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                (prop 
as ShellProperty<uint?>).Value = value as uint?;
            }

            
else if (prop.ValueType == typeof(int?))
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                (prop 
as ShellProperty<int?>).Value = value as int?;
            }

            
else if (prop.ValueType == typeof(ulong?))
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                (prop 
as ShellProperty<ulong?>).Value = value as ulong?;
            }

            
else if (prop.ValueType == typeof(long?))
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                (prop 
as ShellProperty<long?>).Value = value as long?;
            }

            
else if (prop.ValueType == typeof(DateTime?))
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                (prop 
as ShellProperty<DateTime?>).Value = value as DateTime?;
            }

            
else if (prop.ValueType == typeof(double?))
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                (prop 
as ShellProperty<double?>).Value = value as double?;
            }

        }
 
        
#endregion


ContractedSubBlock.gifExpandedSubBlockStart.gif        
MediaPropertyAttribute#region MediaPropertyAttribute
        [AttributeUsage(AttributeTargets.Property, Inherited 
= false, AllowMultiple = true)]
        
sealed class MediaPropertyAttribute : Attribute
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
ExpandedSubBlockStart.gifContractedSubBlock.gif            
public string PropertyKey getprivate set; }
            
public MediaPropertyAttribute(string propertyKey)
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                
this.PropertyKey = propertyKey;
            }


            
public override string ToString()
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                
return PropertyKey;
            }

        }
 
        
#endregion

    }

}

整个代码非常简单,要进行增加其它属性也只需要加入两行代码而已。目前发现这种方式的一个唯一不足是不支持缩略图的写操作(可以读取),但这个可以通过结合上面的那些开源软件很容易的解决。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值