DeviLeo

DeviLeo 查看完整档案

上海编辑  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

DeviLeo 发布了文章 · 2020-12-21

如何制作Live Photo并存入照片库

本文主要是讲如何将一张照片和一部视频合并为动态照片,然后存入照片库。

动态照片(Live Photo)的秘密

动态照片表面上看就是一张图片加一部短视频,它们在手机中的文件名是相同的,只是扩展名不同,一个是JPG,一个是MOV,都是大写的。

注:iOS是区分大小写的,而macOS是不区分大小写的,但是在显示的时候是区分大小写的。

我曾经试图将电脑中的短视频,外加一张截图,把它们名字改成一样的,然后放入我的手机中(/User/Media/DCIM/100APPLE/),接着删除照片库的数据库(/User/Media/PhotoData/Photos.sqlite),重启手机再进入照片库,发现系统只能认出截图,并没有将它识别为动态照片。

上StackOverflow搜索发现,图片和视频中都有额外的元数据用于识别动态照片。
1、 图片(JPEG)

  • 元数据(Metadata)
{
  "{MakerApple}" : {
    "17" : "<Identifier>"
  }
}

2、 视频(MOV)

  • H.264编码
  • YUV420P颜色编码
  • 顶层元数据(Metadata)
{
  "com.apple.quicktime.content.identifier" : "<Identifier>"
}
  • 元数据轨道(Metadata Track)
{
  "MetadataIdentifier" : "mdta/com.apple.quicktime.still-image-time",
  "MetadataDataType" : "com.apple.metadata.datatype.int8"
}
  • 元数据轨道中的元数据
{
  "com.apple.quicktime.still-image-time" : 0
}

其中,图片和视频的<Identifier>必须一致。

使用代码添加元数据

知道了动态照片的秘密,那么事情就好办了,我们只需要添加相应的元数据到图片和视频中就可以了。
虽然不需要对视频重新编码,但是也没法简单地使用一两行代码就可以完成元数据的插入。只能使用AVAssetReaderAVAssetWriter边读边写。

前提

1、iOS版本需要9.1以上,否则不支持将动态照片存入照片库。
2、导入以下头文件。

#import <Photos/Photos.h>
#import <CoreMedia/CMMetadata.h>
#import <MobileCoreServices/MobileCoreServices.h>

图片添加元数据

给图片添加元数据非常简单,代码如下。

- (void)addMetadataToPhoto:(NSURL *)photoURL outputPhotoFile:(NSString *)outputFile identifier:(NSString *)identifier {
    NSMutableData *data = [NSData dataWithContentsOfURL:photoURL].mutableCopy;
    UIImage *image = [UIImage imageWithData:data];
    CGImageRef imageRef = image.CGImage;
    NSDictionary *imageMetadata = @{(NSString *)kCGImagePropertyMakerAppleDictionary : @{@"17" : identifier}};
    CGImageDestinationRef dest = CGImageDestinationCreateWithData((CFMutableDataRef)data, kUTTypeJPEG, 1, nil);
    CGImageDestinationAddImage(dest, imageRef, (CFDictionaryRef)imageMetadata);
    CGImageDestinationFinalize(dest);
    [data writeToFile:outputFile atomically:YES];
}

其中,kCGImagePropertyMakerAppleDictionary的值是{MakerApple}identifier的值由[NSUUID UUID].UUIDString生成。

视频添加元数据

给视频添加元数据非常麻烦,需要使用AVAssetReaderAVAssetWriter,前者读的同时,后者同时写。
在给出详细代码前,先对AVAssetReaderAVAssetWriter有个大概的认识。

AVAssetReader

AVAsset可以看成视频对象。
AVAssetReader可以看成是AVAsset的数据读取管理器,它不负责读数据,只负责读取状态变更。
AVAssetReaderOutput可以看成数据读取器,负责数据的读取工作,它需要加入到AVAssetReader中才能工作。可以创建多个AVAssetReaderOutput加入到AVAssetReader中。
AVAssetReaderTrackOutputAVAssetReaderOutput的子类,传入[AVAsset tracks]中的轨道来创建轨道数据读取器。
[AVAssetReader startReading]表示AVAssetReaderTrackOutput可以开始读取数据。
[AVAssetReaderOutput copyNextSampleBuffer]表示读取下一段数据。可以是音频数据,也可以是视频数据,也可以是其它数据。
[AVAssetReader cancelReading]表示停止读取数据。停止后任何AVAssetReaderOutput都无法读取数据。

AVAssetWriter

