/// <summary> /// Uploads image. /// </summary> /// <param name="source"></param> /// <param name="userId"></param> /// <param name="fileName"></param> /// <param name="contentType">e.g. "image/jpeg"</param> /// <param name="uploadFrom"></param> /// <returns></returns> public async Task <Media> UploadAsync(Stream source, int userId, string fileName, string contentType, EUploadedFrom uploadFrom) { // check if file type is supported var ext = Path.GetExtension(fileName).ToLower(); var ctype = "." + contentType.Substring(contentType.LastIndexOf("/") + 1).ToLower(); if (ext.IsNullOrEmpty() || !Accepted_Image_Types.Contains(ext) || !Accepted_Image_Types.Contains(ctype)) { throw new NotSupportedException(ERR_MSG_FILETYPE); } // check file size if (source.Length > MAX_FILE_SIZE) { throw new FanException(ERR_MSG_FILESIZE); } // uploadedOn var uploadedOn = DateTimeOffset.UtcNow; // get the slugged filename and title from original filename var(fileNameSlugged, title) = ProcessFileName(fileName, uploadFrom); // get unique filename var uniqueFileName = await GetUniqueFileNameAsync(fileNameSlugged, uploadedOn); // get image resizes var resizes = contentType.Equals("image/gif") ? GetImageResizeListForGif(uploadedOn) : GetImageResizeList(uploadedOn); return(await _mediaSvc.UploadImageAsync(source, resizes, uniqueFileName, contentType, title, uploadedOn, EAppType.Blog, userId, uploadFrom)); }
/// <summary> /// Uploads image by resizing and storing it. /// </summary> /// <param name="source"></param> /// <param name="resizes"></param> /// <param name="fileName"></param> /// <param name="contentType"></param> /// <param name="uploadedOn"></param> /// <param name="appType"></param> /// <param name="userId"></param> /// <param name="uploadFrom"></param> /// <returns></returns> public async Task <Media> UploadImageAsync(Stream source, List <ImageResizeInfo> resizes, string fileName, string contentType, string title, DateTimeOffset uploadedOn, EAppType appType, int userId, EUploadedFrom uploadFrom = EUploadedFrom.Browser) { int resizeCount = 0; var(widthOrig, heightOrig) = GetOriginalSize(source); foreach (var resize in resizes) { using (var dest = new MemoryStream()) { // each time source is read, it needs reset source.Position = 0; // don't resize original png and gif may output large file size, save it as is if (resize.TargetSize == int.MaxValue) { await _storageProvider.SaveFileAsync(source, fileName, resize.Path, resize.PathSeparator); } else if (Math.Max(widthOrig, heightOrig) > resize.TargetSize) // only resize and save when it's larger than target { resizeCount++; Resize(source, dest, resize.TargetSize); dest.Position = 0; await _storageProvider.SaveFileAsync(dest, fileName, resize.Path, resize.PathSeparator); } } } // create record in db var media = new Media { UserId = userId, AppType = appType, FileName = fileName, Title = title, Description = null, Length = source.Length, MediaType = EMediaType.Image, UploadedOn = uploadedOn, UploadedFrom = uploadFrom, Width = widthOrig, Height = heightOrig, Caption = title, ContentType = contentType, Alt = title, ResizeCount = resizeCount, }; await _mediaRepo.CreateAsync(media); return(media); }
/// <summary> /// Uploads image by resizing and storing it. /// </summary> /// <param name="source"></param> /// <param name="resizes"></param> /// <param name="fileName"></param> /// <param name="contentType"></param> /// <param name="uploadedOn"></param> /// <param name="appType"></param> /// <param name="userId"></param> /// <param name="uploadFrom"></param> /// <returns></returns> public async Task <Media> UploadImageAsync(Stream source, List <ImageResizeInfo> resizes, string fileName, string contentType, string title, DateTimeOffset uploadedOn, EAppType appType, int userId, EUploadedFrom uploadFrom = EUploadedFrom.Browser) { int widthOrig; int heightOrig; int resizeCount = 0; if (contentType.Equals("image/gif")) { using (var imageColl = new MagickImageCollection(source)) { widthOrig = imageColl[0].Width; heightOrig = imageColl[0].Height; // currently for gif I only save original so there is no resizing // TODO: with ImageMagick resizing a gif take a long time // plus the resized gif has a larger file size than original // I tried limit gif length to 800px, but even resizing to small has these issues // resize and store foreach (var resize in resizes) { // save original without resizing if (resize.TargetSize == int.MaxValue) { source.Position = 0; await _storageProvider.SaveFileAsync(source, fileName, resize.Path, resize.PathSeparator); } //else if (Math.Max(widthOrig, heightOrig) > resize.TargetSize) //{ // resizeCount++; // var (width, height) = GetNewSize(widthOrig, heightOrig, resize.TargetSize); // imageColl.Coalesce(); // foreach (var image in imageColl) // { // var colors = image.TotalColors; // image.Resize(width, height); // resize will make # of colors higher // image.Quantize(new QuantizeSettings // { // Colors = colors, // set it back to the smaller original colors // DitherMethod = DitherMethod.No // }); // } // imageColl.Optimize(); // await _storageProvider.SaveFileAsync(imageColl.ToByteArray(), fileName, resize.Path, resize.PathSeparator); //} } } } else if (contentType.Equals("image/png")) { using (var image = new MagickImage(source)) { widthOrig = image.Width; heightOrig = image.Height; foreach (var resize in resizes) { // save original without resizing for png if (resize.TargetSize == int.MaxValue) { source.Position = 0; await _storageProvider.SaveFileAsync(source, fileName, resize.Path, resize.PathSeparator); } else if (Math.Max(widthOrig, heightOrig) > resize.TargetSize) { resizeCount++; var(width, height) = GetNewSize(widthOrig, heightOrig, resize.TargetSize); //image.Quality = 75; // does not seem to affect output file size, so commented out image.Resize(width, height); await _storageProvider.SaveFileAsync(image.ToByteArray(), fileName, resize.Path, resize.PathSeparator); } } } } else // jpg { using (var image = new MagickImage(source)) { widthOrig = image.Width; heightOrig = image.Height; foreach (var resize in resizes) { if (resize.TargetSize == int.MaxValue || Math.Max(widthOrig, heightOrig) > resize.TargetSize) { if (resize.TargetSize != int.MaxValue) { resizeCount++; } var(width, height) = GetNewSize(widthOrig, heightOrig, resize.TargetSize); // setting the Quality is needed for having it here makes a difference for a smaller output file size! image.Quality = 85; // 75 is default image.Resize(width, height); await _storageProvider.SaveFileAsync(image.ToByteArray(), fileName, resize.Path, resize.PathSeparator); } } } } // create record in db var media = new Media { UserId = userId, AppType = appType, FileName = fileName, Title = title, Description = null, Length = source.Length, MediaType = EMediaType.Image, UploadedOn = uploadedOn, UploadedFrom = uploadFrom, Width = widthOrig, Height = heightOrig, Caption = title, ContentType = contentType, Alt = title, ResizeCount = resizeCount, }; await _mediaRepo.CreateAsync(media); return(media); }
/// <summary> /// Takes the original filename and returns a slugged filename and title attribute. /// </summary> /// <remarks> /// If the filename is too long it shorten it. Then it generates a slugged filename which /// is hyphen separeated value for english original filenames, a random string value for /// non-english filenames. The title attribute is original filename html-encoded for safe /// display. /// </remarks> /// <param name="fileNameOrig">Original filename user is uploading.</param> /// <param name="uploadFrom">This is used solely because of olw quirks I have to handle.</param> /// <returns></returns> private (string fileNameSlugged, string title) ProcessFileName(string fileNameOrig, EUploadedFrom uploadFrom) { // extra filename without ext, note this will also remove the extra path info from OLW var fileNameWithoutExt = Path.GetFileNameWithoutExtension(fileNameOrig); // make sure file name is not too long if (fileNameWithoutExt.Length > MediaService.MEDIA_FILENAME_MAXLEN) { fileNameWithoutExt = fileNameWithoutExt.Substring(0, MediaService.MEDIA_FILENAME_MAXLEN); } // there is a quirk file uploaded from olw had "_2" suffixed to the name if (uploadFrom == EUploadedFrom.MetaWeblog && fileNameWithoutExt.EndsWith("_2")) { fileNameWithoutExt = fileNameWithoutExt.Remove(fileNameWithoutExt.Length - 2); } // slug file name var slug = Util.Slugify(fileNameWithoutExt); if (slug.IsNullOrEmpty()) // slug may end up empty { slug = Util.RandomString(6); } else if (uploadFrom == EUploadedFrom.MetaWeblog && slug == "thumb") // or may end up with only "thumb" for olw { slug = string.Concat(Util.RandomString(6), "_thumb"); } var ext = Path.GetExtension(fileNameOrig).ToLower(); var fileNameSlugged = $"{slug}{ext}"; var fileNameEncoded = WebUtility.HtmlEncode(fileNameWithoutExt); return(fileNameSlugged : fileNameSlugged, title : fileNameEncoded); }
/// <summary> /// Returns media url after upload to storage. /// </summary> /// <param name="userId">Id of the user uploading the media.</param> /// <param name="fileName">File name with ext.</param> /// <param name="content">File content</param> /// <param name="appId">Which app it uploaded it.</param> /// <returns></returns> /// <remarks> /// Note: This method is optimized for metaweblog use with olw, other apps have totally /// different file logic. /// /// Depending on the storage provider, the returned media url could be relative path /// (File Sys) or absolute path (Azure Blog). /// </remarks> public async Task <string> UploadMediaAsync(int userId, string fileName, byte[] content, EAppType appId, EUploadedFrom uploadFrom) { // verify ext is supported var ext = Path.GetExtension(fileName); if (ext.IsNullOrEmpty() || !Accepted_Image_Types.Contains(ext, StringComparer.InvariantCultureIgnoreCase)) { throw new FanException("Upload file type is not supported."); } // time var uploadedOn = DateTimeOffset.UtcNow; var year = uploadedOn.Year.ToString(); var month = uploadedOn.Month.ToString("d2"); // make sure file name is not too long var fileNameWithoutExt = Path.GetFileNameWithoutExtension(fileName); if (fileNameWithoutExt.Length > MEDIA_FILENAME_MAXLEN) { fileNameWithoutExt = fileNameWithoutExt.Substring(0, MEDIA_FILENAME_MAXLEN); } // there is a quirk file uploaded from olw had "_2" suffixed to the name if (uploadFrom == EUploadedFrom.MetaWeblog && fileNameWithoutExt.EndsWith("_2")) { fileNameWithoutExt = fileNameWithoutExt.Remove(fileNameWithoutExt.Length - 2); } // slug file name // chinese fn ends up emtpy and the thumb file with chinese fn ends up with only "thumb" var slug = Util.FormatSlug(fileNameWithoutExt); if (slug.IsNullOrEmpty()) { slug = Util.RandomString(6); } else if (uploadFrom == EUploadedFrom.MetaWeblog && slug == "thumb") { slug = string.Concat(Util.RandomString(6), "_thumb"); } string fileNameSlugged = $"{slug}{ext}"; // save file to storage and get back file path var filePath = await _storageProvider.SaveFileAsync(userId, fileNameSlugged, year, month, content, EAppType.Blog); // encode filename var fileNameEncoded = WebUtility.HtmlEncode(fileNameWithoutExt); // since file name could have been updated for uniqueness var start = filePath.LastIndexOf('/') + 1; var uniqueFileName = filePath.Substring(start, filePath.Length - start); // save record to db var media = new Media { UserId = userId, AppId = appId, FileName = uniqueFileName, // unique filename from storage provider Title = fileNameEncoded, // original filename Description = null, Length = content.LongLength, MediaType = EMediaType.Image, UploadedOn = uploadedOn, UploadedFrom = uploadFrom, }; await _mediaRepo.CreateAsync(media); return(filePath); }
/// <summary> /// Uploads image by resizing and storing it. /// </summary> /// <param name="source"></param> /// <param name="resizes"></param> /// <param name="fileName"></param> /// <param name="contentType"></param> /// <param name="uploadedOn"></param> /// <param name="appType"></param> /// <param name="userId"></param> /// <param name="uploadFrom"></param> /// <returns></returns> public async Task <Media> UploadImageAsync(Stream source, List <ImageResizeInfo> resizes, string fileName, string contentType, string title, DateTimeOffset uploadedOn, EAppType appType, int userId, EUploadedFrom uploadFrom = EUploadedFrom.Browser) { int widthOrig; int heightOrig; int resizeCount = 0; if (contentType.Equals("image/gif")) { using (var imageColl = new MagickImageCollection(source)) { widthOrig = imageColl[0].Width; heightOrig = imageColl[0].Height; // resize and store foreach (var resize in resizes) { // save original without resizing, currently I couldn't dec original file size by resizing with ImageMagick if (resize.TargetSize == int.MaxValue) { source.Position = 0; await _storageProvider.SaveFileAsync(source, fileName, resize.Path, resize.PathSeparator); } else if (Math.Max(widthOrig, heightOrig) > resize.TargetSize) { resizeCount++; var(width, height) = GetNewSize(widthOrig, heightOrig, resize.TargetSize); imageColl.Coalesce(); foreach (var image in imageColl) { var colors = image.TotalColors; image.Resize(width, height); // resize will make # of colors higher image.Quantize(new QuantizeSettings { Colors = colors, // set it back to the smaller original colors DitherMethod = DitherMethod.No }); } imageColl.Optimize(); await _storageProvider.SaveFileAsync(imageColl.ToByteArray(), fileName, resize.Path, resize.PathSeparator); } } } } else if (contentType.Equals("image/png")) { using (var image = new MagickImage(source)) { widthOrig = image.Width; heightOrig = image.Height; foreach (var resize in resizes) { // save original without resizing for png if (resize.TargetSize == int.MaxValue) { source.Position = 0; await _storageProvider.SaveFileAsync(source, fileName, resize.Path, resize.PathSeparator); } else if (Math.Max(widthOrig, heightOrig) > resize.TargetSize) { resizeCount++; var(width, height) = GetNewSize(widthOrig, heightOrig, resize.TargetSize); //image.Quality = 75; // does not seem to affect output file size, so commented out image.Resize(width, height); await _storageProvider.SaveFileAsync(image.ToByteArray(), fileName, resize.Path, resize.PathSeparator); } } } } else // jpg { using (var image = new MagickImage(source)) { widthOrig = image.Width; heightOrig = image.Height; foreach (var resize in resizes) { if (resize.TargetSize == int.MaxValue || Math.Max(widthOrig, heightOrig) > resize.TargetSize) { if (resize.TargetSize != int.MaxValue) { resizeCount++; } var(width, height) = GetNewSize(widthOrig, heightOrig, resize.TargetSize); image.Quality = 75; // though 75 is default, having it here does make a difference on making output file size smaller! image.Resize(width, height); await _storageProvider.SaveFileAsync(image.ToByteArray(), fileName, resize.Path, resize.PathSeparator); } } } } // create record in db var media = new Media { UserId = userId, AppType = appType, FileName = fileName, Title = title, Description = null, Length = source.Length, MediaType = EMediaType.Image, UploadedOn = uploadedOn, UploadedFrom = uploadFrom, Width = widthOrig, Height = heightOrig, Caption = title, ContentType = contentType, Alt = title, ResizeCount = resizeCount, }; await _mediaRepo.CreateAsync(media); return(media); }