a7fa284ee06438da5be5db9165fadf74924b8358
[platform/core/csapi/tizenfx.git] / src / Tizen.Multimedia.Metadata / MetadataEditor / MetadataEditor.cs
1 /*
2  * Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  * Licensed under the Apache License, Version 2.0 (the License);
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an AS IS BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 using System;
18 using System.IO;
19 using System.Runtime.InteropServices;
20
21 namespace Tizen.Multimedia
22 {
23     /// <summary>
24     /// Provides a means to edit the metadata of MP3 and MP4 files.
25     /// Since 6.0, WAV, FLAC, OGG files are supported as well.
26     /// </summary>
27     /// <privilege>
28     /// If you want to access only an internal storage,
29     /// you should add privilege http://tizen.org/privilege/mediastorage.<br/>
30     /// Or if you want to access only an external storage,
31     /// you should add privilege http://tizen.org/privilege/externalstorage.
32     /// </privilege>
33     /// <since_tizen> 3 </since_tizen>
34     public class MetadataEditor : IDisposable
35     {
36         private bool _disposed = false;
37         private IntPtr _handle = IntPtr.Zero;
38         private bool _isFileReadOnly;
39
40         private IntPtr Handle
41         {
42             get
43             {
44                 if (_handle == IntPtr.Zero)
45                 {
46                     throw new ObjectDisposedException(nameof(MetadataEditor));
47                 }
48
49                 return _handle;
50             }
51         }
52
53         /// <summary>
54         /// Initializes a new instance of the <see cref="MetadataEditor"/> class with the specified path.
55         /// </summary>
56         /// <since_tizen> 3 </since_tizen>
57         /// <param name="path">The path of the media file to edit the metadata.</param>
58         /// <exception cref="ArgumentNullException"><paramref name="path"/> is null.</exception>
59         /// <exception cref="ArgumentException"><paramref name="path"/> is a zero-length string, contains only white space.</exception>
60         /// <exception cref="FileFormatException">The file is not supported.</exception>
61         /// <exception cref="FileNotFoundException">The file does not exist.</exception>
62         /// <exception cref="UnauthorizedAccessException">The caller does not have required privilege to access the file.</exception>
63         public MetadataEditor(string path)
64         {
65             if (path == null)
66             {
67                 throw new ArgumentNullException(nameof(path));
68             }
69
70             if (string.IsNullOrWhiteSpace(path))
71             {
72                 throw new ArgumentException($"{nameof(path)} is a zero-length string.", nameof(path));
73             }
74
75             Interop.MetadataEditor.Create(out _handle).ThrowIfError("Failed to create metadata");
76
77             try
78             {
79                 Interop.MetadataEditor.SetPath(Handle, path).ThrowIfError("Failed to set path");
80
81                 _isFileReadOnly = File.GetAttributes(path).HasFlag(FileAttributes.ReadOnly);
82             }
83             catch (Exception)
84             {
85                 Interop.MetadataEditor.Destroy(_handle);
86                 throw;
87             }
88         }
89
90         private string GetParam(MetadataEditorAttr attr)
91         {
92             IntPtr val = IntPtr.Zero;
93
94             try
95             {
96                 Interop.MetadataEditor.GetMetadata(Handle, attr, out val)
97                     .ThrowIfError("Failed to get metadata");
98
99                 return Marshal.PtrToStringAnsi(val);
100             }
101             finally
102             {
103                 Interop.Libc.Free(val);
104             }
105         }
106
107         private void SetParam(MetadataEditorAttr attr, string value)
108         {
109             if (_isFileReadOnly)
110             {
111                 throw new InvalidOperationException("The media file is read-only.");
112             }
113
114             Interop.MetadataEditor.SetMetadata(Handle, attr, value).ThrowIfError("Failed to set value");
115         }
116
117         /// <summary>
118         /// Gets or sets the artist of media.
119         /// </summary>
120         /// <since_tizen> 3 </since_tizen>
121         /// <exception cref="InvalidOperationException">
122         /// The file is read-only.<br/>
123         /// -or-<br/>
124         /// The malformed file which cannot be updatable.<br/>
125         /// -or-<br/>
126         /// Internal error.
127         /// </exception>
128         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed of.</exception>
129         public string Artist
130         {
131             get
132             {
133                 return GetParam(MetadataEditorAttr.Artist);
134             }
135
136             set
137             {
138                 SetParam(MetadataEditorAttr.Artist, value);
139             }
140         }
141
142         /// <summary>
143         /// Gets or sets the title of media.
144         /// </summary>
145         /// <since_tizen> 3 </since_tizen>
146         /// <exception cref="InvalidOperationException">
147         /// The file is read-only.<br/>
148         /// -or-<br/>
149         /// The malformed file which cannot be updatable.<br/>
150         /// -or-<br/>
151         /// Internal error.
152         /// </exception>
153         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed of.</exception>
154         public string Title
155         {
156             get
157             {
158                 return GetParam(MetadataEditorAttr.Title);
159             }
160
161             set
162             {
163                 SetParam(MetadataEditorAttr.Title, value);
164             }
165         }
166
167         /// <summary>
168         /// Gets or sets the album name of media.
169         /// </summary>
170         /// <since_tizen> 3 </since_tizen>
171         /// <exception cref="InvalidOperationException">
172         /// The file is read-only.<br/>
173         /// -or-<br/>
174         /// The malformed file which cannot be updatable.<br/>
175         /// -or-<br/>
176         /// Internal error.
177         /// </exception>
178         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed of.</exception>
179         public string Album
180         {
181             get
182             {
183                 return GetParam(MetadataEditorAttr.Album);
184             }
185
186             set
187             {
188                 SetParam(MetadataEditorAttr.Album, value);
189             }
190         }
191
192         /// <summary>
193         /// Gets or sets the genre of media.
194         /// </summary>
195         /// <since_tizen> 3 </since_tizen>
196         /// <exception cref="InvalidOperationException">
197         /// The file is read-only.<br/>
198         /// -or-<br/>
199         /// The malformed file which cannot be updatable.<br/>
200         /// -or-<br/>
201         /// Internal error.
202         /// </exception>
203         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed of.</exception>
204         public string Genre
205         {
206             get
207             {
208                 return GetParam(MetadataEditorAttr.Genre);
209             }
210
211             set
212             {
213                 SetParam(MetadataEditorAttr.Genre, value);
214             }
215         }
216
217         /// <summary>
218         /// Gets or sets the author of media.
219         /// </summary>
220         /// <since_tizen> 3 </since_tizen>
221         /// <exception cref="InvalidOperationException">
222         /// The file is read-only.<br/>
223         /// -or-<br/>
224         /// The malformed file which cannot be updatable.<br/>
225         /// -or-<br/>
226         /// Internal error.
227         /// </exception>
228         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed of.</exception>
229         public string Author
230         {
231             get
232             {
233                 return GetParam(MetadataEditorAttr.Author);
234             }
235
236             set
237             {
238                 SetParam(MetadataEditorAttr.Author, value);
239             }
240         }
241
242         /// <summary>
243         /// Gets or sets the copyright of media.
244         /// </summary>
245         /// <since_tizen> 3 </since_tizen>
246         /// <exception cref="InvalidOperationException">
247         /// The file is read-only.<br/>
248         /// -or-<br/>
249         /// The malformed file which cannot be updatable.<br/>
250         /// -or-<br/>
251         /// Internal error.
252         /// </exception>
253         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed of.</exception>
254         public string Copyright
255         {
256             get
257             {
258                 return GetParam(MetadataEditorAttr.Copyright);
259             }
260
261             set
262             {
263                 SetParam(MetadataEditorAttr.Copyright, value);
264             }
265         }
266
267         /// <summary>
268         /// Gets or sets the date of media.
269         /// </summary>
270         /// <since_tizen> 3 </since_tizen>
271         /// <remarks>
272         /// If the media contains the ID3 tag, this refers to the recorded date.
273         /// If the media is a mp4 format, this refers to the year, and the value to set will be converted into integer.
274         /// </remarks>
275         /// <exception cref="InvalidOperationException">
276         /// The file is read-only.<br/>
277         /// -or-<br/>
278         /// The malformed file which cannot be updatable.<br/>
279         /// -or-<br/>
280         /// Internal error.
281         /// </exception>
282         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed of.</exception>
283         public string Date
284         {
285             get
286             {
287                 return GetParam(MetadataEditorAttr.Date);
288             }
289
290             set
291             {
292                 SetParam(MetadataEditorAttr.Date, value);
293             }
294         }
295
296         /// <summary>
297         /// Gets or sets the description of media.
298         /// </summary>
299         /// <since_tizen> 3 </since_tizen>
300         /// <exception cref="InvalidOperationException">
301         /// The file is read-only.<br/>
302         /// -or-<br/>
303         /// The malformed file which cannot be updatable.<br/>
304         /// -or-<br/>
305         /// Internal error.
306         /// </exception>
307         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed of.</exception>
308         public string Description
309         {
310             get
311             {
312                 return GetParam(MetadataEditorAttr.Description);
313             }
314
315             set
316             {
317                 SetParam(MetadataEditorAttr.Description, value);
318             }
319         }
320
321         /// <summary>
322         /// Gets or sets the comment of media.
323         /// </summary>
324         /// <since_tizen> 3 </since_tizen>
325         /// <exception cref="InvalidOperationException">
326         /// The file is read-only.<br/>
327         /// -or-<br/>
328         /// The malformed file which cannot be updatable.<br/>
329         /// -or-<br/>
330         /// Internal error.
331         /// </exception>
332         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed of.</exception>
333         public string Comment
334         {
335             get
336             {
337                 return GetParam(MetadataEditorAttr.Comment);
338             }
339
340             set
341             {
342                 SetParam(MetadataEditorAttr.Comment, value);
343             }
344         }
345
346         /// <summary>
347         /// Gets or sets the track number of media.
348         /// </summary>
349         /// <since_tizen> 3 </since_tizen>
350         /// <exception cref="InvalidOperationException">
351         /// The file is read-only.<br/>
352         /// -or-<br/>
353         /// The malformed file which cannot be updatable.<br/>
354         /// -or-<br/>
355         /// Internal error.
356         /// </exception>
357         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed of.</exception>
358         public string TrackNumber
359         {
360             get
361             {
362                 return GetParam(MetadataEditorAttr.TrackNumber);
363             }
364
365             set
366             {
367                 SetParam(MetadataEditorAttr.TrackNumber, value);
368             }
369         }
370
371         /// <summary>
372         /// Gets the count of album arts of media.
373         /// </summary>
374         /// <since_tizen> 3 </since_tizen>
375         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed of.</exception>
376         public int PictureCount
377         {
378             get => int.TryParse(GetParam(MetadataEditorAttr.PictureCount), out var value) ? value : 0;
379         }
380
381         /// <summary>
382         /// Gets or sets the conductor of media.
383         /// </summary>
384         /// <since_tizen> 3 </since_tizen>
385         /// <exception cref="InvalidOperationException">
386         /// The file is read-only.<br/>
387         /// -or-<br/>
388         /// The malformed file which cannot be updatable.<br/>
389         /// -or-<br/>
390         /// Internal error.
391         /// </exception>
392         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed of.</exception>
393         public string Conductor
394         {
395             get
396             {
397                 return GetParam(MetadataEditorAttr.Conductor);
398             }
399
400             set
401             {
402                 SetParam(MetadataEditorAttr.Conductor, value);
403             }
404         }
405
406         /// <summary>
407         /// Gets or sets the unsynchronized lyrics of media.
408         /// </summary>
409         /// <since_tizen> 3 </since_tizen>
410         /// <exception cref="InvalidOperationException">
411         /// The file is read-only.<br/>
412         /// -or-<br/>
413         /// The malformed file which cannot be updatable.<br/>
414         /// -or-<br/>
415         /// Internal error.
416         /// </exception>
417         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed of.</exception>
418         public string UnsyncLyrics
419         {
420             get
421             {
422                 return GetParam(MetadataEditorAttr.UnsyncLyrics);
423             }
424
425             set
426             {
427                 SetParam(MetadataEditorAttr.UnsyncLyrics, value);
428             }
429         }
430
431         /// <summary>
432         /// Writes the modified metadata to the media file.
433         /// </summary>
434         /// <exception cref="InvalidOperationException">
435         /// The file is read-only.<br/>
436         /// -or-<br/>
437         /// Internal error.<br/>
438         /// </exception>
439         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed of.</exception>
440         /// <since_tizen> 3 </since_tizen>
441         public void Commit()
442         {
443             if (_isFileReadOnly)
444             {
445                 throw new InvalidOperationException("The media file is read-only.");
446             }
447
448             Interop.MetadataEditor.UpdateMetadata(Handle).ThrowIfError("Failed to update file");
449         }
450
451         /// <summary>
452         /// Gets the artwork image in the media file.
453         /// </summary>
454         /// <since_tizen> 3 </since_tizen>
455         /// <param name="index">The index of the picture to import.</param>
456         /// <returns>The artwork included in the media file.</returns>
457         /// <exception cref="InvalidOperationException">An internal error occurs.</exception>
458         /// <exception cref="ArgumentOutOfRangeException">
459         ///     <paramref name="index"/> is less than zero.<br/>
460         ///     -or-<br/>
461         ///     <paramref name="index"/> is greater than or equal to <see cref="PictureCount"/>.
462         /// </exception>
463         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed of.</exception>
464         public Artwork GetPicture(int index)
465         {
466             if (index < 0)
467             {
468                 throw new ArgumentOutOfRangeException(nameof(index), index,
469                     "Index should not be less than zero.");
470             }
471
472             if (index >= PictureCount)
473             {
474                 throw new ArgumentOutOfRangeException(nameof(index), index,
475                     "Index should not be greater thor or equal to PictureCount.");
476             }
477
478             IntPtr data = IntPtr.Zero;
479             IntPtr mimeType = IntPtr.Zero;
480
481             try
482             {
483                 Interop.MetadataEditor.GetPicture(Handle, index, out data, out var size, out mimeType).
484                     ThrowIfError("Failed to get the value");
485
486                 if (size > 0)
487                 {
488                     byte[] tmpBuf = new byte[size];
489                     Marshal.Copy(data, tmpBuf, 0, size);
490
491                     return new Artwork(tmpBuf, Marshal.PtrToStringAnsi(mimeType));
492                 }
493
494                 return null;
495             }
496             finally
497             {
498                 if (data != IntPtr.Zero)
499                 {
500                     Interop.Libc.Free(data);
501                 }
502
503                 if (mimeType != IntPtr.Zero)
504                 {
505                     Interop.Libc.Free(mimeType);
506                 }
507             }
508         }
509
510         /// <summary>
511         /// Appends the picture to the media file.
512         /// </summary>
513         /// <since_tizen> 3 </since_tizen>
514         /// <param name="path">The path of the picture for adding to the metadata.</param>
515         /// <exception cref="InvalidOperationException">
516         /// The file is read-only.<br/>
517         /// -or-<br/>
518         /// The malformed file which cannot be updatable.<br/>
519         /// -or-<br/>
520         /// Internal error.
521         /// </exception>
522         /// <exception cref="ArgumentNullException"><paramref name="path"/> is null.</exception>
523         /// <exception cref="FileNotFoundException">The file does not exist.</exception>
524         /// <exception cref="UnauthorizedAccessException">The caller does not have required privilege to access the file.</exception>
525         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed of.</exception>
526         /// <exception cref="FileFormatException">The specified file is not supported.</exception>
527         public void AddPicture(string path)
528         {
529             if (path == null)
530             {
531                 throw new ArgumentNullException(nameof(path));
532             }
533
534             if (File.Exists(path) == false)
535             {
536                 throw new FileNotFoundException("File does not exist.", path);
537             }
538
539             if (_isFileReadOnly)
540             {
541                 throw new InvalidOperationException("The media file is read-only.");
542             }
543
544             Interop.MetadataEditor.AddPicture(Handle, path).
545                 ThrowIfError("Failed to append picture");
546         }
547
548         /// <summary>
549         /// Removes the picture from the media file.
550         /// </summary>
551         /// <since_tizen> 3 </since_tizen>
552         /// <param name="index">The index of the picture to remove.</param>
553         /// <exception cref="InvalidOperationException">
554         ///     An internal error occurs.<br/>
555         ///     -or-<br/>
556         ///     The media file is read-only.
557         /// </exception>
558         /// <exception cref="ArgumentOutOfRangeException">
559         ///     <paramref name="index"/> is less than zero.<br/>
560         ///     -or-<br/>
561         ///     <paramref name="index"/> is greater than or equal to <see cref="PictureCount"/>.
562         /// </exception>
563         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed of.</exception>
564         public void RemovePicture(int index)
565         {
566             if (index < 0)
567             {
568                 throw new ArgumentOutOfRangeException("Index should be larger than 0 [" + index + "]");
569             }
570
571             if (index >= PictureCount)
572             {
573                 throw new ArgumentOutOfRangeException(nameof(index), index,
574                     "Index should not be greater thor or equal to PictureCount.");
575             }
576
577             if (_isFileReadOnly)
578             {
579                 throw new InvalidOperationException("The media file is read-only.");
580             }
581
582             Interop.MetadataEditor.RemovePicture(Handle, index).ThrowIfError("Failed to remove picture");
583         }
584
585         /// <summary>
586         /// Finalizes an instance of the MetadataEditor class.
587         /// </summary>
588         ~MetadataEditor()
589         {
590             Dispose(false);
591         }
592
593         /// <summary>
594         /// Releases the resources used by the <see cref="MetadataEditor"/> object.
595         /// </summary>
596         /// <param name="disposing">
597         /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
598         /// </param>
599         /// <since_tizen> 3 </since_tizen>
600         protected virtual void Dispose(bool disposing)
601         {
602             if (!_disposed)
603             {
604                 if (_handle != IntPtr.Zero)
605                 {
606                     Interop.MetadataEditor.Destroy(_handle);
607                     _handle = IntPtr.Zero;
608                 }
609
610                 _disposed = true;
611             }
612         }
613
614         /// <summary>
615         /// Releases all resources used by the <see cref="MetadataEditor"/> object.
616         /// </summary>
617         /// <since_tizen> 3 </since_tizen>
618         public void Dispose()
619         {
620             Dispose(true);
621             GC.SuppressFinalize(this);
622         }
623     }
624 }