AVAssetWriter可以看成是视频数据写入管理器,它不负责写数据,只负责写入状态的变更。
AVAssetWriterInput可以看成轨道数据写入器,负责数据的写入工作,它需要加入到AVAssetWriter才能工作。可以创建多个AVAssetWriterInput加入到AVAssetWriter中。
[AVAssetWriter startWriting]表示AVAssetWriterInput可以开始写入数据。
[AVAssetWriter startSessionAtSourceTime:kCMTimeZero]表示从音频或视频时间第0秒开始写入数据。
AVAssetWriterInput.readyForMoreMediaData表示有足够的缓冲区可供写入数据。
AVAssetWriterInput appendSampleBuffer:buffer]表示将数据buffer写入缓冲区。当AVAssetWriterInput的缓冲区写满时,会对数据进行处理并清空缓冲区。
注意:如果有多个AVAssetWriterInput,当其中一个AVAssetWriterInput写满缓冲区时,并不会对数据进行处理,而是等待其它AVAssetWriterInput写入相应时长的数据后,才会对数据进行处理。
[AVAssetWriterInput markAsFinished]表示已经没有数据可以写入,并且不再接收任何数据。
[AVAssetWriter finishWritingWithCompletionHandler表示所有写入已经完成,处理所有数据,生成一个完整的视频。

读写流程

1、 初始化AVAssetReaderAVAssetWriter
2、 通过AVAsset获取轨道,用于创建AVAssetReaderTrackOutput,以及对应的AVAssetWriterInput
3、 AVAssetReader读取顶层元数据,修改后让AVAssetWriter写入顶层元数据。
4、 让AVAssetReaderAVAssetWriter进入读写状态。
5、 AVAssetReaderOuput读取轨道数据,AVAssetWriterInput写入轨道数据。
6、 数据全部读完后,让AVAssetReader变为停止读取状态。让所有AVAssetWriterInput标记为写完状态。
7、 让AVAssetWriter变为完成状态,至此视频创建完成。

代码详解

创建顶层元数据

- (AVMetadataItem *)createContentIdentifierMetadataItem:(NSString *)identifier {
    AVMutableMetadataItem *item = [AVMutableMetadataItem metadataItem];
    item.keySpace = AVMetadataKeySpaceQuickTimeMetadata;
    item.key = AVMetadataQuickTimeMetadataKeyContentIdentifier;
    item.value = identifier;
    return item;
}

此处视频的identifier必须和图片的identifier的值一样。

创建元数据轨道

- (AVAssetWriterInput *)createStillImageTimeAssetWriterInput {
    NSArray *spec = @[@{(NSString *)kCMMetadataFormatDescriptionMetadataSpecificationKey_Identifier : @"mdta/com.apple.quicktime.still-image-time",
                        (NSString *)kCMMetadataFormatDescriptionMetadataSpecificationKey_DataType : (NSString *)kCMMetadataBaseDataType_SInt8 }];
    CMFormatDescriptionRef desc = NULL;
    CMMetadataFormatDescriptionCreateWithMetadataSpecifications(kCFAllocatorDefault, kCMMetadataFormatType_Boxed, (__bridge CFArrayRef)spec, &desc);
    AVAssetWriterInput *input = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeMetadata outputSettings:nil sourceFormatHint:desc];
    return input;
}

创建元数据轨道中的元数据

- (AVMetadataItem *)createStillImageTimeMetadataItem {
    AVMutableMetadataItem *item = [AVMutableMetadataItem metadataItem];
    item.keySpace = AVMetadataKeySpaceQuickTimeMetadata;
    item.key = @"com.apple.quicktime.still-image-time";
    item.value = @(-1);
    item.dataType = (NSString *)kCMMetadataBaseDataType_SInt8;
    return item;
}

注意:这里dataType必须赋值,否则在插入到元数据轨道时会出错。

创建AVAssetReaderAVAssetWriter

首先,定义一个添加元数据到视频中的入口方法。

- (void)addMetadataToVideo:(NSURL *)videoURL outputFile:(NSString *)outputFile identifier:(NSString *)identifier;

然后,创建AVAssetReaderAVAssetWriter,同时添加顶层元数据com.apple.quicktime.content.identifier

NSError *error = nil;
  
// Reader
AVAsset *asset = [AVAsset assetWithURL:videoURL];
AVAssetReader *reader = [AVAssetReader assetReaderWithAsset:asset error:&error];
if (error) {
    NSLog(@"Init reader error: %@", error);
    return;
}
  
// Add content identifier metadata item
NSMutableArray<AVMetadataItem *> *metadata = asset.metadata.mutableCopy;
AVMetadataItem *item = [self createContentIdentifierMetadataItem:identifier];
[metadata addObject:item];
  
// Writer
NSURL *videoFileURL = [NSURL fileURLWithPath:outputFile];
[self deleteFile:outputFile];
AVAssetWriter *writer = [AVAssetWriter assetWriterWithURL:videoFileURL fileType:AVFileTypeQuickTimeMovie error:&error];
if (error) {
    NSLog(@"Init writer error: %@", error);
    return;
}
[writer setMetadata:metadata];

创建AVAssetReaderTrackOutputAVAssetWriterInput

// Tracks
NSArray<AVAssetTrack *> *tracks = [asset tracks];
for (AVAssetTrack *track in tracks) {
    NSDictionary *readerOutputSettings = nil;
    NSDictionary *writerOuputSettings = nil;
    if ([track.mediaType isEqualToString:AVMediaTypeAudio]) {
        readerOutputSettings = @{AVFormatIDKey : @(kAudioFormatLinearPCM)};
        writerOuputSettings = @{AVFormatIDKey : @(kAudioFormatMPEG4AAC),
                                AVSampleRateKey : @(44100),
                                AVNumberOfChannelsKey : @(2),
                                AVEncoderBitRateKey : @(128000)};
    }
    AVAssetReaderTrackOutput *output = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:track outputSettings:readerOutputSettings];
    AVAssetWriterInput *input = [AVAssetWriterInput assetWriterInputWithMediaType:track.mediaType outputSettings:writerOuputSettings];
    if ([reader canAddOutput:output] && [writer canAddInput:input]) {
        [reader addOutput:output];
        [writer addInput:input];
    }
}

针对音频轨道,在AVAssetReaderTrackOutput读取数据时,解码生成kAudioFormatLinearPCM编码格式的数据。在AVAssetWriterInput写入数据时,以kAudioFormatMPEG4AAC编码格式进行编码写入,如果不想重新编码,可以将nil传入outputSettings参数,这样最后生成的音频轨道是kAudioFormatLinearPCM编码格式。
针对视频轨道outputSettings参数传入nil表示不重新编码。

**注意:
根据官方文档,AVAssetReaderTrackOutput只能生成无压缩的编码格式。
针对音频轨道,只能是kAudioFormatLinearPCM
针对视频轨道,无压缩编码格式需要遵循AVVideoSettings.h文件中指定的规则。当然,出于性能考虑,对于设备支持的原生解码器,可以不用转换。例如,使用YUV420P颜色编码的H.264编码的视频就不需要转换。
完整的文档内容如下。**

The track must be one of the tracks contained by the target AVAssetReader's asset.  
  
