277 lines
8.2 KiB
Go
277 lines
8.2 KiB
Go
package vidmod
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"91porn-server/common/log"
|
|
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
"go.mongodb.org/mongo-driver/mongo/options"
|
|
)
|
|
|
|
func h265URLMissingFilter() bson.A {
|
|
return bson.A{
|
|
bson.M{"h265Url": ""},
|
|
bson.M{"h265Url": nil},
|
|
bson.M{"h265Url": bson.M{"$exists": false}},
|
|
}
|
|
}
|
|
|
|
func h265FailCountRetryableFilter() bson.A {
|
|
return bson.A{
|
|
bson.M{"h265FailCount": bson.M{"$exists": false}},
|
|
bson.M{"h265FailCount": bson.M{"$lt": H265MaxFailCount}},
|
|
}
|
|
}
|
|
|
|
func h265QueueableStatusFilter() bson.A {
|
|
return bson.A{
|
|
bson.M{"h265Status": bson.M{"$exists": false}},
|
|
bson.M{"h265Status": H265StatusNone},
|
|
bson.M{"h265Status": H265StatusFailed, "$or": h265FailCountRetryableFilter()},
|
|
}
|
|
}
|
|
|
|
// QueueH265Transcode 将审核通过且尚无 H.265 地址的长视频加入等待队列。
|
|
func QueueH265Transcode(ids []primitive.ObjectID) (int64, error) {
|
|
if len(ids) == 0 {
|
|
return 0, nil
|
|
}
|
|
now := time.Now()
|
|
filter := bson.M{
|
|
"_id": bson.M{"$in": ids},
|
|
"status": CheckPass,
|
|
"newsType": SP,
|
|
"sourceURL": bson.M{"$exists": true, "$ne": ""},
|
|
"$and": bson.A{
|
|
bson.M{"$or": h265URLMissingFilter()},
|
|
bson.M{"$or": h265QueueableStatusFilter()},
|
|
},
|
|
}
|
|
update := bson.M{
|
|
"$set": bson.M{
|
|
"h265Status": H265StatusQueued,
|
|
"h265QueuedAt": now,
|
|
"updatedAt": now,
|
|
},
|
|
"$unset": bson.M{"h265PendingAt": ""},
|
|
}
|
|
result, err := coll(nil).UpdateMany(filter, update)
|
|
if err != nil {
|
|
log.Error("QueueH265Transcode UpdateMany failed", log.Any("ids", ids), log.E(err))
|
|
return 0, err
|
|
}
|
|
return result.ModifiedCount, nil
|
|
}
|
|
|
|
// CountH265Pending 统计已经提交云端、尚未取得结果的有效长视频。
|
|
func CountH265Pending() (int64, error) {
|
|
filter := bson.M{
|
|
"h265Status": H265StatusPending,
|
|
"status": CheckPass,
|
|
"newsType": SP,
|
|
"sourceURL": bson.M{"$exists": true, "$ne": ""},
|
|
"$or": h265URLMissingFilter(),
|
|
}
|
|
count, err := coll(nil).Count(filter)
|
|
if err != nil {
|
|
log.Error("CountH265Pending Count failed", log.E(err))
|
|
}
|
|
return count, err
|
|
}
|
|
|
|
// GetQueuedH265Videos 获取等待提交或允许重试的 H.265 任务。
|
|
func GetQueuedH265Videos(limit int64) ([]*VideoModel, error) {
|
|
if limit <= 0 {
|
|
return []*VideoModel{}, nil
|
|
}
|
|
filter := bson.M{
|
|
"h265Status": bson.M{"$in": bson.A{H265StatusQueued, H265StatusFailed}},
|
|
"status": CheckPass,
|
|
"newsType": SP,
|
|
"sourceURL": bson.M{"$exists": true, "$ne": ""},
|
|
"$and": bson.A{
|
|
bson.M{"$or": h265URLMissingFilter()},
|
|
bson.M{"$or": h265FailCountRetryableFilter()},
|
|
},
|
|
}
|
|
opts := options.Find().
|
|
SetLimit(limit).
|
|
SetSort(bson.D{{Key: "h265QueuedAt", Value: 1}, {Key: "reviewAt", Value: -1}})
|
|
var out []*VideoModel
|
|
if err := coll(nil).Find(&out, filter, opts); err != nil {
|
|
log.Error("GetQueuedH265Videos Find failed", log.Any("filter", filter), log.E(err))
|
|
return nil, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// GetPendingH265Videos 获取已经提交云端、等待轮询结果的 H.265 任务。
|
|
func GetPendingH265Videos(limit int64) ([]*VideoModel, error) {
|
|
if limit <= 0 {
|
|
return []*VideoModel{}, nil
|
|
}
|
|
filter := bson.M{
|
|
"h265Status": H265StatusPending,
|
|
"status": CheckPass,
|
|
"newsType": SP,
|
|
"sourceURL": bson.M{"$exists": true, "$ne": ""},
|
|
"$or": h265URLMissingFilter(),
|
|
}
|
|
opts := options.Find().SetLimit(limit).SetSort(bson.D{{Key: "h265PendingAt", Value: 1}})
|
|
var out []*VideoModel
|
|
if err := coll(nil).Find(&out, filter, opts); err != nil {
|
|
log.Error("GetPendingH265Videos Find failed", log.Any("filter", filter), log.E(err))
|
|
return nil, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// ClaimH265Pending 原子地将一个等待任务标记为云端处理中,并返回本次
|
|
// claim 的整秒时间。SKD 用该时间生成稳定的限时拉流 URL,重启后仍能算出
|
|
// 与提交时相同的云端 file_id。
|
|
func ClaimH265Pending(id primitive.ObjectID) (time.Time, bool, error) {
|
|
now := time.Now().UTC().Truncate(time.Second)
|
|
filter := bson.M{
|
|
"_id": id,
|
|
"h265Status": bson.M{"$in": bson.A{H265StatusQueued, H265StatusFailed}},
|
|
"status": CheckPass,
|
|
"newsType": SP,
|
|
"sourceURL": bson.M{"$exists": true, "$ne": ""},
|
|
"$and": bson.A{
|
|
bson.M{"$or": h265URLMissingFilter()},
|
|
bson.M{"$or": h265FailCountRetryableFilter()},
|
|
},
|
|
}
|
|
result, err := coll(nil).UpdateOne(filter, bson.M{"$set": bson.M{
|
|
"h265Status": H265StatusPending,
|
|
"h265PendingAt": now,
|
|
"updatedAt": now,
|
|
}})
|
|
if err != nil {
|
|
log.Error("ClaimH265Pending UpdateOne failed", log.Any("id", id), log.E(err))
|
|
return time.Time{}, false, err
|
|
}
|
|
if result.ModifiedCount == 0 {
|
|
return time.Time{}, false, nil
|
|
}
|
|
return now, true, nil
|
|
}
|
|
|
|
// MarkH265Queued 将当前 pending 尝试重新放回等待队列。
|
|
// sourceURL 与 pendingAt 共同标识一次云端尝试,避免旧实例把同视频的
|
|
// 新尝试或已经成功回填的状态回退成 queued。
|
|
func MarkH265Queued(id primitive.ObjectID, sourceURL string, pendingAt time.Time) (bool, error) {
|
|
now := time.Now()
|
|
filter := pendingH265AttemptFilter(id, sourceURL, pendingAt)
|
|
result, err := coll(nil).UpdateOne(filter, bson.M{
|
|
"$set": bson.M{
|
|
"h265Status": H265StatusQueued,
|
|
"h265QueuedAt": now,
|
|
"updatedAt": now,
|
|
},
|
|
"$unset": bson.M{"h265PendingAt": ""},
|
|
})
|
|
if err != nil {
|
|
log.Error("MarkH265Queued UpdateOne failed", log.Any("id", id), log.E(err))
|
|
return false, err
|
|
}
|
|
return result.ModifiedCount > 0, nil
|
|
}
|
|
|
|
// MarkH265Success 保存上游明确返回的可播放地址并将任务标记为成功。
|
|
// 老司机重新导入属于权威回填,不受旧云转码任务状态限制。
|
|
func MarkH265Success(id primitive.ObjectID, h265URL string) error {
|
|
h265URL = strings.TrimSpace(h265URL)
|
|
if h265URL == "" {
|
|
return fmt.Errorf("empty h265 url")
|
|
}
|
|
now := time.Now()
|
|
_, err := coll(nil).UpdateOne(bson.M{"_id": id}, bson.M{
|
|
"$set": bson.M{
|
|
"h265Url": h265URL,
|
|
"h265Status": H265StatusSuccess,
|
|
"h265FailCount": 0,
|
|
"updatedAt": now,
|
|
},
|
|
"$unset": bson.M{
|
|
"h265QueuedAt": "",
|
|
"h265PendingAt": "",
|
|
},
|
|
})
|
|
if err != nil {
|
|
log.Error("MarkH265Success UpdateOne failed", log.Any("id", id), log.E(err))
|
|
}
|
|
return err
|
|
}
|
|
|
|
// MarkPendingH265Success 仅完成仍对应同一源地址的 pending 任务。
|
|
// 如果视频源已变更,或其他流程已经回填 H.265,旧云任务结果必须被忽略。
|
|
func MarkPendingH265Success(id primitive.ObjectID, sourceURL string, pendingAt time.Time, h265URL string) (bool, error) {
|
|
h265URL = strings.TrimSpace(h265URL)
|
|
if strings.TrimSpace(sourceURL) == "" {
|
|
return false, fmt.Errorf("empty source url")
|
|
}
|
|
if pendingAt.IsZero() {
|
|
return false, fmt.Errorf("empty H265 pending time")
|
|
}
|
|
if h265URL == "" {
|
|
return false, fmt.Errorf("empty h265 url")
|
|
}
|
|
now := time.Now()
|
|
filter := pendingH265AttemptFilter(id, sourceURL, pendingAt)
|
|
result, err := coll(nil).UpdateOne(filter, bson.M{
|
|
"$set": bson.M{
|
|
"h265Url": h265URL,
|
|
"h265Status": H265StatusSuccess,
|
|
"h265FailCount": 0,
|
|
"updatedAt": now,
|
|
},
|
|
"$unset": bson.M{
|
|
"h265QueuedAt": "",
|
|
"h265PendingAt": "",
|
|
},
|
|
})
|
|
if err != nil {
|
|
log.Error("MarkPendingH265Success UpdateOne failed", log.Any("id", id), log.E(err))
|
|
return false, err
|
|
}
|
|
return result.ModifiedCount > 0, nil
|
|
}
|
|
|
|
// MarkH265Failed 记录当前 pending 尝试失败;达到 H265MaxFailCount 后将不再自动重试。
|
|
// 条件更新会跳过新尝试或已经成功回填的视频,避免旧轮询结果回退状态。
|
|
func MarkH265Failed(id primitive.ObjectID, sourceURL string, pendingAt time.Time) (bool, error) {
|
|
now := time.Now()
|
|
filter := pendingH265AttemptFilter(id, sourceURL, pendingAt)
|
|
result, err := coll(nil).UpdateOne(filter, bson.M{
|
|
"$set": bson.M{
|
|
"h265Status": H265StatusFailed,
|
|
"updatedAt": now,
|
|
},
|
|
"$inc": bson.M{"h265FailCount": 1},
|
|
"$unset": bson.M{
|
|
"h265QueuedAt": "",
|
|
"h265PendingAt": "",
|
|
},
|
|
})
|
|
if err != nil {
|
|
log.Error("MarkH265Failed UpdateOne failed", log.Any("id", id), log.E(err))
|
|
return false, err
|
|
}
|
|
return result.ModifiedCount > 0, nil
|
|
}
|
|
|
|
func pendingH265AttemptFilter(id primitive.ObjectID, sourceURL string, pendingAt time.Time) bson.M {
|
|
return bson.M{
|
|
"_id": id,
|
|
"sourceURL": sourceURL,
|
|
"h265Status": H265StatusPending,
|
|
"h265PendingAt": pendingAt,
|
|
"$or": h265URLMissingFilter(),
|
|
}
|
|
}
|