[ThumbnailExtractor] Add description for Extract method (#932)
[platform/core/csapi/tizenfx.git] / src / Tizen.Multimedia.Util / ThumbnailExtractor / ThumbnailExtractor.cs
1 /*
2  * Copyright (c) 2018 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 using System.Threading;
21 using System.Threading.Tasks;
22 using Handle = Interop.ThumbnailExtractorHandle;
23 using Native = Interop.ThumbnailExtractor;
24
25 namespace Tizen.Multimedia.Util
26 {
27     /// <summary>
28     /// Provides the ability to extract the thumbnail from media files.
29     /// </summary>
30     /// <since_tizen> 4 </since_tizen>
31     public static class ThumbnailExtractor
32     {
33         private static Handle CreateHandle()
34         {
35             Native.Create(out var handle).ThrowIfError("Failed to extract.");
36
37             return handle;
38         }
39
40         /// <summary>
41         /// Extracts the thumbnail for the given media with the specified path.
42         /// </summary>
43         /// <since_tizen> 4 </since_tizen>
44         /// <returns>A task that represents the asynchronous extracting operation.</returns>
45         /// <remarks>The size of the thumbnail will be the default size (320x240).</remarks>
46         /// <param name="path">The path of the media file to extract the thumbnail.</param>
47         /// <exception cref="ArgumentNullException"><paramref name="path"/> is null.</exception>
48         /// <exception cref="FileNotFoundException"><paramref name="path"/> does not exist.</exception>
49         /// <exception cref="InvalidOperationException">An internal error occurs.</exception>
50         /// <exception cref="UnauthorizedAccessException">The caller does not have required privilege for accessing the <paramref name="path"/>.</exception>
51         /// <exception cref="FileFormatException">The specified file is not supported.</exception>
52         public static Task<ThumbnailExtractionResult> ExtractAsync(string path)
53         {
54             return RunExtractAsync(path, null, CancellationToken.None);
55         }
56
57         /// <summary>
58         /// Extracts the thumbnail for the given media with the specified path.
59         /// </summary>
60         /// <returns>A task that represents the asynchronous extracting operation.</returns>
61         /// <remarks>The size of the thumbnail will be the default size(320x240).</remarks>
62         /// <param name="path">The path of the media file to extract the thumbnail.</param>
63         /// <param name="cancellationToken">The token to stop the operation.</param>
64         /// <exception cref="ArgumentNullException"><paramref name="path"/> is null.</exception>
65         /// <exception cref="FileNotFoundException"><paramref name="path"/> does not exist.</exception>
66         /// <exception cref="InvalidOperationException">An internal error occurs.</exception>
67         /// <exception cref="UnauthorizedAccessException">The caller does not have required privilege for accessing the <paramref name="path"/>.</exception>
68         /// <exception cref="FileFormatException">The specified file is not supported.</exception>
69         /// <since_tizen> 4 </since_tizen>
70         public static Task<ThumbnailExtractionResult> ExtractAsync(string path, CancellationToken cancellationToken)
71         {
72             return RunExtractAsync(path, null, cancellationToken);
73         }
74
75         /// <summary>
76         /// Extracts the thumbnail for the given media with the specified path and size.
77         /// </summary>
78         /// <since_tizen> 4 </since_tizen>
79         /// <returns>A task that represents the asynchronous extracting operation.</returns>
80         /// <remarks>
81         /// If the width is not a multiple of 8, it can be changed by the inner process.<br/>
82         /// The width will be a multiple of 8 greater than the set value.
83         /// </remarks>
84         /// <param name="path">The path of the media file to extract the thumbnail.</param>
85         /// <param name="size">The size of the thumbnail.</param>
86         /// <exception cref="ArgumentNullException"><paramref name="path"/> is null.</exception>
87         /// <exception cref="FileNotFoundException"><paramref name="path"/> does not exist.</exception>
88         /// <exception cref="InvalidOperationException">An internal error occurs.</exception>
89         /// <exception cref="UnauthorizedAccessException">The caller does not have required privilege for accessing the <paramref name="path"/>.</exception>
90         /// <exception cref="ArgumentOutOfRangeException">
91         ///     The width or the height of <paramref name="size"/> is less than or equal to zero.
92         /// </exception>
93         /// <exception cref="FileFormatException">The specified file is not supported.</exception>
94         public static Task<ThumbnailExtractionResult> ExtractAsync(string path, Size size)
95         {
96             return RunExtractAsync(path, size, CancellationToken.None);
97         }
98
99         /// <summary>
100         /// Extracts the thumbnail for the given media with the specified path and size.
101         /// </summary>
102         /// <since_tizen> 4 </since_tizen>
103         /// <returns>A task that represents the asynchronous extracting operation.</returns>
104         /// <remarks>
105         /// If the width is not a multiple of 8, it can be changed by the inner process.<br/>
106         /// The width will be a multiple of 8 greater than the set value.
107         /// </remarks>
108         /// <param name="path">The path of the media file to extract the thumbnail.</param>
109         /// <param name="size">The size of the thumbnail.</param>
110         /// <param name="cancellationToken">The token to stop the operation.</param>
111         /// <exception cref="ArgumentNullException"><paramref name="path"/> is null.</exception>
112         /// <exception cref="FileNotFoundException"><paramref name="path"/> does not exist.</exception>
113         /// <exception cref="InvalidOperationException">An internal error occurs.</exception>
114         /// <exception cref="UnauthorizedAccessException">The caller does not have required privilege for accessing the <paramref name="path"/>.</exception>
115         /// <exception cref="ArgumentOutOfRangeException">
116         ///     The width or the height of <paramref name="size"/> is less than or equal to zero.
117         /// </exception>
118         /// <exception cref="FileFormatException">The specified file is not supported.</exception>
119         public static Task<ThumbnailExtractionResult> ExtractAsync(string path, Size size,
120             CancellationToken cancellationToken)
121         {
122             return RunExtractAsync(path, size, cancellationToken);
123         }
124
125         private static Task<ThumbnailExtractionResult> RunExtractAsync(string path, Size? size,
126             CancellationToken cancellationToken)
127         {
128             if (path == null)
129             {
130                 throw new ArgumentNullException(nameof(path));
131             }
132
133             if (File.Exists(path) == false)
134             {
135                 throw new FileNotFoundException("File does not exists.", path);
136             }
137
138             if (size.HasValue)
139             {
140                 if (size.Value.Width <= 0)
141                 {
142                     throw new ArgumentOutOfRangeException(nameof(size), size.Value.Width,
143                         "The width must be greater than zero.");
144                 }
145
146                 if (size.Value.Height <= 0)
147                 {
148                     throw new ArgumentOutOfRangeException(nameof(size), size.Value.Height,
149                         "The height must be greater than zero.");
150                 }
151             }
152
153             return cancellationToken.IsCancellationRequested ?
154                 Task.FromCanceled<ThumbnailExtractionResult>(cancellationToken) :
155                 ExtractAsyncCore(path, size, cancellationToken);
156         }
157
158
159         private static async Task<ThumbnailExtractionResult> ExtractAsyncCore(string path, Size? size,
160             CancellationToken cancellationToken)
161         {
162             using (var handle = CreateHandle())
163             {
164                 Native.SetPath(handle, path).ThrowIfError("Failed to extract; failed to set the path.");
165
166                 if (size.HasValue)
167                 {
168                     Native.SetSize(handle, size.Value.Width, size.Value.Height).
169                         ThrowIfError("Failed to extract; failed to set the size");
170                 }
171
172                 var tcs = new TaskCompletionSource<ThumbnailExtractionResult>();
173
174                 IntPtr id = IntPtr.Zero;
175
176                 try
177                 {
178                     var cb = GetCallback(tcs);
179                     using (var cbKeeper = ObjectKeeper.Get(cb))
180                     {
181                         Native.Extract(handle, cb, IntPtr.Zero, out id)
182                             .ThrowIfError("Failed to extract.");
183
184                         using (RegisterCancellationToken(tcs, cancellationToken, handle, Marshal.PtrToStringAnsi(id)))
185                         {
186                             return await tcs.Task;
187                         }
188                     }
189                 }
190                 finally
191                 {
192                     LibcSupport.Free(id);
193                 }
194             }
195         }
196
197         private static Native.ThumbnailExtractCallback GetCallback(TaskCompletionSource<ThumbnailExtractionResult> tcs)
198         {
199             return (error, requestId, thumbWidth, thumbHeight, thumbData, dataSize, _) =>
200             {
201                 if (error == ThumbnailExtractorError.None)
202                 {
203                     try
204                     {
205                         tcs.TrySetResult(new ThumbnailExtractionResult(thumbData, thumbWidth, thumbHeight, dataSize));
206                     }
207                     catch (Exception e)
208                     {
209                         tcs.TrySetException(new InvalidOperationException("[" + error + "] Failed to create ThumbnailExtractionResult instance.", e));
210                     }
211                     finally
212                     {
213                         LibcSupport.Free(thumbData);
214                     }
215                 }
216                 else
217                 {
218                     tcs.TrySetException(error.ToException("Failed to extract."));
219                 }
220             };
221         }
222
223         private static IDisposable RegisterCancellationToken(TaskCompletionSource<ThumbnailExtractionResult> tcs,
224             CancellationToken cancellationToken, Handle handle, string id)
225         {
226             if (cancellationToken.CanBeCanceled == false)
227             {
228                 return null;
229             }
230
231             return cancellationToken.Register(() =>
232             {
233                 if (tcs.Task.IsCompleted)
234                 {
235                     return;
236                 }
237
238                 Native.Cancel(handle, id).ThrowIfError("Failed to cancel.");
239                 tcs.TrySetCanceled();
240             });
241         }
242
243         /// <summary>
244         /// Extracts the thumbnail for the given media with the specified path and size.
245         /// The generated thumbnail will be returned in <see cref="ThumbnailExtractionResult"/>.
246         /// </summary>
247         /// <remarks>
248         /// The size of generated thumbnail will be 320x240.<br/>
249         /// But, if the size of <paramref name="path"/> has different ratio with 320x240 (approximately 1.33:1),<br/>
250         /// thumbnail will be generated in a way to keep the ratio of <paramref name="path"/>, which based on short axis of <paramref name="path"/>.<br/>
251         /// For example, if the size of <paramref name="path"/> is 900x500 (1.8:1), the size of generated thumbnail is 432x240(1.8:1).<br/>
252         /// If you want to set the size, which is different with 320x240, please use <see cref="Extract(string, Size)"/>.<br/>
253         /// <br/>
254         /// If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage. <br/>
255         /// If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage.
256         /// </remarks>
257         /// <privilege>http://tizen.org/privilege/mediastorage</privilege>
258         /// <privilege>http://tizen.org/privilege/externalstorage</privilege>
259         /// <param name="path">The path of the media file to extract the thumbnail.</param>
260         /// <exception cref="ArgumentNullException"><paramref name="path"/> is null.</exception>
261         /// <exception cref="FileNotFoundException"><paramref name="path"/> does not exist.</exception>
262         /// <exception cref="InvalidOperationException">An internal error occurs.</exception>
263         /// <exception cref="FileFormatException">The specified file is not supported.</exception>
264         /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
265         /// <returns>The result of extracting operation.</returns>
266         /// <since_tizen> 6 </since_tizen>
267         public static ThumbnailExtractionResult Extract(string path)
268         {
269             return Extract(path, new Size(320, 240));
270         }
271
272         /// <summary>
273         /// Extracts the thumbnail for the given media with the specified path and size.
274         /// The generated thumbnail will be returned in <see cref="ThumbnailExtractionResult"/>.
275         /// </summary>
276         /// <remarks>
277         /// The size of generated thumbnail will be <paramref name="size"/>.<br/>
278         /// But, if the size of <paramref name="path"/> has different ratio with <paramref name="size"/>,<br/>
279         /// thumbnail will be generated in a way to keep the ratio of <paramref name="path"/>, which based on short axis of <paramref name="path"/>.<br/>
280         /// For example, if the size of <paramref name="path"/> is 900x500 (1.8:1)) and <paramref name="size"/> is 320x240,<br/>
281         /// the size of generated thumbnail is 432x240(1.8:1).<br/>
282         /// <br/>
283         /// If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage. <br/>
284         /// If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage.
285         /// </remarks>
286         /// <privilege>http://tizen.org/privilege/mediastorage</privilege>
287         /// <privilege>http://tizen.org/privilege/externalstorage</privilege>
288         /// <param name="path">The path of the media file to extract the thumbnail.</param>
289         /// <param name="size">The size of the thumbnail.</param>
290         /// <exception cref="ArgumentNullException"><paramref name="path"/> is null.</exception>
291         /// <exception cref="FileNotFoundException"><paramref name="path"/> does not exist.</exception>
292         /// <exception cref="InvalidOperationException">An internal error occurs.</exception>
293         /// <exception cref="ArgumentOutOfRangeException">
294         ///     The width or the height of <paramref name="size"/> is less than or equal to zero.
295         /// </exception>
296         /// <exception cref="FileFormatException">The specified file is not supported.</exception>
297         /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
298         /// <returns>The result of extracting operation.</returns>
299         /// <since_tizen> 6 </since_tizen>
300         public static ThumbnailExtractionResult Extract(string path, Size size)
301         {
302             if (path == null)
303             {
304                 throw new ArgumentNullException(nameof(path));
305             }
306
307             if (File.Exists(path) == false)
308             {
309                 throw new FileNotFoundException("File does not exists.", path);
310             }
311
312             if (size.Width <= 0)
313             {
314                 throw new ArgumentOutOfRangeException(nameof(size), size.Width,
315                     "The width must be greater than zero.");
316             }
317
318             if (size.Height <= 0)
319             {
320                 throw new ArgumentOutOfRangeException(nameof(size), size.Height,
321                     "The height must be greater than zero.");
322             }
323
324             Native.ExtractToBuffer(path, (uint)size.Width, (uint)size.Height, out IntPtr thumbData,
325                 out int dataSize, out uint thumbWidth, out uint thumbHeight).
326                 ThrowIfError("Failed to extract thumbnail to buffer");
327
328             try
329             {
330                 return new ThumbnailExtractionResult(thumbData, (int)thumbWidth, (int)thumbHeight,
331                     dataSize);
332             }
333             finally
334             {
335                 if (thumbData != IntPtr.Zero)
336                 {
337                     LibcSupport.Free(thumbData);
338                 }
339             }
340         }
341
342         /// <summary>
343         /// Extracts the thumbnail for the given media with the specified path and size.
344         /// The generated thumbnail will be saved in <paramref name="resultThumbnailPath"/>.
345         /// </summary>
346         /// <remarks>
347         /// The size of <paramref name="resultThumbnailPath"/> image will be 320x240.<br/>
348         /// But, if the size of <paramref name="path"/> has different ratio with 320x240 (approximately 1.33:1),<br/>
349         /// thumbnail will be generated in a way to keep the ratio of <paramref name="path"/>, which based on short axis of <paramref name="path"/>.<br/>
350         /// For example, if the size of <paramref name="path"/> is 900x500 (1.8:1), the size of <paramref name="resultThumbnailPath"/> is 432x240(1.8:1).<br/>
351         /// If you want to set the size, which is different with 320x240, please use <see cref="Extract(string, Size, string)"/>.<br/>
352         /// <br/>
353         /// If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage. <br/>
354         /// If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage.
355         /// </remarks>
356         /// <privilege>http://tizen.org/privilege/mediastorage</privilege>
357         /// <privilege>http://tizen.org/privilege/externalstorage</privilege>
358         /// <param name="path">The path of the media file to extract the thumbnail.</param>
359         /// <param name="resultThumbnailPath">The path to save the generated thumbnail.</param>
360         /// <exception cref="ArgumentNullException"><paramref name="path"/> is null.</exception>
361         /// <exception cref="FileNotFoundException"><paramref name="path"/> does not exist.</exception>
362         /// <exception cref="InvalidOperationException">An internal error occurs.</exception>
363         /// <exception cref="FileFormatException">The specified file is not supported.</exception>
364         /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
365         /// <since_tizen> 6 </since_tizen>
366         public static void Extract(string path, string resultThumbnailPath)
367         {
368             Extract(path, new Size(320, 240), resultThumbnailPath);
369         }
370
371         /// <summary>
372         /// Extracts the thumbnail for the given media with the specified path and size.
373         /// The generated thumbnail will be saved in <paramref name="resultThumbnailPath"/>.
374         /// </summary>
375         /// <remarks>
376         /// The size of <paramref name="resultThumbnailPath"/> image will be <paramref name="size"/>.<br/>
377         /// But, if the size of <paramref name="path"/> has different ratio with <paramref name="size"/>,<br/>
378         /// thumbnail will be generated in a way to keep the ratio of <paramref name="path"/>, which based on short axis of <paramref name="path"/>.<br/>
379         /// For example, if the size of <paramref name="path"/> is 900x500 (1.8:1) and <paramref name="size"/> is 320x240,<br/>
380         /// the size of <paramref name="resultThumbnailPath"/> is 432x240(1.8:1).<br/>
381         /// <br/>
382         /// If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage. <br/>
383         /// If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage.
384         /// </remarks>
385         /// <privilege>http://tizen.org/privilege/mediastorage</privilege>
386         /// <privilege>http://tizen.org/privilege/externalstorage</privilege>
387         /// <param name="path">The path of the media file to extract the thumbnail.</param>
388         /// <param name="size">The size of the thumbnail.</param>
389         /// <param name="resultThumbnailPath">The path to save the generated thumbnail.</param>
390         /// <exception cref="ArgumentNullException"><paramref name="path"/> is null.</exception>
391         /// <exception cref="FileNotFoundException"><paramref name="path"/> does not exist.</exception>
392         /// <exception cref="InvalidOperationException">An internal error occurs.</exception>
393         /// <exception cref="ArgumentOutOfRangeException">
394         ///     The width or the height of <paramref name="size"/> is less than or equal to zero.
395         /// </exception>
396         /// <exception cref="FileFormatException">The specified file is not supported.</exception>
397         /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
398         /// <since_tizen> 6 </since_tizen>
399         public static void Extract(string path, Size size, string resultThumbnailPath)
400         {
401             if (path == null)
402             {
403                 throw new ArgumentNullException(nameof(path));
404             }
405
406             if (File.Exists(path) == false)
407             {
408                 throw new FileNotFoundException("File does not exists.", path);
409             }
410
411             if (size.Width <= 0)
412             {
413                 throw new ArgumentOutOfRangeException(nameof(size), size.Width,
414                     "The width must be greater than zero.");
415             }
416
417             if (size.Height <= 0)
418             {
419                 throw new ArgumentOutOfRangeException(nameof(size), size.Height,
420                     "The height must be greater than zero.");
421             }
422
423             Native.ExtractToFile(path, (uint)size.Width, (uint)size.Height, resultThumbnailPath).
424                 ThrowIfError("Failed to extract thumbnail to file.");
425         }
426     }
427 }