A value of nil for outputSettings configures the output to vend samples in their original format as stored by the specified track.  
Initialization will fail if the output settings cannot be used with the specified track.  
  
AVAssetReaderTrackOutput can only produce uncompressed output.  
For audio output settings, this means that AVFormatIDKey must be kAudioFormatLinearPCM.  
For video output settings, this means that the dictionary must follow the rules for uncompressed video output, as laid out in AVVideoSettings.h.  
AVAssetReaderTrackOutput does not support the AVAudioSettings.h key AVSampleRateConverterAudioQualityKey or the following AVVideoSettings.h keys:  
  
  AVVideoCleanApertureKey  
  AVVideoPixelAspectRatioKey  
  AVVideoScalingModeKey  
  
When constructing video output settings the choice of pixel format will affect the performance and quality of the decompression.  
For optimal performance when decompressing video the requested pixel format should be one that the decoder supports natively to avoid unnecessary conversions.  
Below are some recommendations:  
  
For H.264 use kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, or kCVPixelFormatType_420YpCbCr8BiPlanarFullRange if the video is known to be full range.  
For JPEG on iOS, use kCVPixelFormatType_420YpCbCr8BiPlanarFullRange.  
  
For other codecs on OSX, kCVPixelFormatType_422YpCbCr8 is the preferred pixel format for video and is generally the most performant when decoding.  
If you need to work in the RGB domain then kCVPixelFormatType_32BGRA is recommended on iOS and kCVPixelFormatType_32ARGB is recommended on OSX.  
  
ProRes encoded media can contain up to 12bits/ch.  
If your source is ProRes encoded and you wish to preserve more than 8bits/ch during decompression then use one of the following pixel formats:  
kCVPixelFormatType_4444AYpCbCr16, kCVPixelFormatType_422YpCbCr16, kCVPixelFormatType_422YpCbCr10, or kCVPixelFormatType_64ARGB.  
AVAssetReader does not support scaling with any of these high bit depth pixel formats.  
If you use them then do not specify kCVPixelBufferWidthKey or kCVPixelBufferHeightKey in your outputSettings dictionary.  
If you plan to append these sample buffers to an AVAssetWriterInput then note that only the ProRes encoders support these pixel formats.  
  
ProRes 4444 encoded media can contain a mathematically lossless alpha channel.  
To preserve the alpha channel during decompression use a pixel format with an alpha component such as kCVPixelFormatType_4444AYpCbCr16 or kCVPixelFormatType_64ARGB.  
To test whether your source contains an alpha channel check that the track's format description has kCMFormatDescriptionExtension_Depth and that its value is 32.

创建元数据轨道

// Metadata track
AVAssetWriterInput *input = [self createStillImageTimeAssetWriterInput];
AVAssetWriterInputMetadataAdaptor *adaptor = [AVAssetWriterInputMetadataAdaptor assetWriterInputMetadataAdaptorWithAssetWriterInput:input];
if ([writer canAddInput:input]) {
    [writer addInput:input];
}

其中,AVAssetWriterInputMetadataAdaptor的作用是将 元数据(Metadata) 作为 校准元数据组(Timed Metadata Groups) 写入单个AVAssetWriterInput

开始读写

// Start reading and writing
[writer startWriting];
[writer startSessionAtSourceTime:kCMTimeZero];
[reader startReading];

将元数据写入元数据轨道

// Write metadata track's metadata
AVMetadataItem *timedItem = [self createStillImageTimeMetadataItem];
CMTimeRange timedRange = CMTimeRangeMake(kCMTimeZero, CMTimeMake(1, 100));
AVTimedMetadataGroup *timedMetadataGroup = [[AVTimedMetadataGroup alloc] initWithItems:@[timedItem] timeRange:timedRange];
[adaptor appendTimedMetadataGroup:timedMetadataGroup];

**注意:
timedRange必须是个有范围的值,如果为0,则会写入失败。
[AVTimedMetadataGroup appendTimedMetadataGroup:]必须在[AVAssetWriter startWriting]之后才能写入。**

异步读写轨道数据

// Write other tracks
self.reader = reader;
self.writer = writer;
self.queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
self.group = dispatch_group_create();
for (NSInteger i = 0; i < reader.outputs.count; ++i) {
    dispatch_group_enter(self.group);
    [self writeTrack:i];
}

此处,保存AVAssetReaderAVAssetWriter对象以及dispatch_queue_tdispatch_group_t,会在主要的读写操作方法- (void)writeTrack:(NSInteger)trackIndex;中使用。
使用dispatch_group是为了在异步读写各个轨道数据全部完成后,进行最后的收尾工作。
至此,- (void)addMetadataToVideo:(NSURL *)videoURL outputFile:(NSString *)outputFile identifier:(NSString *)identifier;方法的代码已经全部完成。
下面是异步读写轨道数据的代码。

- (void)writeTrack:(NSInteger)trackIndex {
    AVAssetReaderOutput *output = self.reader.outputs[trackIndex];
    AVAssetWriterInput *input = self.writer.inputs[trackIndex];
    
    [input requestMediaDataWhenReadyOnQueue:self.queue usingBlock:^{
        while (input.readyForMoreMediaData) {
            AVAssetReaderStatus status = self.reader.status;
            CMSampleBufferRef buffer = NULL;
            if ((status == AVAssetReaderStatusReading) &&
                (buffer = [output copyNextSampleBuffer])) {
                BOOL success = [input appendSampleBuffer:buffer];
                CFRelease(buffer);
                if (!success) {
                    NSLog(@"Track %d. Failed to append buffer.", (int)trackIndex);
                    [input markAsFinished];
                    dispatch_group_leave(self.group);
                    return;
                }
            } else {
                if (status == AVAssetReaderStatusReading) {
                    NSLog(@"Track %d complete.", (int)trackIndex);
                } else if (status == AVAssetReaderStatusCompleted) {
                    NSLog(@"Reader completed.");
                } else if (status == AVAssetReaderStatusCancelled) {
                    NSLog(@"Reader cancelled.");
                } else if (status == AVAssetReaderStatusFailed) {
                    NSLog(@"Reader failed.");
                }
                [input markAsFinished];
                dispatch_group_leave(self.group);
                return;
            }
        }
    }];
}

