/// <summary> /// Get properties from MediaInfo /// </summary> /// <param name="fname"></param> public bool CopyMediaInfo(string fname) { try { var logger = GlobalServiceProvider.Get <MediaInfo.ILogger>(); var mi = new MediaInfoWrapper(fname, logger); if (!mi.MediaInfoNotloaded) { mi.WriteInfo(); var audioStream = mi.BestAudioStream; if (audioStream != null) { FileType = audioStream.Format; Codec = audioStream.Codec.ToCodecString(); BitRate = (int)(audioStream.Bitrate / 1000); Duration = (int)audioStream.Duration.TotalSeconds; Channels = audioStream.Channel; SampleRate = (int)audioStream.SamplingRate; // TODO: Add bitrate mode BitRateMode = string.Empty; } } } catch (Exception) { return(false); } return(true); }
public static bool CreateVideoThumb(string aVideoPath, string aThumbPath, bool aCacheThumb, bool aOmitCredits) { //Log.Debug("VideoThumbCreator: args {0}, {1}!", aVideoPath, aThumbPath); LoadSettings(); if (String.IsNullOrEmpty(aVideoPath) || String.IsNullOrEmpty(aThumbPath)) { Log.Warn("VideoThumbCreator: Invalid arguments to generate thumbnails of your video!"); return(false); } if (!Util.Utils.FileExistsInCache(aVideoPath)) { Log.Warn("VideoThumbCreator: File {0} not found!", aVideoPath); return(false); } if (!Util.Utils.FileExistsInCache(ExtractorPath)) { Log.Warn("VideoThumbCreator: No {0} found to generate thumbnails of your video!", ExtractApp); return(false); } if (!LeaveShareThumb && !aCacheThumb) { Log.Warn( "VideoThumbCreator: No share thumbs wanted by config option AND no caching wanted - where should the thumb go then? Aborting.."); return(false); } IVideoThumbBlacklist blacklist = GlobalServiceProvider.Get <IVideoThumbBlacklist>(); if (blacklist != null && blacklist.Contains(aVideoPath)) { Log.Debug("Skipped creating thumbnail for {0}, it has been blacklisted because last attempt failed", aVideoPath); return(false); } // Params for ffmpeg // string ExtractorArgs = string.Format(" -i \"{0}\" -vframes 1 -ss {1} -s {2}x{3} \"{4}\"", aVideoPath, @"00:08:21", (int)Thumbs.ThumbLargeResolution, (int)Thumbs.ThumbLargeResolution, aThumbPath); // Params for mplayer (outputs 00000001.jpg in video resolution into working dir) -vf scale=600:-3 //string ExtractorArgs = string.Format(" -noconsolecontrols -nosound -vo jpeg:quality=90 -vf scale -frames 1 -ss {0} \"{1}\"", "501", aVideoPath); // Params for mtm (http://moviethumbnail.sourceforge.net/usage.en.html) // -D 8 : edge detection; 0:off >0:on; higher detects more; try -D4 -D6 or -D8 // -B 420/E 600 : omit this seconds from the beginning / ending TODO: use pre- / postrecording values // -c 2 / r 2 : # of column / # of rows // -b 0.60 : skip if % blank is higher; 0:skip all 1:skip really blank >1:off // -h 100 : minimum height of each shot; will reduce # of column to fit // -t : time stamp off // -i : info text off // -w 0 : width of output image; 0:column * movie width // -n : run at normal priority // -W : dont overwrite existing files, i.e. update mode // -P : dont pause before exiting; override -p const double flblank = 0.6; string blank = flblank.ToString("F", CultureInfo.CurrentCulture); int preGapSec = preRecordInterval * 60; int TimeToSeek = 3; int Duration = 0; // intRnd is used if user refresh the thumbnail from context menu int intRnd = 0; if (aOmitCredits) { Random rnd = new Random(); intRnd = rnd.Next(10, 300); } preGapSec = preGapSec + intRnd; Log.Debug("VideoThumbCreator: random value: {0}", intRnd); bool Success = false; var logger = GlobalServiceProvider.Get <MediaInfo.ILogger>(); MediaInfo = new MediaInfoWrapper(aVideoPath, logger); MediaInfo.WriteInfo(); if (MediaInfo != null) { Duration = (int?)MediaInfo.BestVideoStream?.Duration.TotalSeconds ?? 0; } if (Duration == 0) { Log.Debug("VideoThumbCreator: the {0} is corrupt.", aVideoPath); return(false); } if (preGapSec > Duration) { preGapSec = (Duration / 100) * 20; // 20% of the duration } TimeBetweenThumbs = (Duration - preGapSec) / ((PreviewColumns * PreviewRows) + 1); Log.Debug("{0} duration is {1}.", aVideoPath, Duration); // Honour we are using a unix app //ExtractorArgs = ExtractorArgs.Replace('\\', '/'); try { // Set String string strFilenamewithoutExtension = Path.ChangeExtension(aVideoPath, null); // Use this for the working dir to be on the safe side string TempPath = Path.GetTempPath(); string OutputThumb = string.Format("{0}{1}", Path.ChangeExtension(aVideoPath, null), ".jpg"); string ShareThumb = OutputThumb.Replace(".jpg", ".jpg"); // Use Temp folder string strFilenamewithoutExtensionTemp = Path.GetTempPath() + Path.GetFileName(strFilenamewithoutExtension); string ShareThumbTemp = Path.GetTempPath() + Path.GetFileName(ShareThumb); // ffmpeg string ffmpegFallbackArgs = string.Format("yadif=0:-1:0,scale=600:337,setsar=1:1,tile={0}x{1}", PreviewColumns, PreviewRows); string ExtractorFallbackArgs = string.Format("-loglevel quiet -ss {0} -i \"{1}\" -y -ss {2} -vf {3} -vframes 1 -vsync 0 -an \"{4}.jpg\"", 5, aVideoPath, TimeToSeek, ffmpegFallbackArgs, strFilenamewithoutExtensionTemp); if ((LeaveShareThumb && !Util.Utils.FileExistsInCache(ShareThumb)) // No thumb in share although it should be there || (aOmitCredits) // or a refress needs by user (from context menu) || (!LeaveShareThumb && !Util.Utils.FileExistsInCache(aThumbPath))) // No thumb cached and no chance to find it in share { Log.Debug("VideoThumbCreator: No thumb in share {0} - trying to create.", aVideoPath); if (aOmitCredits) { File.Delete(ShareThumb); } List <string> pictureList = new List <string>(); string ffmpegArgs = null; string ExtractorArgs = null; int TimeOffset = 0; int i; for (i = 0; i < (PreviewColumns * PreviewRows); i++) { TimeOffset = preGapSec + i * TimeBetweenThumbs; ffmpegArgs = string.Format("yadif=0:-1:0,scale=600:337,setsar=1:1,tile=1x1"); ExtractorArgs = string.Format("-loglevel quiet -ss {0} -i \"{1}\" -y -ss {2} -vf {3} -vframes 1 -vsync 0 -an \"{4}_{5}.jpg\"", TimeOffset, aVideoPath, TimeToSeek, ffmpegArgs, strFilenamewithoutExtensionTemp, i); Success = Utils.StartProcess(ExtractorPath, ExtractorArgs, TempPath, 120000, true, GetMtnConditions()); Log.Debug("VideoThumbCreator: thumb creation {0}", ExtractorArgs); if (!Success) { Log.Debug("VideoThumbCreator: failed, try to fallback {0}", strFilenamewithoutExtensionTemp); break; } else { pictureList.Add(string.Format("{0}_{1}.jpg", strFilenamewithoutExtensionTemp, i)); } } // generate thumb if all sub pictures was created if (i == PreviewColumns * PreviewRows) { if (Util.Utils.CreateTileThumb(pictureList, string.Format("{0}.jpg", strFilenamewithoutExtensionTemp), PreviewColumns, PreviewRows)) { Log.Debug("VideoThumbCreator: thumb creation success {0}", ShareThumbTemp); File.SetAttributes(ShareThumbTemp, File.GetAttributes(ShareThumbTemp) & ~FileAttributes.Hidden); } else { Log.Debug("VideoThumbCreator: failed, try to fallback {0}", strFilenamewithoutExtensionTemp); } } else { // Maybe the pre-gap was too large or not enough sharp & light scenes could be caught Thread.Sleep(100); Success = Utils.StartProcess(ExtractorPath, ExtractorFallbackArgs, TempPath, 120000, true, GetMtnConditions()); if (!Success) { Log.Info("VideoThumbCreator: {0} has not been executed successfully with arguments: {1}", ExtractApp, ExtractorFallbackArgs); /*Utils.KillProcess(Path.ChangeExtension(ExtractApp, null)); * return false;*/ } } // give the system a few IO cycles Thread.Sleep(100); // make sure there's no process hanging Utils.KillProcess(Path.ChangeExtension(ExtractApp, null)); } else { // We have a thumbnail in share but the cache was wiped out - make sure it is recreated if (LeaveShareThumb && Util.Utils.FileExistsInCache(ShareThumb) && !Util.Utils.FileExistsInCache(aThumbPath)) { Success = true; } } Thread.Sleep(30); if (aCacheThumb && Success) { if (Picture.CreateThumbnailVideo(ShareThumbTemp, aThumbPath, (int)Thumbs.ThumbResolution, (int)Thumbs.ThumbResolution, 0, false)) { Picture.CreateThumbnailVideo(ShareThumbTemp, Utils.ConvertToLargeCoverArt(aThumbPath), (int)Thumbs.ThumbLargeResolution, (int)Thumbs.ThumbLargeResolution, 0, false); } } if (LeaveShareThumb) { if (Utils.FileExistsInCache(aThumbPath)) { try { string aThumbPathLarge = Utils.ConvertToLargeCoverArt(aThumbPath); if (Utils.FileExistsInCache(aThumbPathLarge)) { aThumbPath = aThumbPathLarge; } File.Copy(aThumbPath, ShareThumb); File.SetAttributes(ShareThumb, File.GetAttributes(ShareThumb) & ~FileAttributes.Hidden); } catch (Exception ex) { Log.Debug("TvThumbnails.VideoThumbCreator: Exception on File.Copy({0}, {1}) {2}", ShareThumbTemp, ShareThumb, ex.Message); } } } // delete final thumb from temp folder try { if (Utils.FileExistsInCache(ShareThumbTemp)) { File.Delete(ShareThumbTemp); } } catch (FileNotFoundException ex) { Log.Debug("VideoThumbCreator: {0}", ex.Message); } } catch (Exception ex) { Log.Error("VideoThumbCreator: Thumbnail generation failed - {0}!", ex.ToString()); } if (Util.Utils.FileExistsInCache(aThumbPath)) { return(true); } else { if (blacklist != null) { blacklist.Add(aVideoPath); } return(false); } }
/// <summary> /// Read MusicTag information from cueFakeTrack /// Not thread safe! /// </summary> /// <param name="cueFakeTrackFileName">Cue fake track file name</param> /// <returns>MusicTag filled with cue track information</returns> public static MusicTag CueFakeTrackFile2MusicTag(string cueFakeTrackFileName) { lock (cacheLock) { // This metod called twice for each single file. So, cache data! if (cueFakeTrackFileName == cueFakeTrackFileNameCache) { return(musicTagCache); } cueFakeTrackFileNameCache = cueFakeTrackFileName; // Cache CueSheet to pervent parsing it for each track in the album CueFakeTrack cueFakeTrack = parseCueFakeTrackFileName(cueFakeTrackFileName); if (cueSheetCacheFileNameCache != cueFakeTrack.CueFileName) { cueSheetCache = new CueSheet(cueFakeTrack.CueFileName); cueSheetCacheFileNameCache = cueFakeTrack.CueFileName; } int trackPosition = cueFakeTrack.TrackNumber - cueSheetCache.Tracks[0].TrackNumber; Track track = cueSheetCache.Tracks[trackPosition]; musicTagCache = new MusicTag(); if (track.TrackNumber < cueSheetCache.Tracks[cueSheetCache.Tracks.Length - 1].TrackNumber) { Track nextTrack = cueSheetCache.Tracks[trackPosition + 1]; musicTagCache.Duration = cueIndexToIntTime(nextTrack.Indices[0]) - cueIndexToIntTime(track.Indices[0]); } string fname = Path.Combine(Path.GetDirectoryName(cueFakeTrack.CueFileName), track.DataFile.Filename); try { if (fname != cacheFName) { TagLib.File file = TagLib.File.Create(fname); tagCache = new TagCache(); tagCache.CopyTags(file); } cacheFName = fname; musicTagCache.FileType = tagCache.FileType; musicTagCache.Codec = tagCache.Codec; musicTagCache.Year = tagCache.Year; musicTagCache.BitRate = tagCache.BitRate; musicTagCache.DiscID = tagCache.DiscId; musicTagCache.DiscTotal = tagCache.DiscTotal; musicTagCache.Channels = tagCache.Channels; musicTagCache.SampleRate = tagCache.SampleRate; musicTagCache.BitRateMode = tagCache.BitRateMode; if (musicTagCache.Duration == 0) { musicTagCache.Duration = tagCache.Duration - cueIndexToIntTime(track.Indices[0]); } } catch (Exception) { // If we end up here this means that we were not able to read the file // Most probably because of taglib-sharp not supporting the audio file // For example DTS file format has no Tags, but can be replayed by BASS // Use MediaInfo to read the properties if (fname != cacheFName) { tagCache = new TagCache(); if (tagCache.CopyMediaInfo(fname)) { musicTagCache.FileType = tagCache.FileType; musicTagCache.Codec = tagCache.Codec; musicTagCache.BitRate = tagCache.BitRate; musicTagCache.Channels = tagCache.Channels; musicTagCache.SampleRate = tagCache.SampleRate; musicTagCache.BitRateMode = tagCache.BitRateMode; if (musicTagCache.Duration == 0) { musicTagCache.Duration = tagCache.Duration - cueIndexToIntTime(track.Indices[0]); } } } cacheFName = fname; } // In case of having a multi file Cue sheet, we're not able to get the duration // from the index entries. use MediaInfo then if (musicTagCache.Duration == 0) { try { var logger = GlobalServiceProvider.Get <MediaInfo.ILogger>(); var mi = new MediaInfoWrapper(fname, logger); if (!mi.MediaInfoNotloaded) { mi.WriteInfo(); musicTagCache.Duration = (int?)mi.BestAudioStream?.Duration.TotalSeconds ?? 0; } } catch (Exception ex1) { Log.Warn("CueFakeTrackFile2MusicTag: Exception retrieving duration for file {0}. {1}", fname, ex1.Message); } } if (string.IsNullOrEmpty(musicTagCache.Artist)) { // if track has a performer set use this value for artist tag // else use global performer defined for cue sheet if (!string.IsNullOrEmpty(track.Performer)) { musicTagCache.Artist = track.Performer; } else { musicTagCache.Artist = cueSheetCache.Performer; } } if (string.IsNullOrEmpty(musicTagCache.Album)) { musicTagCache.Album = cueSheetCache.Title; } if (string.IsNullOrEmpty(musicTagCache.AlbumArtist)) { if (!string.IsNullOrEmpty(cueSheetCache.Performer)) { musicTagCache.AlbumArtist = cueSheetCache.Performer; musicTagCache.HasAlbumArtist = true; } else { musicTagCache.HasAlbumArtist = false; } } // let tagged genre override cuesheet genre if (string.IsNullOrEmpty(musicTagCache.Genre) && !string.IsNullOrEmpty(cueSheetCache.Genre)) { musicTagCache.Genre = cueSheetCache.Genre; } // let tagged year override cuesheet year if (musicTagCache.Year == 0 && cueSheetCache.Year != 0) { musicTagCache.Year = cueSheetCache.Year; } // let tagged composer override cuesheet songwriter if (string.IsNullOrEmpty(musicTagCache.Composer) && !string.IsNullOrEmpty(cueSheetCache.Songwriter)) { musicTagCache.Composer = cueSheetCache.Songwriter; } // in case we were not able to read the file type via taglib, we will get it vai extension if (string.IsNullOrEmpty(musicTagCache.FileType)) { var extension = Path.GetExtension(fname); if (extension != null) { musicTagCache.FileType = extension.Substring(1).ToLowerInvariant(); } } musicTagCache.FileName = cueFakeTrackFileName; musicTagCache.Title = track.Title; musicTagCache.Track = track.TrackNumber; musicTagCache.TrackTotal = cueSheetCache.Tracks.Length; return(musicTagCache); } }