Merge remote-tracking branch 'nui/merge_work'
[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 the media file.
25     /// </summary>
26     /// <privilege>
27     /// If you want to access only internal storage,
28     /// you should add privilege http://tizen.org/privilege/mediastorage. \n
29     /// Or if you want to access only external storage,
30     /// you should add privilege http://tizen.org/privilege/externalstorage. \n
31     /// </privilege>
32     public class MetadataEditor : IDisposable
33     {
34         private bool _disposed = false;
35         private IntPtr _handle = IntPtr.Zero;
36         private bool _isFileReadOnly;
37
38         private IntPtr Handle
39         {
40             get
41             {
42                 if (_handle == IntPtr.Zero)
43                 {
44                     throw new ObjectDisposedException(nameof(MetadataEditor));
45                 }
46
47                 return _handle;
48             }
49         }
50
51         /// <summary>
52         /// Initializes a new instance of the <see cref="MetadataEditor"/> class with the specified path.
53         /// </summary>
54         /// <since_tizen> 3 </since_tizen>
55         /// <param name="path">The path of the media file to edit metadata.</param>
56         /// <exception cref="ArgumentNullException"><paramref name="path"/> is null.</exception>
57         /// <exception cref="ArgumentException"><paramref name="path"/> is a zero-length string, contains only white space.</exception>
58         /// <exception cref="FileFormatException">The file is not supported.</exception>
59         /// <exception cref="FileNotFoundException">File does not exist.</exception>
60         /// <exception cref="UnauthorizedAccessException">Caller does not have required privilege to access the file.</exception>
61         public MetadataEditor(string path)
62         {
63             if (path == null)
64             {
65                 throw new ArgumentNullException(nameof(path));
66             }
67
68             if (string.IsNullOrWhiteSpace(path))
69             {
70                 throw new ArgumentException($"{nameof(path)} is a zero-length string.", nameof(path));
71             }
72
73             Interop.MetadataEditor.Create(out _handle).ThrowIfError("Failed to create metadata");
74
75             try
76             {
77                 Interop.MetadataEditor.SetPath(Handle, path).ThrowIfError("Failed to set path");
78
79                 _isFileReadOnly = File.GetAttributes(path).HasFlag(FileAttributes.ReadOnly);
80             }
81             catch (Exception)
82             {
83                 Interop.MetadataEditor.Destroy(_handle);
84                 throw;
85             }
86         }
87
88         private string GetParam(MetadataEditorAttr attr)
89         {
90             IntPtr val = IntPtr.Zero;
91
92             try
93             {
94                 Interop.MetadataEditor.GetMetadata(Handle, attr, out val)
95                     .ThrowIfError("Failed to get metadata");
96
97                 return Marshal.PtrToStringAnsi(val);
98             }
99             finally
100             {
101                 Interop.Libc.Free(val);
102             }
103         }
104
105         private void SetParam(MetadataEditorAttr attr, string value)
106         {
107             if (_isFileReadOnly)
108             {
109                 throw new InvalidOperationException("The media file is read-only.");
110             }
111
112             Interop.MetadataEditor.SetMetadata(Handle, attr, value).ThrowIfError("Failed to set value");
113         }
114
115         /// <summary>
116         /// Gets or sets the artist of media.
117         /// </summary>
118         /// <since_tizen> 3 </since_tizen>
119         /// <exception cref="InvalidOperationException">The file is read-only.</exception>
120         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed.</exception>
121         public string Artist
122         {
123             get
124             {
125                 return GetParam(MetadataEditorAttr.Artist);
126             }
127
128             set
129             {
130                 SetParam(MetadataEditorAttr.Artist, value);
131             }
132         }
133
134         /// <summary>
135         /// Gets or sets the title of media.
136         /// </summary>
137         /// <since_tizen> 3 </since_tizen>
138         /// <exception cref="InvalidOperationException">The file is read-only.</exception>
139         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed.</exception>
140         public string Title
141         {
142             get
143             {
144                 return GetParam(MetadataEditorAttr.Title);
145             }
146
147             set
148             {
149                 SetParam(MetadataEditorAttr.Title, value);
150             }
151         }
152
153         /// <summary>
154         /// Gets or sets the album name of media.
155         /// </summary>
156         /// <since_tizen> 3 </since_tizen>
157         /// <exception cref="InvalidOperationException">The file is read-only.</exception>
158         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed.</exception>
159         public string Album
160         {
161             get
162             {
163                 return GetParam(MetadataEditorAttr.Album);
164             }
165
166             set
167             {
168                 SetParam(MetadataEditorAttr.Album, value);
169             }
170         }
171
172         /// <summary>
173         /// Gets or sets the genre of media.
174         /// </summary>
175         /// <since_tizen> 3 </since_tizen>
176         /// <exception cref="InvalidOperationException">The file is read-only.</exception>
177         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed.</exception>
178         public string Genre
179         {
180             get
181             {
182                 return GetParam(MetadataEditorAttr.Genre);
183             }
184
185             set
186             {
187                 SetParam(MetadataEditorAttr.Genre, value);
188             }
189         }
190
191         /// <summary>
192         /// Gets or sets the author of media.
193         /// </summary>
194         /// <since_tizen> 3 </since_tizen>
195         /// <exception cref="InvalidOperationException">The file is read-only.</exception>
196         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed.</exception>
197         public string Author
198         {
199             get
200             {
201                 return GetParam(MetadataEditorAttr.Author);
202             }
203
204             set
205             {
206                 SetParam(MetadataEditorAttr.Author, value);
207             }
208         }
209
210         /// <summary>
211         /// Gets or sets the copyright of media.
212         /// </summary>
213         /// <since_tizen> 3 </since_tizen>
214         /// <exception cref="InvalidOperationException">The file is read-only.</exception>
215         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed.</exception>
216         public string Copyright
217         {
218             get
219             {
220                 return GetParam(MetadataEditorAttr.Copyright);
221             }
222
223             set
224             {
225                 SetParam(MetadataEditorAttr.Copyright, value);
226             }
227         }
228
229         /// <summary>
230         /// Gets or sets the date of media.
231         /// </summary>
232         /// <since_tizen> 3 </since_tizen>
233         /// <remarks>
234         /// If the media contains ID3 tag, this refers to the recorded date.
235         /// If the media is a mp4 format, this refers to the year and the value to set will be converted into integer.
236         /// </remarks>
237         /// <exception cref="InvalidOperationException">The file is read-only.</exception>
238         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed.</exception>
239         public string Date
240         {
241             get
242             {
243                 return GetParam(MetadataEditorAttr.Date);
244             }
245
246             set
247             {
248                 SetParam(MetadataEditorAttr.Date, value);
249             }
250         }
251
252         /// <summary>
253         /// Gets or sets the description of media.
254         /// </summary>
255         /// <since_tizen> 3 </since_tizen>
256         /// <exception cref="InvalidOperationException">The file is read-only.</exception>
257         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed.</exception>
258         public string Description
259         {
260             get
261             {
262                 return GetParam(MetadataEditorAttr.Description);
263             }
264
265             set
266             {
267                 SetParam(MetadataEditorAttr.Description, value);
268             }
269         }
270
271         /// <summary>
272         /// Gets or sets the comment of media.
273         /// </summary>
274         /// <since_tizen> 3 </since_tizen>
275         /// <exception cref="InvalidOperationException">The file is read-only.</exception>
276         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed.</exception>
277         public string Comment
278         {
279             get
280             {
281                 return GetParam(MetadataEditorAttr.Comment);
282             }
283
284             set
285             {
286                 SetParam(MetadataEditorAttr.Comment, value);
287             }
288         }
289
290         /// <summary>
291         /// Gets or sets the track number of media.
292         /// </summary>
293         /// <since_tizen> 3 </since_tizen>
294         /// <exception cref="InvalidOperationException">The file is read-only.</exception>
295         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed.</exception>
296         public string TrackNumber
297         {
298             get
299             {
300                 return GetParam(MetadataEditorAttr.TrackNumber);
301             }
302
303             set
304             {
305                 SetParam(MetadataEditorAttr.TrackNumber, value);
306             }
307         }
308
309         /// <summary>
310         /// Gets the count of album arts of media.
311         /// </summary>
312         /// <since_tizen> 3 </since_tizen>
313         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed.</exception>
314         public int PictureCount
315         {
316             get => int.TryParse(GetParam(MetadataEditorAttr.PictureCount), out var value) ? value : 0;
317         }
318
319         /// <summary>
320         /// Gets or sets the conductor of media.
321         /// </summary>
322         /// <since_tizen> 3 </since_tizen>
323         /// <exception cref="InvalidOperationException">The file is read-only.</exception>
324         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed.</exception>
325         public string Conductor
326         {
327             get
328             {
329                 return GetParam(MetadataEditorAttr.Conductor);
330             }
331
332             set
333             {
334                 SetParam(MetadataEditorAttr.Conductor, value);
335             }
336         }
337
338         /// <summary>
339         /// Gets or sets the unsynchronized lyrics of media.
340         /// </summary>
341         /// <since_tizen> 3 </since_tizen>
342         /// <exception cref="InvalidOperationException">The file is read-only.</exception>
343         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed.</exception>
344         public string UnsyncLyrics
345         {
346             get
347             {
348                 return GetParam(MetadataEditorAttr.UnsyncLyrics);
349             }
350
351             set
352             {
353                 SetParam(MetadataEditorAttr.UnsyncLyrics, value);
354             }
355         }
356
357         /// <summary>
358         /// Writes the modified metadata to the media file.
359         /// </summary>
360         /// <exception cref="InvalidOperationException">
361         ///     An internal error occurs.\n
362         ///     -or-\n
363         ///     The file is read-only.
364         /// </exception>
365         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed.</exception>
366         public void Commit()
367         {
368             if (_isFileReadOnly)
369             {
370                 throw new InvalidOperationException("The media file is read-only.");
371             }
372
373             Interop.MetadataEditor.UpdateMetadata(Handle).ThrowIfError("Failed to update file");
374         }
375
376         /// <summary>
377         /// Gets the artwork image in the media file.
378         /// </summary>
379         /// <since_tizen> 3 </since_tizen>
380         /// <param name="index">The index of picture to import.</param>
381         /// <returns> Artwork included in the media file.</returns>
382         /// <returns>Artwork included in the media file.</returns>
383         /// <exception cref="InvalidOperationException">An internal error occurs.</exception>
384         /// <exception cref="ArgumentOutOfRangeException">
385         ///     <paramref name="index"/> is less than zero.\n
386         ///     -or-\n
387         ///     <paramref name="index"/> is greater than or equal to <see cref="PictureCount"/>.\n
388         /// </exception>
389         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed.</exception>
390         public Artwork GetPicture(int index)
391         {
392             if (index < 0)
393             {
394                 throw new ArgumentOutOfRangeException(nameof(index), index,
395                     "Index should not be less than zero.");
396             }
397
398             if (index >= PictureCount)
399             {
400                 throw new ArgumentOutOfRangeException(nameof(index), index,
401                     "Index should not be greater thor or equal to PictureCount.");
402             }
403
404             IntPtr data = IntPtr.Zero;
405             IntPtr mimeType = IntPtr.Zero;
406
407             try
408             {
409                 Interop.MetadataEditor.GetPicture(Handle, index, out data, out var size, out mimeType).
410                     ThrowIfError("Failed to get the value");
411
412                 if (size > 0)
413                 {
414                     byte[] tmpBuf = new byte[size];
415                     Marshal.Copy(data, tmpBuf, 0, size);
416
417                     return new Artwork(tmpBuf, Marshal.PtrToStringAnsi(mimeType));
418                 }
419
420                 return null;
421             }
422             finally
423             {
424                 if (data != IntPtr.Zero)
425                 {
426                     Interop.Libc.Free(data);
427                 }
428
429                 if (mimeType != IntPtr.Zero)
430                 {
431                     Interop.Libc.Free(mimeType);
432                 }
433             }
434         }
435
436         /// <summary>
437         /// Appends the picture to the media file.
438         /// </summary>
439         /// <since_tizen> 3 </since_tizen>
440         /// <param name="path">The path of picture for adding to the metadata.</param>
441         /// <exception cref="InvalidOperationException">
442         ///     An internal error occurs.\n
443         ///     -or-\n
444         ///     The media file is read-only.
445         /// </exception>
446         /// <exception cref="ArgumentNullException"> Picture path is null</exception>
447         /// <exception cref="FileNotFoundException">File does not exist.</exception>
448         /// <exception cref="UnauthorizedAccessException">Caller does not have required privilege to access the file.</exception>
449         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed.</exception>
450         /// <exception cref="FileFormatException">The specified file is not supported.</exception>
451         public void AddPicture(string path)
452         {
453             if (path == null)
454             {
455                 throw new ArgumentNullException(nameof(path));
456             }
457
458             if (File.Exists(path) == false)
459             {
460                 throw new FileNotFoundException("File does not exist.", path);
461             }
462
463             if (_isFileReadOnly)
464             {
465                 throw new InvalidOperationException("The media file is read-only.");
466             }
467
468             Interop.MetadataEditor.AddPicture(Handle, path).
469                 ThrowIfError("Failed to append picture");
470         }
471
472         /// <summary>
473         /// Removes the picture from the media file.
474         /// </summary>
475         /// <since_tizen> 3 </since_tizen>
476         /// <param name="index">The index of picture to remove.</param>
477         /// <exception cref="InvalidOperationException">
478         ///     An internal error occurs.\n
479         ///     -or-\n
480         ///     The media file is read-only.
481         /// </exception>
482         /// <exception cref="ArgumentOutOfRangeException">
483         ///     <paramref name="index"/> is less than zero.\n
484         ///     -or-\n
485         ///     <paramref name="index"/> is greater than or equal to <see cref="PictureCount"/>.\n
486         /// </exception>
487         /// <exception cref="ObjectDisposedException">The <see cref="MetadataEditor"/> has already been disposed.</exception>
488         public void RemovePicture(int index)
489         {
490             if (index < 0)
491             {
492                 throw new ArgumentOutOfRangeException("Index should be larger than 0 [" + index + "]");
493             }
494
495             if (index >= PictureCount)
496             {
497                 throw new ArgumentOutOfRangeException(nameof(index), index,
498                     "Index should not be greater thor or equal to PictureCount.");
499             }
500
501             if (_isFileReadOnly)
502             {
503                 throw new InvalidOperationException("The media file is read-only.");
504             }
505
506             Interop.MetadataEditor.RemovePicture(Handle, index).ThrowIfError("Failed to remove picture");
507         }
508
509         ~MetadataEditor()
510         {
511             Dispose(false);
512         }
513
514         protected virtual void Dispose(bool disposing)
515         {
516             if (!_disposed)
517             {
518                 if (_handle != IntPtr.Zero)
519                 {
520                     Interop.MetadataEditor.Destroy(_handle);
521                     _handle = IntPtr.Zero;
522                 }
523
524                 _disposed = true;
525             }
526         }
527
528         /// <summary>
529         /// Releases all resources used by the <see cref="MetadataEditor"/> object.
530         /// </summary>
531         public void Dispose()
532         {
533             Dispose(true);
534             GC.SuppressFinalize(this);
535         }
536     }
537 }