[AVAssetWriterInput requestMediaDataWhenReadyOnQueue:usingBlock:]的block中应该不停地将数据添加到AVAssetWriterInput,直到AVAssetWriterInput.readyForMoreMediaData属性值变为NO,或者没有数据可供添加(通常会调用[AVAssetWriterInput markAsFinished]方法)。然后退出block。
退出block后,且[AVAssetWriterInput markAsFinished]还没有被调用,一旦AVAssetWriterInput处理完数据,AVAssetWriterInput.readyForMoreMediaData属性值就会变为YES,block将会再次被调用,以获取更多的数据。

收尾工作

当数据全部读完写完之后,进行收尾工作。

- (void)finishWritingTracksWithPhoto:(NSString *)photoFile video:(NSString *)videoFile complete:(void (^)(BOOL success, NSString *photoFile, NSString *videoFile, NSError *error))complete {
    [self.reader cancelReading];
    [self.writer finishWritingWithCompletionHandler:^{
        if (complete) complete(YES, photoFile, videoFile, nil);
    }];
}

简单地停止读取和完成写入即可,在视频文件完全生成之后会有回调,在回调中将视频存入照片库即可。

封装

图片和视频的元数据的添加代码都已经完成,将其封装一下,代码如下。

- (void)useAssetWriter:(NSURL *)photoURL video:(NSURL *)videoURL identifier:(NSString *)identifier complete:(void (^)(BOOL success, NSString *photoFile, NSString *videoFile, NSError *error))complete {
    // Photo
    NSString *photoName = [photoURL lastPathComponent];
    NSString *photoFile = [self filePathFromDoc:photoName];
    [self addMetadataToPhoto:photoURL outputFile:photoFile identifier:identifier];
    
    // Video
    NSString *videoName = [videoURL lastPathComponent];
    NSString *videoFile = [self filePathFromDoc:videoName];
    [self addMetadataToVideo:videoURL outputFile:videoFile identifier:identifier];
    
    if (!self.group) return;
    dispatch_group_notify(self.group, dispatch_get_main_queue(), ^{
        [self finishWritingTracksWithPhoto:photoFile video:videoFile complete:complete];
    });
}

存入照片库

检查设备是否支持动态照片

BOOL available = [PHAssetCreationRequest supportsAssetResourceTypes:@[@(PHAssetResourceTypePhoto), @(PHAssetResourceTypePairedVideo)]];
if (!available) {
    NSLog(@"Device does NOT support LivePhoto.");
    return;
}

授权

访问照片库之前,需要进行授权。
首先在Info.plst文件中添加NSPhotoLibraryUsageDescription键值对。
程序运行时,主动请求授权。

[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
    if (status != PHAuthorizationStatusAuthorized) {
        NSLog(@"Photo Library access denied.");
        return;
    }
}];

存入照片库

NSURL *photo = [NSURL fileURLWithPath:photoFile];
NSURL *video = [NSURL fileURLWithPath:videoFile];
  
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
    PHAssetCreationRequest *request = [PHAssetCreationRequest creationRequestForAsset];
    [request addResourceWithType:PHAssetResourceTypePhoto fileURL:photo options:nil];
    [request addResourceWithType:PHAssetResourceTypePairedVideo fileURL:video options:nil];
} completionHandler:^(BOOL success, NSError * _Nullable error) {
    if (success) { NSLog(@"Saved."); }
    else { NSLog(@"Save error: %@", error); }
}];

针对照片,PHAssetResourceTypePhoto
针对视频,PHAssetResourceTypePairedVideo

完整代码

完整代码已上传至GitHub: DeviLeo/LivePhotoConverter

参考

查看原文

赞 0 收藏 0 评论 0

DeviLeo 发布了文章 · 2020-08-11

OpenSSL scripts

Decrypt encrypted ts file

# index.m3u8
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:7
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-KEY:METHOD=AES-128,URI="https://localhost/ts.key",IV=0x00000000000000000000000000000000
#EXTINF:4.000000,
index0.ts
#EXTINF:4.000000,
index1.ts
#EXTINF:4.000000,
index2.ts
#EXTINF:4.000000,
index3.ts
#EXTINF:4.000000,
index4.ts
#EXTINF:4.000000,
index5.ts
#EXT-X-ENDLIST
curl "https://localhost/ts.key" -o ts.key
$KEY=`xxd -ps ts.key`
$IV="00000000000000000000000000000000"

FFMPEG_CONCAT_FILENAME="f.txt"
MP4_FILENAME="index.mp4"
ECRYPTED_FOLDER="encrypted"
DECRYPTED_FOLDER="decrypted"
mkdir $DECRYPTED_FOLDER

for i in {0..5}
do

TS_FILENAME=index$i.ts
ENC_TS_FILE=$ECRYPTED_FOLDER/$TS_FILENAME
curl "https://localhost/$TS_FILENAME" -o $ENC_TS_FILE

DEC_TS_FILE=$DECRYPTED_FOLDER/$TS_FILENAME
openssl aes-128-cbc -d -in $ENC_TS_FILE -out $DEC_TS_FILE -nosalt -K $KEY -iv $IV

echo "file $DEC_TS_FILE" >> $FFMPEG_CONCAT_FILENAME
done

ffmpeg -f concat -i $FFMPEG_CONCAT_FILENAME -codec copy $MP4_FILENAME

How To Generate a /etc/passwd password hash via the Command Line on Linux

If you're looking to generate the /etc/shadow hash for a password for a Linux user (for instance: to use in a Puppet manifest), you can easily generate one at the command line.

$ openssl passwd -1
Password:
Verifying - Password:
$1$3JUKmV3R$vZVeb51f1t6QZUecwuRHX0

If you want to pass along a salt to your password;

$ openssl passwd -1 -salt yoursalt
Password:
$1$yoursalt$5WA5NN0quMJ62v5LCu8kj1

The above examples all prompt your password, so it won't be visible in the history of the server or in the process listing. If you want to directly pass the password as a parameter, use one of these examples.

$ openssl passwd -1
Password:
Verifying - Password:
$1$rr7ygbpo$v.zYy4J3/B73NF/qsrDZJ0

$ echo 'joske' | openssl passwd -1 -stdin
$1$8HOL7Lpu$wYO7x5kUDw39GfQaVqelP/

By default, this will use an md5 algoritme for your password hash. The openssl tool only allows for those md5 hashes, so if you're looking for a more secure sha256 hash you can use this python script as shared by Red Hat.

$ python -c "import crypt; print crypt.crypt('joske')"
$6$0LNgXS95nJv2B6hm$BRNf00hyT5xGNRnsLSSn3xDPXIs6l34g2kpex4mh0w/fvGz4MYs02qWjVU5NrbVktoNVNRsHU6MUTUua4J5nO0

There you go!

查看原文

赞 0 收藏 0 评论 0

DeviLeo 发布了文章 · 2020-06-17

Linux Shell 常用命令和脚本

Replace contents by using sed

# Replace all
sed -i "" "s|pattern|replace|g" "file.ext" # osx
sed -i"" "s|pattern|replace|g" "file.ext" # linux

# Replace the first line string
sed -i "" "1s|pattern|replace|g" "file.ext" # osx
sed -i"" "1s|pattern|replace|g" "file.ext" # linux

# Replace with \t
sed -i "" $'1s|pattern|\t|g' "file.ext" # osx
sed -i"" $'1s|pattern|\t|g' "file.ext" # linux

Delete files or folders except the specified one

rm -rf !("Except 1"|"Except 2"|"Except N")

'=' and '==' in if statement

#!/bin/bash
if [ "foo" = "foo" ]; then
    echo expression evaluated as true
else
    echo expression evaluated as false
fi

# or 

#!/bin/bash
if [[ "foo" == "foo" ]]; then
    echo expression evaluated as true
else
    echo expression evaluated as false
fi

Get the modified time of file

# osx
stat -f "%Sm" -t "%Y%m%dT%H%M%S" filename # datetime
stat -f "%Sm" -t "%s" filename # timestamp in seconds

# linux
stat -c %y filename # datetime
stat -c %Y filename # timestamp in seconds

Datetime and timestamp

# Datetime
echo `date '+%Y-%m-%d %H:%M:%S.%N'`

# Timestamp in seconds
echo `date '+%s'`

# Elapsed time
STARTs=`date '+%s'`
STARTn=`date '+%N' | sed 's/^0*//'`
echo "Do something"
ENDs=`date '+%s'`
ENDn=`date '+%N' | sed 's/^0*//'`
SECS=$(($ENDs - $STARTs))
NANO=$(($ENDn - $STARTn))
if (( $NANO < 0 )); then
  NANO=$((1000000000 + $NANO))
  SECS=$(($SECS - 1))
fi

echo "Cost $SECS.$NANO seconds."

Check os type

if [[ "$OSTYPE" == "linux-gnu" ]]; then
        # ...
elif [[ "$OSTYPE" == "darwin"* ]]; then
        # Mac OSX
elif [[ "$OSTYPE" == "cygwin" ]]; then
        # POSIX compatibility layer and Linux environment emulation for Windows
elif [[ "$OSTYPE" == "msys" ]]; then
        # Lightweight shell and GNU utilities compiled for Windows (part of MinGW)
elif [[ "$OSTYPE" == "win32" ]]; then
        # I'm not sure this can happen.
elif [[ "$OSTYPE" == "freebsd"* ]]; then
        # ...
else
        # Unknown.
fi

Read file from arguments or stdin

content=""
while read line
do
  content="${content}\n${line}"
done < "${1:-/dev/stdin}"
echo $content

#or 

file=${1--}
content=$(cat $file)
echo $content

The substitution ${1:-...} takes $1 if defined,
otherwise the file name of the standard input of the own process is used.

Function return value

# Define a function
myfunc() {
    return 1
}

# Call the function
myfunc

# Get the result from the last command, 
# here is the result of `myfunc`
result=$?

Remove duplicate string(no comma contains) from list

for string in $list; do unique[$string]=1; done
echo ${!unique[@]}

Read file

# entire file
content=`cat filename.txt`
echo $content

# read line by line
while IFS='' read -r line || [[ -n "$line" ]]; do
    echo "Text read from file: $line"
done < filename.txt

Batch rename as numeric

n=1
for i in $(ls | sort -n);
do 
newfile=$(printf "%03d%s" $n .${i##*.})
mv "${i}" "${newfile}"
echo "${i} -> ${newfile}"
((++n))
done

Format number

printf "%05d\n" 52

Remove string chars

original="Hello World"
world="${original:6}"
hell="${original::4}"
hello="${original::-6}"
lo_wo="${original:3:-3}"

For loop

# For loop
for (( i=0; i<10; i++ )); do echo "$i"; done

# For loop range
# {START..END..INCREMENT}
for i in {1..10..2}; do echo "$i"; done

# For loop through files with spaces
for i in *; do echo "$i"; done;
for i in *.ext; do echo "$i"; done;

# For loop through ls results only show extension
for i in $(ls); do echo "${i##*.}"; done;

# For loop through ls results only show filename
for i in $(ls); do echo "${i%.*}"; done;

# For loop through ls results sort as numeric
for i in $(ls | sort -n); do echo "$i"; done;

List files as numeric sort

ls | sort -n

Batch rename

for i in *.ext;
    do mv "$i" "${i%.ext}.newext";
done

Modify permission

sudo chown -R root:wheel [file|dir]

Lock & Unlock (macOS)

# lock
chflags -R uchg *

# unlock
chflags -R nouchg *

Delete unversioned files under svn

svn status --no-ignore | grep '^\?' | sed 's/^\?      //'
svn status --no-ignore | grep '^\?' | sed 's/^\?      //'  | xargs -Ixx rm -rf xx

Find

# Find and delete empty folders
find . -type d -empty -delete

# Find over 100MB files and sort
find / -type f -size +100M -exec ls -lh {} \; | sort -n -r

# Find files between the specified dates and copy them to the specified folder
find . -type f -newermt '2018-05-25' ! -newermt '2018-05-26' | xargs -I {} cp {} "folder/"
查看原文

赞 0 收藏 0 评论 0

DeviLeo 发布了文章 · 2020-06-17

Linux Shell MySQL

修改自增起始值

ALTER TABLE table AUTO_INCREMENT = 100000;

创建用户

CREATE USER 'newuser'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON *.* TO 'newuser'@'localhost';
FLUSH PRIVILEGES;

显示用户

SELECT * FROM mysql.USER;

查看权限

SHOW GRANTS FOR 'newuser'@'localhost';

查看字符编码列表

SHOW CHARACTER SET;

查看字符排序规则列表

SHOW COLLATION;

显示数据库

SHOW DATABASES;

查看数据库字符编码和字符排序规则

SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME
FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = 'database_name';

# OR

USE 'database_name';
SELECT @@character_set_database, @@collation_database;

创建数据库

CREATE DATABASE IF NOT EXISTS 'database_name'
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;

utf8已被弃用,因为最多支持3字节字符,使用utf8mb4可以支持4字节字符。
COLLATE对于字符类型会影响索引创建、搜索排序、比较。

  • cs - Case Sensitive - 大小写敏感
  • ci - Case Insensitive - 大小写无关
  • ai - Accent Insensitive - 发音无关

推荐使用utf8mb4_unicode_ci,相对于默认的utf8mb4_general_ci更符合西方的语言习惯,其它没有区别。
MySQL 8.0开始默认使用utf8mb4_0900_ai_ci,0900指unicode比较算法的编号(Unicode Collation Algorithm version)。

选则数据库

USE 'database_name';

查看选中的数据库

SELECT DATABASE();

查看数据库表

SHOW TABLES;

显示表结构

DESCRIBE mysql.USER;

使用mysql_native_password密码登录

# MySQL
ALTER USER 'yourusername'@'localhost' IDENTIFIED WITH mysql_native_password BY 'youpassword';

# MariaDB
UPDATE `mysql`.`user` SET plugin="mysql_native_password";
查看原文

赞 0 收藏 0 评论 0

DeviLeo 发布了文章 · 2020-06-17

Linux Shell FLAC

Embed cue

metaflac --import-cuesheet-from="file.cue" --set-tag-from-file="CUESHEET=file.cue" "file.flac"

Extract cue

Remove "CUESHEET=" from the first line in cue file after the extraction.

metaflac --show-tag="CUESHEET" "file.flac" > "file.cue"

Generate cue

metaflac --export-cuesheet-to="file.cue" "file.flac"

Remove all metadata

metaflac --remove-all "file.flac"

Remove picture without padding remaining

metaflac --dont-use-padding --remove --block-type=PICTURE "file.flac"

Import picture

metaflac --import-picture-from="cover.jpg" "file.flac"
metaflac --import-picture-from="3|image/jpeg|description|300x300x24|cover.jpg" "file.flac"

Split single file to multiple files

# Install cuetools, shntool, flac
brew install cuetools shntool flac

# Download `cuetag` script from 
# https://github.com/gumayunov/split-cue/blob/master/cuetag
chmod +x cuetag && mv cuetag /usr/local/bin

# Extract cue
metaflac --show-tag="CUESHEET" "file.flac" > "file.cue"

# Edit cue 
cuesheet=REM GENRE Soundtrack
# Remove 'cuesheet=' from the first line, it will be the following
REM GENRE Soundtrack
sed -i "" "1s|cuesheet=||g" "file.cue" # for mac
sed -i"" "1s|cuesheet=||g" "file.cue" # for linux

# split
shnsplit -o flac -f "file.cue" "file.flac"

# cue to each track file
cuetag "file.cue" [0-9]*.flac
查看原文

赞 0 收藏 0 评论 0

DeviLeo 发布了文章 · 2020-06-17

Linux Shell FFmpeg

常用编码

ffmpeg -i input.mov -s 800x450 -b 1M -vcodec h264 -acodec aac output.mp4

裁切画面

# width: 宽度
# height: 高度
# left: 左侧边距
# top: 顶部边距
ffmpeg -i input.mp4 -vf crop=[width]:[height]:[left]:[top] -b 1M -vcodec h264 output.mp4

# 保留左下角四分之一画面
# iw: input width
# ih: input height
#  -----------
# |     |     |
# |-----+-----|
# |/////|     |
#  -----------
ffmpeg -i input.mp4 -vf crop=iw/2:ih/2:0:ih/2 -b 1M -vcodec h264 output.mp4

MP4转MOV

ffmpeg -i input.mp4 -acodec copy -vcodec copy -f mov output.mov

图片转视频

# mspf: millisecond per frame
# %06d.jpg: 000001.jpg, 000002.jpg, ...
# vf: 宽高为奇数的图片转换成宽高为偶数的视频,并使用黑色填充空缺部分
ffmpeg -r 1000/$mspf -f image2 -i "%06d.jpg" -vcodec libx264 -pix_fmt yuv420p -vf "pad=ceil(iw/2)*2:ceil(ih/2)*2:color=black" -an "$videofile"

截图

ffmpeg -i input.mp4 -ss 00:00:00.000 -vframes 1 screenshot.png

旋转

# 0 = 90CounterCLockwise and Vertical Flip (default)
# 1 = 90Clockwise
# 2 = 90CounterClockwise
# 3 = 90Clockwise and Vertical Flip
ffmpeg -i input.mp4 -vf "transpose=1" output.mp4

生成缩略图

# 每秒一张
ffmpeg -i input.mp4 -vf fps=1 screenshot_%d.png

# 每分钟一张
ffmpeg -i input.mp4 -vf fps=1/60 screenshot_%d.png

# 每十分钟一张
ffmpeg -i input.mp4 -vf fps=1/600 screenshot_%d.png

去水印

1、用show参数在视频上显示绿色框预估水印位置

ffmpeg -i eagles.mpg -vf delogo=x=700:y=0:w=100:h=50:show=1 nologo.mpg

2、确定水印位置后,去水印

ffmpeg -i eagles.mpg -vf delogo=x=730:y=0:w=70:h=46 nologo.mpg

3、去水印同时添加文本

ffmpeg -i "input.mkv" -c:a aac -b:a 320K -c:v h264_nvenc -b:v 5M -maxrate 20M -vf delogo=x=1680:y=1030:w=230:h=44:show=0,drawtext=fontfile=YouSheBiaoTiHei.ttf:text=文本内容:x=1772:y=1054:fontcolor=white:fontsize=25 "output.mp4"

连接视频和音频

1、不转码

# -shortest,输出文件的时长 = 所有输入文件中最短的时长
# 否则,输出文件的时长 = 所有输入文件中最长的时长
ffmpeg -i video.mp4 -i audio.m4a -c copy -shortest output.mp4

2、转码

ffmpeg -i video.mp4 -i audio.wav -c:v copy -c:a aac -strict experimental output.mp4

3、替换已有音频

ffmpeg -i video.mp4 -i audio.wav -c:v copy -c:a aac -strict experimental -map 0:v:0 -map 1:a:0 output.mp4

连接多个音频

1、新建文件list.txt,内容如下

file '1.mp3'
file '2.mp3'
file '3.mp3'
file '4.mp3'
file '5.mp3'

2、连接

ffmpeg -f concat -i list.txt -c copy output.mp3

反色

如果需要支持macOS的QuickTime播放,加上“-pix_fmt yuv420p”参数。

ffmpeg -i input.mp4 -vf lutrgb="r=negval:g=negval:b=negval" -pix_fmt yuv420p -c:a copy output.mp4

修改视频播放速度

PTS(Presentation Time Stamp,显示时间戳)

# 加速
ffmpeg -i input.mp4 -vf setpts=PTS/8 output.mp4
# 减速
ffmpeg -i input.mp4 -vf setpts=PTS/(1/4) output.mp4

修改音频播放速度

由于atempo参数取值范围在0.5~2.0

需通过组合使用来实现8倍速或0.25倍速播放

# 加速
ffmpeg -i input.mp4 -af atempo=2.0,atempo=2.0,atempo=2.0,atempo=2.0 output.mp4
# 减速
ffmpeg -i input.mp4 -af atempo=0.5,atempo=0.5 output.mp4

同时修改视频和音频播放速度

# 加速
ffmpeg -i input.mp4 -vf setpts=PTS/8 -af atempo=2.0,atempo=2.0,atempo=2.0,atempo=2.0 output.mp4
# 减速
ffmpeg -i input.mp4 -vf setpts=PTS/(1/4) -af atempo=0.5,atempo=0.5 output.mp4

Extract Audio from Video

ffmpeg -i input.mp4 -vn -acodec copy output-audio.aac

Extract Video from Audio

ffmpeg -i input.mp4 -an -vcodec copy output-video.mp4

Speeding up/slowing down video

You can change the speed of a video stream using the ​setpts video filter. Note that in the following examples, the audio stream is not changed, so it should ideally be disabled with -an.

To double the speed of the video, you can use:

ffmpeg -i input.mkv -filter:v "setpts=0.5*PTS" output.mkv

The filter works by changing the presentation timestamp (PTS) of each video frame. For example, if there are two succesive frames shown at timestamps 1 and 2, and you want to speed up the video, those timestamps need to become 0.5 and 1, respectively. Thus, we have to multiply them by 0.5.

Note that this method will drop frames to achieve the desired speed. You can avoid dropped frames by specifying a higher output frame rate than the input. For example, to go from an input of 4 FPS to one that is sped up to 4x that (16 FPS):

ffmpeg -i input.mkv -r 16 -filter:v "setpts=0.25*PTS" output.mkv

To slow down your video, you have to use a multiplier greater than 1:

ffmpeg -i input.mkv -filter:v "setpts=2.0*PTS" output.mkv

Smooth

You can smooth out slow/fast video with the ​minterpolate video filter. This is also known as "motion interpolation" or "optical flow".

ffmpeg -i input.mkv -filter "minterpolate='mi_mode=mci:mc_mode=aobmc:vsbmc=1:fps=120'" output.mkv

Other options include ​slowmoVideo and ​Butterflow.

Speeding up/slowing down audio

You can speed up or slow down audio with the ​atempo audio filter. To double the speed of audio:

ffmpeg -i input.mkv -filter:a "atempo=2.0" -vn output.mkv

The atempo filter is limited to using values between 0.5 and 2.0 (so it can slow it down to no less than half the original speed, and speed up to no more than double the input). If you need to, you can get around this limitation by stringing multiple atempo filters together. The following with quadruple the audio speed:

ffmpeg -i input.mkv -filter:a "atempo=2.0,atempo=2.0" -vn output.mkv

Using a complex filtergraph, you can speed up video and audio at the same time:

ffmpeg -i input.mkv -filter_complex "[0:v]setpts=0.5*PTS[v];[0:a]atempo=2.0[a]" -map "[v]" -map "[a]" output.mkv
查看原文

赞 0 收藏 0 评论 0

DeviLeo 发布了文章 · 2020-06-17

Linux Shell Array

Array Operation

Basic

# Initial
arr=(Hello World)

# Print
echo ${arr[0]} ${arr[1]}

# Assign
arr[0]=Hello
arr[1]=World

# Append
# Notice: arr+="Hello" is wrong
arr+=("Hello")
arr+=("World")

Iteration

${arr[*]}  # All of the items in the array  
${!arr[*]}  # All of the indexes in the array  
${#arr[*]}  # Number of items in the array  
${#arr[0]}  # Length of item zero  

Basic Examples

#!/bin/bash
array=(one two three four [5]=five)

echo "Array size: ${#array[*]}"

# --------
# Array size: 5
# --------

echo "Array items:"
for item in ${array[*]}
do
    printf "   %s\n" $item
done

# --------
# Array items:
#    one
#    two
#    three
#    four
#    five
# --------

echo "Array indexes:"
for index in ${!array[*]}
do
    printf "   %d\n" $index
done

# --------
# Array indexes:
#    0
#    1
#    2
#    3
#    5
# --------

echo "Array items and indexes:"
for index in ${!array[*]}
do
    printf "%4d: %s\n" $index ${array[$index]}
done

# --------
# Array items and indexes:
#    0: one
#    1: two
#    2: three
#    3: four
#    5: five
# --------

Advanced Examples

#!/bin/bash
array=("first item" "second item" "third" "item")

echo "Number of items in original array: ${#array[*]}"
for ix in ${!array[*]}
do
    printf "   %s\n" "${array[$ix]}"
done
echo

# --------
# Number of items in original array: 4
#    first item
#    second item
#    third
#    item
# --------

arr=(${array[*]})
echo "After unquoted expansion: ${#arr[*]}"
for ix in ${!arr[*]}
do
    printf "   %s\n" "${arr[$ix]}"
done
echo

# --------
# After unquoted expansion: 6
#    first
#    item
#    second
#    item
#    third
#    item
# --------

arr=("${array[*]}")
echo "After * quoted expansion: ${#arr[*]}"
for ix in ${!arr[*]}
do
    printf "   %s\n" "${arr[$ix]}"
done
echo

# --------
# After * quoted expansion: 1
#    first item second item third item
# --------

arr=("${array[@]}")
echo "After @ quoted expansion: ${#arr[*]}"
for ix in ${!arr[*]}
do
    printf "   %s\n" "${arr[$ix]}"
done

# --------
# After @ quoted expansion: 4
#    first item
#    second item
#    third
#    item
# --------
查看原文

赞 0 收藏 0 评论 0

DeviLeo 发布了文章 · 2020-05-08

如何让老Mac机支持USB安装Windows

一些老Mac机的用户想装Windows,却发现自己的系统上的Boot Camp Assistant(以下简称BCA)没有USB安装Windows的选项。
下面以我的MacBook Pro (13-inch, Late 2011), OS X EI Capitan为例,让BCA支持USB安装Windows。

0、打开“/Applications/Utilities/System Information.app”,记下“Model Identifier”和“Boot ROM Version”的值(我的机器是“MacBookPro8,1”和“MBP81.0047.B2C”),稍后需要用到它们。

1、首先从“/Applications/Utilities/”目录下将“Boot Camp Assistant.app”复制一份到“~/Downloads/”目录下。

2、打开“~/Downloads/Boot Camp Assistant.app/Content/Info.plist”文件。

3、找到“PreUSBBootSupportedModels”键,将其改为“USBSupportedModels”。

4、在“USBSupportedModels”键下,将第0步记下的“Model Identifier”追加进数组。如下:

<key>USBSupportedModels</key>
<array>
    <string>MacBookPro8,1</string>
    <string>MacBook7,1</string>
    <string>MacBookAir3,2</string>
    <string>MacBookPro8,3</string>
    <string>MacPro5,1</string>
    <string>Macmini4,1</string>
    <string>iMac12,2</string>
</array>

5、在“DARequiredROMVersions”键下,将第0步记下的“Boot ROM Version”追加进数组。

<key>DARequiredROMVersions</key>
<array>
    <string>MBP81.0047.B2C</string>
    <string>IM41.0055.B08</string>
    <string>IM42.0071.B03</string>
    <string>IM51.0090.B03</string>
    <string>IM52.0090.B03</string>
    <string>IM61.0093.B01</string>
    <string>MP11.005C.B04</string>
    <string>MB11.0061.B03</string>
    <string>MBP11.0055.B08</string>
    <string>MBP12.0061.B03</string>
    <string>MM11.0055.B08</string>
</array>

6、保存“Info.plist”文件。

7、签名。在Terminal中输入以下命令:

$ sudo codesign -fs - ~/Downloads/Boot Camp Assistant.app/

8、运行~/Downloads/Boot Camp Assistant.app,即可看到BCA多了个“Create a Windows 7 or later version install disk”选项了。

参考:

  1. Bootcamp - No ISO Option

最后说一句,当你成功将Windows的ISO安装到U盘,分区并重启后,会发现无法从USB启动,不要急,这时候你可以选择长按电源键强制关机,然后重新开机,默默地拿出DVD刻录盘乖乖地把ISO刻进光盘吧,因为老Mac根本不支持从USB启动,哈哈哈~~我被参考的帖子给坑了(╯‵□′)╯︵┻━┻

查看原文

赞 0 收藏 0 评论 0

DeviLeo 关注了专栏 · 2020-05-08

SegmentFault 思否观察

SegmentFault 思否对开发者行业的洞见、观察与报道

关注 27822

DeviLeo 关注了专栏 · 2020-05-08

full stack dev stills

back-end: Nodejs front-end: iOS, Android, Js

关注 1675

认证与成就

  • 获得 0 次点赞
  • 获得 0 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 0 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2020-05-08
个人主页被 1k 人浏览